關鍵詞提取算法:TF-IDF、TextRank、LSA/LSI/LDA

首先推薦一下在NLP方面比較好的一個博客,本文也是參考這位博主寫的。鏈接點這裏。本文主要對TF-IDF代碼重新改寫了一下,幾個函數順序調用比原文較好理解一點,對於剛入NLP的我們來說用來學習是挺好的,如果掌握的就可以嘗試用面向對象的思想來寫。LSA/LSI/LDA這集中算法應爲沒有接觸過,多以暫時不介紹,想了解的可以參考原文。如果有時間我再去理解修改。

1、關鍵詞提取技術概述:

      相對於有監督的方法而言,無監督的方法對數據的要求就低多了。既不需要一張人工生成、維護的詞表,也不需要人工標準語料輔助進行訓練。因此,這類算法在關鍵詞提取領域的應用更受到大家的青睞。目前常用的算法有TF-IDF算法、TextRank算法和主題模型算法(包括LSA、LSI、LDA等)

2、關鍵詞提取算法TF-IDF算法:

      TF-IDF算法介紹可以參考我的博客文章,還是易懂的。https://blog.csdn.net/genius_man/article/details/87177574

3、TextRank算法:

      TextRand算法的一個重要特點是可以脫離語料庫的背景,僅對單篇文檔進行分析就可以提取該文檔的關鍵詞。最早用於文檔的自動摘要,基於句子維度的分析,對於每個句子進行打分,挑選出分數最高的n個句子作爲文檔的關鍵句,以達到自動摘要的效果。

