LDA文档主题生成模型入门

一、LDA简介

LDA(Latent Dirichlet Allocation)是一种文档主题生成模型,也称为一个三层贝叶斯概率模型,包含词、主题和文档三层结构。所谓生成模型,就是说,我们认为一篇文章的每个词都是通过“以一定概率选择了某个主题,并从这个主题中以一定概率选择某个词语”这样一个过程得到。文档到主题服从多项式分布,主题到词服从多项式分布。

LDA是一种非监督机器学习技术,可以用来识别大规模文档集(document collection)或语料库(corpus)中潜藏的主题信息。它采用了词袋(bag of words)的方法,这种方法将每一篇文档视为一个词频向量,从而将文本信息转化为了易于建模的数字信息。但是词袋方法没有考虑词与词之间的顺序,这简化了问题的复杂性,同时也为模型的改进提供了契机。每一篇文档代表了一些主题所构成的一个概率分布,而每一个主题又代表了很多单词所构成的一个概率分布。

二、安装LDA库

pip install lda

安装完成后,可以在python安装目录下的Lib/site-packages目录下看到lda相关的目录。

三、了解数据集

1.png

数据集位于lda安装目录的tests文件夹中,包含三个文件:reuters.ldac, reuters.titles, reuters.tokens。
reuters.titles包含了395个文档的标题
reuters.tokens包含了这395个文档中出现的所有单词,总共是4258个
reuters.ldac有395行,第i行代表第i个文档中各个词汇出现的频率。以第0行为例,第0行代表的是第0个文档,从reuters.titles中可查到该文档的标题为“UK: Prince Charles spearheads British royal revolution. LONDON 1996-08-20”。
第0行的数据为:
159 0:1 2:1 6:1 9:1 12:5 13:2 20:1 21:4 24:2 29:1 ……
第一个数字159表示第0个文档里总共出现了159个单词(每个单词出现一或多次),
0:1表示第0个单词出现了1次,从reuters.tokens查到第0个单词为church
2:1表示第2个单词出现了1次,从reuters.tokens查到第2个单词为years
6:1表示第6个单词出现了1次,从reuters.tokens查到第6个单词为told
9:1表示第9个单词出现了1次,从reuters.tokens查到第9个单词为year
12:5表示第12个单词出现了5次,从reuters.tokens查到第12个单词为charles
……
这里第1、3、4、5、7、8、10、11……个单词序号和次数没列出来,表示出现的次数为0

注意:
395个文档的原文是没有的。上述三个文档是根据这395个文档处理之后得到的。

四、程序实现

(一)载入数据

(1)查看文档中词出现的频率

import numpy as np
import lda
import lda.datasets

# document-term matrix
X = lda.datasets.load_reuters()
print("type(X): {}".format(type(X)))
print("shape: {}\n".format(X.shape))
print(X[:5, :5])        #前五行的前五列

运行结果:

type(X): <class 'numpy.ndarray'>
shape: (395, 4258)

[[ 1  0  1  0  0]
 [ 7  0  2  0  0]
 [ 0  0  0  1 10]
 [ 6  0  1  0  0]
 [ 0  0  0  2 14]]

观察reuters.ldac中的前5行的前5列,发现:
第0行的前5列,单词编号为0,1,2,3,4的出现频次,正是1,0,1,0,0
第1行的前5列,单词编程为0,1,2,3,4的出现频次,正是7,0,2,0,0
……

(2)查看词

# the vocab
vocab = lda.datasets.load_reuters_vocab()
print("type(vocab): {}".format(type(vocab)))
print("len(vocab): {}\n".format(len(vocab)))
print(vocab[:5])

运行结果:

type(vocab): <class 'tuple'>
len(vocab): 4258

('church', 'pope', 'years', 'people', 'mother')

可以看出,reuters.tokens中有4258个单词,前五个分别是church, pope, years, people, mother.

(3)查看文档标题

# titles for each story
titles = lda.datasets.load_reuters_titles()
print("type(titles): {}".format(type(titles)))
print("len(titles): {}\n".format(len(titles)))
print(titles[:5])       # 打印前五个文档的标题

运行结果:

type(titles): <class 'tuple'>
len(titles): 395

