LDA主题模型
LDA模型简介
LDA指两者算法,一种叫线性判别分析,一种叫文档主题生成模型,在NLP中我们当然指的是后者。
LDA是一种基于统计的生成模型,它可以根据语料库生成主题模型,并根据这个模型来预测一篇文章属于哪些主题。
算法理论
前提假设
不同于传统的词袋模型,LDA模型认为,一篇文章的生成,是首先随机地决定一些主题,再从每个主题中随机地选择一些词语,这些词语构成了一篇文章。LDA并不考虑词语在文章中的顺序。
LDA的模型,实际上由一个个主题组成,每个主题下有不同的词语,每个主题下的词语对应一个概率值,表明在给定该主题时该词出现的条件概率,即p(w|t)。
而文章的主题预测,即根据文章中出现的词语以及各词语出现的次数,来判断这篇文章最符合哪些主题中词语的概率分布,从而判定文章由哪些词语构成。
LDA的主题模型
贝叶斯学派的人认为,万物皆随机。那么,在LDA模型中上文中提到的条件概率的分布也被认为是随机的。这可以说是LDA模型与PLSA模型最大的不同。
所以在LDA中一篇文章的产生,实际上是随机生成了K个词语-主题的概率分布(K表示假设的主题数量),又随机生成了一个主题-文档的概率分布,根据这个主题-文档分布的结果去在K个词语-主题分布中找到符合要求的那个分布,根据这个分布生成了文档中的一个词语,重复此过程最终生成了一篇文档。主题与主题之间相互独立,词语与词语之间也相互独立,所以上文中两个概率分布生成都是多项式分布,即
p(\vec w) = \prod_{i=1}^n p_i^{\alpha_i},其中\alpha_i表示第i个值的样本的个数。
这样每生成一个词就要做两次随机过程,由于词与词之间是独立的,那么干脆先进行2N次随机过程,把每个词的主题以及相应的概率分布确定了,再去逐个生成词语。
主题-文档的概率分布与词语-主题的概率分布,显然不会是真的随机,而是某种概率。那生成某个概率分布的概率到底是怎样的呢?Dirichlet分布便是可以表示概率的概率的一种分布,它根据一个人为设定的先验参数生成样本,再根据生成结果来更新自己得到后验概率。关于Dirichlet分布可以见下文,这里仅说明,公式\frac{\Delta(\vec n+\vec \alpha)}{\Delta(\alpha)}用来表示在根据先验分布\vec \alpha得到的概率分布下生成样本分布为\vec n的概率,\vec n即(n_1,...,n_k)。
那么,语料库主题生成的概率,便可以表示为p(\vec z|\vec \alpha) = \prod_{i=1}^m p(\vec z_m|\vec \alpha) = \prod_{i=1}^m \frac{\Delta(\vec n_m+\vec \alpha)}{\Delta(\alpha)},其中m为语料库中的文本数。同样的,给定主题分布后,语料库中词的生成概率也可以表示为
p(\vec w| \vec z, \vec \beta) = \prod_{i=1}^k p(\vec w_k| \vec z_k, \vec \beta) = \prod_{i=1}^k \frac{\Delta(\vec n_k+\vec \alpha)}{\Delta(\alpha)},其中k表示语料库中的主题数量。这里虽然两个公式都用了n来表示分布,但它们并不表示同一个变量的分布。综上,我们可以得到语料库的主题-词语联合概率分布p(\vec w, \vec z|\vec \alpha, \vec \beta) = p(\vec w| \vec z, \vec \beta)p(\vec z|\vec \alpha)
数据采样
(这里的数据采样是LDA算法中最重要的一个步骤,所以单独拎出来进行说明。)
上面的公式只是表现了语料库的生成过程,但现在我们实际上已经有了语料库,我们希望得到的是几个概率分布的值。由于语料库已知的情况下\vec w是已知的,故我们真正需要计算的便是p(\vec z|\vec w)。
之前也提过,实际上我们可以从语料库来计算主题。我们用z_i表示语料库中第i个词的主题,i = (m, n),表示这个词是第m个文档中第n个词,我们用\vec z_{\lnot i}表示语料库中i以外词语文档主题。那么对于我们要求的p(\vec z|\vec w)其实就可以转化成\vec z中的每一维的条件概率,即p(\vec z_i | \vec z_{\lnot i}, \vec w)。至于这里为什么在条件中加上\vec z_{\lnot i},想必直观上也多少能感觉是有些关系的,它的作用请看下文中提到的Gibbs Sampling。由于语料库是已知的,所以这里的第i个词的值实际上是可观测的,所以原式等价于
p(z_i=k, w_i=t|\vec z_{\lnot i}, \vec w_{\lnot i})。
之前也提到了用Dirichlet分布表示概率的概率,那么这里用\vec \theta_m表示第m篇文档主题分布的概率的概率分布,用\vec \phi_k表示第k个主题词语分布的概率的概率分布,同时注意到去掉语料库中第i个词并不影响分布的性质,即之前的条件概率中的条件变量也是符合Dirichlet分布的,所以
p(\vec \theta_m|\vec z_{\lnot i}, \vec w_{\lnot i}) = Dir(\vec \theta_m|\vec n_{m,\lnot i} + \alpha)
p(\vec \phi_k|\vec z_{\lnot i}, \vec w_{\lnot i}) = Dir(\vec \phi_k|\vec n_{k, \lnot i} + \beta)
根据上面两个式子,我们可以如下推导,得到采样计算公式:
p(\vec z_i | \vec z_{\lnot i}, \vec w) \sim p(z_i=k, w_i=t|\vec z_{\lnot i}, \vec w_{\lnot i}) \\ = \int p(z_i=k, w_i=t,\vec \theta_m, \vec \phi_k|\vec z_{\lnot i}, \vec w_{\lnot i})d\vec \theta_md\vec \phi_k \\ = \int p(z_i=k, \vec \theta_m|\vec z_{\lnot i}, \vec w_{\lnot i})d\vec \theta_m \cdot \int p(w_i=t, \vec \phi_k|\vec z_{\lnot i}, \vec w_{\lnot i})d\vec \phi_k \\ = \int p(z_i=k|\vec \theta_m)p(\vec \theta_m|\vec z_{\lnot i}, \vec w_{\lnot i})d\vec \theta \cdot \int p(w_i=t|\vec \phi_k)p(\vec \phi_k|\vec z_{\lnot i}, \vec w_{\lnot i})d\vec \phi_k \\ = E(\theta_{mk}) \cdot E(\phi_{kt})
根据连续变量概率密度的定义,倒数第二步那两个积分值就是对应联合分布中离散变量取特定值时的概率,即得到最后一步的结果。这里\theta_{mk}表示第m篇文档中出现主题k,\phi_{kt}表示从主题k中选择词语t,这里我们取不考虑词语i时主题为k的词语在文档中占得比例结合先验\alpha作为\theta_{mk}概率,即
E(\theta_{mk}) = \frac{n_{m, \lnot i}^{(k)}+\alpha_k}{(\sum_{i=1}^k n_{m, \lnot i}^{(t)})+\alpha_k}
取不考虑词语i时所有被赋予主题k的词语中词语(*,n)的比例结合先验\beta作为期望,即
E(\phi_{kt}) = \frac{n_{k, \lnot i}^{(t)}+\beta_t}{(\sum_{i=1}^v n_{k, \lnot i}^{(t)})+\beta_t}
所以,我们最终得到,
p(\vec z_i | \vec z_{\lnot i}, \vec w) \sim \frac{n_{m, \lnot i}^{(k)}+\alpha_k}{(\sum_{i=1}^k n_{m, \lnot i}^{(t)})+\alpha_k} \cdot \frac{n_{k, \lnot i}^{(t)}+\beta_t}{(\sum_{i=1}^v n_{k, \lnot i}^{(t)})+\beta_t}
###训练与预测
LDA算法说起来分为两个部分,一个部分是根据语料库训练出主题模型,另一部分是根据主题模型预测一篇输入文档的主题。我们分开来进行说明。
####训练
训练的最终结果,自然是计算出主题-文档的条件概率分布\theta与词语-主题的条件概率分布\phi。
训练的过程很简单,随机初始化语料库中每个词的所属主题,然后循环“数据采样”这一过程。即每次循环中,对每一个词用上面的公式预测它的主题。关于条件变量的取值,在本次循环已计算过的词语则取本次计算中的结果值,尚未计算的词语则取上次循环的结果,对于第一次循环,“上次的结果”自然就是初始化值。
循环到什么时候呢?这里需要再提一下,数据采样中提到的Gibbs Sampling是一个基于马可夫链的采样方法,它最终是收敛的,且收敛值与初始值无关。也就是说,上面的循环经过一段时间后,对词语的预测结果将几乎不再更新,此时循环就可以结束了。实现上,可以设定一个阈值,来判断是否可以停止循环。至于说,为什么这样采样结果就是收敛的,即使在下文也只是一个粗略的介绍,感兴趣可以查找更多的资料。
####预测
对于一篇新的文档,预测时,同样要先为其中每个词随机赋予主题。不同的是,这次\phi即词语-主题条件概率分布是根据训练结果得出的,即预测过程中这个值不会变更,变更的只是\theta即主题-文档条件概率分布。
预测的过程,同样是跟循环进行Gibbs Sampling,直至结果收敛。统计每个词的主题,最终结果即文档的主题分布。
###相关数学理论简介
上面的介绍其实已经可以让你大概的了解LDA的原理,下面仅是对上文中提到的一些数学名词进行解释。
####Dirichlet分布
讲Dirichlet分布之前,我们需要先介绍一下beta分布。
公式:f(x) = \frac{\Gamma(\alpha+\beta)}{\Gamma(\alpha)\Gamma(\beta)}x^{\alpha-1}(1-x)^{\beta-1} = \frac{1}{B(\alpha,\beta)}x^{\alpha-1}(1-x)^{\beta-1}
其中,Gamma函数定义如下:\Gamma(x+1) = x\Gamma(x)显然易得\Gamma(n+1) = n!
beta分布表示一个定义在[0,1]上的连续变量的概率密度函数。它是伯努利分布和二项分布的共轭先验分布,共轭即beta分布的先验分布和后验分布都是beta分布。
beta的先验分布即初始给定的\alpha与\beta值,这两个值表现了一个先验的随机事件发生的概率是\frac{\alpha}{\alpha+\beta}。而开始进行抽样或观测后,根据结果,去更新这个\alpha与\beta的值,每出现一个正样本\alpha值增1,每出现一个负样本\beta值增1。假如出现了m次正样本,n次负样本,则加入后验分布后该随机变量新的概率分布为f(x) = \frac{1}{B(\alpha+m,\beta+n)}x^{\alpha+m-1}(1-x)^{\beta+n-1}
这个概率密度函数可以帮助我们根据事件的发生去更新概率模型。
想要更直观地了解可以查看如何通俗理解beta分布?
至于为什么这个分布有这样的性质,它是怎么推导出来的,有兴趣地可以找相关资料更细致学习,这里仅提供公式特性以及一个感性的认识。
刚才的beta分布仅考虑了一个随机变量的情况,假如有多个随机变量呢?
Dirichlet分布即是beta分布向多维的一个推广,三维时公式如下:
f(x_1,x_2,x_3) = \frac{\Gamma(\alpha_1+\alpha_2+\alpha_3)}{\Gamma(\alpha_1)\Gamma(\alpha_2)\Gamma(\alpha_3)}x_1^{\alpha_1-1}x_2^{\alpha_2-1}x_3^{\alpha_3-1}
其性质与beta分布类似,这里就不再赘述。
####Gibbs Sampling
对于普通的一维的概率分布函数,我们可以先用线性同余产生一个符合均匀分布Uniform(0,1)的样本,再通过数学变换,得到符合要求的分布的样本序列。但对于一些分布复杂的随机变量,或者高维的概率分布函数,我们没有办法通过上面的方法得到符合要求的样本,这时怎么办呢?
对于高维的概率分布,它的概率函数很难表示,但一个一维随机变量的条件概率却很容易表示。对于n维的概率分布,我们构造一个n阶的方阵,它刻画了某事物的状态转移,其中(i,j)表示从状态i转移到状态j的概率。我们记分布\pi_i = [\pi_i(1),\pi_i(2),...,\pi_i(n)]表示经过i次转移后样本集合中各状态的样本的比例,显然\pi_k = \pi_0P^k,其中P表示n阶状态转移矩阵。研究发现,当i足够大时,分布\pi_i是收敛的,且这个收敛值与初始分布\pi_0无关,仅与状态转移矩阵P有关。也就是说,对于一个n维的概率分布,有一个状态转移矩阵P收敛于这个分布,且有\pi(j) = \sum_{i=0}^\infty \pi(i)P(i,j)。那么我们构造一个状态转移矩阵P,则在k次转移完收敛后,再根据P产生的新样本都是符合分布\pi的。
有了上面的启发,假如我们能根据已知的概率分布p(x)构造一个转移矩阵P使其恰好收敛到p(x),那么就可以生成符合p(x)的样本了。该矩阵的构造依赖于一个细致平稳条件的定理。
细致平稳条件要求,对于一个非周期马氏链的转移矩阵P以及分布\pi(x),满足\pi(i)P(i,j) = \pi(j)P(j,i)。
对于一个转移矩阵Q以及已知的p(x),上面的公式一般而言是不满足的,但我们可以在两侧引入参数\alpha(i,j)与\alpha(j,i),使得p(i)q(i,j)\alpha(i,j) = p(j)q(j,i)\alpha(j,i)成立。观察对称性,分别令\alpha(i,j) = p(j)q(j,i)以及\alpha(j,i) = p(i)q(i,j)即可。这样我们就得到了一个新矩阵Q’,Q'(i,j) = Q(i,j)\alpha(i,j),这个Q’符合细致平稳分布的条件,就是我们要找的转移矩阵。其中的\alpha(i,j)可以理解为一个“接受概率”,即样本经过状态转移矩阵得到转移结果后,是否“接受”这个转移。事实上,为了加速算法,\alpha值可以调整为min\{\frac{p(j)q(j,i)}{p(i)q(j,i)}, 1\},这样转移的接受概率将更高使得收敛速度更快。这就是传统的MCMC算法。
但是即使放大接受概率,在高维时由于这一概率存在,收敛速度依旧很慢,我们更希望两边的转移的接受概率都是1。重新观察概率公式,注意到p(x_1,y_1)p(y_2|x_1) = p(x1,y_2)p(y_1|x_1),即对于二维分布,沿着其中一维坐标轴平行线的转移总是满足细致平稳条件的。那么,自然的,我们可以将采样的循环更改为
y_{t+1} \sim p(y|x_t)
x_{t+1} \sim p(x|y_{t+1})
对于更多维的情况,也仅需改成
x_{(i)}^{t+1} \sim p(x_{(i)}|x_1^{t+1},...,x_{(i-1)}^{t+1},x_{(i+1)}^t,...,x_{(n)}^t)
##代码实现
实际上你也可以看出,这一算法虽然容易,但原理上很复杂。所以基本上不是很推荐去使用自己实现的代码,除非你的水平确实很高,或者应用场景需要对LDA算法内部进行某些修改。当然,自己实现一次可以锻炼你的算法能力,同时有助于你对算法有更深的理解。我仅仅是说不推荐实际使用自己实现的代码,因为很多库都提供了简洁轻量的API来帮助你使用LDA模型来对主题进行预测。下面是使用gensim.models.LdaModel的一个简单例子
from gensim.corpora import Dictionary
from gensim.models import LdaModel
# 语料库是一个列表的列表,内部每个列表表示一篇文档,内部列表元素是文档词语
corpus = get_corpus()
# 从语料库获取词典,每个词对应一个编号
word_dict = Dictionary(corpus)
# 根据词典,将原来的语料库转换成词袋模型语料库
bow_corpus = [word_dict.bow(doc) for doc in corpus]
# 训练LDA模型,其中num_topics为设定的主题数量
lda_model = LdaModel(bow_corpus, id2word=word_dict, num_topics=100)
# 显示主题下词语概率分布,这里显示10个主题以及每个主题下概率最大的10个词
print(lda_model.show_topcis(num_topics=10, num_words=10))
# 将要预测的文档转换为词袋模型
bow_doc = word_dict.bow(get_doc())
# 预测
print(lda_model[bow_doc])
关于gensim的LdaModel的更多用法,可以前往官方文档查看
##总结
LDA在2003年被提出,直到现在,依然是一个不错的主题模型。但一方面,使用Gibbs Sampling的话每一个数据的更新依赖于之前的结果难以并行化,并行化的话只能在迭代优化部分使用更慢的EM或Online;另一方面,对于短文本,LDA的效果往往不是很好。
在大数据时代,随着词向量模型的提出,新的主题聚类方法可能随时会出现。
