2019.06.09
본 글은 https://wikidocs.net/book/2155을 참고하여 작성되었음을 밝힙니다.
핵심키워드
- TDM; Term-Document Matrix
- TF-IDF; Term Frequency-Inverse Document Frequency
단어 문서 행렬(Term-Document Matrix, TDM)
이번 챕터에서는 각 문서에 대한 BoW 표현 방법을 그대로 갖고 와서, 서로 다른 문서들의 BoW들을 결합한 표현 방법인 TDM 표현 방법을 배워보도록 한다. 이렇게 하면 서로 다른 문서들을 비교할 수 있게 된다.
1. 단어 문서 행렬(Term-Document Matrix, TDM)의 표기법
단어 문서 행렬(Term-Document Matrix, TDM)이란 다수의 문서에서 등장하는 각 단어들의 빈도를 행렬로 표현한 것을 말한다. 쉽게 생각하면 각 문서에 대한 BoW를 하나의 행렬도 만든 것으로 생각할 수 있으며, BoW와 다른 표현 방법이 아니라 BoW 표현 방법 중 하나라고 볼 수 있다. 줄여서 TDM이라고도 부른다. 예를 들어 이렇게 4개의 문서가 있다고 해보자.
문서1 : 먹고 싶은 사과
문서 2 : 먹고 싶은 바나나
문서 3 : 길고 노란 바나나 바나나
문서4 : 저는 과일이 좋아요
이를 단어 문서 행렬로 표현하면 다음과 같다.
각 문서에서 등장한 단어의 빈도를 행렬의 값으로 표기한다. 단어 문서 행렬은 문서들을 서로 비교할 수 있도록 수치화할 수 있다는 점에서 의의를 갖는다. (여기서는 하지 않으나, 원하면 한국어에서 불용어에 해당하는 조사들 또한 제거하여 더 정제된 TDM을 만들 수도 있다.)
2. 단어 문서 행렬(Term-Document Matrix)의 한계
여러 문서에 등장하는 모든 단어에 대해서 빈도 표기를 하는 이런 방법은 문서들의 비교, 분석을 어렵게 만든다. 원래 분석은 고려해야 할 변수들이 많을 수록 어려워지기 때문이다. 쉽게 말해서 영어에 대해서 단어 문서 행렬을 만들었을 때, 불용어인 the는 어떤 문서이든 자주 등장할 수 밖에 없다. 그런데 유사한 문서인지 비교하고 싶은 문서1, 문서2, 문서3에서 동일하게 the가 빈도수가 높다고 해서 이 문서들이 유사한 문서라고 판단해서는 안된다.
각 문서에서 중요한 단어와 불필요한 단어들이 혼재되어 있다. 앞서 불용어(stopwords)는 자연어 처리에 있어 의미를 거의 갖지 못하는 단어라고 언급한 바 있다. 그렇다면 TDM에 불용어와 중요한 단어에 대해서 가중치를 줄 수 있는 방법은 없을까? 이를 위해 사용하는 것이 다음 챕터에서 배우게 될 TF-IDF이다.
TF-IDF(Term Frequency-Inverse Document Frequency)
이번 챕터에서는 TDM 내에 있는 각 단어에 대한 중요도를 계산할 수 있는 TF-IDF 가중치에 대해 알아본다. TF-IDF를 사용하면, 기존의 TDM을 사용하는 것보다 정확하게 문서들을 비교할 수 있다.
1. TF-DIF(단어 빈도-역 문서 빈도, Term Frequency-Inverse Document Frequency)
TF-IDF는 Term Frequency-Inverse Document Frequency의 줄임말로, 단어의 빈도와 역 문서 빈도(문서의 빈도에 특정 식을 취함)를 사용하여 TDM 내의 각 단어들마다 중요한 정도를 가중치로 주는 방법이다. 사용 방법은 우선 TDM을 만든 후에, 거기에 TF-IDF 가중치를 주면된다.
TF-IDF는 주로 문서의 유사도를 구하는 작업, 검색 시스템에서 검색 결과의 중요도를 정하는 작업, 문서 내에서 특정 단어의 중요도를 구하는 작업 등에 쓰일 수 있다.
TF-IDF는 TF와 IDF를 곱한 값을 의미하는데 이를 식으로 표현해보자. 문서를 d, 단어를 t, 문서의 총 개수를 n이라고 표현할 때 TF, DF, IDF는 각각 다음과 같이 정의할 수 있다.
(1) tf(d,t) : 특정 문서 d에서의 특정 단어 t의 등장 횟수.
생소한 글자때문에 어려워보일 수 있지만, 잘 생각해보면 TF는 이미 앞에서 구한 적이 있다. TF는 앞에서 배운 단어 문서 행렬의 예제에서 각 단어들이 가진 값들이다. TDM이 각 문서에서의 각 단어 등자 빈도를 나타내는 값이었기 때문이다.
(2) df(t) : 특정 단어 t가 등장한 문서의 수.
여기서 특정 단어가 각 문서, 또는 문서들에서 몇 번 등장했는지는 관심가지지 않으며 오직 특정 단어 t가 등장한 문서의 수에만 관심을 가진다. 앞서 배운 단어 문서 행렬에서 바나나는 문서2와 문서3에서 등장했다. 즉, 바나나의 df는 2이다. 문서3에서 바나나가 두 번 등장했지만, 그것은 중요한 것이 아니다. 심지어 바나나란 단어가 문서2에서 100번 등장했고, 문서3에서 200번 등장했다고 하더라도 바나나의 df는 2가 된다.
(3) idf(d, t) : df(t)에 반비례하는 수.
IDF라는 이름을 보고 DF의 역수가 아닐까 생각했다면, IDF는 DF의 역수를 취하고 싶은 것이 맞다. 그런데 log와 분모에 1을 더해주는 식에 의아할 수 있다. log를 사용하지 않았을 때, IDF를 DF의 역수 (n/df(t) 라는 식)로 사용한다면 총 문서의 수 n이 커질 수록, IDF의 값은 기하급수적으로 커지게 된다. 그렇기 때문에 log를 사용한다.
왜 log가 필요한지 n = 1,000,000일 때의 예를 들어보자. log의 밑은 10을 사용한다고 가정한다.
idf(d,t) = log(n/df(t))
n = 1,000,000
그렇다면 log를 사용하지 않으면 idf의 값이 어떻게 거대해지는지 보자.
idf(d,t) = n/df(t)
n = 1,000,000
또한 log 안의 식에서 분모에 1을 더해주는 이유는 특정 단어가 전체 문서에서 등장하지 않을 경우에 분모가 0이 되는 상황을 방지하기 위함이다.
TF-IDF는 모든 문서에서 자주 등장하는 단어는 중요도가 낮다고 판단하며, 특정 문서에서만 자주 등장하는 단어는 중요도가 높다고 판단한다. TF-IDF 값이 낮으면 중요도가 낮은 것이며, TF-IDF값이 크면 중요도가 큰 것이다. 즉, the나 a와 같이 불용어의 경우에는 모든 문서에 자주 등장하기 마련이기 때문에 자연스럽게 불용어의 TF-IDF의 값은 다른 단어의 TF-IDF에 비해서 낮아지게 된다.
앞서 단어 문서 행렬을 설명하기위해 들었던 위의 TDM 예제를 가지고 TF-IDF에 대해 이해해보도록 하자. 우선 TF는 앞서 사용한 TDM을 그대로 사용하면, 그것이 각 문서에서의 각 단어의 TF가 된다.
그렇다며 이제 구해야할 것은 TF와 곱해야할 값인 IDF이다. 로그는 자연 로그를 사용하도록 한다. 자연 로그는 로그의 밑을 자연 상수 e(2.718281…)를 사용하는 로그를 말한다. IDF 계산을 위해 사용하는 로그의 밑은 TF-IDF를 사용하는 사용자가 임의의로 정할 수 있는데, 여기서 로그는 마치 기존의 값에 곱하여 값의 크기를 조절하는 상수의 역할을 한다.
그런데 보통 각종 프로그래밍 언어나 프로그램에서 패키지로 지원하는 TF-IDF의 로그는 대부분 자연 로그를 사용한다. 그렇기 때문에 글을 쓰신 분 또한 자연 로그를 사용한다고 한다. 자연 로그는 보통 log가 아닌 ln으로 표현한다.
문서의 총 수는 n=4이기 때문에 ln 안에서 분자는 늘 4로 동일하다. 분모의 경우에는 각 단어가 등장한 문서의 수(DF)를 의미하는데, 예를 들어 ‘먹고’의 경우에는 총 2개의 문서(문서1, 문서2)에 등장했기 때문에 2라는 값을 가진다. 각 단어에 대해서 IDF의 값을 비교해보면 문서 1개에만 등장한 단어와 문서2개에만 등장한 단어는 값의 차이를 보인다. IDF는 여러 문서에서 등장한 단어의 가중치를 낮추는 역할을 하기 때문이다.
그러면 이제 TF-IDF를 계산해보도록 하자. TF는 TDM을 그대로 가져오면 각 문서에서의 각 단어의 TF를 가져오게 되기 때문에, 앞서 사용한 TDM에서 단어 별로 위의 IDF값을 그대로 곱해주면 TF-IDF가 나오게 된다.
사실 예제 문서가 굉장히 간단하기 때문에 계산은 매우 쉽다. 문서3에서의 바나나만 TF 값이 2이므로 IDF에 2를 곱해주고, 나머진 TF 값이 1이므로 그대로 IDF 값을 가져오면 된다. 문서2에서의 바나나의 TF-IDF 가중치와 문서3에서의 바나나의 TF-IDF 가중치가 다른 것을 볼 수 있다. 수식적으로 말하면, TF가 각각 1과 2로 달랐기 때문인데 TF-IDF에서의 관점에서 보자면 TF-IDF는 특정 문서에서 자주 등장하는 단어는 그 문서 내에서 중요한 단어로 판단하기 때문이다. 문서2에서는 바나나를 한 번 언급했지만, 문서3에서는 바나나를 두 번 언급했기 때문에 문서3에서의 바나나를 더우 중요한 단어라고 판단하는 것이다. (유니크한 단어가 해당 문서에서 많이 등장하면 높은 값을 가짐)
2. 사이킷 런을 이용한 TDM과 TF-IDF 실습
from sklearn.feature_extraction.text import CountVectorizer
corpus = [
'you know I want your love',
'I like you',
'what should I do',
]
vector = CountVectorizer()print(vector.fit_transform(corpus).toarray())
# 코퍼스로부터 각 단어의 빈도 수를 기록한다.
print(vector.vocabulary_)
# 각 단어으 인덱스가 어떻게 부여되었는지를 보여준다.[[0 1 0 1 0 1 0 1 1]
[0 0 1 0 0 0 0 1 0]
[1 0 0 0 1 0 1 0 0]]
{'you': 7, 'know': 1, 'want': 5, 'your': 8, 'love': 3, 'like': 2, 'what': 6, 'should': 4, 'do': 0}
TDM이 완성되었다. TDM에서 각 단어의 인덱스가 어떻게 부여되었는지를 확인하기 위해 인덱스를 확인해보았다. 첫번째 열의 경우에는 0의 인덱스를 가진 do이다. do는 세번째 문서에만 등장했기 때문에, 세번째 행에서만 1의 값을 가진다. 두번째 열의 경우에는 1의 인덱스를 가진 know이다. know는 첫번째 문서에만 등장했기 때문에 첫번째 행에서만 1의 값을 가진다.
사이킷런은 TF-IDF를 자동 계산해주는 TfidfVectorizer 클래스를 제공한다. 향후 실습을 하다가 혼란이 생기지 않도록 언급하자면, 사이킷런의 TF-IDF느 우리가 위에서 배웠던 보편적인 TF-IDF식에서 좀 더 조정된 다른 식을 사용한다. 하지만 크게 다른 식은 아니며(IDF 계산 시 분자에다가도 1을 더해주며, TF-IDF에 L2 정규화라는 방법으로 값을 조정하는 등의 차이), 여전히 TF-IDF가 가진 의도를 그대로 갖고 있으므로 사이킷런의 TF-IDF를 그대로 사용해도 좋다.
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
'you know I want your love',
'I like you',
'what should I do',
]
tfidfv = TfidfVectorizer().fit(corpus)print(tfidfv.transform(corpus).toarray())
print(tfidfv.vocabulary_)[[0. 0.46735098 0. 0.46735098 0. 0.46735098
0. 0.35543247 0.46735098]
[0. 0. 0.79596054 0. 0. 0.
0. 0.60534851 0. ]
[0.57735027 0. 0. 0. 0.57735027 0.
0.57735027 0. 0. ]]{'you': 7, 'know': 1, 'want': 5, 'your': 8, 'love': 3, 'like': 2, 'what': 6, 'should': 4, 'do': 0}
이제 BoW, TDM, TF-IDF 가중치에 대해서 전부 학습했다. 그러면 문서들 간의 유사도를 구하기 위한 재료 손질하는 방법을 배운 것이다. 이제 문서들간의 유사도를 구하는 방법론에 대해서 다음 챕터에서 배워보고, 유사도를 구하는 실습을 진행하도록 한다.