主题模型LDA的实现及其可视化pyLDAvis
由于是无监督算法,意味着你不需要去准备训练集,不用去苦逼地打标签,在数据准备阶段任务量至少省掉了一半啊。
而作为数学小白,其中具体原理如无必要我一向是不愿深究的,只要有人证明可行,那我拿来就用。聪明的脑袋要各尽其用嘛(笑
但也许我之后始终逃不掉要学习原理吧,先把原理解释放在这里,之后需要时再看。
故而本篇主要介绍的是如何上手实现及如何分析结果,这才是Data Scientist该干的事嘛
书归正传。以本次工作为例,我需要分析爬取到500名用户每人50页微博,大概500* 50* 10=250000条微博文本的主题情况。也就是说想看看这些人的微博主要是在关心些什么,他们共同关注哪些方面。
编程环境:
python 3.6 + pycharm 2018,
LDA实现使用的包是gensim,
分词还是用我们的老朋朋朋朋友jieba.
数据准备:
爬到的原始数据存在csv中,我想的是将所有的微博文本分行读到一个txt中,然后分词去停用词,就达到gensim的输入标准了。
def stopwordslist(filepath):
stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]
return stopwords
# 对句子进行分词
def seg_sentence(sentence):
sentence = re.sub(u'[0-9\.]+', u'', sentence)
jb.add_word('光线摄影学院') # 这里是加入用户自定义的词来补充jieba词典。
jb.add_word('曾兰老师') # 同样,如果你想删除哪个特定的未登录词,就先把它加上然后放进停用词表里。
jb.add_word('网页链接')
jb.add_word('微博视频')
jb.add_word('发布了头条文章')
jb.add_word('青春有你')
jb.add_word('青你')
sentence_seged = jb.cut(sentence.strip())
stopwords = stopwordslist('stopWords/stopwords.txt') # 这里加载停用词的路径
outstr = ''
for word in sentence_seged:
if word not in stopwords and word.__len__()>1:
if word != '\t':
outstr += word
outstr += " "
return outstr
inputs = open('input/like_mi10_user_all_retweet.txt', 'r', encoding='utf-8')
outputs = open('output1/mi10_user_retweet_fc.txt', 'w',encoding='utf-8')
for line in inputs:
line_seg = seg_sentence(line) # 这里的返回值是字符串
outputs.write(line_seg + '\n')
outputs.close()
inputs.close()
准备数据两小时,接口调用三分钟,等待结果两小时。
gensim很友好,词典、词袋模型、lda模型都是一句话搞定。
【code】
from gensim import corpora
from gensim.models import LdaModel
from gensim.corpora import Dictionary
train = []
fp = codecs.open('output1/mi10_user_retweet_fc.txt','r',encoding='utf8')
for line in fp:
if line != '':
line = line.split()
train.append([w for w in line])
dictionary = corpora.Dictionary(train)
corpus = [dictionary.doc2bow(text) for text in train]
lda = LdaModel(corpus=corpus, id2word=dictionary, num_topics=20, passes=60)
# num_topics:主题数目
# passes:训练伦次
# num_words:每个主题下输出的term的数目
for topic in lda.print_topics(num_words = 20):
termNumber = topic[0]
print(topic[0], ':', sep='')
listOfTerms = topic[1].split('+')
for term in listOfTerms:
listItems = term.split('*')
print(' ', listItems[1], '(', listItems[0], ')', sep='')
最后的输出是
0:
"北京" (0.024)
"疫情" ( 0.021)
"中国联通" ( 0.019)
"领券" ( 0.019)
"购物" ( 0.016)
"新冠" ( 0.016)
"专享" ( 0.012)
"元券" ( 0.012)
"确诊" ( 0.012)
"上海" ( 0.011)
"优惠" ( 0.011)
"肺炎" ( 0.010)
"新闻" ( 0.010)
"病例" ( 0.010)
"汽车"( 0.009)
1:
"小米" (0.133)
"Redmi" ( 0.019)
"新浪" ( 0.019)
"智慧" ( 0.018)
"雷军" ( 0.014)
"众测" ( 0.012)
"体验" ( 0.012)
"智能" ( 0.012)
"MIUI" ( 0.012)
"电视" ( 0.012)
"红米" ( 0.011)
"空调" ( 0.009)
"产品" ( 0.009)
"品牌" ( 0.009)
"价格"( 0.008)
2:
"抽奖" (0.056)
"平台" ( 0.032)
"评测" ( 0.022)
"生活" ( 0.013)
"红包" ( 0.013)
"关注" ( 0.012)
"这条" ( 0.012)
"视频" ( 0.012)
"工具" ( 0.011)
"获得" ( 0.011)
"有效" ( 0.011)
"进行" ( 0.010)
"恭喜" ( 0.010)
"用户" ( 0.010)
"公正"( 0.010)
.....
这种。他只会返回给你规定主题数下的多少词,每个词后面的小数可认为是这个词属于这个主题的概率,主题下所有词的概率和为1;而这个主题应该是什么,就要靠你人工后面分析来定义了。
那,盯着这一堆冷冰冰的词语和数字,也许你能通过概率衡量出这个词和这个主题的关系,但你能看出不同主题之间的关系吗?你能看出一个词和其他主题的关系吗?
有点困难。这时就要引出我们的LDA可视化分析工具了。
pip install pyldavis
pyLDAvis支持三种包内lda模型的直接传入:sklearn、gensim、graphlab,好像也可以自己算。这里当然紧接上文直接用gensim得到的lda模型了。
pyLDAvis也很友好,同样一句话完成实现:
import pyLDAvis.gensim
'''插入之前的代码片段'''
d=pyLDAvis.gensim.prepare(lda, corpus, dictionary)
'''
lda: 计算好的话题模型
corpus: 文档词频矩阵
dictionary: 词语空间
'''
pyLDAvis.show(d) #展示在浏览器
# pyLDAvis.displace(d) #展示在notebook的output cell中
数据量很大,运行时间稍长。
同时,如果你希望能够将这个结果保存为独立网页以便分享或放到web系统中,那么可以这样
d=pyLDAvis.gensim.prepare(lda, corpus, dictionary)
pyLDAvis.save_html(d, 'lda_pass10.html') # 将结果保存为该html文件
就不用每次都等好久时间跑结果了。。
是的,这个可视化过程真的很慢。。我用time来计时了,测试时为节省时间,gensim只训练一遍,耗时58s,然后等待pyLDAvis的渲染。。等了足足一个多小时吧?4200s…才终于出来了。
然后保存为网页很快。
d=pyLDAvis.gensim.prepare(lda, corpus, dictionary, mds='mmds')
酱紫。
实际测试了,可以说,没有效果。。。。。
同时这个选择算法的参数还可以为tsne,不同算法的区别还是看文档吧。
出来的这个页面,说复杂也不复杂,左边的气泡分布是不同主题,右边就是主题内前30个特征词了。浅蓝色的表示这个词在整个文档中出现的频率(权重),深红色的表示这个词在这个主题中所占的权重。右上角可以调节一个参数λ,其作用接着往下看。
于是我们最后回答LDAvis作者开发这个工具所要解决的三个问题:
通过鼠标悬浮在左边的气泡上,我们可以选择查看具体的某个主题。选定后,右侧面板会相应地显示出跟这个主题相关的词汇,通过总结这些词汇表达的意义,我们就可以归纳出该主题的意义。
同时,哪个词对主题的权重更高呢?某个词语主题的相关性,就由λ参数来调节。
如果λ接近1,那么在该主题下更频繁出现的词,跟主题更相关;
如果λ越接近0,那么该主题下更特殊、更独有(exclusive)的词,跟主题更相关(有点TF-IDF的意思了)。
所以我们可以通过调节λ的大小来改变词语跟主题的相关性,探索更加sense的主题意义。
在跑完主题建模之后,我们就可以知道每个主题出现的频率。LDAvis的作者将这个数字,用圆圈的大小来表示,同时也按顺序标号为1~n。所以气泡的大小及编号即表示主题出现的频率。
这里作者用多维尺度分析,提取出主成分做维度,将主题分布到这两个维度上,主题相互之间的位置远近,就表达了主题之间的接近性。气泡距离采用的是JSD距离,(应该)可认为是主题间的差异度,气泡有重叠说明这两个话题里的特征词有交叉。
知道这些后,就是看词说话了。看看这些词应该是在说些什么东西,提炼出不同的topic来,这才是有实际应用价值的结果。若无最后的结果输出,前面所做的一切都是废纸一张。
先总结到这里吧,抛去理论层面,应该是够用了。