1. 程式人生 > >關鍵詞提取演算法—TF/IDF演算法

關鍵詞提取演算法—TF/IDF演算法

關鍵詞提取演算法一般可分為有監督學習和無監督學習兩類。
有監督的關鍵詞提取方法可以通過分類的方式進行,通過構建一個較為完善的詞表,然後判斷每個文件與詞表中的每個詞的匹配程度,以類似打標籤的方式,達到關鍵詞提取的效果。優點是可以獲得較高的精度,缺點是需要大批量的標註資料,並且要對詞表進行人工維護。

無監督學習既不需要詞表也不需要標註語料,也因此無監督的學習得到了大量的應用。

TF-IDF(term frequency–inverse document frequency,詞頻—逆文件頻次演算法)
是一種用於資訊檢索與資訊探勘的常用加權技術,是一種統計方法。常用於評估在一個文件集中一個詞對某份文件的重要性,一個詞對文件越重要越有可能成為關鍵詞。

TF-IDF演算法由兩部分組成:TF演算法以及IDF演算法。TF演算法是統計一個詞在一篇文件中出現的頻次。也即是一個詞在文件中出現的次數越多,其對文件的表達能力也越強。而IDF演算法則是統計一個詞在文件集的多少個文件中出現,即是如果一個詞在越少的文件中出現,則其對文件的區分能力也越強。

但TF僅衡量詞的出現頻次,但沒有考慮到詞對文件的區分能力,所以在實際應用中一般都是將TF和IDF演算法一起使用,從詞頻、逆文件頻次兩個角度對詞的重要性進行衡量。

(1)TF的計算方式:
t

f i j = n i j
k n k j
tf_{ij}=\frac {n_{ij}}{\sum_k n_{kj}}
在計算公式中,其中 n i j n_{ij} 表示詞 i i 在文件中 j j 中的出現頻次,但僅是用頻次來表示,長文字中的詞出現頻次高的概率會更大,因此會影響到不同文件之間關鍵詞權值的比較,所以這裡對詞頻進行了歸一化,分母就是統計文件中每個詞出現次數的總和,也即是一篇文件中總詞數。

(2)IDF的計算方式:
i d f i = l o g ( D 1 + D i ) idf_i=log \begin{pmatrix} \frac{|D|}{1+|D_i|} \end{pmatrix}

其中 D |D| 為文件中文件總數, D i |D_i| 為文件集中出現詞 i i 的文件數量。分母之所以加1是採用了拉普拉斯平滑,比避免有部分新的詞沒有在語料庫中出現過而使分母為0的情況出現,增強演算法的健壯性。

(3)TF—IDF的計算方式:
t f × i d f ( i , j ) = t f i j × i d f i = n i j k n k j × l o g ( D 1 + D i ) tf \times idf(i,j) =tf_{ij} \times idf_i=\frac {n_{ij}}{\sum_k n_{kj}} \times log \begin{pmatrix} \frac{|D|}{1+|D_i|} \end{pmatrix}
TF—IDF演算法是TF和IDF演算法的綜合使用,這裡的 t f tf i d f idf 相乘是經驗所得。

下面是一個具體的案例:
這裡假設已經安裝好了jieba和gensim包,下面是程式碼實現:

(1)idf的計算

  • 載入已有的文件資料集
  • 載入停用表
  • 對資料集中的文件進行分詞
  • 根據停用詞表過濾掉干擾詞
  • 根據過濾好詞統計計算idf值

(2)新文件的關鍵詞提取

  • 對新文件進行分詞
  • 根據停用詞表過濾掉干擾詞
  • 根據計算好的{詞:tf-idf值}得出新文件的關鍵詞
#!/usr/bin/env python3
# -*- coding: utf-8 -*-


import math

import jieba
import jieba.posseg as psg
from gensim import corpora, models
from jieba import analyse
import functools


