【NLP基础】NLP关键字提取技术之LDA算法原理与实践

人们是如何从大量文本资料中便捷得浏览和获取信息?答案你肯定会说通过关键字。仔细想想,我们人类是怎么提取关键词?我们从小就接触语言,语法,当听到或者看到一句话时,我们大脑自动会对这句话按规则分词(小学是不是做过断句的训练),还记得语文老师讲过,一句话中主语(名词),谓语(动词),宾语(名词)通常就是重点,这样我们大脑从小就会根据词性和语法对句中词进行打标签,训练分类器,随着我们接触到的语料越来越多,分类器也越来越准确(如果你是从事语言学的,那你的分类器就更准)。仅仅通过词性和语法,会在长文本中出现一个问题,因为一篇文章中会出现很多主语,谓语,宾语,不可能所有的这些词都是关键词,这样我们大脑是怎么处理的,如果我们对一篇文章的背景和主题很熟悉的话,我们会很准确得从一篇文章中提取关键词,但当我们接触一篇比较陌生的文章,我们往往很难准确提取关键词。

算法

上面其实对应的是机器学习的两种方法:监督学习和无监督学习。监督学习的关键字提取方法是通过分类的方式进行,通过打标签,训练分类器,从而实现关键字提取,但缺点就是需要大批量的标注数据,人工成本太高。相对于监督学习,无监督学习的方法就无需标注数据,常用的无监督关键词提取算法包括:TF-IDF算法、TextRank算法和主题模型算法(LDA、LSA、LSI),现重点介绍LDA算法,其他算法后续再讲.

我不喜欢讲大多学术上比较难懂的词,下面我将通俗得去讲解LDA算法原理。通常我们可以定义主题是一种关键词集合,如果一篇文章出现这些关键词,我们可以直接判断这篇文章属于某种主题。但这种定义主题会有个弊端,比如一篇文章出现了一个球星的名字,那么这篇文章的主题就是体育。可能你马上反驳说不一定,文章确实有球星的名字,但是里面全部在讲球星的性丑闻,和篮球没半毛钱关系,此时主题是娱乐还差不多。所以一个词不能硬性地扣一个主题的帽子,如果说一篇文章出现了某个球星的名字,我们只能说有很大概率他属于体育的主题,但也有小概率属于娱乐的主题。同一个词,在不同的主题背景下,它出现的概率是不同的。LDA认为文章都是用基本的词汇组合而成, LDA通过词汇的概率分布来反映主题!

由此可以定义LDA的生成过程:

1.对每篇文档,在主题分布中抽取一个主题

2.对抽到的主题所对应的单词分布中随机抽取一个单词

3.重复上述过程直至遍历整篇文档中的每个单词

4.经过以上三步,就可以看一下两个分布的乘积,是否符合给定文章的分布,以此来调整。

LDA的训练就是根据现有的数据集生成 文档-主题分布矩阵 和 主题-词分布矩阵

所以LDA的核心,其实就是这个公式

P(词 | 文档)=P(词 | 主题)P(主题 | 文档)

实练

上面说了这么多,下面我们通过代码去实现吧,Gensim中有实现好的训练方法,直接调用即可。Gensim是一款开源的第三方Python工具包,用于从原始的非结构化文本中,无监督地学习到文本隐层的主题向量表达。

训练一个关键词提取算法需要以下步骤:

  • 加载已有的文档数据集

  • 加载停用词表

  • 对数据集中的文档进行分词

  • 根据停用词表,过滤干扰词

  • 根据训练集训练算法

(很多博客上都是通过jieba分词,但我个人认为结巴分词不是很准确,如果分词都不准确,那怎么提取准确的关键词呢),个人采用pyhanlp的感知机算法进行分词,这是通过多次工作实践,感觉分词最准确的一种算法。

a.导入相关库

import mathimport numpy as npfrom pyhanlp import *import functoolsfrom gensim import corpora,models

b.定义好停用词表的加载方法

def get_stopword_list():    stop_word_path='stopwords.txt'    stopword_list=[sw.replace('\n','') for swin open(stop_word_path).readlines()]    return stopword_list

c.定义一个分词方法

def seg_to_list(sentence,pos=False): seg_list = HanLP.newSegment(“perceptron”).seg(sentence) return seg_list

d.定义干扰词过滤方法:根据分词结果对干扰词进行过滤

def word_filter(seg_list,pos=False):   stopword_list=get_stopword_list()   filter_list = [str(s.word) for sin seg_list if not s.word in stopword_list and len(s.word) > 1]   return filter_list

e.加载数据集,对数据集中的数据分词和过滤干扰词,每个文本最后变成一个非干扰词组成的词语列表

defload_data(pos=False):    doc_list=[]    ll =[]    for line in open('corpus.txt','r',encoding='utf-8'):        ll.append(line.strip())    content=’’.join(ll)    seg_list=seg_to_list(content,pos)    filter_list=word_filter(seg_list,pos)    doc_list.append(filter_list)    return doc_list

f.训练LDA模型

# doc_list:加载数据集方法的返回结果# keyword_num:关键词数量# model:主题模型的具体算法# num_topics:主题模型的主题数量class TopicModel(object):   def __init__(self,doc_list,keyword_num,model='LDA',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       self.model =self.train_lda()              #得到数据集的 主题-词分布       word_dic=self.word_dictionary(doc_list)       self.wordtopic_dic=self.get_wordtopic(word_dic)   def train_lda(self):       lda=models.LdaModel(self.corpus_tfidf,num_topics=self.num_topics,id2word=self.dictionary)       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   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 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 insorted(sim_dic.items(),key=functools.cmp_to_key(cmp),reverse=True)[:self.keyword_num]:           print(k+'/',end='')       print()    #词空间构建方法和向量化方法,在没有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

g.调用主函数,对目标文本进行关键词提取

if __name__ == '__main__':    text = '会上,中华社会救助基金会与“第二届中国爱心城市大会”承办方晋江市签约,许嘉璐理事长接受晋江市参与“百万孤老关爱行动”向国家重点扶贫地区捐赠的价值400万元的款物。'   pos=False   seg_list=seg_to_list(text,pos)   filter_list=word_filter(seg_list,pos)   print('LDA模型结果:') topic_extract(filter_list,'LDA',pos)

LDA模型结果:

重点/许嘉璐/行动/签约/百万/理事长/爱心/款物/晋江市/接受/

总体来说结果还算准确。

(0)

相关推荐