jieba關鍵詞提取的源碼解析

一、簡介

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計算公式如下:
TF=詞頻(TF)=\frac{某個詞在文章中出現的次數}{文章的總詞數}
IDF=log(+1)逆文檔頻率(IDF)=log(\frac{語料庫的文檔總數}{包含該詞的文檔數+1})

一個詞IDF值的計算是根據語料庫得出的,如果一個詞在語料庫中越常見,那麼分母就越大,IDF就越小越接近0。分母之所以要加1,是爲了避免分母爲0(即所有文檔都不包含該詞)。該詞在語料庫中越常見,IDF值越小。

最終得到TF-IDF值:
TFIDF=TF×IDFTF-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總結

  1. extract_tags()函數將原始文本作爲輸入,輸出文本的關鍵詞集合,代碼大致分爲四個部分:(1)中文分詞 (2)計算詞頻TF (3)計算IDF (4)將所有詞排序得到關鍵詞集合。
  2. idf的值時通過語料庫統計得到的,所以,實際使用時,可能需要依據使用環境,替換爲使用對應的語料庫統計得到的idf值。
  3. 需要從分詞結果中去除停用詞。
  4. 如果指定了僅提取指定詞性的關鍵詞,則詞性分割非常重要,詞性分割中準確程度,影響關鍵字的提取。
  5. 詳見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算法的比較

  1. 從算法原理上來看,基礎都是詞頻統計,只是TD-IDF通過IDF來調整詞頻的權值,而TextRank通過上下文的連接數來調整詞頻的權值。TextRank通過滑動窗口的方式,來實現詞的位置對詞的權值的影響。

  2. TD-IDF計算簡單,運行性能更好。

參考網址:
使用python的jieba庫中的TF-IDF算法進行關鍵詞提取
【NLP】【三】jieba源碼分析之關鍵字提取(TF-IDF/TextRank)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章