實際上TFIDF分成兩個部份,TF和IDF。分別表示詞頻(term frequency,tf)和逆向檔案頻率(inverse document frequency,idf)。和Word2Vec一樣,是種將文字轉換為向量的方式。
不過我只理解TFIDF而已。TextRank也是一種辦法,是由PageRank變形而來,但也只有一些概念而已。
接著簡單介紹TF和IDF這兩個部份,理解也有助於使用scikit-learn裡的TFIDF。
TFIDF最常被使用的一個目的是,找到文件當中的關鍵字。怎樣的關鍵字是重要的?一個直覺的想法是出現最多次的字。這可能可以,不過因為每個文件的字數不同,無法比較。所以在用文件內的字數作為分母,將所有文件得到數值加以規範化。這也就是TF,特定單字在文件出現次數/文件總次數:
不過只是這樣,很有可能讓一些定冠詞,或是常用單字,像是a, an, the, and, or 等等,得到很高的分數。一個簡單的作法是先把這些字詞去掉(stopword),不過還有一種方式是將低這些單字的分數。IDF會去計算一個字出現在文件的逆向頻率,這表示出現頻率越高,出現在越多文件之中,但是得分會越低。透過TF和IDF相乘:TFxIDF,得到的綜合分數就是TFIDF。
一般stopword和idf會一起使用。
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd
CountVectorizer
會計算單字出現在文件的次數;再透過TfidfVectorizer
轉換成TFIDF和IDF。也可以直接使用TfidfTransformer
計算TFIDF。但先來建立幾個假文:
d1 = 'a b d e d f a f e fa d s a b n'
d2 = 'a z a f e fa h'
d3 = 'a z a f e fa h'
這裡建立簡單的三個文本。
可以很簡單的使用新增CountVectorizer
和TfidfVectorizer
,並使用其方法fit()
。來看看:
vectorizer = CountVectorizer(stop_words=None, token_pattern="(?u)\\b\\w+\\b")
X = vectorizer.fit_transform([d1,d2,d3])
r = pd.DataFrame(X.toarray(),columns=vectorizer.get_feature_names())
print("CountVector")
r
vectorizer.get_feature_names()
可以取得計算的單字。另外,原本的token_pattern
是(?u)\\b\\w\\w+\\b
,會過濾掉兩個字母以下的內容,但測試文本使用單個字母來測試,所以要加以改寫。將stop_word
設為None
也是同樣道理,比免去除單字,因為只是範例,而想看看所有結果:
CountVector:
a b d e f fa h n s z
d1 3 2 3 2 2 1 0 1 1 0
d2 2 0 0 1 1 1 1 0 0 1
d3 2 0 0 1 1 1 1 0 0 1
轉換方式很類似:
transformer = TfidfTransformer(smooth_idf=True)
Z = transformer.fit_transform(X)
r = pd.DataFrame(Z.toarray(),columns=vectorizer.get_feature_names(), index=['d1', 'd2', 'd3'])
print("TFIDF")
r
然後結果:
TFIDF:
a b d e f fa h n s z
d1 0.384107 0.433566 0.650349 0.256071 0.256071 0.128036 0.000000 0.216783 0.216783 0.000000
d2 0.622686 0.000000 0.000000 0.311343 0.311343 0.311343 0.400911 0.000000 0.000000 0.400911
d3 0.622686 0.000000 0.000000 0.311343 0.311343 0.311343 0.400911 0.000000 0.000000 0.400911
在transformer
有一個屬性idf_
儲存IDF值。
print("IDF")
pd.DataFrame([transformer.idf_], columns=vectorizer.get_feature_names())
直接使用的方式也很類似,就不贅述。
vectorizer = TfidfVectorizer(sublinear_tf=False, stop_words=None, token_pattern="(?u)\\b\\w+\\b", smooth_idf=True)
X = vectorizer.fit_transform([d1,d2,d3])
r = pd.DataFrame(X.toarray(),columns=vectorizer.get_feature_names(), index=['d1', 'd2', 'd3'])
print("TFIDF")
r
其接果也一模一樣,IDF值也可以透過vectorizer.idf_
取得。
根據IDF的公式:
將字母a
的結果帶入,應該是lg(3/3)=lg(1)=0
,但得到結果卻是1?翻翻看原始碼,是加1了沒錯,但是其他結果似乎也不太對。只有CountVector很簡單是一樣的,其他不管是TFIDF的結果還是IDF的結果都與公式不符。在看過其他文章後,才知道TFIDF有其他變形。TfidfTransformer
和TfidfVectorizer
有可多可選的參數可調整,這接參數也會影響結果。
這個結果以前沒去注意都沒發現。還想說是不是我算錯,但是也有人是有同樣發現,scikit-learn裡的TFIDF並不是經典的算法。
最後需要說明的是,由於函式 TfidfVectorizer() 有很多引數,我們這裡僅僅採用了預設的形式,所以輸出的結果可能與採用前面介紹的(最基本最原始的)演算法所得出之結果有所差異(但數量的大小關係並不會改變)。