📝머신 러닝 워크플로우
- 1) 수집(Acquisition)
- 머신 러닝을 하기 위해서는 기계에 학습시켜야 할 데이터가 필요합니다.
- 2) 점검 및 탐색(Inspection and exploration)
- 데이터를 점검하고 탐색하는 단계입니다. 여기서는 데이터의 구조, 노이즈 데이터, 머신 러닝 적용을 위해서 데이터를 어떻게 정제해야하는지 등을 파악해야 합니다.
- 3) 전처리 및 정제(Preprocessing and Cleaning)
- 데이터에 대한 파악이 끝났다면, 머신 러닝 워크플로우에서 가장 까다로운 작업 중 하나인 데이터 전처리 과정에 들어갑니다.
- 4) 모델링 및 훈련(Modeling and Training)
- 머신 러닝에 대한 코드를 작성하는 단계인 모델링 단계에 들어갑니다. 적절한 머신 러닝 알고리즘을 선택하여 모델링이 끝났다면, 전처리가 완료 된 데이터를 머신 러닝 알고리즘을 통해 기계에게 학습(training)시킵니다. 여기서 주의해야 할 점은 대부분의 경우에서 모든 데이터를 기계에게 학습시켜서는 안 된다는 점입니다. 데이터 중 일부는 테스트용으로 남겨두고 훈련용 데이터만 훈련에 사용해야 합니다. 그래야만 기계가 학습을 하고나서, 테스트용 데이터를 통해서 현재 성능이 얼마나 되는지를 측정할 수 있으며 과적합(overfitting) 상황을 막을 수 있습니다. 사실 최선은 훈련용, 테스트용으로 두 가지만 나누는 것보다는 훈련용, 검증용, 테스트용. 데이터를 이렇게 세 가지로 나누고 훈련용 데이터만 훈련에 사용하는 것입니다.
- 5) 평가(Evaluation)
- 기계가 다 학습이 되었다면 테스트용 데이터로 성능을 평가하게 됩니다. 평가 방법은 기계가 예측한 데이터가 테스트용 데이터의 실제 정답과 얼마나 가까운지를 측정합니다.
- 6) 배포(Deployment)
- 성공적으로 훈련이 된 것으로 판단된다면 완성된 모델이 배포되는 단계가 됩니다.
훈련용 vs 검증용 vs 테스트용?
수능 시험에 비유하자면 훈련용은 학습지, 검증용은 모의고사, 테스트용은 수능 시험이라고 볼 수 있습니다. 학습지를 풀고 수능 시험을 볼 수도 있겠지만, 모의 고사를 풀며 부족한 부분이 무엇인지 검증하고 보완하는 단계를 하나 더 놓는 방법도 있겠지요. 사실 현업의 경우라면 검증용 데이터는 거의 필수적입니다. 모델의 성능을 수치화하여 평가하기 위해 사용됩니다. 쉽게 말해 시험에 비유하면 채점하는 단계입니다.
📝 단어 토큰화(Word Tokenization)
토큰화의 경우 문장을 의미를 갖는 단어로 쪼개는 과정을 의미합니다. 보통 토큰화 작업에는 특수문자나 구두점 등을 제거하는 작업만으로는 충분하지 않습니다. 제거함으로 그 의미를 잃어버리는 경우가 있기 때문이고 띄어쓰기로 사용하는 경우는 영어의 경우는 괜찮지만 한국어는 구분하기 어렵습니다. 또한 한국어는 띄어쓰기가 잘 지켜지지 않습니다.
- 구두점, 특수문자 제외 예외상황
- Ph.D, 12/45 (12시 45분)
- 줄임말과 단어 내에 띄어쓰기가 있는 예외상황
- New York
📝품사태깅
단어는 표기는 같지만 품사에 따라서 단어의 의미가 달라지기도 합니다. 예를 들어서 영어 단어 'fly'는 동사로는 '날다'라는 의미를 갖지만, 명사로는 '파리'라는 의미를 갖고있습니다. 한국어도 마찬가지입니다.
📝토큰화 작업 전 정제 및 정규화
토큰화 작업 전, 후에는 텍스트 데이터를 용도에 맞게 정제(cleaning) 및 정규화(normalization)하는 일이 항상 함께합니다.
- 정제(cleaning)
- 갖고 있는 코퍼스로부터 노이즈 데이터를 제거한다.
- 정규화(normalization)
- 표현 방법이 다른 단어들을 통합시켜서 같은 단어로 만들어준다
📝정제 및 정규화
- 규칙에 기반한 표기가 다른 단어들의 통합
- USA와 US는 같은 의미를 가지므로 하나의 단어로 정규화해볼 수 있습니다
- 대, 소문자 통합
- 영어에서의 대,소문자 통합이 있다. (하지만 예외도 존재 us와 US의 차이)
- 불필요한 단어의 제거
- 아무 의미도 갖지 않는 글자들(특수 문자 등)을 의미하기도 하지만, 분석하고자 하는 목적에 맞지 않는 불필요 단어들을 노이즈 데이터라고 하기도 합니다. 일반적으로 불필요 단어들을 제거하는 방법으로는 불용어 제거와 등장 빈도가 적은 단어, 길이가 짧은 단어들을 제거하는 방법이 있습니다.
- 등장 빈도가 적은 단어와 길이가 짧은 단어가 있다. 등장 빈도가 낮은 경우 너무 적게 등장해서 자연어 처리에 도움이 되지 않는 단어를 의미하며 길이가 짧은 단어의 경우는 영어권에서 일반적으로 짧은 단어일수록 의미가 없는 경우가 매우 많다.
- 표제어 추출
- 서로 다른 단어들이지만, 하나의 단어로 일반화시킬 수 있다면 하나의 단어로 일반화시켜서 문서 내의 단어 수를 줄이겠다는 것입니다. 이러한 정규화의 지향점은 언제나 갖고 있는 코퍼스로부터 복잡성을 줄이는 일입니다. 예를 들면 "맛있는" → "맛있다"에서 파생된 단어로서 "맛있겠다"도 같은 의미를 가지고 있습니다.
- 불용어
- 갖고 있는 데이터에서 유의미한 단어 토큰만을 선별하기 위해서는 큰 의미가 없는 단어 토큰을 제거하는 작업이 필요합니다. 여기서 큰 의미가 없다라는 것은 자주 등장하지만 분석을 하는 것에 있어서는 큰 도움이 되지 않는 단어들을 말합니다. 예를 들면, I, my, me, over, 조사, 접미사 같은 단어들은 문장에서는 자주 등장하지만 실제 의미 분석을 하는데는 거의 기여하는 바가 없는 경우가 있습니다.
- 패딩
- 아래에서 설명
📝원핫인코딩
원-핫 인코딩은 단어 집합의 크기를 벡터의 차원으로 하고, 표현하고 싶은 단어의 인덱스에 1의 값을 부여하고, 다른 인덱스에는 0을 부여하는 단어의 벡터 표현 방식입니다. 이렇게 표현된 벡터를 원-핫 벡터(One-Hot vector)라고 합니다.
from konlpy.tag import Okt
def one_hot_encoding(word, word_to_index):
one_hot_vector = [0] * (len(word_to_index))
index = word_to_index[word]
one_hot_vector[index] = 1
return one_hot_vector
okt = Okt()
tokens = okt.morphs("나는 자연어 처리를 배운다")
print(tokens)
# ['나', '는', '자연어', '처리', '를', '배운다']
word_to_index = {
word: index for index, word in enumerate(tokens)
}
print('단어 집합 :', word_to_index)
# 단어 집합 : {'나': 0, '는': 1, '자연어': 2, '처리': 3, '를': 4, '배운다': 5}
result = one_hot_encoding("자연어", word_to_index)
print(result)
# [0, 0, 1, 0, 0, 0]
직접 함수를 정의해서 보여줬지만 제공하는 라이브러리의 함수를 사용 할 수도 있습니다.
이러한 표현 방식은 단어의 개수가 늘어날 수록, 벡터를 저장하기 위해 필요한 공간이 계속 늘어난다는 단점이 있습니다. 다른 표현으로는 벡터의 차원이 늘어난다고 표현합니다. 원 핫 벡터는 단어 집합의 크기가 곧 벡터의 차원 수가 됩니다. 가령, 단어가 1,000개인 코퍼스를 가지고 원 핫 벡터를 만들면, 모든 단어 각각은 모두 1,000개의 차원을 가진 벡터가 됩니다. 다시 말해 모든 단어 각각은 하나의 값만 1을 가지고, 999개의 값은 0의 값을 가지는 벡터가 되는데 이는 저장 공간 측면에서는 매우 비효율적인 표현 방법입니다.
📝패딩 (Padding)
from keras.src.legacy.preprocessing.text import Tokenizer
from keras.src.utils import pad_sequences
preprocessed_sentences = [
['barber', 'person'],
['barber', 'good', 'person'], ['barber', 'huge', 'person'], ['knew', 'secret'],
['secret', 'kept', 'huge', 'secret'], ['huge', 'secret'], ['barber', 'kept', 'word'],
['barber', 'kept', 'word'], ['barber', 'kept', 'secret'],
['keeping', 'keeping', 'huge', 'secret', 'driving', 'barber', 'crazy'],
['barber', 'went', 'huge', 'mountain']
]
# 원핫 인코딩
tokenizer = Tokenizer()
tokenizer.fit_on_texts(preprocessed_sentences)
encoded = tokenizer.texts_to_sequences(preprocessed_sentences)
print(encoded)
# [
# [1, 5], [1, 8, 5],
# [1, 3, 5], [9, 2],
# [2, 4, 3, 2], [3, 2], [1, 4, 6],
# [1, 4, 6], [1, 4, 2],
# [7, 7, 3, 2, 10, 1, 11], [1, 12, 3, 13]
# ]
max_len = max(len(item) for item in encoded)
print('최대 길이 :', max_len)
# 최대 길이 : 7
padded = pad_sequences(encoded, padding='post', truncating='post', maxlen=5)
print(padded)
# [
# [ 1 5 0 0 0 0 0]
# [ 1 8 5 0 0 0 0]
# [ 1 3 5 0 0 0 0]
# [ 9 2 0 0 0 0 0]
# [ 2 4 3 2 0 0 0]
# [ 3 2 0 0 0 0 0]
# [ 1 4 6 0 0 0 0]
# [ 1 4 6 0 0 0 0]
# [ 1 4 2 0 0 0 0]
# [ 7 7 3 2 10 1 11]
# [ 1 12 3 13 0 0 0]
# ]
자연어 처리를 하다보면 각 문장(또는 문서)은 서로 길이가 다를 수 있습니다. 그런데 기계는 길이가 전부 동일한 문서들에 대해서는 하나의 행렬로 보고, 한꺼번에 묶어서 처리할 수 있습니다. 다시 말해 병렬 연산을 위해서 여러 문장의 길이를 임의로 동일하게 맞춰주는 작업이 필요할 때가 있습니다. 길이가 7보다 짧은 문장에는 전부 숫자 0이 뒤로 붙어서 모든 문장의 길이가 전부 7로 맞춥니다. 이것을 0으로 채웠기 때문에 제로 패딩이라고 합니다.
인코딩을 하는 궁극적인 이유는 컴퓨터에서 추후 처리를 위해 숫자로 변환시키는게 좋기 때문에 하는 작업이다.
🔗 참고 및 출처