一、概念
1.TF-IDF算法
TF-IDF算法(Term Frequency-Inverse Document Frequency,词频–逆文档频次算法)是一种基于统计的计算方法,常用于评估在一个文档集中一个词对某份文档的重要程度。这种作用显然很符合关键词抽取的需求,一个词对文档越重要,那就越可能是文档的关键词,人们常将TF-IDF算法应用于关键词提取中。
从算法的名称就可以看出,TF-IDF算法由两部分组成:TF算法以及IDF算法。TF算法是统计一个词在一篇文档中出现的频次,其基本思想是,一个词在文档中出现的次数越多,则其对文档的表达能力也就越强。而IDF算法则是统计一个词在文档集的多少个文档中出现,其基本的思想是,如果一个词在越少的文档中出现,则其对文档的区分能力也就越强。
2.TextRank算法
其他算法的关键词提取都要基于一个现成的语料库。如在TF-IDF中需要统计每个词在语料库中的多少个文档有出现过,也就是逆文档频率;主题模型的关键词提取算法则是要通过对大规模文档的学习,来发现文档的隐含主题。而TextRank算法则是可以脱离语料库的背景,仅对单篇文档进行分析就可以提取该文档的关键词。这也是TextRank算法的一个重要特点。TextRank算法最早用于文档的自动摘要,基于句子维度的分析,利用TextRank对每个句子进行打分,挑选出分数最高的n个句子作为文档的关键句,以达到自动摘要的效果。
3.LSA/LSI算法
LSA(Latent Semantic Analysis,潜在语义分析)和LSI(Latent Semantic Index,潜在语义索引),二者通常被认为是同一种算法,只是应用的场景略有不同,LSA是在需要构建的相关任务中的叫法。可以说,LSA和LSI都是对文档的潜在语义进行分析,但是潜在语义索引在分析后,还会利用分析的结果建立相关的索引。
LSA的主要步骤如下:
- 使用BOW模型将每个文档表示为向量;
- 将所有的文档词向量拼接起来构成词–文档矩阵(m×n);
- 对词–文档矩阵进行奇异值分解(SVD)操作([m×r]·[r×r]·[r×n]);
- 根据SVD的结果,将词–文档矩阵映射到一个更低维度k([m×k]·[k×k]·[k×n],0<k<r)的近似SVD结果,每个词和文档都可以表示为k个主题构成的空间中的一个点,通过计算每个词和文档的相似度(相似度计算可以通过余弦相似度或者是KL相似度进行),可以得到每个文档中对每个词的相似度结果,取相似度最高的一个词即为文档的关键词。
4.LDA算法
LDA根据词的共现信息的分析,拟合出词–文档–主题的分布,进而将词、文本都映射到一个语义空间中。
结合吉布斯采样的LDA模型训练过程一般如下:
- 随机初始化,对语料中每篇文档中的每个词w,随机地赋予一个topic编号z。
- 重新扫描语料库,对每个词w按照吉布斯采样公式重新采样它的topic,在语料中进行更新。
- 重复以上语料库的重新采样过程直到吉布斯采样收敛。
- 统计语料库的topic-word共现频率矩阵,该矩阵就是LDA的模型。
经过以上的步骤,就得到一个训练好的LDA模型,接下来就可以按照一定的方式针对新文档的topic进行预估,具体步骤如下:
- 随机初始化,对当前文档中的每个词w,随机地赋一个topic编号z。
- 重新扫描当前文档,按照吉布斯采样公式,重新采样它的topic。
- 重复以上过程直到吉布斯采样收敛。
- 统计文档中的topic分布即为预估结果。
通过上面LSA或者是LDA算法,我们得到了文档对主题的分布和主题对词的分布,接下来就是要利用这些信息来对关键词进行抽取。在我们得到主题对词的分布后,也据此得到词对主题的分布。接下来,就可以通过这个分布信息计算文档与词的相似性,继而得到文档最相似的词列表,最后就可以得到文档的关键词。
二、实战
1.步骤
训练一个关键词提取算法需要以下几个步骤:
1)加载已有的文档数据集。
2)加载停用词表。
3)对数据集中的文档进行分词。
4)根据停用词表,过滤干扰词。
5)根据数据集训练算法。
而根据训练好的关键词提取算法对新文档进行关键词提取要经过以下环节:
1)对新文档进行分词。
2)根据停用词表,过滤干扰词。
3)根据训练好的算法提取关键词。
2.代码
gensim进行LSI LSA LDA主题模型,TFIDF关键词提取,jieba TextRank关键词提取
#提取关键词 Jieba+Gensim
import math
import jieba
import jieba.posseg as psg
from gensim import corpora,models
from jieba import analyse
import functools
# 加载停用词
def get_stopword_list():
# 停用词表的存储路径,每一行为一个词,按行读取加载
# 进行编码转换确保匹配准确率
stop_word_path='./stopwords.txt'
stopword_list=[sw.replace('\n','') for sw in open(stop_word_path,'r',encoding='gbk',errors='ignore').readlines()]
return stopword_list
# 分词方法,pos为是否进行词性标注分词
def seg_to_list(sentence,pos=False):
if pos:
#词性标注分词
seg_list=psg.cut(sentence)
else:
# 不词性标注分词
seg_list=jieba.cut(sentence)
return seg_list
# 去除干扰词
def word_filter(seg_list,pos=False):
stopword_list=get_stopword_list()
filter_list=[]
#根据pos参数选择是否标注词性
#如不进行词性过滤,词性默认标记为n
for seg in seg_list:
if pos:
word=seg.word
flag=seg.flag
else:
word=seg
flag='n'
if not flag.startswith('n'):
continue
#屏蔽停用词和长度<2的词
if word in stopword_list or len(word)<2:
continue
filter_list.append(word)
return filter_list
# 数据加载,pos为是否词性标注的参数,corpus_path为数据集路径
def load_data(corpus_path='news1.txt',pos=False):
doc_list=[]
for line in open (path,'r',encoding='gbk',errors='ignore'):
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
# LSI生成文档–主题分布矩阵
def train_lsi(self):
lsi=models.LsiModel(self.corus_tfidf,id2word=self.dictionary,num_topics=self.num_topics)
return lsi
# LDA生成主题–词分布矩阵
def train_lda(self):
lda=models.LdaModel(self.corus_tfidf,id2word=self.dictionary,num_topics=self.num_topics)
return lda
#排序函数,用户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
#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)
tf =self.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)[:self.keyword_num]:
print(k,'/',end='')
print()
#主题模型
class TopicModel(object):
#三个入参:处理后的数据集,关键词数量,具体模型(LSI/LDA),主体数量
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)
# LSI生成文档–主题分布矩阵
def train_lsi(self):
lsi=models.LsiModel(self.corpus_tfidf,id2word=self.dictionary,num_topics=self.num_topics)
return lsi
# LDA生成主题–词分布矩阵
def train_lda(self):
lda=models.LdaModel(self.corpus_tfidf,id2word=self.dictionary,num_topics=self.num_topics)
return lda
# 词空间构建方法和向量化方法,在没有gensim接口时的一般处理方法
def word_dictionary(self, doc_list):
dictionary = []
# 2及变1及结构
for doc in doc_list:
# extend he append 方法有何异同 容易出错
dictionary.extend(doc)
dictionary = list(set(dictionary))
return dictionary
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
#计算词的分布和文档的分布的相似度,取相似度最高的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
#取排序前keyword_num的词作为关键词
for k,v in sorted(sim_dic.items(),key=functools.cmp_to_key(cmp),reverse=True)[:self.keyword_num]:
print(k,'/',end='')
print()
#封装调用接口
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,default_idf,word_list,keyword_num)
tfidf_model.get_tfidf()
def textrank_extract(text,pos=False,keyword_num=10):
textrank=analyse.textrank
keywords=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,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)
print('TF-IDF模型结果:')
tfidf_extract(filter_list,path)
print('TextRank模型结果:')
textrank_extract(text)
print('LSI模型结果:')
topic_extract(filter_list,path,'LSI',pos)
print('LDA模型结果:')
topic_extract(filter_list,path,'LDA',pos)
# LDA模型结果:
# doc_list>>>>>>>>>>> [['南都讯', '记者', '刘凡', '周昌', '日票', '深圳', '地铁', '车厢', '...]..]
# 先构建词空间self.dictionary Dictionary(4064 unique tokens: ['上将', '专门', '乘客', '仪式', '体验']...)
# 使用BOW模型向量化corpus [[(0, 1), (1, 1), (2, 2), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), (8, 1), (9, 1), ...
# dictionary变化前 ['南都讯', '记者', '刘凡', '周昌', '日票', '深圳', '地铁', '车厢', '坐票',..
# dictionary变化后 ['四联', '注音版', '前瞻性', '饲料', '成正比', '省城', '正值', '养白',
# self.wordtopic_dic 得到数据集的主题-词分布 {'四联': [(0, 0.12684152), (1, 0.12683797), (2, 0.12792856),
# (3, 0.61839193)], '注音版': [(0, 0.61396044), (1, 0.12880294), (2, 0.12856448), ....
# 晋江市/ 年度/ 频道/ 民政部/ 大会/ 陈健倩/ 许嘉璐/ 重庆市/ 人大常委会/ 巡视员/
# LDA 和LSI LSA步骤总结
# 数据集处理
# 1先构建词空间 Dictionary(4064 unique tokens: ['上将', '专门', '乘客', '仪式', '体验']...)
# 2使用BOW模型向量化 corpus [[(0, 1), (1, 1), (2, 2), (3, 1),。。。
# 3对每个词,根据tf-idf进行加权,得到加权后的向量表示
# 根据数据集获得模型
# 4得到数据集的主题-词分布 model (得到每个词的向量)(文档转列表 再转集合去重,再转列表)
# {'白血病': [(0, 0.1273009), (1, 0.6181468), (2, 0.12732704), (3, 0.12722531)], '婴儿': [。。。
# 然后求文档 文档的分布的
# 5词》向量》tf/idf加权》同第4步得到文档的分布向量 [(0, 0.033984687), (1, 0.033736005), (2, 0.8978361), (3, 0.03444325)]
# 计算余弦距离得到结果
# list.append(object) 向列表中添加一个对象object
# list.extend(sequence) 把一个序列seq的内容添加到列表中
# music_media = ['compact disc', '8-track tape', 'long playing record']
# new_media = ['DVD Audio disc', 'Super Audio CD']
# music_media.append(new_media)
# print music_media
# >>>['compact disc', '8-track tape', 'long playing record', ['DVD Audio disc', 'Super Audio CD']]
# 使用append的时候,是将new_media看作一个对象,整体打包添加到music_media对象中。
# music_media = ['compact disc', '8-track tape', 'long playing record']
# new_media = ['DVD Audio disc', 'Super Audio CD']
# music_media.extend(new_media)
# print music_media
# >>>['compact disc', '8-track tape', 'long playing record', 'DVD Audio disc', 'Super Audio CD']