使用LDA进行文档主题建模
LDA简介
LDA(Latent Dirichlet Allocation)是一种文档主题模型,包含词、主题和文档三层结构。
LDA认为一篇文档由一些主题按照一定概率组成,一个主题又由一些词语按照一定概率组成。早期人们用词袋模型对一篇文章进行建模,把一篇文档表示为若干单词的计数。无论是中文还是英文,都由大量单词组成,这就造成词袋向量的维数巨大,少则几千多则上万,在使用分类模型进行训练时,非常容易造成训练缓慢以及过拟合。LDA本质上把词袋模型进行了降维,把一篇文档以主题的形式进行了表示。主题的个数通常为几百,这就把文档使用了维数为几百的向量进行了表示,大大加快了训练速度,并且相对不容易造成过拟合。从某种程度上来说,主题是对若干词语的抽象表示。
以最近一部电视剧《南方有乔木》为例。假设一篇文章介绍了这部电视剧的主要内容。我们可以把这篇文章表示为:
0.30*"创业"+0.3*"三角恋"+0.2*"无人机" |
然后我们可以把三角恋表示为:
0.4*"南乔"+0.3*"时樾"+0.3*"安宁" |
需要指出的是,计算出文档、主题以及词语之间的表示关系,需要基于大量的文档,这样才具有普遍的意义。LDA正是提供了这种算法,自动从训练文档中计算出这种对应关系。
数据集
本文演示用的数据集,依然使用搜狗新闻数据集SogouCS。我们从SogouCS中提取正文内容,每个URL对应的正文当做一篇文档,并且使用jieba进行了分词。演示期间我们提取SogouCS的前10000条数据用于计算LDA。
def load_sougou_content(): |
计算LDA时,需要删除停用词,加载我们之前保存的停用词。
def load_stopwords(): |
计算主题
我们使用gensim提供的API进行LDA计算。首先从语料中提取字典,并用该字典把预料转换成词袋。
# 得到文档-单词矩阵 (直接利用统计词频得到特征) |
然后进行LDA计算,演示期间设置只计算5个主题,通常生产环境经验值为200。
num_topics=5 |
其中比较重要的几个参数含义如下:
corpus,计算LDA的语料
id2word,语料对应的字典
num_topics,计算的主题的数量
我们打印前5个主题。
for index,topic in lda.print_topics(5): |
主题内容如下所示。
0.007*"月" + 0.006*"中" + 0.005*"年" + 0.005*"日" + 0.004*"公司" + 0.004*"时间" + 0.003*"北京" + 0.003*"比赛" + 0.002*"中国" + 0.002*"记者" |
如果需要设置每个话题对应的关键字的个数,可以通过参数num_words设置,默认为10,这里的num_topics参数容易导致误解,它的含义是显示排名前几个话题,类似topN参数。
print_topics(num_topics=20, num_words=10) |
词袋处理后的结果,使用TFIDF算法处理后,可以进一步提升LDA的效果。
# 利用tf-idf来做为特征进行处理 |
运行的效果如下所示。
0.001*"比赛" + 0.001*"联赛" + 0.001*"意甲" + 0.001*"主场" + 0.001*"轮" + 0.001*"赛季" + 0.001*"时间" + 0.001*"孩子" + 0.001*"北京" + 0.000*"航天员" |
使用LDA提取文档特征
通常LDA的结果可以作为进一步文档分类、文档相似度计算以及文档聚类的依据,可以把LDA当做一种特征提取方法。
#获取语料对应的LDA特征 |
输出0号文档对应的LDA值如下,即把0号文档以5个话题形式表示。
[(0, 0.019423252), (1, 0.019521076), (2, 0.92217809), (3, 0.01954053), (4, 0.019337002)] |
这里需要解释的是,无论是词袋模型还是LDA生成的结果,都可能存在大量的0,这会占用大量的内存空间。因此默认情况下,词袋以及LDA计算的结果都以稀疏矩阵的形式保存。稀疏矩阵的最小单元定义为:
(元素所在的位置,元素的值) |
比如一个稀疏矩阵只有0号和2号元素不为0,分别为1和5,那么它的表示方法如下:
[(0,1),(2,5)] |
使用多核计算
LDA在生产环境中运行遇到的最大问题就是默认只能使用单核资源,运行速度过慢。gensim针对这一情况也提供了多核版本。
lda = models.ldamulticore.LdaMulticore(corpus=texts_tf_idf, id2word=dictionary, num_topics=num_topics) |
该版本默认情况默认使用cpu_count()-1 即使用几乎全部CPU,仅保留一个CPU不参与LDA计算,也可以通过参数workers指定使用的CPU个数,这里的CPU指的物理CPU,不是超线程的CPU数。一般认为:
CPU总核数 = 物理CPU个数 * 每颗物理CPU的核数 |
可以尝试使用如下命令查看服务器的CPU信息。
# 查看CPU信息(型号) |
gensim官网上公布了一组测试数据,数据集为Wikipedia英文版数据集,该数据集有350万个文档,10万个特征,话题数设置为100的情况下运行LDA算法。硬件环境为一台拥有4个物理i7 CPU的服务器。使用单核接口需要使用3小时44分,当使用多核接口且使用3个物理CPU仅需要1小时6分钟。我们延续上面的例子,继续使用搜狗的数据集,为了要效果更加明显,我们使用SogouCS的前50000的数据进行LDA运算,话题数设置为100,并使用time.clock()获取当前时间,便于计算各个环节消耗的时间,比如计算LDA消耗的时间的方法如下所示。
#获取当前时间 |
分别计算使用1,2,4等不同CPU的情况,统计了预处理环节、LDA环节的耗时,其中预处理环节主要进行了词袋处理和TFIDF处理,测试用的服务器一共有12个物理CPU。
<table> |
测试结果表明,使用多CPU资源可以提升LDA的计算效率,尤其对于CPU使用个数较少时,几乎成线性下降,当使用CPU个数较多时,性能改善不明显。在本例中默认参数将使用11个CPU,因此默认参数下计算速率略低于使用12个CPU。
在线学习
LDA可以进行在线学习,动态更新模型。
lda = LdaMulticore(corpus, num_topics=10) |