NLP——关键词提取
文章目录
- NLP——关键词提取
- 前言
- 一、TF-IDF算法
- 1. 基本原理
- 2. 算法改进
- 二、TextRank算法
- 1. 基本原理
- 2. PageRank算法
- 3. TextRank算法
- 4. TextRank算法在关键词提取的应用
- 三、LSA/LSI/LDA算法
- 1. LSA/LSI算法
- 2. LDA算法
- 四、实战练习
前言
关键词提取分为有监督和无监督两种方法:
- 有监督:通过分类的方式进行,通过构建一个较为丰富和完善的词表,然后判断每个文档与词表中每个词的匹配度,以类似打标签的方式达到关键词提取的效果。
优点:能够获得较高的精度;
缺点:需要人工建立和维护含有大批量标注数据的词表,人工成本过高 - 无监督方法:不需要人工生成和维护的词表,也不需要人工标准语料进行辅助训练,注意包括TF-IDF算法、TextRank算法、LSA/LSI/LDA算法、LSA/LSI算法、LDA算法等
一、TF-IDF算法
中文全称:词频–逆文档频词算法,是一种基于统计的计算方法,常用于评估一个词对于某分文档的重要程度。
1. 基本原理
主要包括两部分:
1)TF:统计一个词在一篇文档中出现的频词,出现的次数越多,其对文档的表达能力也就越强;
2)IDF:统计一个词在文档集中的多少个文档出现,如果一个词在文档集中出现的次数越少,则其对文档的表达能力也越强;
2. 算法改进
在传统的TF-IDF算法的基础上,有很多的加权变种方案:
1)加入词的词性作为一种加权因素,给特定词性的词(比如名词,一种定义现实实体的词,结果往往更加合理)更大的权重,在特定场景中,能够获得更好的效果;
2)考虑词在文档中所处的位置,例如文档的首尾段落,往往对于文章很重要(结合文章的结构),给首尾段落的词更大的权重,就能够更准确的提取出关键词;
二、TextRank算法
1. 基本原理
脱离语料库的背景,仅对单篇文档进行分析就可以提取该文档的关键词。
其基本思想来源于Google的pagerank算法。
2. PageRank算法
1)网页质量由两部分因素决定:
a:入链数量:从其他网页进入该网页的链接数量;
b:入链质量:进入该网页的各个网页链接的质量;
不考虑阻尼系数情况下,网页质量的计算公式:
实际的计算过程:
先将各个网页的质量初始化为1,然后建立二维转移矩阵(各个网页之间的入链关系),通过网页质量向量的多次迭代,在网页质量向量不再发生变化之后(收敛之后),即得到各个网页的质量分数。
在迭代时会限制迭代次数,达到迭代次数之后,迭代停止,输出当时的网页质量分数向量作为迭代结果。
实际使用时可能遇到两个问题:
1)等级泄露:如果一个网页没有出链,多次迭代后,会使得其他网页的质量分数逼近0(相当于吸收了其他网页的分数);
2)等级沉默:如果一个网页没有入链,多次迭代后,自身的质量分数会逼近0(相当于散功);
解决方法:引入阻尼系数,对应的公式为:
理解方法:现实中,对一个网页的访问不一定是从别的网页链接进入,也可能是直接输入网址访问,因此,这里添加阻尼系数,相当于是设定一个概率值,人们访问网页时,有多大可能是从别的网页链接进入的。
这样即使部分网页没有入链或者出链,也不会发生等级泄露或等级沉默。
3. TextRank算法
基本原理与PageRank类似。
但是应用在自动摘要场合,有一点不同:
PageRank在各个网页出入链接上是有向无权图,而TextRank算法则采用的是有权图,在对句子的重要性进行计分时,除了考虑连接句子的重要性之外还需要考虑两个句子之间的相似性(作为连接权重)。
每个句子对与它连接的其他句子的贡献不是平均分配的,而是根据他们之间各自的相似度,分别确定一个权重值来分配的,句子之间的相似度通常采用编辑距离和余弦相似度等来衡量。
在对一篇文档进行自动摘要时,默认每个语句与其他句子都是有连接关系的,也就是一个有向完全图。
对应的TextRank算法在自动文摘应用场景下的计算公式为:
每个句子的重要性包括:
各个关联句子的重要性 * 本句与各个句子的相似度 / 本句与其他所有关联句子之间的相似度之和
4. TextRank算法在关键词提取的应用
与自动摘要应用时的区别:
1)词与词之间的关联没有权重;
2)每个词不是与文档中的所有词都有链接;
因此每个词仍然是将自己的重要性分数平均的分配给与自己关联的词,对应的重要性分数的计算公式与PageRank基本一致:
词与词之间链接关系的确定方法:
定义了窗口的概念,在文本经过分词之后,设定一个窗口大小(窗口中含有的词的数量),认为每个窗口中的词是有链接关系的。(一种上下文关系)
自动摘要中的句子之间的连接关系是通过句子之间的相似度来确定的,而各个句子之间必然会有一定的相似度,或大或小。因此,可以认为是文本中的每个句子之间,在这种情况下,都是有连接关系的,且连接关系之间是有权重(相似度的大小)的。
三、LSA/LSI/LDA算法
当文档的关键词没有显示的出现在文档中时,前面的TF-IDF算法和TextRank算法就不能够提取出重要的关键词信息,这时候就需要用到主题模型。
主题模型认为在词和文档之间没有直接的联系,他们应当还有一个维度将他们串联起来,这就是主题。
每个文档都对应着一个或多个主题,每个主题都会有对应的词分布,通过主题,就可以得到每个文档的词分布,一句上述原理,即可得到主题模型的核心公式: p ( w i ∣ d j ) = ∑ k = 1 K p ( w i ∣ t k ) ∗ p ( t k ∣ d j ) p(w_{i}|d_{j}) = \sum^K_{k=1} p(w_{i}|t_{k})*p(t_{k}|d_{j}) p(wi∣dj)=k=1∑Kp(wi∣tk)∗p(tk∣dj)
意义:第j篇文档中第i个词出现的频率 = 该文档中各个主题的频率 * 各个主题中该词出现的频率 再求和
1. LSA/LSI算法
采用奇异值分解(SVD)的方法进行暴力求解。
关于奇异值分解相关,非常清晰透彻的讲解文章
LSA(Latent Semantic Analysis):潜在语义分析
LSI(Latent Semantic Index):潜在语义索引:在LSA的基础上(对潜在语义进行分析之后),还会利用分析结果建立相应的索引。
LSA算法的主要步骤:
1)使用BOW模型将每个文档表示为向量;
2)将所有的文档词向量拼接起来构成词—文档矩阵(mn);
3)对词—文档矩阵进行奇异值分解(SVD)([mr] · [rr] · [rn]);
4)根据SVD分解的结果,将词—文档矩阵映射到一个更低的维度([mk] · [kk] · [k*n])(取前k个奇异值及其对应的奇异向量来表示原矩阵);
选取的前k个奇异向量就是k个主题,每个词和文档都可以表示为k个主题构成的多维空间中的一个点,通过计算每个词和文档的相似度(余弦相似度或KL相似度),可以得到每个文档中对每个词的相似度结果,相似度越高的词也就对文档越关键。
优点:
LSA通过奇异值分解(SVD)将词、文档映射到一个低维的语义空间,挖掘出词、文档的浅层语义信息,从而对词、文档进行更本质的表达。
缺点:
1)计算复杂度高,特征空间的维度较大时,计算效率很低下;
2)LSA得到的分布信息是基于已有数据集的,每次得到一个新的文档都需要对整个空间进行训练,以得到新文档对应的分布信息;
3)对词的频率分布不敏感,物理解释性稀薄;
2. LDA算法
LAtent Dirichlet Allocatio 隐含迪利克雷分布
基于贝叶斯理论,根据词的共现信息的分析,拟合出词—文档—主题的分布,进而将词、文本都映射到一个语义空间中。
前提假设:文档中主题的先验分布和主题中词的先验分布都是迪利克雷分布。
(先验分布+数据集(似然) = 后验分布)
核心方法:对已有数据集进行统计,得到每篇文档中主题的多项式分布和每个主题对应词的多项式分布。然后结合先验的迪利克雷分布和观测数据集得到的多项式分布,得到一组Dirichlet-multi共轭,并据此来推断文档中主题的后验分布和主题中词的后验分布。
LDA模型求解的一种主流方法就是吉布斯采样,主要的模型训练过程为:
1)随机初始化,对语料中每篇文档的每个词w,随机的赋予一个topic编号z;
2)重新扫描语料库,对每个词w按照吉布斯采样公式重新采样它的topic,并在语料中更新;
3)重复以上语料库的重新采样过程直到吉布斯采样收敛;
4)统计语料库的topic-word共现频率矩阵,该矩阵就是LDA模型;
新文档主题的预估方法:
1)随机初始化,对当前文档的每个词w,随机的夫一个topic编号z;
重新扫描当前的文档,按照吉布斯采样公式,重新采样他的topic;
3)重复以上过程直到吉布斯采样收敛;
4)统计文档中的topic分布即为预估结果;
四、实战练习
基于上述的理论知识,这里基于搜狗新闻语料库,对一则公益新闻文本进行上述几种算法在关键词提取方面的对比实战。
import math
import jieba#进行中文分词,不区分词性
import jieba.posseg as psg#进行区分词性的中文分词
from gensim import corpora, models#corpora建立词空间和词袋模型, models用于调用gensim中的TF-IDF、LSI、LDA原生模型
from jieba import analyse#jieba的analyse有TextRank算法的模型
import functools#用于sorted函数中,对目标按照一定的函数规则进行排序#分词方法,调用jieba接口,包括不识别词性和识别词性两种方式,用pos参数进行控制
def seg_to_list(text, pos=False):if not pos:seg_list = jieba.cut(text)else:seg_list = psg.cut(text)return seg_list#加载停用词表,对文本中的无关词进行识别清洗
def get_stopword_list():stop_word_path = r"D:\DATAanalysis\NLP\learning-nlp-master\chapter-5\stopword.txt"stopword_list = [sw.replace("\n", " ") for sw in open(stop_word_path, encoding='utf-8').readlines()]return stopword_list#去除干扰词,这里干扰词包括停用词表中的词,和非名词词性的词(在很多场合,名字作为一种实体定义的词性,更能够体现文本的关键信息,当然不绝对,根据实际情况进行设置调整)
#通过pos参数控制,是否对词性进行过滤
def word_filter(seg_list, pos=False):stopword_list = get_stopword_list()filter_list = []for seg in seg_list:if not pos:word = segflag = "n"else:word = seg.wordflag = seg.flagif not flag.startswith("n"):continueif not word in stopword_list and len(word) > 1:filter_list.append(word)return filter_list#加载语料数据集,并进行分词、过滤等预处理操作
def load_data(pos=False, corpus_path=r"D:\DATAanalysis\NLP\yuliao\sougou_news_2012\news_sohusite_xml_utf8.txt"):doc_list = []for line in open(corpus_path, "r", encoding='utf-8'):content = line.strip()seg_list = seg_to_list(content)filter_list = word_filter(seg_list)doc_list.append(filter_list)return doc_list#根据语料库文本数据计算idf(逆文档频词:每个词在文档集中出现的次数,一篇文档出现就+1),具体公式见前文TF-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) + 1.0#一篇文档出现就+1for k, v in idf_dic.items():idf_dic[k] = math.log(tt_count / (v + 1.0))#对词在文档集中出现的频词进行+1拉普拉斯平滑处理,防止待评估文本中出现训练文本中没有的词,而导致的除0错误default_idf = math.log(tt_count / 1.0)#如果出现训练集中没有的词,就将其归为一类return idf_dic, default_idf#比较函数,用于比较列表对象中某个位置的元素值大小,并返回比较结果
#这里实际进行比较的是(词,tfidf分数/词与文档的主题分布的相似度),因此首先比较第二个索引元素,如果相同再按照词的字典序排序
def cmp(e1, e2):import numpy as npres = np.sign(e1[1] - e2[1])if res != 0:return reselse:a = e1[0] + e2[0]b = e2[0] + e1[0]if a > b:return 1elif a == b:return 0else:return -1#自定义tf-idf算法类:
class TfIdf(object):def __init__(self, idf_dic, default_idf, word_list, keyword_num):'''计算测试文档的tf-idf词典,并排序输出topN关键词:param idf_dic: train_idf输出的训练语料文档的idf字典:param default_idf: 如果测试集文本出现训练集未出现的词,其idf的默认值(训练集文档数的倒数):param word_list: 测试集文本分词后的词列表:param keyword_num: 需要提取的关键词的数量'''self.word_list = word_listself.idf_dic, self.default_idf = idf_dic, default_idfself.keyword_num = keyword_numself.tf_dic = self.get_tf_dic()def get_tf_dic(self):#计算测试文档中各个词在测试文档中的出现的次数,并进行归一化处理tf_dic = {}for word in self.word_list:tf_dic[word] = tf_dic.get(word, 0) + 1.0tt_count = len(tf_dic)for k, v in tf_dic.items():tf_dic[k] = float(v) / tt_countreturn tf_dicdef get_tfidf(self):#根据idf词典和td词典得到测试文档中词的tf-idf词典,并进行排序输出topN关键词tfidf_dic = {}for word in self.word_list:tf = self.tf_dic.get(word, 0)idf = self.idf_dic.get(word, self.default_idf)tfidf = tf * idftfidf_dic[word] = tfidffor k, v in sorted(tfidf_dic.items(), key=functools.cmp_to_key(cmp), reverse=True)[:self.keyword_num]:print(k + ' / ', end = "")print()#主题模型的自定义类:
class TopicModel(object):def __init__(self, doc_list, keyword_num, model='LSI', num_topics=10):'''主题模型的训练、词-主题分布、文档-主题分布、词-主题分布和文档-主题分布的相似度比较、topN关键词提取:param doc_list: 经过预处理(分词、过滤)之后的训练文档数据:param keyword_num: 关键词数量:param model: 模型,包括LSI和LDA两种:param num_topics: 模型进行训练时的主题数量'''#根据预处理之后的训练文档建立词典空间(对词进行编码)self.dictionary = corpora.Dictionary(doc_list)#词袋模型(统计每个文档中包含的词及其出现的次数#结果是一个二维列表,形状为:文档数*词总数量,各个元素值为每个词在对应文档中出现的次数)corpus = [self.dictionary.doc2bow(doc) for doc in doc_list]#根据训练文档集训练tf-idf模型(后续的LSI和LDA算法根据训练集建立语义空间需要tf-idf模型的结果)self.tfidf_model = models.TfidfModel(corpus)self.corpus_tfidf = self.tfidf_model[corpus]self.keyword_num = keyword_numself.num_topics = num_topics#调用models中的LSI和LDA模型,根据训练数据进行训练if model == "LSI":self.model = self.train_lsi()elif model == "LDA":self.model = self.train_lda()else:raise NameError("model")#得到训练文档集的词典集合word_dic = self.word_dictionary(doc_list)#计算词-主题分布self.wordtopic_dic = self.get_wordtopic(word_dic)#根据训练文档集训练LSI模型,需要的输入:训练文档集的tfidf字典;词和词编号的映射字典;主题数量)def train_lsi(self):lsi = models.LsiModel(self.corpus_tfidf, id2word=self.dictionary, num_topics=self.num_topics)return lsi#根据训练文档集训练LDA模型,需要的输入:训练文档集的tfidf字典;词和词编号的映射字典;主题数量)def train_lda(self):lda = models.LdaModel(self.corpus_tfidf, id2word=self.dictionary, num_topics=self.num_topics)return lda#输入对象的主题分布的求解函数,如果输入的是单个词,得到的就是这个词的词-主题分布,如果输入的一个文档(词列表),得到的就是文档-主题分布#具体的求解方法:先利用doc2bow得到输入的词袋模型,然后将词袋模型输入到训练好的语义空间中,即可得到输入对象的主题分布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] = wordtopicreturn wordtopic_dic#求解训练文档集的词典集合,将原文档集展开,取集合即可def word_dictionary(self, doc_list):dictionary = []for doc in doc_list:dictionary.extend(doc)dictionary = list(set(dictionary))return dictionary#求解测试文档中各个词的词-主题分布与文档-主题分布的相似度,取相似度最高的topN个词作为关键词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.0for t1, t2 in zip(l1, l2):x1 = t1[1]x2 = t2[1]a += x1 ** 2b += x1 ** 2c += x2 ** 2sim = a / math.sqrt(b * c) if not (b * c) == 0 else 0.0return sim#利用余弦相似度来衡量测试文档的文档-主题分布与测试文档中词-主题分布的相似度sim_dic = {}for k, v in self.wordtopic_dic.items():if k not in word_list:continuesim = calsim(v, senttopic)sim_dic[k] = sim#相似度topN的词作为关键词for k, v in sorted(sim_dic.items(), key=functools.cmp_to_key(cmp), reverse=True)[:self.keyword_num]:print(k + ' / ', end='')print()#测试文档基于tf-idf算法的关键词提取函数
def tfidf_extract(word_list, pos=False, keyword_num=10):doc_list = load_data(pos)idf_dic, default_idf = train_idf(doc_list)tfidf_model = TfIdf(idf_dic=idf_dic, default_idf=default_idf, word_list=word_list, keyword_num=keyword_num)tfidf_model.get_tfidf()#基于textrank算法的关键词提取函数,通过jieba的analyse模块实现,其中需要的输入是未经分词的初始文档
def textrank_extract(text, pos=False, keyword_num=10):textrank = analyse.textrankkeywords = textrank(text, keyword_num)for keyword in keywords:print(keyword + ' / ', end='')print()#基于主题模型的关键词提取函数
def topic_extract(word_list, model, pos=False, keyword_num=10):doc_list = load_data(pos)topic_model = TopicModel(doc_list=doc_list, keyword_num=keyword_num, model=model)topic_model.get_simword(word_list)if __name__ == '__main__':text = '6月19日,《2012年度“中国爱心城市”公益活动新闻发布会》在京举行。' + \'中华社会救助基金会理事长许嘉璐到会讲话。基金会高级顾问朱发忠,全国老龄' + \'办副主任朱勇,民政部社会救助司助理巡视员周萍,中华社会救助基金会副理事长耿志远,' + \'重庆市民政局巡视员谭明政。晋江市人大常委会主任陈健倩,以及10余个省、市、自治区民政局' + \'领导及四十多家媒体参加了发布会。中华社会救助基金会秘书长时正新介绍本年度“中国爱心城' + \'市”公益活动将以“爱心城市宣传、孤老关爱救助项目及第二届中国爱心城市大会”为主要内容,重庆市' + \'、呼和浩特市、长沙市、太原市、蚌埠市、南昌市、汕头市、沧州市、晋江市及遵化市将会积极参加' + \'这一公益活动。中国雅虎副总编张银生和凤凰网城市频道总监赵耀分别以各自媒体优势介绍了活动' + \'的宣传方案。会上,中华社会救助基金会与“第二届中国爱心城市大会”承办方晋江市签约,许嘉璐理' + \'事长接受晋江市参与“百万孤老关爱行动”向国家重点扶贫地区捐赠的价值400万元的款物。晋江市人大' + \'常委会主任陈健倩介绍了大会的筹备情况。'pos = Trueseg_list = seg_to_list(text, pos)filter_list = word_filter(seg_list, pos)print("TF-IDF模型结果:")tfidf_extract(filter_list, pos, keyword_num=10)print("TextRank模型结果:")textrank_extract(text, keyword_num=10)print("LSI模型结果:")topic_extract(filter_list, "LSI", pos, keyword_num=10)print("LDA模型结果:")topic_extract(filter_list, "LDA", pos, keyword_num=10)#过滤分词结果,只提取名词作为关键词的各个算法的提取结果:
TF-IDF模型结果:
晋江市 / 城市 / 爱心 / 基金会 / 大会 / 中华 / 社会 / 中国 / 陈健倩 / 重庆市 /
TextRank模型结果:
城市 / 爱心 / 救助 / 中国 / 社会 / 晋江市 / 基金会 / 大会 / 介绍 / 公益活动 /
LSI模型结果:
中国 / 项目 / 社会 / 爱心 / 基金会 / 中华 / 年度 / 城市 / 全国 / 公益活动 /
LDA模型结果:
中国 / 项目 / 城市 / 基金会 / 爱心 / 新闻 / 方案 / 发布会 / 社会 / 民政部 /