欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

LDA的直观解释

发布时间:2024/5/14 编程问答 67 豆豆
生活随笔 收集整理的这篇文章主要介绍了 LDA的直观解释 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

缘起

这篇文章的起源是当年看LDA的推导,开始的时候觉得整个数学推导太复杂了,特别是从概率建模推导出Gibbs Sampling演化公式的过程,感觉不太对我的口味了,我更喜欢从自演化的角度来理解,所以就自己尝试折腾一个“Topic Model”。结果在折腾完之后,和LDA的结果进行对比,神奇地发现基本是一致的。
可惜后来发现其实别人也从LDA的推导结果中观察出了这个演化规则了。要是我看得更认真一点的话,直接就能看到这个结论了。不过反过来想,既然我不愿意顺着别人的思路来推导这个模型,自然也不会认真地观察最终的结论,所以最终还是要靠这个方法才能得到LDA的直观理解。

大致介绍一下LDA。LDA是文本分析里面一个很有名的topic
model,它基于一个简单的词袋模型,通过概率建模,得到文档和词汇的主题分布。这个模型很为人称道的一个特点,是它的数学推导是比较优雅的,由给定的先验Dirichlet分布,得到文档生成的似然函数,然后得到Gibbs
Sampling收敛时的分布,就是topic的对应分布。LDA在前些日子还是挺流行的,网络上好的介绍文章很多,比如这个blog,新浪的同学写的LDA漫游指南,还有腾讯的LDA数学八卦,都有很详细的推导过程。

以下就是我折腾这个“Topic Model”的过程。

直观topic model的思路

最简单的想法,当然就是基于聚类的思想,本质上LDA也是一种聚类,比如GMM也是通过概率模型得到似然函数来实现聚类的。同时LDA已经给我们提供了非常好的训练方法了,那就是Gibbs Sampling,可以简单地把它理解为一种迭代算法,或者本质一点就是将系统演化到热力学平衡态的方式,Markov模型的稳态对应的就是热力学平衡态。那么这就是一个典型的Ising model,我们现在要做的事情非常简单,就是给出一些更直观的演化规则,也就是每个词每一次应该如何决定跳转到哪个topic

在思考规则之前,先简化一下问题,方便验证新的规则是否能得到合理的topic分布。同时还应该找个标准的LDA实现,跟标准结果进行对比。

问题的极端版本

这里先处理极端版本,给定3篇文档,9个词,每个文档拥有3个词,每个词只属于一篇文档,用简单的词项向量来表示文档如下


d1 = [(1, 10.0), (2, 10.0), (3, 10.0)]
d2 = [(4, 10.0), (5, 10.0), (6, 10.0)]
d3 = [(7, 10.0), (8, 10.0), (9, 10.0)]

1-9就是词id,后面的10是词的词频。这里权重统一取10是为了后面拓展对比。虽然这里每个文档出现多次的同一个词抽象成了词频,对实际上在训练的时候,是应该每个词(不同位置算两个)单独跳转的。

非常显然,对于这种情况,如果设定topic数目为3,最合理的topic分布是每篇文章完全1个topic,每个词也完全对应一个topic。(如果topic数目设为4呢?理想的情况当然是还是只有3个topic是有意义的,比如前面讨论cluster算法的鲁棒性,这里暂不考虑这个问题)

标准LDA结果

这里的标准LDA实现用了gensim,代码和结果如下
测试代码

corpus = [[(1, 10.0), (2, 10.0), (3, 10.0)],[(4, 10.0), (5, 10.0), (6, 10.0)],[(7, 10.0), (8, 10.0), (9, 10.0)]] model = gensim.models.LdaModel(corpus, num_topics=3, update_every=0, passes=20) print 'topic 0:', model.print_topic(0) print 'topic 1:', model.print_topic(1) print 'topic 2:', model.print_topic(2) for i in range(1, 10):print 'word', i, model.get_term_topics(i)

输出结果(事实上,即便迭代次数设到100,仍然不能保证每次都出来这个理想情况,但大部分时候还是可以的)

topic 0: 0.310*5 + 0.310*6 + 0.310*4 + 0.010*1 + 0.010*3 + 0.010*2 + 0.010*9 + 0.010*7 + 0.010*8 + 0.010*0 topic 1: 0.310*9 + 0.310*8 + 0.310*7 + 0.010*1 + 0.010*3 + 0.010*2 + 0.010*6 + 0.010*4 + 0.010*5 + 0.010*0 topic 2: 0.310*2 + 0.310*3 + 0.310*1 + 0.010*4 + 0.010*5 + 0.010*6 + 0.010*8 + 0.010*7 + 0.010*9 + 0.010*0 word 1 [(2, 0.29958881624461359)] word 2 [(2, 0.29960836529561002)] word 3 [(2, 0.29960656074961889)] word 4 [(0, 0.29960501759439934)] word 5 [(0, 0.29960503256664051)] word 6 [(0, 0.29960502961451951)] word 7 [(1, 0.29957133395083341)] word 8 [(1, 0.29957135950646596)] word 9 [(1, 0.2995714166623073)]