('0 UK: Prince Charles spearheads British royal revolution. LONDON 1996-08-20', 
'1 GERMANY: Historic Dresden church rising from WW2 ashes. DRESDEN, Germany 1996-08-21',
"2 INDIA: Mother Teresa's condition said still unstable. CALCUTTA 1996-08-23", 
'3 UK: Palace warns British weekly over Charles pictures. LONDON 1996-08-25', 
'4 INDIA: Mother Teresa, slightly stronger, blesses nuns. CALCUTTA 1996-08-25')

(4)查看前5个文档第0个词出现的次数

doc_id = 0
word_id = 0
while doc_id < 5:
    print("doc id: {} word id: {}".format(doc_id, word_id))
    print("-- count: {}".format(X[doc_id, word_id]))
    print("-- word : {}".format(vocab[word_id]))
    print("-- doc  : {}\n".format(titles[doc_id]))
    doc_id += 1

运行结果:

doc id: 0 word id: 0
-- count: 1
-- word : church
-- doc  : 0 UK: Prince Charles spearheads British royal revolution. LONDON 1996-08-20

doc id: 1 word id: 0
-- count: 7
-- word : church
-- doc  : 1 GERMANY: Historic Dresden church rising from WW2 ashes. DRESDEN, Germany 1996-08-21

doc id: 2 word id: 0
-- count: 0
-- word : church
-- doc  : 2 INDIA: Mother Teresa's condition said still unstable. CALCUTTA 1996-08-23

doc id: 3 word id: 0
-- count: 6
-- word : church
-- doc  : 3 UK: Palace warns British weekly over Charles pictures. LONDON 1996-08-25

doc id: 4 word id: 0
-- count: 0
-- word : church
-- doc  : 4 INDIA: Mother Teresa, slightly stronger, blesses nuns. CALCUTTA 1996-08-25

(二)训练模型

设置20个主题,500次迭代

model = lda.LDA(n_topics=20, n_iter=500, random_state=1)
model.fit(X)          # model.fit_transform(X) is also available

(三)主题-单词分布

计算前3个单词在所有主题(共20个)中所占的权重

topic_word = model.topic_word_
print("type(topic_word): {}".format(type(topic_word)))
print("shape: {}".format(topic_word.shape))
print(vocab[:3])
print(topic_word[:, :3])    #打印所有行(20)行的前3列

运行结果:

type(topic_word): <class 'numpy.ndarray'>
shape: (20, 4258)
('church', 'pope', 'years')
[[2.72436509e-06 2.72436509e-06 2.72708945e-03]
 [2.29518860e-02 1.08771556e-06 7.83263973e-03]
 [3.97404221e-03 4.96135108e-06 2.98177200e-03]
 [3.27374625e-03 2.72585033e-06 2.72585033e-06]
 [8.26262882e-03 8.56893407e-02 1.61980569e-06]
 [1.30107788e-02 2.95632328e-06 2.95632328e-06]
 [2.80145003e-06 2.80145003e-06 2.80145003e-06]
 [2.42858077e-02 4.66944966e-06 4.66944966e-06]
 [6.84655429e-03 1.90129250e-06 6.84655429e-03]
 [3.48361655e-06 3.48361655e-06 3.48361655e-06]
 [2.98781661e-03 3.31611166e-06 3.31611166e-06]
 [4.27062069e-06 4.27062069e-06 4.27062069e-06]
 [1.50994982e-02 1.64107142e-06 1.64107142e-06]
 [7.73480150e-07 7.73480150e-07 1.70946848e-02]
 [2.82280146e-06 2.82280146e-06 2.82280146e-06]
 [5.15309856e-06 5.15309856e-06 4.64294180e-03]
 [3.41695768e-06 3.41695768e-06 3.41695768e-06]
 [3.90980357e-02 1.70316633e-03 4.42279319e-03]
 [2.39373034e-06 2.39373034e-06 2.39373034e-06]
 [3.32493234e-06 3.32493234e-06 3.32493234e-06]]

计算所有行的比重之和(等于1)

for n in range(20):
    sum_pr = sum(topic_word[n,:])   # 第n行所有列的比重之和,等于1
    print("topic: {} sum: {}".format(n, sum_pr))

计算结果:

