TF-IDF概述
TF-IDF是Term Frequency - Inverse Document Frequency的縮寫桅锄,即“詞頻-逆文本頻率”瓣铣。它由兩部分組成诸衔,TF和IDF尼酿。
前面的TF也就是我們前面說到的詞頻恭理,我們之前做的向量化也就是做了文本中各個(gè)詞的出現(xiàn)頻率統(tǒng)計(jì),并作為文本特征剿牺,這個(gè)很好理解企垦。關(guān)鍵是后面的這個(gè)IDF,即“逆文本頻率”如何理解晒来。在上一節(jié)中竹观,我們講到幾乎所有文本都會(huì)出現(xiàn)的"to"其詞頻雖然高,但是重要性卻應(yīng)該比詞頻低的"China"和“Travel”要低潜索。我們的IDF就是來幫助我們來反應(yīng)這個(gè)詞的重要性的,進(jìn)而修正僅僅用詞頻表示的詞特征值懂酱。
概括來講竹习, IDF反應(yīng)了一個(gè)詞在所有文本中出現(xiàn)的頻率,如果一個(gè)詞在很多的文本中出現(xiàn)列牺,那么它的IDF值應(yīng)該低整陌,比如上文中的“to”。而反過來如果一個(gè)詞在比較少的文本中出現(xiàn),那么它的IDF值應(yīng)該高泌辫。比如一些專業(yè)的名詞如“Machine Learning”随夸。這樣的詞IDF值應(yīng)該高。一個(gè)極端的情況震放,如果一個(gè)詞在所有的文本中都出現(xiàn)宾毒,那么它的IDF值應(yīng)該為0。
上面是從定性上說明的IDF的作用殿遂,那么如何對(duì)一個(gè)詞的IDF進(jìn)行定量分析呢诈铛?這里直接給出一個(gè)詞
其中,N代表語料庫中文本的總數(shù)墨礁,而N(x)代表語料庫中包含詞x的文本總數(shù)幢竹。為什么IDF的基本公式應(yīng)該是是上面這樣的而不是像N/N(x)這樣的形式呢?這就涉及到信息論相關(guān)的一些知識(shí)了恩静。感興趣的朋友建議閱讀吳軍博士的《數(shù)學(xué)之美》第11章焕毫。
上面的IDF公式已經(jīng)可以使用了,但是在一些特殊的情況會(huì)有一些小問題驶乾,比如某一個(gè)生僻詞在語料庫中沒有邑飒,這樣我們的分母為0, IDF沒有意義了轻掩。所以常用的IDF我們需要做一些平滑幸乒,使語料庫中沒有出現(xiàn)的詞也可以得到一個(gè)合適的IDF值。平滑的方法有很多種唇牧,最常見的IDF平滑后的公式之一為:
tf-idf 計(jì)算公式
"tf-idf 計(jì)算公式"
tf-idf(x)= (log(N+1/(N(x)+1))+1) ~~IDF(x)~~ *(WordCount/totalWord)
TfidfVectorizer 中文處理方法
第一種:CountVectorizer+TfidfTransformer的組合
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer
corpus=["I come to China to travel",
"This is a car polupar in China",
"I love tea and Apple ",
"The work is to write some papers in science"]
vectorizer=CountVectorizer()
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(vectorizer.fit_transform(corpus))
print tfidf
第二種
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf2 = TfidfVectorizer()
re = tfidf2.fit_transform(corpus)
print re
corpus = ["I have a pen.", "I have an apple."]
輸出的各個(gè)文本各個(gè)詞的TF-IDF值和第一種的輸出完全相同罕扎。大家可以自己去驗(yàn)證一下。
由于第二種方法比較的簡(jiǎn)潔丐重,因此在實(shí)際應(yīng)用中推薦使用腔召,一步到位完成向量化,TF-IDF與標(biāo)準(zhǔn)化扮惦。
例1 :遍歷某一類文本下的詞語權(quán)重
import jieba
import jieba.posseg as pseg
import os
import sys
from sklearn import feature_extraction
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer
if __name__ == "__main__":
corpus=["我 來到 北京 清華大學(xué)",#第一類文本切詞后的結(jié)果臀蛛,詞之間以空格隔開
"他 來到 了 網(wǎng)易 杭研 大廈",#第二類文本的切詞結(jié)果
"小明 碩士 畢業(yè) 與 中國(guó) 科學(xué)院",#第三類文本的切詞結(jié)果
"我 愛 北京 天安門"]#第四類文本的切詞結(jié)果
vectorizer=CountVectorizer()#該類會(huì)將文本中的詞語轉(zhuǎn)換為詞頻矩陣,矩陣元素a[i][j] 表示j詞在i類文本下的詞頻
transformer=TfidfTransformer()#該類會(huì)統(tǒng)計(jì)每個(gè)詞語的tf-idf權(quán)值
tfidf=transformer.fit_transform(vectorizer.fit_transform(corpus))#第一個(gè)fit_transform是計(jì)算tf-idf崖蜜,第二個(gè)fit_transform是將文本轉(zhuǎn)為詞頻矩陣
word=vectorizer.get_feature_names()#獲取詞袋模型中的所有詞語
weight=tfidf.toarray()#將tf-idf矩陣抽取出來浊仆,元素a[i][j]表示j詞在i類文本中的tf-idf權(quán)重
for i in range(len(weight)):#打印每類文本的tf-idf詞語權(quán)重,第一個(gè)for遍歷所有文本豫领,第二個(gè)for遍歷某一類文本下的詞語權(quán)重
print u"-------這里輸出第",i,u"類文本的詞語tf-idf權(quán)重------"
for j in range(len(word)):
print word[j],weight[i][j]
元素a[i][j]表示j詞在i類文本中的tf-idf權(quán)重
打印結(jié)果:
-------這里輸出第 0 類文本的詞語tf-idf權(quán)重------ #該類對(duì)應(yīng)的原文本是:"我來到北京清華大學(xué)"
中國(guó) 0.0
北京 0.52640543361
大廈 0.0
天安門 0.0
小明 0.0
來到 0.52640543361
杭研 0.0
畢業(yè) 0.0
清華大學(xué) 0.66767854461
碩士 0.0
科學(xué)院 0.0
網(wǎng)易 0.0
-------這里輸出第 1 類文本的詞語tf-idf權(quán)重------ #該類對(duì)應(yīng)的原文本是: "他來到了網(wǎng)易杭研大廈"
中國(guó) 0.0
北京 0.0
大廈 0.525472749264
天安門 0.0
小明 0.0
來到 0.414288751166
杭研 0.525472749264
畢業(yè) 0.0
清華大學(xué) 0.0
碩士 0.0
科學(xué)院 0.0
網(wǎng)易 0.525472749264
-------這里輸出第 2 類文本的詞語tf-idf權(quán)重------ #該類對(duì)應(yīng)的原文本是: "小明碩士畢業(yè)于中國(guó)科學(xué)院“
中國(guó) 0.4472135955
北京 0.0
大廈 0.0
天安門 0.0
小明 0.4472135955
來到 0.0
杭研 0.0
畢業(yè) 0.4472135955
清華大學(xué) 0.0
碩士 0.4472135955
科學(xué)院 0.4472135955
網(wǎng)易 0.0
-------這里輸出第 3 類文本的詞語tf-idf權(quán)重------ #該類對(duì)應(yīng)的原文本是: "我愛北京天安門"
中國(guó) 0.0
北京 0.61913029649
大廈 0.0
天安門 0.78528827571
小明 0.0
來到 0.0
杭研 0.0
畢業(yè) 0.0
清華大學(xué) 0.0
碩士 0.0
科學(xué)院 0.0
網(wǎng)易 0.0
例2 原始文本轉(zhuǎn)化為tf-idf的特征矩陣
TfidfVectorizer可以把原始文本轉(zhuǎn)化為tf-idf的特征矩陣抡柿,從而為后續(xù)的文本相似度計(jì)算,主題模型等恐,文本搜索排序等一系列應(yīng)用奠定基礎(chǔ)洲劣”蛤荆基本應(yīng)用如:
#coding=utf-8
from sklearn.feature_extraction.text import TfidfVectorizer
document = ["I have a pen.",
"I have an apple."]
tfidf_model = TfidfVectorizer().fit(document)
sparse_result = tfidf_model.transform(document) # 得到tf-idf矩陣,稀疏矩陣表示法
print(sparse_result)
# (0, 3) 0.814802474667
# (0, 2) 0.579738671538
# (1, 2) 0.449436416524
# (1, 1) 0.631667201738
# (1, 0) 0.631667201738
print(sparse_result.todense()) # 轉(zhuǎn)化為更直觀的一般矩陣
# [[ 0. 0. 0.57973867 0.81480247]
# [ 0.6316672 0.6316672 0.44943642 0. ]]
print(tfidf_model.vocabulary_) # 詞語與列的對(duì)應(yīng)關(guān)系
# {'have': 2, 'pen': 3, 'an': 0, 'apple': 1}
但是要把它運(yùn)用到中文上還需要一些特別的處理囱稽,故寫此文分享我的經(jīng)驗(yàn)郊尝。
第一步:分詞
中文不比英文,詞語之間有著空格的自然分割战惊,所以我們首先要進(jìn)行分詞處理流昏,再把它轉(zhuǎn)化為與上面的document類似的格式。這里采用著名的中文分詞庫jieba進(jìn)行分詞:
import jieba
text = """我是一條天狗呀样傍!
我把月來吞了横缔,
我把日來吞了,
我把一切的星球來吞了衫哥,
我把全宇宙來吞了茎刚。
我便是我了!"""
sentences = text.split()
sent_words = [list(jieba.cut(sent0)) for sent0 in sentences]
document = [" ".join(sent0) for sent0 in sent_words]
print(document)
# ['我 是 一條 天狗 呀 撤逢!', '我 把 月 來 吞 了 膛锭,', '我 把 日來 吞 了 ,', '我 把 一切 的 星球 來 吞 了 蚊荣,', '我 把 全宇宙 來 吞 了 初狰。', '我 便是 我 了 !']
PS:語料來自郭沫若《天狗》互例。另外奢入,由于分詞工具的不完善,也會(huì)有一些錯(cuò)誤媳叨,比如這邊錯(cuò)誤地把"日來"分到了一起腥光。
第二步:建模
理論上,現(xiàn)在得到的document的格式已經(jīng)可以直接拿來訓(xùn)練了糊秆。讓我們跑一下模型試試武福。
tfidf_model = TfidfVectorizer().fit(document)
print(tfidf_model.vocabulary_)
# {'一條': 1, '天狗': 4, '日來': 5, '一切': 0, '星球': 6, '全宇宙': 3, '便是': 2}
sparse_result = tfidf_model.transform(document)
print(sparse_result)
# (0, 4) 0.707106781187
# (0, 1) 0.707106781187
# (2, 5) 1.0
# (3, 6) 0.707106781187
# (3, 0) 0.707106781187
# (4, 3) 1.0
# (5, 2) 1.0
沒有錯(cuò)誤,但有一個(gè)小問題痘番,就是單字的詞語捉片,如“我”、“吞”汞舱、“呀”等詞語在我們的詞匯表中怎么都不見了呢伍纫?為了處理一些特殊的問題,讓我們深入其中的一些參數(shù)昂芜。
第三步:參數(shù)
查了一些資料以后翻斟,發(fā)現(xiàn)單字的問題是token_pattern這個(gè)參數(shù)搞的鬼。它的默認(rèn)值只匹配長(zhǎng)度≥2的單詞说铃,就像其實(shí)開頭的例子中的'I'也被忽略了一樣访惜,一般來說,長(zhǎng)度為1的單詞在英文中一般是無足輕重的腻扇,但在中文里债热,就可能有一些很重要的單字詞,所以修改如下:
tfidf_model2 = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b").fit(document)
print(tfidf_model2.vocabulary_)
# {'我': 8, '是': 12, '一條': 1, '天狗': 7, '呀': 6, '把': 9, '月': 13, '來': 14, '吞': 5, '了': 2, '日來': 10, '一切': 0, '的': 15, '星球': 11, '全宇宙': 4, '便是': 3}
token_pattern這個(gè)參數(shù)使用正則表達(dá)式來分詞幼苛,其默認(rèn)參數(shù)為r"(?u)\b\w\w+\b"窒篱,其中的兩個(gè)\w決定了其匹配長(zhǎng)度至少為2的單詞,所以這邊減到1個(gè)舶沿。對(duì)這個(gè)參數(shù)進(jìn)行更多修改墙杯,可以滿足其他要求,比如這里依然沒有得到標(biāo)點(diǎn)符號(hào)括荡,在此不詳解了高镐。
當(dāng)然有些時(shí)候我們還是要過濾掉一些無意義的詞,下面有些別的參數(shù)也可以幫助我們實(shí)現(xiàn)這一目的:
1.max_df/min_df: [0.0, 1.0]內(nèi)浮點(diǎn)數(shù)或正整數(shù), 默認(rèn)值=1.0
當(dāng)設(shè)置為浮點(diǎn)數(shù)時(shí)畸冲,過濾出現(xiàn)在超過max_df/低于min_df比例的句子中的詞語嫉髓;正整數(shù)時(shí),則是超過max_df句句子。
這樣就可以幫助我們過濾掉出現(xiàn)太多的無意義詞語邑闲,如下面的"我"就被過濾(雖然這里“我”的排比在文學(xué)上是很重要的)算行。
# 過濾出現(xiàn)在超過60%的句子中的詞語
tfidf_model3 = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b", max_df=0.6).fit(document)
print(tfidf_model3.vocabulary_)
# {'是': 8, '一條': 1, '天狗': 5, '呀': 4, '月': 9, '來': 10, '日來': 6, '一切': 0, '的': 11, '星球': 7, '全宇宙': 3, '便是': 2}
2.stop_words: list類型
直接過濾指定的停用詞。
tfidf_model4 = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b", max_df=0.6, stop_words=["是", "的"]).fit(document)
print(tfidf_model4.vocabulary_)
# {'一條': 1, '天狗': 5, '呀': 4, '月': 8, '來': 9, '日來': 6, '一切': 0, '星球': 7, '全宇宙': 3, '便是': 2}
3.vocabulary: dict類型
只使用特定的詞匯苫耸,其形式與上面看到的tfidf_model4.vocabulary_相同州邢,也是指定對(duì)應(yīng)關(guān)系。這一參數(shù)的使用有時(shí)能幫助我們專注于一些詞語褪子,比如我對(duì)本詩中表達(dá)感情的一些特定詞語(甚至標(biāo)點(diǎn)符號(hào))感興趣量淌,就可以設(shè)定這一參數(shù),只考慮他們:
tfidf_model5 = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b",vocabulary={"我":0, "呀":1,"!":2}).fit(document)
print(tfidf_model5.vocabulary_)
# {'我': 0, '呀': 1, '!': 2}
print(tfidf_model5.transform(document).todense())
# [[ 0.40572238 0.91399636 0. ]
# [ 1. 0. 0. ]
# [ 1. 0. 0. ]
# [ 1. 0. 0. ]
# [ 1. 0. 0. ]