本文主要介紹了自然語言處理領(lǐng)域中文本表示的一個重要算法:TF-IDF算法。包括其基本概念,以及簡單的代碼實現(xiàn)。
TF-IDF概述
什么是TF-IDF?
詞頻-逆文檔頻率(Term Frequency-Inverse Document Frequency,TF-IDF)是一種常用于文本處理的統(tǒng)計方法,可以評估一個單詞在一份文檔中的重要程度。簡單來說就是可以用于文檔關(guān)鍵詞的提取。
TF-IDF的基本思想
看到下面這段文本,我們應該很容易就能看出“籃球”應該是一個關(guān)鍵詞,但是我們?nèi)绾瓮ㄟ^算法的形式讓計算機也能夠辨別呢?
籃球,是以手為中心的身體對抗性體育運動,是奧運會核心比賽項目。
1891年12月21日,由美國馬薩諸塞州斯普林菲爾德基督教青年會訓練學校體育教師詹姆士·奈史密斯發(fā)明。1896年,籃球運動傳入中國天津。1904年,圣路易斯奧運會上第1次進行了籃球表演賽。1936年,籃球在柏林奧運會中被列為正式比賽項目,中國也首次派出籃球隊參加奧運會籃球項目。1992年,巴塞羅那奧運會開始,職業(yè)選手可以參加奧運會籃球比賽。
籃球的最高組織機構(gòu)為國際籃球聯(lián)合會,于1932年成立,總部設(shè)在瑞士日內(nèi)瓦。中國最高組織機構(gòu)為中國籃球協(xié)會,于1956年10月成立。
腦海中想到的第一個方法就是對單詞出現(xiàn)的次數(shù)進行統(tǒng)計,也就是詞頻。如果一個單詞在文中出現(xiàn)的頻率很高,那我們是否可以認為這個單詞就是文章的關(guān)鍵詞呢?
其實不一定,詞頻很高的單詞往往更有可能是一些沒有意義的停用詞(stopword),例如“我”,“的”,“了”等等。
與此同時,在文章中出現(xiàn)次數(shù)很少的單詞也不一定是不重要的單詞。
因此,TF-IDF的基本思想是:如果某個單詞在一篇文章的出現(xiàn)的頻率很高,同時在其他文章中很少出現(xiàn),則認為該單詞大概率是一個關(guān)鍵詞。
詞頻(Term Frequency,TF)
詞頻統(tǒng)計的思路:單詞w在文檔d中出現(xiàn)的頻率。
最簡單的計算公式如下:
- count(d, w):單詞w在文檔d中出現(xiàn)的次數(shù)。
- count(d, *): 文檔d的總詞數(shù)。
逆文檔頻率(Inverse Document Frequency,IDF)
逆文檔頻率的思路:如果一個單詞在很多的文檔中出現(xiàn),則意味著該單詞的的重要性不高;反之則意味著該單詞的重要性很高。主要是考慮了單詞的重要性。
單詞w的IDF計算方法如下:
- N: 語料庫中的文檔總數(shù)。
- N(w): 單詞w出現(xiàn)在多少個文檔中。
文檔數(shù)量越大,同時單詞出現(xiàn)在越少的文檔中,IDF值就越大,則說明單詞越重要。
上面IDF公式已經(jīng)可以使用了,但是在一些特殊情況下可能會有一些小問題,比如某一個生僻詞在我們的語料庫中沒有出現(xiàn)過,那么分母N(w)=0,IDF就沒有意義了。
所以常用的IDF需要做平滑處理,使得沒有在語料庫中出現(xiàn)的單詞也可以得到一個合適的IDF值。
參考TF-IDF概述,常見的IDF平滑公式之一為:
TF-IDF計算公式
最終,單詞w的TF-IDF計算公式如下:
一個單詞的TF-IDF值越大,意味著該單詞越重要。