这个结果还是比较符合预期的,每个topic基本就对应一个文档拥有的3个词,每个词只对应一个topic。证明LDA可以处理这种极端简单的情况,得到符合预期的结果。

直观规则

现在看看用从最直观的角度出发怎么构建规则。

初始版本

先来试试最简单的思路:

1. 文档应该倾向集中于某些topic,因此词跳转到topic的概率,正比于所在文档的topic分布
2. 词也应该倾向集中于某些topic,因此跳转到topic的概率,正比于这个词在全部文档中所属的topic的分布
3. 最终概率取这两个概率平均归一

这个想法极其简单,但还是稍微解释一下
规则1:一篇文档的topic总是比较集中的,不可能都是漫无主题的,所以如果这篇文档的其它词都集中于某个topic,那么自然这个词属于这个topic的可能性也较高
规则2:一个词所属的topic啊,当然要看所属的文档,但是也要考虑到历史的进程这个词的全局分布。如果这个词在别的地方都是集中于某个topic,当然它属于这个topic的概率也较高
规则3:就是用最无脑的方式简单处理一下,对于极端简化情况,也应该足够了,但是可以预计到肯定是不合理的,后面再改。

先思考一下,如此简单的规则,对于前面提出极端简化情况,预期的结果是否对应稳态,答案显然是肯定的。每个文档一个topic,文档内的词就都只跳转到当前topic,而且每个词的全局topic也是唯一的。

由于是实验性质,代码写得有点随意,就不展示了,反正也很好实现,直接给上结果。

{1: {0: 10.0}, 2: {0: 10.0}, 3: {0: 10.0}, 4: {2: 10.0}, 5: {2: 10.0}, 6: {2: 10.0}, 7: {1: 10.0}, 8: {1: 10.0}, 9: {1: 10.0}}

上面是迭代到最后每个词对应topic的频次(就是未归一的分布),可以看到每个词都属于一个topic,而且属于同一个文档的词都属于同一个topic,分属3个topic。嗯,看起来完全正确嘛。

{1: {2: 10.0}, 2: {2: 10.0}, 3: {2: 10.0}, 4: {2: 10.0}, 5: {2: 10.0}, 6: {2: 10.0}, 7: {2: 10.0}, 8: {2: 10.0}, 9: {2: 10.0}}

然而很不幸地,有时候会收敛到这种情况,就是所有词和文档都属于同一个topic。很不幸地,这种情况对于前面的规则,也是一种稳态,而这种稳态显然没有任何意义。于是必须修改一下规则了。

版本2

现在的目标是,词要倾向于分离到各个topic之中,而不是都聚集到一个topic之中。比较自然的想法是,每个topic应该倾向于集中于少数词,也就是每个词倾向于跳转到它占比比例较高的topic。即便一个词在某个topic下出现次数很多,然而别的词在这个topic下出现次数更多,那么显然这个词也不应该倾向这个topic,而应该倾向于它出现次数比其它词汇更多的topic。这基本就是tf-idf的思路。

那么我们就修改一下第二条规则
2. 词跳转到某个topic的概率,正比于在这个词在该topic的占比比例

显然,对于修改后的规则,预期中的合理分布是稳态,而上面得到的第二个结果则不是,稍加扰动就会向合理分布收敛。

{1: {0: 0.333}, 2: {0: 0.333}, 3: {0: 0.333}, 4: {2: 0.333}, 5: {2: 0.333}, 6: {2: 0.333}, 7: {1: 0.333}, 8: {1: 0.333}, 9: {1: 0.333}}

上图是修改规则各个词在topic上的分布(未归一),版本1正比于频次,所以值是10,这里正比于占比比例,所以是1/3。结果也如前面所料,修改规则后可以很稳定地收敛到合理分布。

问题的微扰动版本

极端版本的问题解决了,就开始尝试更通用的版本,现在将文档修改如下


d1 = [(1, 10.0), (2, 10.0), (3, 10.0), (4, 1.0)]
d2 = [(4, 10.0), (5, 10.0), (6, 10.0), (7, 1.0)]
d3 = [(7, 10.0), (8, 10.0), (9, 10.0), (1, 1.0)]

现在文档之间有共同的词汇了(1,4,7),但是这些词的在不同文档的出现频次差别很大,因为它们还是主要应该属于1个Topic,但会有另一个Topic的少量权重

标准LDA结果

topic 0: 0.301*7 + 0.301*9 + 0.301*8 + 0.038*1 + 0.010*4 + 0.010*5 + 0.010*6 + 0.010*3 + 0.010*2 + 0.010*0 topic 1: 0.301*1 + 0.301*3 + 0.301*2 + 0.038*4 + 0.010*5 + 0.010*6 + 0.010*7 + 0.010*8 + 0.010*9 + 0.010*0 topic 2: 0.301*4 + 0.301*6 + 0.301*5 + 0.038*7 + 0.010*1 + 0.010*2 + 0.010*3 + 0.010*8 + 0.010*9 + 0.010*0 word 1 [(0, 0.025377732076891448), (1, 0.29127688616997127)] word 2 [(1, 0.29074483176216914)] word 3 [(1, 0.29074725226352127)] word 4 [(1, 0.025391350433117077), (2, 0.29124987747253384)] word 5 [(2, 0.29072234310628031)] word 6 [(2, 0.29073483184310916)] word 7 [(0, 0.29119614486736145), (2, 0.025381072884006612)] word 8 [(0, 0.29064756923915058)] word 9 [(0, 0.29064775605257226)]

