LDA主题模型简介及Python实现+ M2 d8 f; L4 m% J7 z' ~# C1 {
0 _% A% D1 W+ q# E+ ]) }
一、LDA主题模型简介 $ @3 c% q1 R" ^2 _ LDA主题模型主要用于推测文档的主题分布,可以将文档集中每篇文档的主题以概率分布的形式给出根据主题进行主题聚类或文本分类。. x8 |+ V- y4 y$ B
7 Y1 y2 P4 _6 G
LDA主题模型不关心文档中单词的顺序,通常使用词袋特征(bag-of-word feature)来代表文档。词袋模型介绍可以参考这篇文章:文本向量化表示——词袋模型 - 知乎$ K( n0 ?6 J$ b M& N
0 b. m5 L& U' [6 p2 b% M
了解LDA模型,我们需要先了解LDA的生成模型,LDA认为一篇文章是怎么形成的呢? ) p. W/ j7 G3 m. B4 W. O4 } % U- j3 Q6 O6 J; a! J+ ~: _ LDA模型认为主题可以由一个词汇分布来表示,而文章可以由主题分布来表示。" G' j/ c$ y( Z7 q) S
) [6 i( g6 z; m# k 比如有两个主题,美食和美妆。LDA说两个主题可以由词汇分布表示,他们分别是:9 s/ j* t# V0 C7 a' O+ ]! _
% z+ J0 O6 D" b2 o9 l6 X- N{面包:0.4,火锅:0.5,眉笔:0.03,腮红:0.07}. v, o8 y* ]7 h3 [- H) N
{眉笔:0.4,腮红:0.5,面包:0.03,火锅:0.07} 2 s- c0 X1 G) H% Y( B ; }# |- j4 v; h2 V: f( b" }0 I 同样,对于两篇文章,LDA认为文章可以由主题分布这么表示: 1 N9 S( f7 ], M; J0 [# z , Q5 {9 i# v) Q* g( l& R0 r; b《美妆日记》{美妆:0.8,美食:0.1,其他:0.1} ) D- a- G7 f: h$ a3 `5 x 4 i2 z. O K! G5 x |7 Y, ~" H- s《美食探索》{美食:0.8,美妆:0.1,其他:0.1} ( a% p& A8 w7 O; `" U ( w. Z8 Y% @9 i8 G' A9 _. q$ O0 F 所以想要生成一篇文章,可以先以一定的概率选取上述某个主题,再以一定的概率选取那个主题下的某个单词,不断重复这两步就可以生成最终文章。* j3 C' I6 t1 H+ a( A' E
], g W$ n3 N1 w$ b
在LDA模型中,一篇文档生成的方式如下: 5 h( k( s6 w( ]; r" f6 F: P : ?4 ^8 L, {9 \4 H9 \# _0 N0 q$ P4 }5 t1 v$ n9 t0 |
3 ] C/ E$ D( N/ P% h! h. i 其中,类似Beta分布是二项式分布的共轭先验概率分布,而狄利克雷分布(Dirichlet分布)是多项式分布的共轭先验概率分布。6 @1 d8 @( I; _/ g" T3 c8 B1 T
( \2 C7 c, y# T% {+ n' H& I$ {2 ?$ y5 f" A3 ?+ s. m$ K M
2 m0 p* \% s o
如果我们要生成一篇文档,它里面的每个词语出现的概率为: ' a2 N: a) h- G8 P6 Y% Q, m, v/ g) q: q' V6 f
$ O9 a% `! S' m/ k' d* v }2 L/ l
. h9 x7 v* V- {; s4 M3 i$ a
更详细的数学推导可以见:通俗理解LDA主题模型_结构之法 算法之道-CSDN博客_lda模型! O' m( n* ^9 Y( Q3 s" A( N }
( V$ s" P. k/ w# H' b
看到文章推断其隐藏的主题分布,就是建模的目的。换言之,人类根据文档生成模型写成了各类文章,然后丢给了计算机,相当于计算机看到的是一篇篇已经写好的文章。现在计算机需要根据一篇篇文章中看到的一系列词归纳出当篇文章的主题,进而得出各个主题各自不同的出现概率:主题分布。8 s3 [, `! b4 R. c2 i
u5 L1 D# s; j2 [7 J1 y% P 至于LDA主题模型在计算机中具体是怎么实现的,我们也可以不必细究,现在有很多可以直接用来进行LDA主题分析的包,我们直接用就行。(没错,我就是调包侠)/ A+ D4 }2 C: @. L" [
" j0 N0 U8 `' Y# x- B A' Q: @; y) q: X
二、Python实现 2 c* `# H& E$ { Q 在用Python进行LDA主题模型分析之前,我先对文档进行了分词和去停用词处理(详情可以看我之前的文章:用python对单一微博文档进行分词——jieba分词(加保留词和停用词)_阿丢是丢心心的博客-CSDN博客_jieba 停用词) . F# U+ ?7 S7 i 4 K/ X$ e+ l* H0 m; z/ G( W6 f 我下面的输入文件也是已经分好词的文件 - A! p/ I8 U8 s/ M6 d& ^+ L $ b8 p" [3 x9 `8 |" D7 E& _" ?1.导入算法包 . l! M$ q) X) V) `. _ `6 p0 iimport gensim , n/ E7 Z* m- Y) S4 Rfrom gensim import corpora# S2 x% S9 L+ G' R: i
import matplotlib.pyplot as plt3 M, u* @" o7 S+ \4 T' \6 B4 c
import matplotlib& z! R! }/ A7 _( G2 C6 t! X0 ?8 h
import numpy as np; {" W# _4 s+ }, W$ u- X
import warnings2 A+ u t9 V% |" L6 a1 S7 ?
warnings.filterwarnings('ignore') # To ignore all warnings that arise here to enhance clarity : Y$ }9 v$ r; g( S7 u$ l N/ N8 a Q. t" z" S: {
from gensim.models.coherencemodel import CoherenceModel 3 w; W& b4 s. v- t1 z) A8 Cfrom gensim.models.ldamodel import LdaModel , G, E7 G/ E4 b) Q2.加载数据: P7 Q! i5 ]1 {, \2 ?# o, a
先将文档转化为一个二元列表,其中每个子列表代表一条微博:4 _( w) k& |/ p/ ^( l- P8 t
, C6 O. E6 W+ sPATH = "E:/data/output.csv" , z8 y9 ^9 j0 K9 Y0 ~0 b0 `" ]' S# W9 A) e/ V+ N* ]
file_object2=open(PATH,encoding = 'utf-8',errors = 'ignore').read().split('\n') #一行行的读取内容' S$ D! y. ~$ `# ]
data_set=[] #建立存储分词的列表 " |. a$ y0 `+ lfor i in range(len(file_object2)):" C z% a! W/ O% v C! v
result=[]( K) X' G z: R/ ^! c# P5 t% L
seg_list = file_object2.split()# ~; |& |% @1 e4 L% Z5 i) q
for w in seg_list : #读取每一行分词 3 O/ Y8 G6 v# I% k# E6 v result.append(w) : n+ i) D) j0 i% ^# I data_set.append(result) 4 Y, z' j8 P9 Q% S! Eprint(data_set): o* O! e4 Q/ a1 U; {" k
构建词典,语料向量化表示:" |$ S$ I7 B& g
: p% i( g: r$ B. Edictionary = corpora.Dictionary(data_set) # 构建词典 * W5 j, `0 w' }. p+ ocorpus = [dictionary.doc2bow(text) for text in data_set] #表示为第几个单词出现了几次/ `6 M: s3 [$ C
3.构建LDA模型: ~% o3 K7 @+ u7 P" J$ Z! b
ldamodel = LdaModel(corpus, num_topics=10, id2word = dictionary, passes=30,random_state = 1) #分为10个主题 3 u: Q; U0 u$ R% ]3 ] y9 wprint(ldamodel.print_topics(num_topics=num_topics, num_words=15)) #每个主题输出15个单词 2 Z5 L- V0 C2 P 这是确定主题数时LDA模型的构建方法,一般我们可以用指标来评估模型好坏,也可以用这些指标来确定最优主题数。一般用来评价LDA主题模型的指标有困惑度(perplexity)和主题一致性(coherence),困惑度越低或者一致性越高说明模型越好。一些研究表明perplexity并不是一个好的指标,所以一般我用coherence来评价模型并选择最优主题,但下面代码两种方法我都用了。# s; T: T+ }9 }2 I5 v
( ?, {- k% U* n0 p" o O
#计算困惑度 % S, h; s A3 Y; I/ B- I2 n) sdef perplexity(num_topics): . {8 }' E. }5 Q( h1 O3 G ldamodel = LdaModel(corpus, num_topics=num_topics, id2word = dictionary, passes=30) & ?. [/ N/ ]! e9 t4 g3 v j4 Y9 R1 W print(ldamodel.print_topics(num_topics=num_topics, num_words=15)); E' l" @ }1 B# m
print(ldamodel.log_perplexity(corpus))/ A5 F% c, P% [" B( o# ]2 T5 U
return ldamodel.log_perplexity(corpus) 7 T; g, Q4 u# g- v! T8 H#计算coherence 0 i0 s4 r' D5 f) w9 Udef coherence(num_topics): . F7 B' N$ l. b$ R+ X ldamodel = LdaModel(corpus, num_topics=num_topics, id2word = dictionary, passes=30,random_state = 1) ! \0 z; K) Y7 D/ D( G. U! O print(ldamodel.print_topics(num_topics=num_topics, num_words=10))% h1 [1 t8 `! E/ V I
ldacm = CoherenceModel(model=ldamodel, texts=data_set, dictionary=dictionary, coherence='c_v')# |9 ]* s2 _6 S; D+ s+ _. ?
print(ldacm.get_coherence())3 l3 \: I3 B& X# A9 O
return ldacm.get_coherence() " j2 H' X1 D) x# Z4.绘制主题-coherence曲线,选择最佳主题数3 T6 p' ?" O3 q- N: b0 |, K
x = range(1,15)9 O7 T7 Z4 M c. [ i
# z = [perplexity(i) for i in x] #如果想用困惑度就选这个 % B; o+ L# Z$ H) K( H& ny = [coherence(i) for i in x] + ~$ Z1 s) D8 x% Vplt.plot(x, y)/ m D* W( \/ y7 H
plt.xlabel('主题数目')8 |, [; o. P2 p. T" q& U3 s7 D+ A M
plt.ylabel('coherence大小') 7 P& l% h# N. A' N$ s* f& s& Lplt.rcParams['font.sans-serif']=['SimHei']1 h" h7 O/ ]' {" q# R
matplotlib.rcParams['axes.unicode_minus']=False / V0 e* o8 O. \) Y- n: eplt.title('主题-coherence变化情况') " q2 C6 @6 H. V( p7 ^$ S1 s" Fplt.show() , L3 G6 |* q' t 最终能得到各主题的词语分布和这样的图形:5 o. O! l& T: Q6 p; L9 R5 W- {
& f6 Q+ T5 \3 V2 V& W
. @, w9 B6 F- |6 W) ~
. p, z( h$ Q3 [, K3 H# V+ m 5.结果输出与可视化 ) h6 a8 N0 d0 r V; j2 t 通过上述主题评估,我们发现可以选择5作为主题个数,接下来我们可以再跑一次模型,设定主题数为5,并输出每个文档最有可能对应的主题 $ z8 b& `' z0 G. V. p6 X! p: X( J: D) N$ S
from gensim.models import LdaModel# }8 Z' L4 Z R0 k X
import pandas as pd: W: ~6 p: q, F
from gensim.corpora import Dictionary5 r/ J, P! K) Q+ H5 x5 {
from gensim import corpora, models ' ~* Y* C- u/ fimport csv' o6 m+ _9 g, K
& [* }+ ]9 E- _+ A5 x# 准备数据: {* _+ ^& q/ h
PATH = "E:/data/output1.csv" 5 }1 k$ {7 s6 Q9 O+ ^4 K! n6 e' k- O7 P2 x2 t: h
file_object2=open(PATH,encoding = 'utf-8',errors = 'ignore').read().split('\n') #一行行的读取内容3 B1 t0 k9 ?, ^4 D2 o
data_set=[] #建立存储分词的列表 2 j2 O+ O3 t9 D, x! z; [: `for i in range(len(file_object2)): + o. ~! u/ @7 k4 B result=[] / b+ Y2 _) T) p, [2 X seg_list = file_object2.split()5 H$ b+ H% ~3 R0 Z$ B! X
for w in seg_list :#读取每一行分词7 h- C3 m' o3 n! \' C
result.append(w) - H, S0 z- o6 H) O- {: c data_set.append(result) ( E; ]% a4 m6 v4 k2 w ' |8 ?1 R0 G& z- r8 ~9 |# Gdictionary = corpora.Dictionary(data_set) # 构建词典 u. x' i0 p- J3 P$ m( }" b. L
corpus = [dictionary.doc2bow(text) for text in data_set] O" k+ E r& n* p+ u' G & c8 Y- p* M$ ]$ l& x4 Plda = LdaModel(corpus=corpus, id2word=dictionary, num_topics=5, passes = 30,random_state=1). M5 a. J. ?$ j {8 {6 Z K1 p) l
topic_list=lda.print_topics() . F P) s8 H" y2 K! G5 Bprint(topic_list)1 W9 ^+ n1 W% F( [6 }
& b: u" b) g) c0 _% g. z
for i in lda.get_document_topics(corpus)[:]:0 G! O- K2 `! R. `
listj=[], W) j- C9 X+ i9 u7 G- G
for j in i: 1 j1 I$ y0 s# o( v4 z; C. T& |4 x listj.append(j[1])# V9 l9 t# P" N! D( r& B
bz=listj.index(max(listj)) 7 S9 O: |4 s9 y# T* u$ Z print(i[bz][0])5 c l3 ^" Z% w* K, r$ J
; h; x! |) k! c# n% _4 H" v* t
同时我们可以用pyLDAvis对LDA模型结果进行可视化: 1 F- _! o' \8 h- Y9 Z4 ?* P3 z" s4 @2 F' o' B e* |) s
import pyLDAvis.gensim 1 s+ \* ~0 v7 _3 K3 p1 hpyLDAvis.enable_notebook() 9 U6 z3 @( ]8 M* z9 l$ l- l" |data = pyLDAvis.gensim.prepare(lda, corpus, dictionary) / |! f' k |2 x5 E2 upyLDAvis.save_html(data, 'E:/data/3topic.html')+ S; S6 [) b6 \' x- v
大概能得到这样的结果: 5 j/ ^' p- R P& ]9 ^7 d% b2 b$ W+ h+ n) ~
7 ]3 C; ~+ n7 b+ ~) R
- _/ B: P9 f" {! i# Z# I 左侧圆圈表示主题,右侧表示各个词语对主题的贡献度。 9 t7 I2 ?4 Q4 _7 B O' p 7 R& g9 W" h Y; _2 d0 Z1 z3 h所有代码如下:7 \& }+ Q: W8 d. ?; V# A
import gensim + @8 I# a% J$ B$ E; ]3 o3 Ifrom gensim import corpora* X7 K) O5 O! x9 q; T6 ` C3 l
import matplotlib.pyplot as plt 0 I& g$ W9 [2 x( e; I2 P6 s8 dimport matplotlib - j8 }, y1 m# u8 E: J% bimport numpy as np9 ^! M( m8 b" n" G! U m3 a
import warnings# X- x1 `% i% u: y2 a# P" P
warnings.filterwarnings('ignore') # To ignore all warnings that arise here to enhance clarity! z" c$ K: f! L8 u1 ?3 i
/ E! _8 h) Z9 n
from gensim.models.coherencemodel import CoherenceModel # J* P$ p2 ~' Dfrom gensim.models.ldamodel import LdaModel # }) w- B' N2 B# ^: _ 5 k. \+ C T) U6 m, o; r1 ?+ i2 @2 R ) x3 H) L4 ~& W# ~( F$ m- i/ M$ I, Q9 P4 ?) O* E9 q
# 准备数据5 n5 Y K/ ]7 S w. @
PATH = "E:/data/output.csv"- P! r$ b) L: @6 h3 R
% T4 J' Q3 y3 V" R# r& Q. K
file_object2=open(PATH,encoding = 'utf-8',errors = 'ignore').read().split('\n') #一行行的读取内容* U0 l c1 Z' l+ ^$ q4 \
data_set=[] #建立存储分词的列表 / {+ O. h' s% n" [( W/ l) sfor i in range(len(file_object2)):: }. Q# n1 _9 d' G
result=[] 1 y" ?2 K# {2 E+ F' M seg_list = file_object2.split() e+ @$ X/ I* E. n( c- X
for w in seg_list :#读取每一行分词, u, G# T9 @. H5 w, m
result.append(w)3 t' g5 D" h; t# K# f5 x
data_set.append(result) / T! E l4 g' ^$ j, Eprint(data_set)8 K3 J F6 ~) Z5 J