topic: 0 sum: 1.0000000000000875
topic: 1 sum: 1.0000000000001148
topic: 2 sum: 0.9999999999998656
topic: 3 sum: 1.0000000000000042
topic: 4 sum: 1.0000000000000928
topic: 5 sum: 0.9999999999999372
topic: 6 sum: 0.9999999999999049
topic: 7 sum: 1.0000000000001694
topic: 8 sum: 1.0000000000000906
topic: 9 sum: 0.9999999999999195
topic: 10 sum: 1.0000000000001261
topic: 11 sum: 0.9999999999998876
topic: 12 sum: 1.0000000000001268
topic: 13 sum: 0.9999999999999034
topic: 14 sum: 1.0000000000001892
topic: 15 sum: 1.0000000000000984
topic: 16 sum: 1.0000000000000768
topic: 17 sum: 0.9999999999999146
topic: 18 sum: 1.0000000000000364
topic: 19 sum: 1.0000000000001434

(四)计算各主题top-N个词

计算每个主题中,比重最大的5个词

n = 5
for i, topic_dist in enumerate(topic_word):
    topic_words = np.array(vocab)[np.argsort(topic_dist)][:-(n+1):-1]
    print('*Topic {}\n- {}'.format(i, ' '.join(topic_words)))

运行结果:

*Topic 0
- government british minister west group
*Topic 1
- church first during people political
*Topic 2
- elvis king wright fans presley
*Topic 3
- yeltsin russian russia president kremlin
*Topic 4
- pope vatican paul surgery pontiff
*Topic 5
- family police miami versace cunanan
*Topic 6
- south simpson born york white
*Topic 7
- order church mother successor since
*Topic 8
- charles prince diana royal queen
*Topic 9
- film france french against actor
*Topic 10
- germany german war nazi christian
*Topic 11
- east prize peace timor quebec
*Topic 12
- n't told life people church
*Topic 13
- years world time year last
*Topic 14
- mother teresa heart charity calcutta
*Topic 15
- city salonika exhibition buddhist byzantine
*Topic 16
- music first people tour including
*Topic 17
- church catholic bernardin cardinal bishop
*Topic 18
- harriman clinton u.s churchill paris
*Topic 19
- century art million museum city

(五)文档-主题分布

总共有395篇文档,计算前10篇文档最可能的主题

doc_topic = model.doc_topic_
print("type(doc_topic): {}".format(type(doc_topic)))
print("shape: {}".format(doc_topic.shape))
for n in range(10):
    topic_most_pr = doc_topic[n].argmax()
    print("doc: {} topic: {}".format(n, topic_most_pr))

运行结果:

type(doc_topic): <class 'numpy.ndarray'>
shape: (395, 20)
doc: 0 topic: 8
doc: 1 topic: 1
doc: 2 topic: 14
doc: 3 topic: 8
doc: 4 topic: 14
doc: 5 topic: 14
doc: 6 topic: 14
doc: 7 topic: 14
doc: 8 topic: 14
doc: 9 topic: 8

(六)可视化分析

(1)绘制主题0、主题5、主题9、主题14、主题19的词出现次数分布

import matplotlib.pyplot as plt

f, ax = plt.subplots(5, 1, figsize=(8, 6), sharex=True)
for i, k in enumerate([0, 5, 9, 14, 19]):
    print(i, k)
    ax[i].stem(topic_word[k, :], linefmt='b-',
               markerfmt='bo', basefmt='w-')
    ax[i].set_xlim(-50, 4350)
    ax[i].set_ylim(0, 0.08)
    ax[i].set_ylabel("Prob")
    ax[i].set_title("topic {}".format(k))

ax[4].set_xlabel("word")

plt.tight_layout()
plt.show()

运行结果:

2.png

(2)绘制文档1、文档3、文档4、文档8和文档9的主题分布

f, ax = plt.subplots(5, 1, figsize=(8, 6), sharex=True)
for i, k in enumerate([1, 3, 4, 8, 9]):
    ax[i].stem(doc_topic[k, :], linefmt='r-',
               markerfmt='ro', basefmt='w-')
    ax[i].set_xlim(-1, 21)
    ax[i].set_ylim(0, 1)
    ax[i].set_ylabel("Prob")
    ax[i].set_title("Document {}".format(k))

ax[4].set_xlabel("Topic")

plt.tight_layout()
plt.show()

运行结果:

3.png

五、完整代码

import numpy as np
import lda
import lda.datasets

# document-term matrix
X = lda.datasets.load_reuters()
print("type(X): {}".format(type(X)))
print("shape: {}\n".format(X.shape))
print(X[:5, :5])        #前五行的前五列

# the vocab
vocab = lda.datasets.load_reuters_vocab()
print("type(vocab): {}".format(type(vocab)))
print("len(vocab): {}\n".format(len(vocab)))
print(vocab[:5])

