저번 글에서 NLP의 통계적 접근 방법을 다뤘습니다.
자연어 처리가 무엇이고 통계적 접근 방법은 또 무엇인지 꽤 장황하게 다뤘습니다.
그런데 NLP의 큰 그림을 안 다루고 처음부터 정신없이 진도를 나가다 보니 약간 혼란이 오네요.
쓰고 보니 내가 지금 NLP 작업 중에 어떤 부분을 하고 있는 건가 싶습니다.
이번 글은 NLP Workflow를 저번 글 내용과 연결해서 이야기하겠습니다.
NLP Workflow는 다음과 같습니다.
- 데이터 수집
- 데이터 전처리
- 임베딩
- Downstream task
- prediction
데이터 수집
저번 글에서 데이터 수집은 다음과 같이 짤막하게 준비했었습니다.
text = "You say goodbye and I say hello."
보통 의미있는 학습을 하려면 "repo"나 크롤링으로 꽤 큰 단위의 데이터를 수집해야 합니다.
그래서 저번 시간에 text의 "I"는 "say"와의 유사도가 0으로 나오고 "goodbye"와는 0.99가 나왔었죠.
직관적으로는 say와도 유사하다고 느껴지지만 말이죠.
사람이 언어를 배울 때도 많이 읽으면 읽기 실력이 느는 것처럼 기계도 많이 읽을수록 성능이 좋습니다.
데이터 전처리
그리고 저번 글에서 text는 영어이기 때문에 빈칸을 기준으로 단어 단위로 데이터를 전처리했습니다.
text = text.lower()
text.replace('.', ' .')
words = text.split(' ')
# ['you', 'say', 'goodbye', 'and', 'i', 'say', 'hello', '.']
위의 예는 영어이기 때문에 띄어쓰기 단위로 데이터를 전처리해도 큰 무리가 없습니다.
그런데 한국어는 어간에 접사가 붙는 교착어이기 때문에 살짝 더 복잡합니다.
예를 들어 "한국어는" 이란 어절은 "한국어"라는 어간과 "는"이라는 접사가 붙습니다.
그래서 한국어의 NLP 데이터 전처리는 형태소 분석을 하기도 합니다.
이런 식으로 텍스트 분석의 단위로 나눠주는 작업을 토큰화(Tokenization)이라고 합니다.
보통은 단어와 같이 의미가 있는 단위로 나눠줍니다.
그래서 NLTK(영어)와 KoNLPy(한국어) 툴을 사용해 의미 단위로 나누고 품사나 형태소로 태깅합니다.
임베딩
저번 글에서는 동시 발생 행렬을 이용해 벡터를 표현했습니다.
def create_co_matrix(corpus, vocab_size, window_size=1):
corpus_size = len(corpus)
co_matrix = np.zeros((vocab_size, vocab_size), dtype=np.int32)
for idx, word_id in enumerate(corpus):
for i in range(1, window_size + 1):
left_idx = idx - 1
right_idx = idx + 1
if left_idx >= 0:
left_word_id = corpus[left_idx]
co_matrix[word_id, left_word_id] += 1
if right_idx < corpus_size:
right_word_id = corpus[right_idx]
co_matrix[word_id, right_word_id] += 1
return co_matrix
array([[0, 1, 0, 0, 0, 0, 0],
[1, 0, 1, 0, 1, 1, 0],
[0, 1, 0, 1, 0, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 1, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 1, 0]], dtype=int32)
하지만 위의 방법으로는 단어의 빈도, 혹은 단어의 중요도 등을 알기 쉽지 않기 때문에
Bag of words(BoW) 그리고 TF-IDF(Term Frequency - Inverse Document Frequency) 등의 방법을 많이 사용합니다.
Bag of Words는 단어들의 순서는 전혀 고려하지 않고,
단어들의 출현 빈도에만 집중하는 텍스트 데이터의 벡터 표현 방법입니다.
마치 가방에 들어있는 물건처럼 데이터는 순서가 없고 정렬되어 있지 않습니다.
위의 "You say goodbye and I say hello."를 Bag of Words로 만들어 보겠습니다.
각 단어들의 ID를 만들어 주고 출현 빈도를 표시해줍니다.
word_to_id = {}
id_to_word = {}
for word in words:
if word not in word_to_id:
new_id = len(word_to_id)
word_to_id[word] = new_id
id_to_word[new_id] = word
print(word_to_id)
print(id_to_word)
{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'hello': 5, '.': 6}
"say"는 두 번 출현하기 때문에 index 1의 값은 2입니다.
bag = [1, 2, 1, 1, 1, 1, 1]
TF-IDF는 어떤 단어가 중요하다는 정보를 단어가 특정 문서에 많이 등장하면 그 문서에서 중요하다고 정의하는 것을 말합니다.
tf는 특정 문서에 해당 단어가 등장하는 횟수를 말합니다.
df는 해당 단어가 존재하는 문서의 수를 말합니다.
TF-IDF는 df가 상대 빈도에 해당하는 가중치로 작용해 상대적인 중요도를 나타냅니다.
이 방법은 꽤 큰 단위의(수십만 이상의 문서) 데이터에서 사용됩니다.
이 외에 word2vec, glove, fastText, ELMO, BERT 등의 임베딩 방법이 있습니다.
위의 방법들로 임베딩을 하면 굉장히 큰 차원의 matrix가 생성됩니다.
그래서 차원의 저주를 예방하기 위해 feature-extraction(PCA, AE 등)을 시행해줘야 합니다.
이 방법들은 다음 글에서 구현해보겠습니다.
Dwonstream task
전 글에서는 단어 간의 유사도를 구하는 task를 수행했습니다.
코사인 유사도를 사용해 단어 간의 유사도를 코드를 구현합니다.
def cos_similarity(x, y, eps=1e-8):
nx = x / np.sqrt(np.sum(x**2) + eps)
ny = y / np.sqrt(np.sum(y**2) + eps)
return np.dot(nx, ny)
text = "You say goodbye and I say hello."
corpus, word_to_id, id_to_word = preprocess(text)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)
c0 = C[word_to_id['you']]
c1 = C[word_to_id['i']]
print(cos_similarity(c0, c1))
# 0.7071067811865475
이 작업은 사실 마지막 작업인 prediction까지 수행한 것입니다.
위의 Language Model은 사실 원시적인 수준이고,,
Question and Answering, Machine Translation, Text Generation, Text Summatization, Semantic Textual Similarity, Named Entity Recognition 등의 task들은 우리들의 실생활에 깊숙이 파고들어 실용적인 서비스를 제공하고 있습니다.
이번 글에서는 저번 글에서 지나쳤던 NLP 작업의 전체적인 맥락을 훑어봤습니다.
다음 글에서는 임베딩 작업에서 발생되는 차원의 저주를 해결할 방법과 word2vec에 대해 보겠습니다.. ㅂ2!
'AI > K-Digital Training' 카테고리의 다른 글
031. 코로나 백신 2차접종후 딥러닝 학습 후기.. (0) | 2021.10.15 |
---|---|
030. NLP, TF-IDF, STS, DTM, BoW, (0) | 2021.10.08 |
028. Hello NLP(Natural Language Processing)! (0) | 2021.10.07 |
027. 딥러닝과 주식투자의 연계성에 대한 인문학적 고찰 (0) | 2021.10.04 |
026. GAN(Generative Adversarial Network) 으로 간다 (0) | 2021.10.04 |