# TF-IDF類
class TfIdf(object):
    # 四個引數分別是:訓練好的idf字典,預設idf值,處理後的待提取文字,關鍵詞數量
    def __init__(self, idf_dic, default_idf, word_list, keyword_num):
        self.word_list = word_list
        self.idf_dic, self.default_idf = idf_dic, default_idf
        self.tf_dic = self.get_tf_dic()
        self.keyword_num = keyword_num

    # 統計tf值
    def get_tf_dic(self):
        tf_dic = {}
        for word in self.word_list:
            tf_dic[word] = tf_dic.get(word, 0.0) + 1.0

        tt_count = len(self.word_list)
        for k, v in tf_dic.items():
            tf_dic[k] = float(v) / tt_count

        return tf_dic

    # 按公式計算tf-idf
    def get_tfidf(self):
        tfidf_dic = {}
        for word in self.word_list:
            idf = self.idf_dic.get(word, self.default_idf) # 這裡是計算待處理文字的詞的idf(針對文件集)
            tf = self.tf_dic.get(word, 0)   # 待處理文字的詞頻(針對自身的文件)

            tfidf = tf * idf
            tfidf_dic[word] = tfidf

        #print(tfidf_dic.items())
        # 根據tf-idf排序,去排名前keyword_num的詞作為關鍵詞
        for k, v in sorted(tfidf_dic.items(), key=functools.cmp_to_key(cmp), reverse=True)[:self.keyword_num]:
            print(k + "/ ", end='')
    
        
#  排序函式,用於topK關鍵詞的按值排序
def cmp(e1, e2):
    import numpy as np
    res = np.sign(e1[1] - e2[1])
    if res != 0:
        return res
    else:
        a = e1[0] + e2[0]
        b = e2[0] + e1[0]
        if a > b:
            return 1
        elif a == b:
            return 0
        else:
            return -1        

# 停用詞表載入方法
def get_stopword_list():
    # 停用詞表儲存路徑,每一行為一個詞,按行讀取進行載入
    # 進行編碼轉換確保匹配準確率
    stop_word_path = './stopword.txt'
    stopword_list = [sw.replace('\n', '') for sw in open(stop_word_path).readlines()]
    return stopword_list


# 去除干擾詞
def word_filter(seg_list, pos=False):
    stopword_list = get_stopword_list()
    #print('stopword_list:',stopword_list)
    filter_list = []
    # 根據POS引數選擇是否詞性過濾    
    for seg in seg_list:
        if not pos:   # 不進行詞性過濾,則將詞性都標記為n,表示全部保留
            word = seg
            flag = 'n'
        else:
            #print(seg.word,seg.flag)
            word = seg.word  # 詞性標註分詞後的詞
            flag = seg.flag  # 詞性標註分詞後的詞性
        if not flag.startswith('n'):
            continue
        # 過濾停用詞表中的詞,以及長度為<2的詞
        if not word in stopword_list and len(word) > 1:
            filter_list.append(word)

    return filter_list


# 分詞方法,呼叫結巴介面
def seg_to_list(sentence, pos=False):
    if not pos:
        # 不進行詞性標註的分詞方法
        seg_list = jieba.cut(sentence)
    else:
        # 進行詞性標註的分詞方法
        seg_list = psg.cut(sentence)
    return seg_list


# 資料載入,pos為是否詞性標註的引數,corpus_path為資料集路徑
def load_data(pos=False, corpus_path='./corpus.txt'):
    # 呼叫上面方式對資料集進行處理,處理後的每條資料僅保留非干擾詞
    doc_list = []
    for line in open(corpus_path, 'r'):
        content = line.strip()
        seg_list = seg_to_list(content, pos) # 這裡對語料進行不標註詞性的分詞
        filter_list = word_filter(seg_list, pos)
        doc_list.append(filter_list)
    return doc_list

