Post

NLP 기초: TF-IDF 개념 , 계산 방법, 코드 구현(직접 구현부터 라이브러리를 사용한 간단 구현까지)

이번 게시물에서는 텍스트 마이닝에서 많이 언급되는 TF-IDF의 개념부터, 공식까지 정리해보자. 특정 단어가 문서 내에서 얼마나 중요한 지를 나타내는 척도인 TF-IDF는 Language 모델 평가지표에도 많이 응용되므로, 먼저 개념을 확실하게 짚고 넘어가도록 해보자.




TF-IDF


TF-IDF는 Term Frequency-Inverse Document Frequency를 의미하고, 여러 문서로 이루어진 문서군이 있을 때 어떤 단어가 특정 문서 내에서 얼마나 중요한 것인지를 나타내는 통계적 수치이다. 주로 텍스트 데이터의 정보 검색, 텍스트 마이닝에서 이용하는 가중치 척도로, 문서의 핵심어를 추출하거나, 검색엔진에서 검색 결과의 순위를 결정하거나, 문서들 사이의 비슷한 정도를 구하는 등의 용도로 사용된다.

TF-IDF는 특정 문서에서 자주 등장하는 단어는 중요도가 높지만(TF), 모든 문서에서 자주 등장하는 단어는 중요도가 낮다(IDF)는 개념으로, 단어의 빈도수에 기반한 방식이다.

\[\text{tf-idf}\;(t,d,D) = \text{tf}(t, d) \times \text{idf} (t, D)\]

t는 특정 단어, d는 특정 문서, D는 전체 문서를 의미한다. TF와 IDF의 곱으로 계산되며, 각각은 다음과 같이 정의할 수 있다.




TF (Term Frequency)

특정 단어가 문서 내에서 자주 등장한다면 해당 단어는 중요하다고 판단한다. TF는 단순히 문서 내에 나타나는 해당 단어의 총 빈도수를 의미한다. 즉, DTM의 각 단어들이 가진 값이다. (단순 빈도 기반 TF)

TF-IDF_1.png

DTM(Document-Term Matrix)* DTM이란 여러개의 문서에서 등장하는 각 단어들의 빈도를 구조화한 행렬로 표현한 것을 말한다. 문서 분석과 자연어 처리의 기초적인 도구이다. 이를 통해 문서 간의 유사도 분석, 단어의 중요도 계산, 정보 검색 등에 활용할 수 있으며, TF-IDF와 같은 추가 기법의 기반이 된다.



혹은 상대적인 단어 중요도를 나타내기 위해 아래와 같이 해당 문서 내의 단어수로 나눠주는 방법을 사용하기도 한다. (상대 빈도 기반 TF)

TF-IDF_2.png




IDF (Inverse Document Frequency)

한 단어가 문서 집합 전체에서 얼마나 공통적으로 나타나는지, 얼마나 흔하게 사용되는지를 나타내는 측정한다. 특정 단어가 모든 문서에서 자주 등장하는 단어라면 해당 단어는 중요하지 않다고 판단하여, 역(Inverse)으로 곱해주게 된다.

TF-IDF_3.png

위의 식에는 분모에 [단어 t를 포함한 문서의 수]라고 정의했지만, 분모가 0이 되는 것을 방지하기 위해 실제로는 [1 + 단어 t를 포함한 문서의 수]로 정의한다고 한다.

추가로 log를 붙이는 이유는, IDF의 값이 기하급수적으로 커지는 것을 방지하기 위함이다. 예를 들어, 총 문서의 수 $∣D∣=1,000,000$ 인 경우를 생각해보자.

단어 t를 포함한 문서의 수log가 없는 경우 idftf=3인 경우(가정) tf-idf 값
10100,0003 * 100,000 = 300,000
10010,0003 * 10,000 = 30,000
10001,0003 * 1,000 = 3,000


단어 t를 포함한 문서의 수log가 있는 경우 idftf=3인 경우(가정) tf-idf 값
1053 * 5 = 15
10043 * 4 = 12
100033 * 3 = 9


위와 같이 scale을 조정해주기 위해 사용한다. 실 사용 측면으로 봤을 때, 일반적인 단어들에 비해 희소한 단어들은 사용량에 있어 수백배의 차이가 존재한다고 한다. log를 사용하지 않는 경우 이러한 희소 단어들은 엄청난 가중치가 부여될 수 있다고 한다.






코드 구현


1. Python 직접 구현

python을 이용하여 TF-IDF를 직접 구현해보자. 필요한 라이브러리를 불러오고, 예시 문서를 다음과 같이 정의한다.

1
2
3
4
5
6
7
8
9
10
11
12
import math
from collections import Counter
import pandas as pd

# 예시 문서들
documents = [
    "this is a sample",                      # 문서 1
    "this is another example example",       # 문서 2
    "this example is a simple sample",       # 문서 3
    "another test document",                 # 문서 4
    "this is a good example"                 # 문서 5
]