動手計算TF-IDF
下面通過3個簡單的文檔,演示一下如何計算TF-IDF。
句子1: 今天 上 NLP 課程
句子2: 今天 的 課程 有 意思
句子3: 數(shù)據(jù) 課程 也 有 意思
Step1 定義詞典
詞典的長度 |詞典|=9 :
[今天,上,NLP,課程,的,有,意思,數(shù)據(jù),也]
Step2 分別把每個句子用TF-IDF向量表示
句子1:
句子2:
句子3:
調(diào)用gensim的TF-IDF模型
先準備好3段文本,作為我們的輸入數(shù)據(jù):
text1 = """
籃球,是以手為中心的身體對抗性體育運動,是奧運會核心比賽項目。
1891年12月21日,由美國馬薩諸塞州斯普林菲爾德基督教青年會訓練學校體育教師詹姆士·奈史密斯發(fā)明。1896年,籃球運動傳入中國天津。1904年,圣路易斯奧運會上第1次進行了籃球表演賽。1936年,籃球在柏林奧運會中被列為正式比賽項目,中國也首次派出籃球隊參加奧運會籃球項目。1992年,巴塞羅那奧運會開始,職業(yè)選手可以參加奧運會籃球比賽。
籃球的最高組織機構(gòu)為國際籃球聯(lián)合會,于1932年成立,總部設(shè)在瑞士日內(nèi)瓦。中國最高組織機構(gòu)為中國籃球協(xié)會,于1956年10月成立。
"""
text2 = """
乒乓球,被稱為中國的“國球”,是一種世界流行的球類體育項目,包括進攻、對抗和防守。 [1]
乒乓球起源于英國,“乒乓球”一名起源自1900年,因其打擊時發(fā)出“Ping Pong”的聲音而得名。在中國大陸以“乒乓球”作為它的官方名稱,中國香港及澳門等地區(qū)亦同。1926年1月,在德國柏林舉行了一次國際乒乓球賽,共有9個國家的64名男運動員參加了比賽。同年12月,國際乒乓球聯(lián)合會正式成立,并把在倫敦舉行的歐洲錦標賽命名為第一屆世界乒乓球錦標賽。
乒乓球組織機構(gòu)設(shè)有國際乒乓球聯(lián)合會、亞洲乒乓球聯(lián)盟、中國乒乓球協(xié)會。
"""
text3 = """
羽毛球,是一項隔著球網(wǎng),使用長柄網(wǎng)狀球拍擊打用羽毛和軟木制作而成的一種小型球類的室內(nèi)運動項目。羽毛球比賽在長方形的場地上進行,場地中間有網(wǎng)相隔,雙方運用各種發(fā)球、擊球和移動等技戰(zhàn)術(shù),將球在網(wǎng)上往返對擊,以不使球落在本方有效區(qū)域內(nèi),或使對方擊球失誤為勝。
羽毛球運動的起源有很多說法,但最認可的是起源于14—15世紀的日本。而現(xiàn)代羽毛球運動是起源于印度,形成于英國。1875年,羽毛球運動正式出現(xiàn)于人們的視野中。1893年,英國的羽毛球俱樂部逐漸發(fā)展起來,成立了第一個羽毛球協(xié)會,規(guī)定了場地的要求和運動的標準。1939年,國際羽聯(lián)通過了各會員國共同遵守的第一部《羽毛球規(guī)則》。2006年,國際羽毛球聯(lián)合會(IBF)的正式名稱更改為羽毛球世界聯(lián)合會(BWF),即世界羽聯(lián)。
羽毛球運動的最高組織機構(gòu)是世界羽聯(lián),1934年在倫敦成立。中國最高組織機構(gòu)是中國羽毛球協(xié)會,1958年9月11日在武漢成立。
"""
Step1 文本預處理
采用以下步驟對上面的文本進行預處理:
- 分詞:這里使用了
jieba庫實現(xiàn)了分詞,如果是英文文本可以使用nltk庫進行相應的處理。 - 去除標點符號:如果要求更嚴格可以通過正則表達式的方式對單詞進行校驗,英文去除標點符號可以直接使用
string.punctuation。
import jieba
# 文本預處理
def get_words(text):
text = text.strip()
# 分詞結(jié)果
words = list(jieba.cut(text))
# 中文標點符號
punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~“”?,!【】()、。:;’‘……¥·"""
tokens = [w for w in words if w not in punctuation]
return tokens
經(jīng)過上面的處理,我們就能得到一段分詞好的文本:

按照上面的方法,我們對所有3個文本都進行分詞處理,組成語料庫:
# get text
count1, count2, count3 = get_words(text1), get_words(text2), get_words(text3)
# 語料庫
count_list = [count1, count2, count3]
Step2 調(diào)用gensim庫實現(xiàn)TF-IDF計算
訓練模型:
# training by TfidfModel in gensim
dictionary = corpora.Dictionary(count_list)
new_dict = {v:k for k,v in dictionary.token2id.items()}
corpus2 = [dictionary.doc2bow(count) for count in count_list]
tfidf2 = models.TfidfModel(corpus2)
corpus_tfidf = tfidf2[corpus2]
對結(jié)果進行輸出打印,只打印每個文本中IF-IDF值top3:
# output
print("\nTraining by gensim Tfidf Model.......\n")
for i, doc in enumerate(corpus_tfidf):
print("Top words in document %d"%(i + 1))
sorted_words = sorted(doc, key=lambda x: x[1], reverse=True) #type=list
for num, score in sorted_words[:3]:
print(" Word: %s, TF-IDF: %s"%(new_dict[num], round(score, 5)))
Output:
Training by gensim Tfidf Model.......
Top words in document 1
Word: 籃球, TF-IDF: 0.54722
Word: 奧運會, TF-IDF: 0.45601
Word: 比賽項目, TF-IDF: 0.18241
Top words in document 2
Word: 乒乓球, TF-IDF: 0.74579
Word: 舉行, TF-IDF: 0.16573
Word: 錦標賽, TF-IDF: 0.16573
Top words in document 3
Word: 羽毛球, TF-IDF: 0.68137
Word: 運動, TF-IDF: 0.30971
Word: 場地, TF-IDF: 0.18583
可以看出3個文本的關(guān)鍵詞分別是“籃球”、“乒乓球”和“羽毛球”,而上面3段文本其實就是百度百科中分別對于“籃球”、“乒乓球”和“羽毛球”的介紹。
自己實現(xiàn)TF-IDF算法
上面通過調(diào)用gensim庫實現(xiàn)了IF-IDF的計算,接下來我們自己實現(xiàn)一個簡單的TF-IDF算法,加深對TF-IDF的理解。
詞頻統(tǒng)計方法
首先,我們需要自己實現(xiàn)一個詞頻統(tǒng)計的方法:
from collections import Counter
# 統(tǒng)計詞頻
def make_count(text):
words = get_words(text)
filtered = words # 這里可以增加一個去處停用詞的步驟
count = Counter(filtered) # 計數(shù)
return count
對文本1進行詞頻統(tǒng)計,同時按照詞頻進行排序輸出,結(jié)果如下:

TF方法
根據(jù)TF的計算公式,我們可以實現(xiàn)TF如下:
def tf(word, count):
"""
計算詞頻
Args:
word (str): [要計算tf的單詞]
count (Counter): [當前文章中每個單詞及對應詞頻組成的字典類型數(shù)據(jù)結(jié)構(gòu)]
"""
return count[word] / sum(count.values())
統(tǒng)計包含單詞w的文本數(shù)N(w)
在統(tǒng)計之前,我們需要先對語料庫中所有文本進行詞頻統(tǒng)計:
count1, count2, count3 = make_count(text1), make_count(text2), make_count(text3)
count_list = [count1, count2, count3]
N(w)統(tǒng)計方法:
def n_containing(word, count_list):
"""
計算所有文章中有多少篇文章包含word
Args:
word (str): [指定單詞]
count_list (list): [由所有文章的Counter組成的list]
Returns:
int: [包含word的文章篇數(shù)]
"""
return sum(1 for count in count_list if word in count)
統(tǒng)計一下“籃球”出現(xiàn)的文章數(shù):

說明“籃球”只在一篇文章中出現(xiàn)。
IDF算法
注意:這里實現(xiàn)的IDF算法以2為底數(shù),同時也沒有進行平滑處理,和上面以10為底數(shù)的的公式不同。
import math
def idf(word, count_list):
"""
計算逆文檔頻率
Args:
word (str): [要計算idf的單詞]
count_list (list): [所有文章的count組成的list]
Returns:
float: [指定單詞的idf值]
"""
return math.log2(len(count_list) / (n_containing(word, count_list))) # 以2為底的對數(shù)
計算“籃球”的IDF值:

TF-IDF算法
分別有了TF和IDF,那么自然就可以得到TF-IDF算法:
def tfidf(word, count, count_list):
"""
Calculate TF-IDF
Args:
word (str): [要計算tfidf的單詞]
count (Counter): [當前文章中每個單詞及對應詞頻組成的字典類型數(shù)據(jù)結(jié)構(gòu)]
count_list (list): [所有文章的count組成的list]
Returns:
[float]: [指定單詞word的tfidf值]
"""
return tf(word, count) * idf(word, count_list)
計算“籃球”的TF-IDF值:

調(diào)用我們自己實現(xiàn)的TF-IDF算法,對所有文本進行關(guān)鍵詞提?。?/p>
print("Training by original algorithm......\n")
for i, count in enumerate(count_list):
print("Top words in document %d"%(i + 1))
scores = {word: tfidf(word, count, count_list) for word in count}
sorted_words = sorted(scores.items(), key=lambda x: x[1], reverse=True) #type=list
for word, score in sorted_words[:3]:
print(" Word: %s, TF-IDF: %s"%(word, round(score, 5)))
Output:
Training by original algorithm......
Top words in document 1
Word: 籃球, TF-IDF: 0.08491
Word: 奧運會, TF-IDF: 0.07076
Word: 比賽項目, TF-IDF: 0.0283
Top words in document 2
Word: 乒乓球, TF-IDF: 0.12089
Word: 舉行, TF-IDF: 0.02686
Word: 錦標賽, TF-IDF: 0.02686
Top words in document 3
Word: 羽毛球, TF-IDF: 0.09033
Word: 運動, TF-IDF: 0.04106
Word: 場地, TF-IDF: 0.02464
可以看出關(guān)鍵詞的順序是和上面gensim算法的結(jié)果一致的,但是TF-IDF值的大小不同,這是因為gensim算法對TF-IDF值做了規(guī)范化(normalize)處理。
對TF-IDF結(jié)果值進行規(guī)范化處理
規(guī)范化處理的代碼如下:
import numpy as np
def unitvec(sorted_words):
""" 對向量做規(guī)范化,normalize """
lst = [item[1] for item in sorted_words]
L2Norm = math.sqrt(sum(np.array(lst)*np.array(lst)))
unit_vector = [(item[0], item[1]/L2Norm) for item in sorted_words]
return unit_vector
增加規(guī)范化處理后的代碼:
print("Training by original algorithm......\n")
for i, count in enumerate(count_list):
print("Top words in document %d"%(i + 1))
scores = {word: tfidf(word, count, count_list) for word in count}
sorted_words = sorted(scores.items(), key=lambda x: x[1], reverse=True) # type=list
sorted_words = unitvec(sorted_words)
for word, score in sorted_words[:3]:
print(" Word: %s, TF-IDF: %s"%(word, round(score, 5)))
Output:
Training by original algorithm......
Top words in document 1
Word: 籃球, TF-IDF: 0.54722
Word: 奧運會, TF-IDF: 0.45601
Word: 比賽項目, TF-IDF: 0.18241
Top words in document 2
Word: 乒乓球, TF-IDF: 0.74579
Word: 舉行, TF-IDF: 0.16573
Word: 錦標賽, TF-IDF: 0.16573
Top words in document 3
Word: 羽毛球, TF-IDF: 0.68137
Word: 運動, TF-IDF: 0.30971
Word: 場地, TF-IDF: 0.18583
可以看到經(jīng)過規(guī)范化處理之后的結(jié)果就和gensim的TF-IDF算法的結(jié)果一摸一樣了。