해당 포스팅은 김기현의 자연어처리 딥러닝캠프 파이토치편 과 딥러닝을 이용한 자연어 처리 입문 과정을 참고하여 정리한 것입니다. 필요한 부분은 추가로 찾아보았습니다.
지난 시간에는 단어의 표현 중 OnehotEncoding과 Word2Vec에 대해서 정리했었다. 이번에는 카운트 기반의 단어표현에 대해서 정리해보고자 한다. 카운트 기반이 먼저 나오고, 딥러닝 기반인 Word2Vec이 나와야 하는데 정리하다보니 카운트 기반을 잊은 것 같아 적는다.
0. 단어의 표현 방법
단어의 표현 방법은 크게 국소 표현(Local Representation)과 분산 표현( Distributed Representation)으로 나뉜다. 국소 표현은 해당 단어 그 자체만 보고 특정 값을 매핑하여 단어를 표현하는 방법이고, 분산 표현은 그 단어를 표현하고자 주변을 참고하여 단어를 표현하는 방법이다.
오늘 설명하는 BoW나 TF-IDF는 본문에 나오는 단어의 횟수를 기반으로 매핑하는 것이기에 국소 표현이라고 할 수 있고 Word2Vec과 같이 주변의 단어를 참고한 모델이 분산 표현이라 할 수 있다.
1. Bag of Words란?
Bag of Words는 단어들의 순서는 전혀 고려하지 않고 단어들의 출현 빈도에만 집중하는 텍스트 데이터의 수치화 표현 방법이다. 이름처럼 가방 안에 단어들이 들어있다 생각하고 해당 문서(가방) 안에서 특정 단어가 N번 등장했다면, 그 특정 단어가 N개 들어이쓴 것이다.
BoW를 만드는 과정은 이렇게 두가 지 과정으로 생각할 수 있다.
- 각 단어에 고유한 정수 인덱스를 부여한다.
- 각 인덱스의 위치에 단어 토큰의 등장 횟수를 기록한 벡터를 만든다.
BoW의 출력은 각 단어의 인덱스에 문서에서 나온 단어의 출현 빈도를 기록한 것이라 보면 된다. 쉬운 예제를 통해 출력값을 직접 보도록 하자.
import numpy as np
def BoW(sentence, word_dict):
token = sentence.split() # split sentence
bow = list(np.zeros(len(word_dict), dtype = int)) # bag of words
for w in token:
if w not in word_dict.keys(): # out of vocabulary
word_dict['unknown'] = word_dict['unknown'] + 1
else:
bow[word_dict[w]] = bow[word_dict[w]] + 1
return bow
sentence = "I have a book and I like romance"
token = sentence.split()
print('sentence : ', sentence)
# create vocabulary
word_set = list(set(token))
word_dict = {w:i for i, w in enumerate(word_set)}
word_dict['unknown'] = len(word_dict)
print('Vocabulary : ', word_dict)
#BoW
print('BoW of sentence : ', BoW(sentence, word_dict))
## 출력
sentence : I have a book and I like romance
Vocabulary : {'I': 0, 'a': 1, 'romance': 2, 'and': 3, 'book': 4, 'have': 5, 'like': 6, 'unknown': 7}
BoW of sentence : [2, 1, 1, 1, 1, 1, 1, 0]
위와 같이 BoW라는 리스트 안에 각 단어인덱스마다 출현빈도가 저장된다.
sklearn의 CountVectorizer 와 같은 패키지를 이용해서 BoW를 만들 수도 있지만 직접 짜보면서 의미를 이해하도록 하자. 불용어를 제거한 후 BoW를 생성할 수 있다.
2. 문서 단어 행렬(Document-Term Matrix, DTM)
문서 단어행렬이란, 다수의 문서에서 등장하는 각 단어들의 빈도를 행렬로 표현한 것을 말한다. 쉽게 생각하면 각 문서에 대한 BoW를 하나의 행렬로 만드는 것이다. 보통 행을 문서로, 열을 BoW로 지정한다.
2.1. 문서 단어행렬의 한계
DTM은 매우 간단하고 구현하는 것도 쉽지만, 본질적으로 몇가지 한계들이 있다.
1) 희소 표현(Sparse representation)
원-핫 인코딩과 마찬가지로 문서 단어행렬도 문서에 들어있는 단어 집합의 크기가 벡터의 차원이 되기 때문에 공간적 낭비와 계산 리소스를 증가시킬 수 있다. 그리고 문서가 겹치는 단어가 많이 없다면 벡터의 대부분의 값이 0을 가질 수도 있다.
2) 단순 빈도 수 기반 접근
여러 문서에서 등장하는 모든 단어에 대해서 빈도 표기를 하는 방법은 때로는 한계를 가지기도 한다. 불용어와 같은 단어들은 모든 문서에서 자주 등장할 수 밖에 없기 때문에 유사한 문서인지 비교하고 싶을 때 혼란을 줄 수 있다.
이런 문제를 해결하기 위해서 TF-IDF 기법이 나오게 되었다.
3. TF-IDF
TF-IDF(Term Frequency-Inverse Document Frequency)는 DTM의 한계를 극복하기 위해 나온 방법이다. 단어의 빈도와 역 문서 빈도(문서의 빈도에 특정 식을 취함)을 사용하여 DTM내의 각 단어에 대한 중요도를 가중치로 주는 방법니다. TF-IDF를 사용하면, 기존의 DTM을 사용하는 것보다 더 많은 정보를 고려하며 문서를 비교할 수 있다.
TF-IDF는 주로 문서의 유사도를 구하는 작업, 검색 시스템에서 검색 결과의 중요도를 정하는 작업, 문서 내에서 특정 단어의 중요도를 구하는 작업 등에 사용된다.
1) TF와 IDF
TF-TDF는 TF와 IDF를 곱한 값을 의미하는데 이를 식으로 표현해보자.
TF-IDF는 출현 빈도를 사용하여 어떤 단어${w}$가 문서${d}$ 내에서 얼마나 중요한지 나타내는 수치이다. 이 수치가 높을수록$w$는$d$를 대표하는 성질을 띠게 된다고 볼수도 있다. 여기서 TF와 IDF의 의미를 보도록 하자
- TF(w, d) : 특정 문서 d에서의 특정 단어 w의 등장 횟수. 각 문서에서 단어의 등장 빈도를 나타내는 값.
- DF(w) : 특정 단어 w가 등장한 문서의 수. 각 문서에서 몇번 등장했는지는 보지 않고 w가 등장한 문서의 수에만 관심을 가짐.
- IDF(w, d): 여기서서 IDF는 DF의 역수에 로그를 취해주는 것을 말한다. 로그를 취하지 않으면 문서의 수가 많아질수록 IDF의 값이 기하급수적으로 커질 수 있기 때문에 log를 취한다. (
김기현의 자연어 딥러닝 책에서는 그냥 역수라고 하는데 대부분은 log를 취한다)
이렇게 tf-idf로 가중치를 취하면 불용어 같은 자주쓰이는 단어들은 비교적 자주쓰이지 않는 단어들보다 최소 수십배 자주 등장하는데, 이때 가중치가 곱해져 격차가 줄어들게 된다.
파이썬으로 직접 구현해보도록 하자.
import pandas as pd from math import log
>docs = [
'drink cold milk',
'drink cold water',
'drink cold cola',
'drink sweet juice',
'drink sweet cola',
'eat delicious bacon',
'eat sweet mango',
'eat delicious cherry',
'eat sweet apple',
'juice with sugar',
'cola with sugar',
'mango is fruit',
'apple is fruit',
'cherry is fruit',
'Berlin is Germany',
'Boston is USA',
'Mercedes from Germany',
'Mercedes is car',
'Ford from USA',
'Ford is car'
]
## create
vocab = list(set(w for doc in docs for w in doc.split())) # vocabulary
vocab.sort()
N = len(docs)
def tf(t,d):
return d.count(t)
def idf(t):
df = 0
for doc in docs:
df += t in doc
return log(N/(df + 1))
def tfidf(t,d):
return tf(t,d) * idf(t)
## tf dataframe
result = []
for i, d in enumerate(docs):
result.append([])
for j, t in enumerate(vocab):
result[-1].append(tf(t, d))
tf_ = pd.DataFrame(result, columns = vocab)
tf_.head()
## idf dataframe
result = []
for j in range(len(vocab)):
t = vocab[j]
result.append(idf(t))
idf_ = pd.DataFrame(result, index = vocab, columns = ["IDF"])
idf_
result = []
for i, d in enumerate(docs):
result.append([])
for j, t in enumerate(vocab):
result[-1].append(tfidf(t,d))
tfidf_ = pd.DataFrame(result, columns = vocab)
tfidf_.head(5)
위와 같이 가중치가 부여된 테이블을 확인할 수 있다.
이로써 카운트 기반, 즉 단어 빈도수 기반의 단어 표현방법 대해서 알아보았다. 오늘 설명한 BoW, TF-IDF 모두 빈도수만 고려하고 단어간의 상관관계는 고려하지 않는다는 단점이 있다. 또한 단어의 개수가 많아질수록 계산해야하는 차원이 많아져 저장공간 또는 계산량이 많아진다는 단점이 있다.
이를 극복하기 위해 워드 임베딩 기법을 사용한 것이다. 워드 임베딩에 대해서는 아래를 참고하면 된다.
2021.05.14 - [Study/NLP] - [NLP/자연어처리] 단어의 표현 - 원핫인코딩과 워드투벡터(Word2Vec)
'Study > NLP' 카테고리의 다른 글
[NLP/자연어처리] LSTM(Long Short-Term Memory), GRU(Gated Recurrent Unit) (0) | 2021.05.21 |
---|---|
[NLP/자연어처리] 순환신경망 (Recurrent Neural Network, RNN) (0) | 2021.05.20 |
[NLP/자연어처리] 단어의 표현(1) - 원핫인코딩과 워드투벡터(Word2Vec) (0) | 2021.05.14 |
[NLP/자연어처리] 자연어처리 전처리(4) - 토치텍스트(TorchText) (0) | 2021.05.13 |
[NLP/자연어처리] 자연어 처리 전처리(3) - 단어집합(Vocabulary), 패딩 (4) | 2021.05.12 |