본문 바로가기
Deep Learning/NLP

[NLP] TF-IDF (Term Frequency - Inverse Document Frequency) 가

by holy_jjjae 2023. 11. 7.

이번에 해커톤을 준비하면서 새롭게 알게 된 개념인데, 여튼 시작해보자.

 

 텍스트를 컴퓨터가 이해할 수 있도록 재표현해주는 text representation 방법 중에서 vectorization approaches 의 하나로서 TF-IDF (Term Frequency - Inverse Document Frequency)이 무엇인지 그리고 수식에 대해서 알아보고, 간단한 예제 텍스트를 사용해서 이해해보자.

 

TF-IDF (Term Frequency - Inverse Document Frequency) 개념 및 예시

먼저, 대표적으로 vectorization apporached 의 text representation 방법으로는

 - One-Hot Encoding

 - Bag of Words (BoW)

 - Bag of N-Grams (BoN)

 - Term Frequency - Inverse Document Frequency (TF-IDF)

등이 있다.  

 

 이 중에서 One-Hot Encoding, Bag of Words (BoW), Bag of N-Grams (BoN) 의 방법은 텍스트 안의 모든 단어를 동일하게 중요하다고 간주한다. 반면에, TF-IDF 는 문서와 말뭉치에서 어떤 단어가 주어졌을 때 다른 단어 대비 상대적인 중요도를 측정한다는 차이가 있다. 

 

 예를 들어, 어떤 단어 w 가 문서 D(i)에서 자주 나타나지만, 다른 문서 D(j) 에서는 별로 나타나지 않을 때, 단어 w 는 문서 D(i) 에서 매우 중요하다고 볼 수 있다.

 

- TF의 핵심 개념

  단어 w의 중요도는 문서 D(i) 에서 출현하는 빈도에 비례해서 증가.

 

- IDF의 핵심 개념

 반면에, 단어 w가 말뭉치의 중요도는 다른 문서 D(j) 에서의 출현 빈도에는 비례해서 감소. 

 


TF-IDF 점수의 수식은 다음과 같다.


 

TF (Term Freqneucy)

문서에서 주어진 단어가 얼마나 자주 출현하는지를 측정한다.

말뭉치(corpus) 안의 여러 문서들은 길이가 서로 다르고,  주어진 단어는 길이가 짧은 문서보다는 길이가 긴 문서에서 더 자주 출현할 가능성이 높다. 따라서 이런 문제를 해결하기 위해 문서에서 단어의 출현 빈도를 문서의 총 단어의 수로 나누어서 표준화를 해준다. 

 

TF(t, d) = (문서 d 에서 단어 t 의 출현 빈도) / (문서 d 에서 총 단어의 수) 

 

IDF (Inverse Document Frequency)

 말뭉치(corpus)에서 단어의 중요도를 측정합니다. 앞서 TF 를 계산할 때 모든 단어에는 동일한 중요도(가중치)가 부여되었다. 하지만 관사(a, the), be 동사(is, am, are) 등의 불용어(stopwords)와 같이 문서에서 자주 출현하지만 별로 중요하지 않은 단어도 있다. 이런 문제를 해결하기 위해,  IDF는 말뭉치의 여러 문서에 공통적으로 출현하는 단어에 대해서는 중요도를 낮추고, 반대로 말뭉치의 여러 문서 중에서 일부 문서에만 드물게 출현하는 단어에 대해서는 중요도를 높인다. 

 

IDF(t, D) = log(말뭉치에서 총 문서의 개수 / 단어 t를 포함하는 문서의 개수)

 

 

TF-IDF score 는 위의 TF점수와 IDF 점수를 곱해주면 된다.

TF-IDF(t, d, D) = TF(t, d) x IDF(t, D)

 


예시

 아래에는 4개의 문서에 나오는 단어를 추출하여 만든 말뭉치를 가지고 TF-IDF 점수를 계산해본 예제이다. 

(출처 : "Practical Natural Language Processing" (Sowmya Vajjala, et.al. 저) 책)

 


 

 

더 알아보기

  Bag of Words(BoW)와 비슷하게 TF-IDF 벡터는 코사인 거리(cosine distance)나 유클리디언 거리(euclidean distance) 를 사용하여 두 텍스트의 유사성을 계산하는데 사용할 수 있다. TF-IDF 는 정보 추출(information retrieval) 이나 텍스트 분류(text classification)에 많이 사용되고 있다. 

 

 하지만 TF-IDF 는 단어 간의 관계를 파악하는데는 한계가 있다. 그리고 TF-IDF 는 텍스트를 희소하고 고차원(sparse and high-dimensional)의 행렬로 표현하므로 차원의 저주(curse of dimensionality) 문제가 생긴다. 또한, 학습 데이터셋에 없는 단어에 대해서는 처리를 못하는 한계가 있다. 

 


 

실습

