一、簡介
1.1 什麼是關鍵詞
關鍵詞是指能反映文本主題或者意思的詞語,如論文中的Keyword字段。
關鍵詞提取是文本挖掘領域一個很重要的部分,通過對文本提取的關鍵詞可以窺探整個文本的主題思想,進一步應用於文本的推薦或文本的搜索。
常用的關鍵詞提取算法:TF-IDF算法、TextRank算法
1.2 jieba關鍵詞提取簡介
利用jieba進行關鍵字提取時,有兩種接口。一個基於TF-IDF算法,一個基於TextRank算法。
TF-IDF算法,完全基於詞頻統計來計算詞的權重,然後排序,再返回TopK個詞作爲關鍵字。
TextRank相對於TF-IDF,基本思路一致,也是基於統計的思想,只不過其計算詞的權重時,還考慮了詞的上下文(通過窗口滑動來實現),而且計算詞的權重時,也考慮了相關聯繫詞的影響。可以說,TextRank實際上是依據位置與詞頻來計算詞的權重的。
下面,結合基於jieba源碼,來分別解釋兩種算法的實現。
二、基於 TF-IDF 算法的關鍵詞抽取
2.1 算法原理
TF-IDF是關鍵詞提取最基本、最簡單易懂的方法。判斷一個詞再一篇文章中是否重要,一個最容易想到的衡量指標就是詞頻,重要的詞往往在文章中出現的頻率也非常高;但另一方面,不是出現次數越多的詞就一定重要,因爲有些詞在各種文章中都頻繁出現(例如:我們),那它的重要性肯定不如哪些只在某篇文章中頻繁出現的詞重要性強。從統計學的角度,就是給予那些不常見的詞以較大的權重,而減少常見詞的權重,最終得分較高的詞語即爲關鍵詞。
TF和IDF計算公式如下:
一個詞IDF值的計算是根據語料庫得出的,如果一個詞在語料庫中越常見,那麼分母就越大,IDF就越小越接近0。分母之所以要加1,是爲了避免分母爲0(即所有文檔都不包含該詞)。該詞在語料庫中越常見,IDF值越小。
最終得到TF-IDF值:
可以看出TF-IDF與一個詞在文檔中的出現次數成正比,與該詞在整個語料庫中出現次數成反比。一個詞的TF-IDF值非常高,說明這個詞比較少見,但是它在這篇文章中多次出現,那麼這個詞就非常可能是我們需要的關鍵詞。
以文章《中國的蜜蜂養殖》爲例,“蜜蜂”和“養殖”兩個詞的TF-IDF值都非常高,作爲這篇文章的關鍵詞實際上看也是非常合適的。另外“中國”這個詞雖然在文章中的詞頻並不低“蜜蜂”和“養殖”低,但因爲它在整個語料庫中經常出現,導致IDF值非常低,所以不會作爲文章的關鍵詞。
2.2 使用及效果
import jieba.analyse
- jieba.analyse.extract_tags(sentence, topK=20, withWeight=False, allowPOS=())
- sentence 爲待提取的文本
- topK 爲返回幾個 TF/IDF 權重最大的關鍵詞,默認值爲 20
- withWeight 爲是否一併返回關鍵詞權重值,默認值爲 False
- allowPOS 僅包括指定詞性的詞,默認值爲空,即不篩選
import jieba.analyse as analyse
import pandas as pd
df = pd.read_csv("./origin_data/technology_news.csv", encoding='utf-8')
df = df.dropna()
lines=df.content.values.tolist()
content = "".join(lines)
keywords = analyse.extract_tags(content, topK=30, withWeight=False, allowPOS=())
print(keywords)
[‘用戶’, ‘2016’, ‘互聯網’, ‘手機’, ‘平臺’, ‘人工智能’, ‘百度’, ‘2017’, ‘智能’, ‘技術’, ‘數據’, ‘360’, ‘服務’, ‘直播’, ‘產品’, ‘企業’, ‘安全’, ‘視頻’, ‘移動’, ‘應用’, ‘網絡’, ‘行業’, ‘遊戲’, ‘機器人’, ‘電商’, ‘內容’, ‘中國’, ‘領域’, ‘通過’, ‘發展’]
2.3 源碼實現
idf.txt
jieba有統計好的idf值,在 jieba/analyse/idf.txt中。
勞動防護 13.900677652
生化學 13.900677652
奧薩貝爾 13.900677652
考察隊員 13.900677652
崗上 11.5027823792
倒車檔 12.2912397395
idf.txt 加載
代碼在 jieba/analyse/tfidf.py
class IDFLoader(object):
def __init__(self, idf_path=None):
self.path = ""
self.idf_freq = {}
# 初始化idf的中位數值
self.median_idf = 0.0
if idf_path:
# 解析idf.txt
self.set_new_path(idf_path)
def set_new_path(self, new_idf_path):
if self.path != new_idf_path:
self.path = new_idf_path
content = open(new_idf_path, 'rb').read().decode('utf-8')
self.idf_freq = {}
# 解析 idf.txt,拿到詞與idf的對應值,key = word,value = idf
for line in content.splitlines():
word, freq = line.strip().split(' ')
self.idf_freq[word] = float(freq)
# 取idf的中位數
self.median_idf = sorted(
self.idf_freq.values())[len(self.idf_freq) // 2]
利用tfidf算法提取關鍵字的接口:extract_tags
def extract_tags(self, sentence, topK=20, withWeight=False, allowPOS=(), withFlag=False):
"""
Extract keywords from sentence using TF-IDF algorithm.
Parameter:
- topK: return how many top keywords. `None` for all possible words.
- withWeight: if True, return a list of (word, weight);
if False, return a list of words.
- allowPOS: the allowed POS list eg. ['ns', 'n', 'vn', 'v','nr'].
if the POS of w is not in this list,it will be filtered.
- withFlag: only work with allowPOS is not empty.
if True, return a list of pair(word, weight) like posseg.cut
if False, return a list of words
"""
# (1)中文分詞
# 判斷提取出哪些詞性的關鍵字
if allowPOS:
allowPOS = frozenset(allowPOS)
# 如果需要提取指定詞性的關鍵字,則先進行詞性分割
words = self.postokenizer.cut(sentence)
else:
# 如果提取所有詞性的關鍵字,則使用精確分詞
words = self.tokenizer.cut(sentence)
# (2)計算詞頻TF
freq = {}
# 按照分詞結果,統計詞頻
for w in words:
if allowPOS:
if w.flag not in allowPOS:
continue
elif not withFlag:
w = w.word
wc = w.word if allowPOS and withFlag else w
# 該詞不能是停用詞
if len(wc.strip()) < 2 or wc.lower() in self.stop_words:
continue
#統計該詞出現的次數
freq[w] = freq.get(w, 0.0) + 1.0
# 計算總的詞數目
total = sum(freq.values())
# (3)計算IDF
for k in freq:
kw = k.word if allowPOS and withFlag else k
# 依據tf-idf公式進行tf-idf值,作爲詞的權重。其中,idf是jieba通過語料庫統計得到的
freq[k] *= self.idf_freq.get(kw, self.median_idf) / total
# (4)排序得到關鍵詞集合
# 對詞頻做個排序,獲取TopK的詞
if withWeight:
tags = sorted(freq.items(), key=itemgetter(1), reverse=True)
else:
tags = sorted(freq, key=freq.__getitem__, reverse=True)
if topK:
return tags[:topK]
else:
return tags
jieba實現tf-idf總結
- extract_tags()函數將原始文本作爲輸入,輸出文本的關鍵詞集合,代碼大致分爲四個部分:(1)中文分詞 (2)計算詞頻TF (3)計算IDF (4)將所有詞排序得到關鍵詞集合。
- idf的值時通過語料庫統計得到的,所以,實際使用時,可能需要依據使用環境,替換爲使用對應的語料庫統計得到的idf值。
- 需要從分詞結果中去除停用詞。
- 如果指定了僅提取指定詞性的關鍵詞,則詞性分割非常重要,詞性分割中準確程度,影響關鍵字的提取。
- 詳見fxsjy/jieba文檔。
三、基於 TextRank 算法的關鍵詞抽取
3.1 算法原理
TextRank採用圖的思想,將文檔中的詞表示成一張無向有權圖,詞爲圖的節點,詞之間的聯繫緊密程度體現爲圖的邊的權值。計算詞的權重等價於計算圖中節點的權重。提取關鍵字,等價於找出圖中權重排名TopK的節點。
如上圖所示:有A B C D E五個詞,詞之間的關係使用邊連接起來,詞之間連接的次數作爲邊的權值。比如:A和C一起出現了2次,則其邊的權重爲2,A與B/C/E都有聯繫,而D僅與B有聯繫。
所以TextRank背後體現的思想爲:與其他詞關聯性強的詞,越重要。通俗一點就是:圍着誰轉,誰就重要。就像大家基本都會圍着領導轉一樣。
圖的構建分爲兩部分:
1)確認圖的節點之間的聯繫
2)確認邊的權值
基本思想:
- 將待抽取關鍵詞的文本進行分詞
- 以固定窗口大小(默認爲5,通過span屬性調整),詞之間的共現關係,構建圖
- 計算圖中節點的PageRank,注意是無向帶權圖
算法論文: TextRank: Bringing Order into Texts
3.2 使用及效果
- jieba.analyse.textrank(sentence, topK=20, withWeight=False, allowPOS=(‘ns’, ‘n’, ‘vn’, ‘v’)) 直接使用,接口相同,注意默認過濾詞性。
- jieba.analyse.TextRank() 新建自定義 TextRank 實例
import jieba.analyse as analyse
import pandas as pd
df = pd.read_csv("./origin_data/military_news.csv", encoding='utf-8')
df = df.dropna()
lines=df.content.values.tolist()
content = "".join(lines)
print(" ".join(analyse.textrank(content, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'))))
print("---------------------我是分割線----------------")
print(" ".join(analyse.textrank(content, topK=20, withWeight=False, allowPOS=('ns', 'n'))))
中國 海軍 訓練 美國 部隊 進行 官兵 航母 作戰 任務 能力 軍事 發展 工作 國家 問題 建設 導彈 編隊 記者
---------------------我是分割線----------------
中國 海軍 美國 部隊 官兵 航母 軍事 國家 任務 能力 導彈 技術 問題 日本 軍隊 編隊 裝備 系統 記者 戰略
3.3 源碼實現
def textrank(self, sentence, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'), withFlag=False):
"""
Extract keywords from sentence using TextRank algorithm.
Parameter:
- topK: return how many top keywords. `None` for all possible words.
- withWeight: if True, return a list of (word, weight);
if False, return a list of words.
- allowPOS: the allowed POS list eg. ['ns', 'n', 'vn', 'v'].
if the POS of w is not in this list, it will be filtered.
- withFlag: if True, return a list of pair(word, weight) like posseg.cut
if False, return a list of words
"""
# 初始化關鍵字詞性過濾條件
self.pos_filt = frozenset(allowPOS)
# 初始化一個無向權值圖
g = UndirectWeightedGraph()
cm = defaultdict(int)
# 使用精確模式進行分詞
words = tuple(self.tokenizer.cut(sentence))
# 遍歷分詞結果
for i, wp in enumerate(words):
# 詞wp如果滿足關鍵詞備選條件,則加入圖中
if self.pairfilter(wp):
# span爲滑動窗口,即詞的上下文,藉此來實現此的共現,完成詞之間的連接。
for j in xrange(i + 1, i + self.span):
if j >= len(words):
break
# 後向詞也要滿足備選詞條件
if not self.pairfilter(words[j]):
continue
if allowPOS and withFlag:
# 共現詞作爲圖一條邊的兩個節點,共現詞出現的次數,作爲邊的權值
cm[(wp, words[j])] += 1
else:
cm[(wp.word, words[j].word)] += 1
# 將 備選詞和與該詞連接的詞加入到graph中,即完成graph的構造
for terms, w in cm.items():
g.addEdge(terms[0], terms[1], w)
# 調用graph的rank接口,完成TextRank算法的計算,即計算出各節點的權重
nodes_rank = g.rank()
if withWeight:
# 對graph中的階段的權重進行排序
tags = sorted(nodes_rank.items(), key=itemgetter(1), reverse=True)
else:
tags = sorted(nodes_rank, key=nodes_rank.__getitem__, reverse=True)
if topK:
return tags[:topK]
else:
return tags
def rank(self):
ws = defaultdict(float)
outSum = defaultdict(float)
# 計算初始化節點的weight值
wsdef = 1.0 / (len(self.graph) or 1.0)
# 初始化各個節點的weight值,並計算各個節點的出度數目
for n, out in self.graph.items():
ws[n] = wsdef
outSum[n] = sum((e[2] for e in out), 0.0)
# this line for build stable iteration
sorted_keys = sorted(self.graph.keys())
# 循環迭代10,迭代計算出各個節點的weight值
for x in xrange(10): # 10 iters
for n in sorted_keys:
s = 0
# 依據TextRank公式計算weight
for e in self.graph[n]:
s += e[2] / outSum[e[1]] * ws[e[1]]
ws[n] = (1 - self.d) + self.d * s
(min_rank, max_rank) = (sys.float_info[0], sys.float_info[3])
for w in itervalues(ws):
if w < min_rank:
min_rank = w
if w > max_rank:
max_rank = w
for n, w in ws.items():
# to unify the weights, don't *100.
ws[n] = (w - min_rank / 10.0) / (max_rank - min_rank / 10.0)
return ws
四、TF-IDF與TextRank算法的比較
-
從算法原理上來看,基礎都是詞頻統計,只是TD-IDF通過IDF來調整詞頻的權值,而TextRank通過上下文的連接數來調整詞頻的權值。TextRank通過滑動窗口的方式,來實現詞的位置對詞的權值的影響。
-
TD-IDF計算簡單,運行性能更好。
參考網址:
使用python的jieba庫中的TF-IDF算法進行關鍵詞提取
【NLP】【三】jieba源碼分析之關鍵字提取(TF-IDF/TextRank)