【一】綜述node
利用jieba進行關鍵字提取時,有兩種接口。一個基於TF-IDF算法,一個基於TextRank算法。TF-IDF算法,徹底基於詞頻統計來計算詞的權重,而後排序,在返回TopK個詞做爲關鍵字。TextRank相對於TF-IDF,基本思路一致,也是基於統計的思想,只不過其計算詞的權重時,還考慮了詞的上下文(經過窗口滑動來實現),並且計算詞的權重時,也考慮了相關聯繫詞的影響。能夠說,TextRank其實是依據位置與詞頻來計算詞的權重的。下面,結合基於jieba源碼,來分別解釋兩種算法的實現。python
【二】TF-IDF算法
1. 原理解析post
假設,共有N篇文檔,分別用 d1,d2,d3,,,,,,,dn來表示。性能
TF = 某個詞在di篇文章中出現的次數/di篇文章的總詞數 = count(W in di)/ count(di)。所以,TF計算的是單個詞在單個文檔中出現的詞頻。ui
IDF = 總的文檔數 / 出現詞W的文檔數 。 IDF其實反映了詞W在文檔之間的區別度。若是W在僅在一篇文檔中出現,則說明可使用W將該文檔與其餘文檔區別開來。即IDF能夠反映W的獨特性 。this
TF*IDF,能夠獲得詞的重要性。好比: 北京和西安在同一篇文檔中的詞頻均爲20%,那如何估計北京是該文的關鍵字,仍是西安呢?若是同時有10篇文章均提到了北京,剛好只有這篇文章提到了西安,則西安做爲這篇文章的關鍵字更爲合理。spa
2. idf.txtcode
jieba有統計好的idf值,在 jieba/analyse/idf.txt中。blog
勞動防禦 13.900677652 生化學 13.900677652 奧薩貝爾 13.900677652 考察隊員 13.900677652 崗上 11.5027823792 倒車檔 12.2912397395
3. 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]
4. 利用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 """ # 判斷提取出哪些詞性的關鍵字 if allowPOS: allowPOS = frozenset(allowPOS) # 若是須要提取指定詞性的關鍵字,則先進行詞性分割 words = self.postokenizer.cut(sentence) else: # 若是提取全部詞性的關鍵字,則使用精確分詞 words = self.tokenizer.cut(sentence) 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()) 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 # 對詞頻作個排序,獲取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
5. jieba實現tf-idf總結
1):idf的值時經過語料庫統計獲得的,因此,實際使用時,可能須要依據使用環境,替換爲使用對應的語料庫統計獲得的idf值。
2):須要從分詞結果中去除停用詞。
3):若是指定了僅提取指定詞性的關鍵詞,則詞性分割很是重要,詞性分割中準確程度,影響關鍵字的提取。
【三】TextRank
1. 算法原理介紹
TextRank採用圖的思想,將文檔中的詞表示成一張無向有權圖,詞爲圖的節點,詞之間的聯繫緊密程度體現爲圖的邊的權值。計算詞的權重等價於計算圖中節點的權重。提取關鍵字,等價於找出圖中權重排名TopK的節點。
如上圖所示:有A B C D E五個詞,詞之間的關係使用邊鏈接起來,詞之間鏈接的次數做爲邊的權值。好比:A和C一塊兒出現了2次,則其邊的權重爲2,A與B/C/E都有聯繫,而D僅與B有聯繫。
因此說,TextRank背後體現的思想爲:與其餘詞關聯性強的詞,越重要。通俗一點就是:圍着誰轉,誰就重要。就像你們基本都會圍着領導轉同樣。
2. 圖的構建
圖的構建分爲兩部分:
1):確認圖的節點之間的聯繫
2):確認邊的權值
jieba是如何作的呢?
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
3. TextRank算法的實現
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算法的比較
1. 從算法原理上來看,基礎都是詞頻統計,只是TD-IDF經過IDF來調整詞頻的權值,而TextRank經過上下文的鏈接數來調整詞頻的權值。TextRank經過滑動窗口的方式,來實現詞的位置對詞的權值的影響。
2. TD-IDF計算簡單,運行性能更好。