먼저, 실습에 사용할 텍스트로서 4개 문서의 간단한 문장을 아래와 같이 리스트로 입력해주고, 대문자를 소문자로 변환하고 불용어(stop words)인 마침표(.)는 없애주는 텍스트 데이터 전처리를 해보자. 

 

## TF-IDF (Term Freqneucy - Inverse Document Frequency) 

## input: corpus, terms in documents
documents = ["Dog bites man.", "Man bites dog.", "Dog eats meat.", "Man eats food."]
processed_docs = [doc.lower().replace(".","") for doc in documents]

print(processed_docs)
#['dog bites man', 'man bites dog', 'dog eats meat', 'man eats food']

 

 

 

 단어별 TF-IDF 점수 계산은 Python Scikit-Learn 모듈의 TfidfVectorizer 메소드를 사용해서 하면된다. 

앞에서 전처리한 텍스트 리스트를 TfidfVectorizer().fit_transform() 메소드를 사용하면 단어 추출과 TF-IDF 점수 계산이 한꺼번에 된다.

 

 

##------------------------------
## TF-IDF using sklearn module
##------------------------------
from sklearn.feature_extraction.text import TfidfVectorizer

tfidf = TfidfVectorizer()
bow_rep_tfidf = tfidf.fit_transform(processed_docs) #  말뭉치 안의 단어 추출 및 TF-IDF 점수 계산


## All words in the vocabulary.
print(tfidf.get_feature_names()) #  말뭉치 안의 모든 단어 리스트
#[Out] ['bites', 'dog', 'eats', 'food', 'man', 'meat']


## sorting vocabulary dict by values in ascending order
sorted(tfidf.vocabulary_.items(), key = lambda x: x[1]) # 단어 사전을 index 기준으로 내림차순 정렬
#[Out] [('bites', 0), ('dog', 1), ('eats', 2), ('food', 3), ('man', 4), ('meat', 5)]


## IDF for all words in the vocabulary
print("IDF :",tfidf.idf_)
#[Out] IDF : [1.51082562 1.22314355 1.51082562 1.91629073 1.22314355 1.91629073]

 

 

 

 본문 상단에 제시한 예제에서 손으로 계산한 TF-IDF 점수와 아래에 Scikit-learn 의 TfidfVedtorizer() 메소드로 계산한 TF-IDF 점수가 서로 다르다.

 

 Scikit-learn 에서 사용한 IDF 수식이 조금 다르다. (소스코드 공식은 여기 참조).

분모가 '0' 일때 'Zero Division Error' 가 발생하지 않도록 분모에 '1'을 더해주었으며, 분자에도 log(0) 도 계산이 안되므로 에러가 발생하지 않도록 분자에도 '1' 을 더해주었고, 전체 값이 '1'을 더해주었다.  

 

Scikit-Learn의 IDF 공식: IDF(t) = log((1+n) / (1+df(t))) + 1

 

 또한, 위의 Scikit-Learn 의 TF-IDF 점수 계산 결과를 유클리디언 거리를 사용해서 표준화를 해주기 때문에 값이 다르다. (자세한 설명은 여기 참조)

 

## IDF for all words in the vocabulary
## “Sklearn’s TF-IDF” vs “Standard TF-IDF”
## : https://towardsdatascience.com/how-sklearns-tf-idf-is-different-from-the-standard-tf-idf-275fa582e73d
## Scikit-Learn's IDF: IDF(t) = log((1+n)/(1+df(t))) + 1
## normalization by the Euclidean norm

print("IDF :",tfidf.idf_)
#[Out] IDF : [1.51082562 1.22314355 1.51082562 1.91629073 1.22314355 1.91629073]


# IDF for 'dog' word
# number of documents, nN=4 
# number of documents which include term t, df(t) = 3
import numpy as np

np.log((1+4)/(1+3)) + 1 # IDF(t) = log((1+n)/(1+df(t))) + 1
#[Out] 1.2231435513142097

 

## TF-IDF representation for all documents in our corpus 
print("TF-IDF representation for all documents in our corpus\n", bow_rep_tfidf.toarray())
# 말뭉치의 모든 문서 내 단어에 대한 TF-IDF 점수를 2차원 배열로 표현

#[Out] TF-IDF representation for all documents in our corpus
# [[0.65782931 0.53256952 0.         0.         0.53256952 0.        ]
# [0.65782931 0.53256952 0.         0.         0.53256952 0.        ]
# [0.         0.44809973 0.55349232 0.         0.         0.70203482]
# [0.         0.         0.55349232 0.70203482 0.44809973 0.        ]]

 

## Get the TF-IDF score using this vocabulary, for a new text
temp = tfidf.transform(["dog and man are friends"]) # 새로운 문서 내 단어에 대한 TF-IDF 점수 계산
print("Tfidf representation for 'dog and man are friends':\n", temp.toarray())

#[Out] Tfidf representation for 'dog and man are friends':
# [[0.         0.70710678 0.         0.         0.70710678 0.        ]]

 

 

 

[Reference]

Sowmya Vajjala, et.al., "Practical Natural Language Processing", O'Reilly