关键词提取算法: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 

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