먼저, DTM을 구현하여 TF-IDF결과와 비교하기 쉽도록 단어들의 빈도수를 출력해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def create_dtm(documents):
    # 모든 문서에서 중복되지 않는 단어의 목록 추출
    dtm_data = []
    all_words = sorted(set([word for doc in documents for word in doc]))

    # 각 문서에 대해 단어 빈도를 계산
    for doc in documents:
        word_count = Counter(doc)
        dtm_data.append([word_count.get(word, 0) for word in all_words])

    # DataFrame으로 변환하여 DTM 생성
    dtm_df = pd.DataFrame(dtm_data, columns=all_words)
    return dtm_df

documents_tokenized = [doc.split() for doc in documents]
dtm = create_dtm(documents_tokenized)

TF-IDF_4.png



이제 TF, IDF를 각각 구현하고 TF-IDF값을 구하는 함수를 만들어보자. TF는 단순 빈도 기반으로 구현했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def compute_tf(document):
    tf_dict = Counter(document)
    return tf_dict
    

def compute_idf(documents):
    N = len(documents)
    idf_dict = {}
    # 모든 문서에서 중복되지 않는 단어의 목록 추출
    all_words = set([word for doc in documents for word in doc])

    for word in all_words:
        # 해당 단어가 등장한 문서의 수 계산
        df = sum(1 for doc in documents if word in doc)
        idf_dict[word] = math.log(N / (df + 1))
    return idf_dict

def tf_idf(documents):
    tf_list = [compute_tf(doc) for doc in documents]
    idf_dict = compute_idf(documents)

    tf_idf_list = []
    for i, doc in enumerate(documents):
        tf_idf_dict = {}
        for word in doc:
            tf = tf_list[i].get(word, 0)
            idf = idf_dict.get(word, 0)
            # TF와 IDF를 곱해서 TF-IDF 값 계산
            tf_idf_dict[word] = tf * idf
        tf_idf_list.append(tf_idf_dict)

    return tf_idf_list



그럼 이제 각 문서들에서 TF-IDF값을 구하고, 출력해보자.

1
2
3
4
5
6
7
8
9
10
11
documents_tokenized = [doc.split() for doc in documents]

# TF-IDF 계산
tf_idf_result = tf_idf(documents_tokenized)

# TF-IDF 결과 출력
for i, doc_tf_idf in enumerate(tf_idf_result):
    print()
    print(f"\nDocument {i+1} TF-IDF:")
    for word, score in doc_tf_idf.items():
        print(f"{word}: {score:.4f}")

TF-IDF_5.png




2. Scikit-Learn을 활용한 구현

직접 구현해보면서 를 익히는 것도 좋지만, scikit-learn을 사용하면 아주 쉽게 TF-IDF를 사용할 수 있다. 먼저 scikit-learn 라이브러리와 예제 문서를 아래와 같이 정의했다.

1
2
3
4
5
6
7
8
9
10
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer

# 예시 문서들
documents = [
    "this is a sample",                      # 문서 1
    "this is another example example",       # 문서 2
    "this example is a simple sample",       # 문서 3
    "another test document",                 # 문서 4
    "this is a good example"                 # 문서 5
]



직접 구현했을 때와 마찬가지로 DTM을 먼저 구해보자.

1
2
3
4
5
6
7
8
9
10
11
# CountVectorizer를 사용하여 DTM 계산
count_vectorizer = CountVectorizer()
dtm = count_vectorizer.fit_transform(documents)

# DTM 출력 (희소 행렬을 밀집 행렬로 변환하여 출력)
print("Document-Term Matrix (DTM):")
print(dtm.toarray())

# 각 단어에 해당하는 열의 이름 출력
print("\nFeature Names (Terms in DTM):")
print(count_vectorizer.get_feature_names_out())

TF-IDF_6.png



다음으로 TfidfVectorizer 객체를 생성하여 TF-IDF를 구현한다. 결과값을 보면 직접 계산한것과는 차이가 있다. 이부분은 정규화 여부와 구현상으로 IDF 구하는 공식의 차이 때문이라고 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# TfidfVectorizer를 사용하여 TF-IDF 계산
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(documents)

# 각 단어에 대응하는 열의 이름 (단어들)
feature_names = vectorizer.get_feature_names_out()

# 각 단어와 그에 해당하는 TF-IDF 값 출력
for doc_idx, doc in enumerate(tfidf_matrix.toarray()):
    print(f"\nDocument {doc_idx+1} TF-IDF:")
    for word_idx, tfidf_value in enumerate(doc):
        if tfidf_value > 0:  # 0이 아닌 값만 출력
            print(f"{feature_names[word_idx]}: {tfidf_value:.4f}")

TF-IDF_7.png



This post is licensed under CC BY 4.0 by the author.