# idf值統計方法
def train_idf(doc_list):
    idf_dic = {}
    # 總文件數
    tt_count = len(doc_list)
    # 每個詞出現的文件數
    for doc in doc_list:
        # 每篇文件的不重複詞
        for word in set(doc):  
            idf_dic[word] = idf_dic.get(word, 0.0) + 1.0

    # 按公式轉換為idf值,分母加1進行平滑處理
    for k, v in idf_dic.items():
        idf_dic[k] = math.log(tt_count / (1.0 + v))

    # 對於沒有在字典中的詞,預設其僅在一個文件出現,得到預設idf值
    default_idf = math.log(tt_count / (1.0))
    return idf_dic, default_idf


def tfidf_extract(word_list, pos=False, keyword_num=10):
    # 載入已有的文件資料集,分詞時不標註詞性
    doc_list = load_data(pos)  
    # IDF值的計算
    idf_dic, default_idf = train_idf(doc_list)
    tfidf_model = TfIdf(idf_dic, default_idf, word_list, keyword_num)
    tfidf_model.get_tfidf()
    

if __name__ == '__main__':
    text = '6月19日,《2012年度“中國愛心城市”公益活動新聞釋出會》在京舉行。' + \
           '中華社會救助基金會理事長許嘉璐到會講話。基金會高階顧問朱發忠,全國老齡' + \
           '辦副主任朱勇,民政部社會救助司助理巡視員周萍,中華社會救助基金會副理事長耿志遠,' + \
           '重慶市民政局巡視員譚明政。晉江市人大常委會主任陳健倩,以及10餘個省、市、自治區民政局' + \
           '領導及四十多家媒體參加了釋出會。中華社會救助基金會祕書長時正新介紹本年度“中國愛心城' + \
           '市”公益活動將以“愛心城市宣傳、孤老關愛救助專案及第二屆中國愛心城市大會”為主要內容,重慶市' + \
           '、呼和浩特市、長沙市、太原市、蚌埠市、南昌市、汕頭市、滄州市、晉江市及遵化市將會積極參加' + \
           '這一公益活動。中國雅虎副總編張銀生和鳳凰網城市頻道總監趙耀分別以各自媒體優勢介紹了活動' + \
           '的宣傳方案。會上,中華社會救助基金會與“第二屆中國愛心城市大會”承辦方晉江市簽約,許嘉璐理' + \
           '事長接受晉江市參與“百萬孤老關愛行動”向國家重點扶貧地區捐贈的價值400萬元的款物。晉江市人大' + \
           '常委會主任陳健倩介紹了大會的籌備情況。'

    pos = True
    seg_list = seg_to_list(text, pos)
    #for i in seg_list:  # 列印輸出進行了詞性標註分詞的結果
        #print(i)    
    filter_list = word_filter(seg_list, pos)
    print(filter_list)
    print('TF-IDF模型結果:')
    tfidf_extract(filter_list)
   

執行結果:

['年度', '中國', '愛心', '城市', '公益活動', '新聞', '釋出會', '在京舉行', '中華', '社會', '基金會', '理事長', '許嘉璐', '講話', '基金會', '朱發忠', '全國', '老齡', '朱勇', '民政部', '社會', '巡視員', '周萍', '中華', '社會', '基金會', '副理事長', '志遠', '重慶市', '民政局', '巡視員', '明政', '晉江市', '人大常委會', '陳健倩', '自治區', '民政局', '領導', '媒體', '釋出會', '中華', '社會', '基金會', '祕書長', '本年度', '中國', '愛心', '城市', '公益活動', '愛心', '城市', '專案', '中國', '愛心', '城市', '大會', '內容', '重慶市', '呼和浩特市', '長沙市', '太原市', '蚌埠市', '南昌市', '汕頭市', '滄州市', '晉江市', '遵化市', '公益活動', '中國', '雅虎', '副總編', '張銀生', '鳳凰網', '城市', '頻道', '總監', '趙耀', '媒體', '優勢', '方案', '中華', '社會', '基金會', '中國', <