Last Updated on 2021-10-19 by Clay
TF-IDF (Term Frequency - Inverse Document Frequency) 是在文字探勘、自然語言處理當中相當著名的一種文字加權方法,能夠反映出『詞彙』對於『文件』的重要性。跟著名的 Word2Vec 同樣能夠將文字轉換成向量,以供電腦進行計算。
以下,我會逐一介紹 TF-IDF 的原理、公式、以及該如何透過程式實作 TF-IDF。
TF-IDF 原理
正如前言所說,TF-IDF 常用於資訊檢索的加權,而所謂的『詞彙對於文件的重要性』可以理解成一個詞彙對於某一特定文件的權重 —— 那麼問題來了,我們該如何定義所謂的『重要性』呢?
TF-IDF 做了以下這樣的假設:
- 一個『詞彙』越常出現在一篇『文件』中,這個『詞彙』越重要
- 一個『詞彙』越常出現在多篇『文件』中,這個『詞彙』越不重要
舉例來說,假設我們今天有一大堆的『美國旅遊介紹文章』,而『的』和『玉米』在 A 文章中都是高頻詞,但是『的』經常出現在其他文章中、而『玉米』則只出現在 A 文章以及其他少數幾篇文章中。
那們我們就會透過 TF-IDF 給予的權重認定,『玉米』對於 A 文章來說是個相當重要的詞彙,能夠很好地讓 A 文章與其他文章做出區分。相對地,『的』這個詞對於 A 文章來說就一點兒也不重要,因為這是大多數的文章都會出現的詞彙。
以上是 TF-IDF 基本原理,以下就來比較詳細地介紹 TF-IDF 的公式。
TF-IDF 公式
TF-IDF 通常會分成 TF 以及 IDF 兩個部份來討論:
TF 就是所謂的『詞頻』,是一個詞彙在一篇文件中的出現頻率,計算方法為『詞彙在該文件中出現次數 / 該文件中所有詞彙數』。公式如下:
分子 n<i,j> 為詞 t<i> 在文件 d<j> 中出現次數。
分母則為文件 d<j> 所有詞彙數量。
IDF 則是所謂的『逆向文件頻率』,計算方法為『log( 總文件數量 / 包含該詞彙的文件數量 )』,跟上方的原理講的是同一個道理,旨在確認某一詞彙是否會出現在許多文件中。
公式如下:
log 為取 10 為底的對數函數。
分子為所有文件數量。
分母為包含該詞彙的文件數量。
不過考慮到分母可能為 0 的情況,通常分母會再額外加 1。
然後所謂的 TF-IDF,就是將 TF 值乘上 IDF 值。
實作 TF-IDF 程式
在開始實作之前,要事先說明 Scikit-Learn 其實已經有寫好的套件了,很多時候,除非要學習或是特別做些調整,否則多半都建議直接呼叫別人寫好的工具,避免重複造輪子。
以下的範例程式會使用到 Scikit-Learn 以及其他套件,若環境中尚未安裝可以使用以下指令安裝:
pip3 install matplotlib
pip3 install pandas
pip3 install scikit-learn
然後我們便來使用 Scikit-Learn 幫我們計算 TF-IDF 吧!測試的 4 個文件為我隨手寫下的。
# coding: utf-8
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
# Documents
doc_0 = 'Today is a nice day'
doc_1 = 'Today is a bad day'
doc_2 = 'Today I want to play all day'
doc_3 = 'I went to play all day yesterday'
doc_all = [doc_0, doc_1, doc_2, doc_3]
# TF-IDF
vectorizer = TfidfVectorizer(smooth_idf=True)
tfidf = vectorizer.fit_transform(doc_all)
result = pd.DataFrame(tfidf.toarray(), columns=vectorizer.get_feature_names())
print('Scikit-Learn:')
print(result)
Output:
以下,則是我按照公式直接寫的 TF-IDF 計算程式:
# coding: utf-8
import math
import pandas as pd
from sklearn.preprocessing import normalize
# Documents
doc_0 = 'Today is a nice day'
doc_1 = 'Today is a bad day'
doc_2 = 'Today I want to play all day'
doc_3 = 'I went to play all day yesterday'
doc_all = [doc_0, doc_1, doc_2, doc_3]
doc_all = [[word.lower() for word in doc.split() if len(word) >= 2] for doc in doc_all]
# TF
tf = dict()
for n in range(len(doc_all)):
for word in doc_all[n]:
if word not in tf: tf[word] = [0 for _ in doc_all]
tf[word][n] = sum([1 for term in doc_all[n] if term == word])/len(doc_all[n])
# IDF
total_D = len(doc_all)
idf = dict()
for doc in doc_all:
for word in doc:
if word not in idf:
word_idf = math.log10(total_D/sum([1 for doc in doc_all if word in doc])+1)
idf[word] = word_idf
# TF-IDF
sorted_word = sorted(set([word for word in tf]))
tfidf = list()
for word in sorted_word:
value = tf[word]
value = [v*idf[word] for v in value]
tfidf.append(value)
tfidf = normalize(tfidf, norm='l2')
results = dict()
for n in range(len(sorted_word)):
results[sorted_word[n]] = tfidf[n]
print(pd.DataFrame(results).transpose())
Output:
會有不一樣的地方相當正常。
在 Scikit-Learn 中,TF 和 IDF 的公式計算都不是經典的,而是變體的 —— 實際上在 Scikit-Learn 中還有許多可以調整的參數細項,至於哪種在任務中好用,就得實際測試之後才能得知了。
References
- https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html
- https://towardsdatascience.com/tf-idf-explained-and-python-sklearn-implementation-b020c5e83275
- https://medium.com/@cmukesh8688/tf-idf-vectorizer-scikit-learn-dbc0244a911a
- https://kavita-ganesan.com/tfidftransformer-tfidfvectorizer-usage-differences/