4、LSA/LSI/LDA算法:

      在某些場景,基於文檔本身的關鍵詞提取還不是非常足夠,有些關鍵詞並不一定會顯示地出現在文檔當中,如一篇講動物生存環境的科普文,通篇介紹獅子老虎等,但是文中並沒有顯示地出現動物二字。

      主題模型認爲在詞與文檔之間沒有直接的聯繫,它們應當還有一個維度將它們串聯起來,主題模型將這個維度稱爲主題。每個文檔都應該對應着一個或多個的主題,而每個主題都會有對應的詞分佈,通過主題,就可以得到每個文檔的詞分佈。核心公式:p(wi | dj) = sumK( p(wi | tk) x p(tk | dj)

      在一個已知的數據集中,每個詞和文檔對應的p(wi | dj)都是已知的。而主題模型就是根據這個已知的信息,通過計算p(wi | tk) 和p(tk | dj)的值,從而得到主題的詞分佈和文檔的主題分佈信息。而要想得到這個分佈信息,現在常用的方法就是LSA(LSI)和LDA。其中LSA主要是採用SVD的方法進行暴力破解,而LDA則是通過貝葉斯學派的方法對分佈信息進行擬合。

 

TextRank代碼實現:

import jieba.analyse as analyse


# 利用textrank算法抽取文章關鍵詞
def textrank_extract_keywords(text, keyword_num=10):
    keywords = analyse.textrank(text, keyword_num)
    # 輸出抽取出的關鍵詞
    print(','.join(keywords))


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

    print('TextRank模型結果:')
    textrank_extract_keywords(text)




TF-IDF代碼實現:

import jieba.posseg as psg
import math
import functools
import numpy as np


# 利用tf-idf算法提權文章關鍵詞


# 加載並返回停用詞列表 list
def get_stopword_list():
    stop_word_path = 'stopwords.txt'
    stopword_list = [word.replace('\n', '') for word in open(stop_word_path, encoding='utf-8').readlines()]
    return stopword_list


# 對文本切分,並同詞性一併返回爲
def split_text_to_list(text):
    word_list = psg.cut(text)
    return word_list


# 去除干擾詞:過濾除名詞外的其他詞性,再判斷詞是否在停用詞表中,長度是否大於等於2等。
def clean_word_list(word_list):
    stopword_list = get_stopword_list()
    cleaned_word_list = []
    # 過濾除名詞外的其他詞性。不進行詞性過濾,則將詞性都標記爲n,表示全部保留
    for seg in word_list:
        word = seg.word
        flag = seg.flag
        if flag.startswith('n'):
            # 過濾高停用詞表中的詞,以及長度爲<2的詞
            if word not in stopword_list and len(word) > 1:
                cleaned_word_list.append(word)
    return cleaned_word_list


# topK
def cmp(e1, e2):
    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 tfidf_extract(cleaned_word_list, keyword_num=10):
    # 加載數據集,並清洗 添加在文檔列表中
    # 文檔集是一個形如[['a','b', ...], ['c','d', ...], [], ...]的列表
    doc_list = []
    for line in open('text2.txt', 'r', encoding='utf-8'):   # 這裏的text2.txt應該是doc_path,我這裏只用了一篇文章作爲文檔集
        word_list = split_text_to_list(line.strip())
        doc_cleaned_word_list = clean_word_list(word_list)
        doc_list.append(doc_cleaned_word_list)
    # 計算idf值
    idf_dic = {}
    doc_count = len(doc_list)   # 文檔數
    # 每個詞出現的文檔數,即一個文章中的詞在整個文檔中出現的次數
    for doc in doc_list:
        for word in set(doc):
            # 如果idf_dic有word這個key的詞,name返回他的value,否則返回默認值0.0
            # {'c1': 2.0, 'b2': 2.0, 'a2': 1.0, 'c2': 1.0, 'b1': 1.0, 'a1': 1.0,......}
            idf_dic[word] = idf_dic.get(word, 0.0) + 1.0  # http://www.runoob.com/python/att-dictionary-get.html
    # 按公式轉換爲idf值
    for k, v in idf_dic.items():  # 循環字典裏面的每一個對象key:value
        idf_dic[k] = math.log(doc_count / (1.0 + v))
    # 對於沒有在字典中的詞,默認其盡在一個文檔出現,得到默認idf值
    default_idf = math.log(doc_count / 1.0)
    # 統計TF值
    tf_dic = {}
    for word in cleaned_word_list:    # 這裏的cleaned_word_list表示處理後的待提取文本
        tf_dic[word] = tf_dic.get(word, 0.0) + 1.0
    word_count = len(cleaned_word_list)
    for k, v in tf_dic.items():
        tf_dic[k] = float(v) / word_count
    # 開始計算tf-idf
    tfidf_dic = {}
    for word in cleaned_word_list:
        idf = idf_dic.get(word, default_idf)
        tf = tf_dic.get(word, 0)
        tfidf = tf * idf
        tfidf_dic[word] = tfidf
    # 根據tf-idf排序,取排名前keyword_num的詞作爲關鍵詞
    for k, v in sorted(tfidf_dic.items(), key=functools.cmp_to_key(cmp), reverse=True)[:keyword_num]:
        print(k + "/", end='')


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


# 結果:
# 城市/社會/愛心/晉江市/基金會/中國/中華/大會/公益活動/陳健倩/





 

LSI和LDA算法代碼實現:

# -*- coding: utf-8 -*-
import math
import jieba
import jieba.posseg as psg
from gensim import corpora, models
import functools


def get_stopword_list():
    stop_word_path = 'stopwords.txt'
    stopword_list = [sw.replace('\n', '') for sw in open(stop_word_path, encoding='utf-8').readlines()]
    return stopword_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判斷是否過濾除名詞外的其他詞性,再判斷詞是否在停用詞表中,長度是否大於等於2等。
def word_filter(seg_list, pos=False):
    stopword_list = get_stopword_list()
    filter_list = []
    # 根據pos參數選擇是否詞性過濾
    # 不進行詞性過濾,則將詞性都標記爲n,表示全部保留
    for seg in seg_list:
        if not pos:
            word = seg
            flag = 'n'
        else:
            word = seg.word
            flag = seg.flag
        if not flag.startswith('n'):
            continue
        # 過濾高停用詞表中的詞,以及長度爲<2的詞
        if word not in stopword_list and len(word) > 1:
            filter_list.append(word)
    return filter_list


# 數據加載
def load_data(pos=False, corpus_path='text2.txt'):
    doc_list = []
    for line in open(corpus_path, 'r', encoding='utf-8'):
        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


# 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


# 主題模型
class TopicModel(object):
    def __init__(self, doc_list, keyword_num, model="LSI", num_topics=4):
        # 使用gensim接口,將文本轉爲向量化表示
        self.dictionary = corpora.Dictionary(doc_list)
        # 使用BOW模型向量化
        corpus = [self.dictionary.doc2bow(doc) for doc in doc_list]
        # 對每個詞,根據tf-idf進行加權,得到加權後的向量表示
        self.tfidf_model = models.TfidfModel(corpus)
        self.corpus_tfidf = self.tfidf_model[corpus]

        self.keyword_num = keyword_num
        self.num_topics = num_topics
        # 選擇加載的模型
        if model == 'LSI':
            self.model = self.train_lsi()
        else:
            self.model = self.train_lda()
        # 得到數據集的主題-詞分佈
        word_dic = self.word_dictionary(doc_list)
        self.wordtopic_dic = self.get_wordtopic(word_dic)

    def train_lsi(self):
        lsi = models.LsiModel(self.corpus_tfidf, id2word=self.dictionary, num_topics=self.num_topics)
        return lsi

    def train_lda(self):
        lda = models.LdaModel(self.corpus_tfidf, id2word=self.dictionary, num_topics=self.num_topics)
        return lda

    def get_wordtopic(self, word_dic):
        wordtopic_dic = {}
        for word in word_dic:
            single_list = [word]
            wordcorpus = self.tfidf_model[self.dictionary.doc2bow(single_list)]
            wordtopic = self.model[wordcorpus]
            wordtopic_dic[word] = wordtopic
        return wordtopic_dic

    # 詞空間構建方法和向量化方法,在沒有gensim接口時的一般處理方法
    def word_dictionary(self, doc_list):
        dictionary = []
        for doc in doc_list:
            dictionary.extend(doc)

        dictionary = list(set(dictionary))

        return dictionary

    def doc2bowvec(self, word_list):
        vec_list = [1 if word in word_list else 0 for word in self.dictionary]
        return vec_list

    # 計算詞的分佈和文檔的分佈的相似度,取相似度最高的keyword_num個詞作爲關鍵詞
    def get_simword(self, word_list):
        sentcorpus = self.tfidf_model[self.dictionary.doc2bow(word_list)]
        senttopic = self.model[sentcorpus]

        # 餘弦相似度計算
        def calsim(l1, l2):
            a, b, c = 0.0, 0.0, 0.0
            for t1, t2 in zip(l1, l2):
                x1 = t1[1]
                x2 = t2[1]
                a += x1 * x1
                b += x1 * x1
                c += x2 * x2
            sim = a / math.sqrt(b * c) if not (b * c) == 0.0 else 0.0
            return sim

        # 計算輸入文本和每個詞的主題分佈相似度
        sim_dic = {}
        for k, v in self.wordtopic_dic.items():
            if k not in word_list:
                continue
            sim = calsim(v, senttopic)
            sim_dic[k] = sim

        for k, v in sorted(sim_dic.items(), key=functools.cmp_to_key(cmp), reverse=True)[:self.keyword_num]:
            print(k + "/ ", end='')
        print()


def topic_extract(word_list, model, pos=False, keyword_num=10):
    doc_list = load_data(pos)
    topic_model = TopicModel(doc_list, keyword_num, model=model)
    topic_model.get_simword(word_list)


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

    pos = True
    seg_list = seg_to_list(text, pos)
    filter_list = word_filter(seg_list, pos)    # 返回的是一個沒有停用詞 並且長度>2的詞的list

    print('LSI模型結果:')
    topic_extract(filter_list, 'LSI', pos)
    print('LDA模型結果:')
    topic_extract(filter_list, 'LDA', pos)

 

轉載信息:
作者:CopperDong 
來源:CSDN 
原文:https://blog.csdn.net/QFire/article/details/81065997 

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