TF(Term Frequency)
우선 TF(Term Frequency)란 1개의 문서 안에서 특정 단어가 출현하는 빈도를 뜻한다. 문단을 문장으로, 문장을 단어로 나누고 더 깊이 단어를 형태소로 나눌 수 있다. 전체 단어 또는 형태소 목록 중 특정 단어가 얼마나 자주 나왔는지 파악해 해당 문서의 특성을 파악할 수 있다. 영어에서는 a bat에서 a와 같이 1글자만 나오는 단어는 생략할 수 있지만 한글을 그러면 문맥이 바뀌는 경우가 있어 생략이 쉽지 않다. 한글은 키보드로 입력하기에 매우 효율적이지만 자연어 처리 부분에서는 굉장히 까다롭다. 아래는 파이썬으로 구현한 tf이다.
def tf(t, d):
return d.count(t)
DF(Document Frequency)
다음으로 DF(Document Frequency)란 특정 단어가 나타나는 문서의 개수를 의미한다. 탐색하고자 하는 대상이 단어인가 문서인가에 따라 TF와 DF로 나누어진다. 아래는 파이썬으로 구현한 df이다.
def df(t):
df = 0
for doc in docs:
df += t in doc
return df
IDF(Inverse Document Frequency)
IDF(Inverse Document Frequency)는 DF에서 역수 변환을 해준 것인데 수식은 아래와 같다.
$$ idf(d, t) = log({{n}\over{1+df(t)}}) $$
여기서 문서를 d, 단어를 t, 문서의 총 개수가 n이다.
즉, TF는 tf(d, t), DF는 df(t), IDF는 idf(d, t)로 표현할 수 있다. 하지만 단순히 역수라 하기엔 로그 변환을 해주기에 일반적인 역수는 아니라는 것을 알 수 있다. 만약 log를 취하지 않으면 \( n\over{df(t)} \)가 되어 문서의 개수(n)가 커질수록 IDF값이 너무 빠른 속도로 커지기 때문이다.
또한 자연어 처리에서 접하게 되는 불용어 사전이 있는데 이 불용어가 나타나는 빈도는 불용어가 아닌 단어의 빈도보다 훨씬 더 자주 등장며, 평소 자주 사용하지 않는 단어가 출현하는 빈도는 자주 사용되는 불용어가 아닌 단어보다 훨씬 적게 등장한다. 따라서 희귀 단어가 중요하지 않은 경우조차 큰 가중치가 부여될 수 있기에 log 변환을 취해주는 것이 좋다. 분모에서 1을 더해주는 것은 분모가 0이 되는 것을 방지하기 위함이다.
아래는 파이썬으로 구현한 idf이다.
def idf(t):
return log(n/(df(t)+1))
TF-IDF
TF-IDF를 사용하는 이유는 분석해야할 데이터의 특징(feature)을 파악하기 위함이다. 단순히 단어 출현 빈도만 카운트하는 CountVectorizer와 같은 방법도 존재하지만 조사, 관사와 같은 단어들의 중요도를 높게 평가할 수 있기 때문에 좋은 분석 방법이 아니다. 따라서 TF-IDF는 많이 등장하는 단어에 패널티를 주고 단어 빈도의 스케일을 맞춰주는 방법이다. 수식으로 보면 TF-IDF=tf*idf로 표현할 수 있다. 아래는 파이썬으로 구현한 tf-idf이다.
def tf(t, d):
return d.count(t)
def idf(t):
return log(n/(df(t)+1))
def tf_idf(t, d):
return tf(t, d) * idf(t)
아래는 Document를 만들어 파이썬 코드를 작성해보았다. 우선 문서를 단어별로 분리한 결과이다. 여기서 단어의 기준은 띄어쓰기이다.
docs = [
'맛있는 빨간 딸기',
'비싼 빨간 딸기',
'동그랗고 빨간 체리',
'맛있고 비싸고 빨간 딸기 딸기',
'과일은 너무 비싸다']
voca = list(set(w for doc in docs for w in doc.split()))
voca.sort()
print(voca)
# ['과일은', '너무', '동그랗고', '딸기', '맛있고', '맛있는', '비싸고', '비싸다', '비싼', '빨간', '체리']
실험
TF의 결과이다. 각 문서에 출현하는 단어의 빈도수를 체크한 것이다.
n = len(docs) # 문서 개수
# TF
tf_list = [[tf(voca[j], docs[i]) for j in range(len(voca))] for i in range(n)]
tf_res = pd.DataFrame(tf_list, columns=voca)
print(tf_res)
다음은 IDF의 결과이다.
# IDF
idf_list = [idf(voca[j]) for j in range(len(voca))]
idf_res = pd.DataFrame(idf_list, index=voca, columns=["IDF"])
자주 나오는 딸기, 빨간과 같은 단어가 낮게 평가된 것을 확인할 수 있다.
마지막으로 TF-IDF의 결과이다.
# TF-IDF
tf_idf_list = [[tf_idf(voca[j], docs[i]) for j in range(len(voca))] for i in range(n)]
tfidf_res = pd.DataFrame(tf_idf_list, columns=voca)
위의 경우에 n은 5였다. 만약 df(t)의 값이 4라면 해당 idf의 수식에 대입해 계산해보면 0이 된다. 이렇게 되면 idf는 가중치 기능을 수행하지 못하게 된다. 따라서 실제로 적용할 때는 기존의 idf의 수식을 조금 수정하여 사용한다.
아래는 전체 python 소스 코드이다.
import pandas as pd
from math import log
def tf(t, d):
return d.count(t)
def df(t):
df = 0
for doc in docs:
df += t in doc
return df
def idf(t):
return log(n/(df(t)+1))
def tf_idf(t, d):
return tf(t, d) * idf(t)
docs = [
'맛있는 빨간 딸기',
'비싼 빨간 딸기',
'동그랗고 빨간 체리',
'맛있고 비싸고 빨간 딸기 딸기',
'과일은 너무 비싸다']
voca = list(set(w for doc in docs for w in doc.split()))
voca.sort()
print(voca)
# ['과일은', '너무', '동그랗고', '딸기', '맛있고', '맛있는', '비싸고', '비싸다', '비싼', '빨간', '체리']
n = len(docs) # 문서 개수
# TF
tf_list = [[tf(voca[j], docs[i]) for j in range(len(voca))] for i in range(n)]
tf_res = pd.DataFrame(tf_list, columns=voca)
# IDF
idf_list = [idf(voca[j]) for j in range(len(voca))]
idf_res = pd.DataFrame(idf_list, index=voca, columns=["IDF"])
# TF-IDF
tf_idf_list = [[tf_idf(voca[j], docs[i]) for j in range(len(voca))] for i in range(n)]
tfidf_res = pd.DataFrame(tf_idf_list, columns=voca)
참고 자료
소스 코드