LDA的结果看起来还是比较合理的,1,4,7三个词在两个Topic上拥有权重,但差别很明显,基本分布跟之前的问题差不多

直观规则

前面一个问题给出的结果是系统在最后一次迭代时的Topic分布,而这次这个问题稍微复杂了,不应该用单次分布,而是取系统平衡之后的多步的平均
随机两次结果
结果1

{1: {0: 0.36, 1: 0.279, 2: 0.361}, 2: {0: 0.366, 1: 0.256, 2: 0.378}, 3: {0: 0.374, 1: 0.27, 2: 0.356}, 4: {0: 0.493, 1: 0.081, 2: 0.426}, 5: {0: 0.408, 1: 0.056, 2: 0.535}, 6: {0: 0.471, 1: 0.055, 2: 0.474}, 7: {0: 0.2, 1: 0.615, 2: 0.185}, 8: {0: 0.166, 1: 0.669, 2: 0.164}, 9: {0: 0.18, 1: 0.669, 2: 0.151}}

结果2

{1: {0: 0.726, 1: 0.202, 2: 0.072}, 2: {0: 0.736, 1: 0.199, 2: 0.065}, 3: {0: 0.764, 1: 0.14, 2: 0.096}, 4: {0: 0.088, 1: 0.055, 2: 0.857}, 5: {0: 0.04, 1: 0.04, 2: 0.92}, 6: {0: 0.031, 1: 0.053, 2: 0.916}, 7: {0: 0.156, 1: 0.76, 2: 0.084}, 8: {0: 0.163, 1: 0.808, 2: 0.03}, 9: {0: 0.246, 1: 0.738, 2: 0.016}}

可以看到面对这个稍复杂一点的问题,直观规则很不幸失效了,虽然有时候能得到还算接近合理的分布(如结果2)。

想想规则1和规则2看起来都没什么大问题,看来要像最随意的规则3动手了。显然,两个概率之间的叠加,不应该使用平均,因为出了互斥事件,概率之间不具备可加性。更合理的办法是相乘,或者也可以理解为一种等幂加权(对数平均)。显然也不一定要等幂,但是为什么不呢?就选择最简单的办法好了。
另外乘法跟加法有个巨大的不同,就是其中一个值为0和接近于0,两种情况是完全不同的。加法是不怎么受影响的,但是相乘差距就很大了,不能因为某个值为0,整个概率就直接为0。所以要设个最低值,类似朴素贝叶斯的做法。
于是规则3就变成
3. 最终概率取这两个概率乘积归一(如果其中一个概率为0,取为0.001)

修改规则后可以稳定地迭代出类似以下的结果

{1: {1: 0.91, 2: 0.09}, 2: {1: 1.0}, 3: {1: 1.0}, 4: {0: 0.923, 1: 0.077}, 5: {0: 1.0}, 6: {0: 1.0}, 7: {0: 0.083, 2: 0.917}, 8: {2: 1.0}, 9: {2: 1.0}}

这个结果和前面gensim跑出来的结果基本是一致的,(1,4,7)都有两个Topic,而且两者基本相差一个量级

因此最终的演化规则就是
1. 文档应该倾向集中于某些topic,因此词跳转到topic的概率,正比于所在文档的topic分布
2. 词跳转到某个topic的概率,正比于在这个词在该topic的占比比例
3. 最终概率取这两个概率乘积归一(如果其中一个概率为0,取为0.001)

跟LDA的对比

目前既然得到了一个看上去还算合理的规则,自然就想跟LDA的Gibbs Sampling公式进行对比。正如前文所言,对比发现是一致的(除了模型先验参数,不过可能喜好贝叶斯的人会觉得这些参数形式很重要),而且也早已有人根据结果指出了这个意义(估计原paper就指出来了)。因此实际上,这就是直接从自演化(聚类)出发得到了LDA的结果。可惜意义并不大,因为从LDA的结果很容易地就能直接观察出这些规则。

不过,这其实也可以提供一些启示。实际上很多的问题,都能从目的论和天演论两种方式进行理解。LDA虽然最终得到了这个结果,但是它是从文档的生成概率出发得到这个结论的,而不是词的topic演化。类似的比如LR模型,可以从最大熵推导得到,但如果从模型最终的演化梯度来想,就是一种非常直观的根据样品演化特征参数的方式。对于一个算法,从底层演化的角度来理解一般是更直观的。很难想象一个人在使用LDA的时候,会从头从文档生成的角度推导一遍得到结果,但是以上文的角度,很直观地就可以写出一个LDA模型了。不过话又说回来,实际项目使用LDA的时候,由于计算效率的考虑,往往会选择VB法,或者像LightLDA这样的变种,这又是另外一回事了。

总结

以上是生活随笔为你收集整理的LDA的直观解释的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。