# titles for each story
titles = lda.datasets.load_reuters_titles()
print("type(titles): {}".format(type(titles)))
print("len(titles): {}\n".format(len(titles)))
print(titles[:5])       # 打印前五个文档的标题

print("\n************************************************************")
doc_id = 0
word_id = 0
while doc_id < 5:
    print("doc id: {} word id: {}".format(doc_id, word_id))
    print("-- count: {}".format(X[doc_id, word_id]))
    print("-- word : {}".format(vocab[word_id]))
    print("-- doc  : {}\n".format(titles[doc_id]))
    doc_id += 1

topicCnt = 20
model = lda.LDA(n_topics = topicCnt, n_iter = 500, random_state = 1)
model.fit(X)          # model.fit_transform(X) is also available

print("\n************************************************************")
topic_word = model.topic_word_
print("type(topic_word): {}".format(type(topic_word)))
print("shape: {}".format(topic_word.shape))
print(vocab[:3])
print(topic_word[:, :3])    #打印所有行(20)行的前3列

for n in range(20):
    sum_pr = sum(topic_word[n,:])   # 第n行所有列的比重之和,等于1
    print("topic: {} sum: {}".format(n, sum_pr))

print("\n************************************************************")
n = 5
for i, topic_dist in enumerate(topic_word):
    topic_words = np.array(vocab)[np.argsort(topic_dist)][:-(n+1):-1]
    print('*Topic {}\n- {}'.format(i, ' '.join(topic_words)))

print("\n************************************************************")
doc_topic = model.doc_topic_
print("type(doc_topic): {}".format(type(doc_topic)))
print("shape: {}".format(doc_topic.shape))
for n in range(10):
    topic_most_pr = doc_topic[n].argmax()
    print("doc: {} topic: {}".format(n, topic_most_pr))

print("\n************************************************************")
import matplotlib.pyplot as plt

f, ax = plt.subplots(5, 1, figsize=(8, 6), sharex=True)
for i, k in enumerate([0, 5, 9, 14, 19]):
    print(i, k)
    ax[i].stem(topic_word[k, :], linefmt='b-',
               markerfmt='bo', basefmt='w-')
    ax[i].set_xlim(-50, 4350)
    ax[i].set_ylim(0, 0.08)
    ax[i].set_ylabel("Prob")
    ax[i].set_title("topic {}".format(k))

ax[4].set_xlabel("word")

plt.tight_layout()
plt.show()

print("\n************************************************************")
f, ax = plt.subplots(5, 1, figsize=(8, 6), sharex=True)
for i, k in enumerate([1, 3, 4, 8, 9]):
    ax[i].stem(doc_topic[k, :], linefmt='r-',
               markerfmt='ro', basefmt='w-')
    ax[i].set_xlim(-1, 21)
    ax[i].set_ylim(0, 1)
    ax[i].set_ylabel("Prob")
    ax[i].set_title("Document {}".format(k))

ax[4].set_xlabel("Topic")

plt.tight_layout()
plt.show()

六、参考资料

(1)
https://blog.csdn.net/eastmount/article/details/50824215

(2)http://chrisstrelioff.ws/sandbox/2014/11/13/getting_started_with_latent_dirichlet_allocation_in_python.html

七、推荐阅读

《LDA漫游指南》


了解小朋友学编程请加QQ307591841(微信与QQ同号),或QQ群581357582。
关注公众号请扫描二维码


qrcode_for_kidscode_258.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • LDA(Latent Dirichlet Allocation)是一种文档主题生成模型,也称为一个三层贝叶斯概率模...
    chaaffff阅读 1,726评论 0 2
  • LDA的代码实现:http://blog.csdn.net/u010551621/article/details/...
    wlj1107阅读 33,872评论 0 31
  • 前面的文章主要从理论的角度介绍了自然语言人机对话系统所可能涉及到的多个领域的经典模型和基础知识。这篇文章,甚至之后...
    我偏笑_NSNirvana阅读 13,648评论 2 64
  • 这个系列的第六个主题,主要谈一些搜索引擎相关的常见技术。 1995年是搜索引擎商业公司发展的重要起点,《浅谈推荐系...
    我偏笑_NSNirvana阅读 6,531评论 3 24
  • 火车上,站台,客运站里,长途车上,我发现,无时无刻不在想着母亲。 心里是空的,恍恍惚惚走着神,这时候若是遇见了坏人...
    司卓阅读 86评论 0 1