主题生成模型

Latent Dirichlet Allocation

Posted by YangLong on January 8, 2018

LDA(Latent Dirichlet Allocation) 主题模型

Refrence

Parameter estimation for text analysis https://users.soe.ucsc.edu/~amichelo/docs/text-est2.pdf
Wikipedia https://en.wikipedia.org/wiki/Latent_Dirichlet_allocation
https://medium.com/@lettier/how-does-lda-work-ill-explain-using-emoji-108abf40fa7d

https://zhuanlan.zhihu.com/p/31470216

LDA(Latent Dirichlet Allocation)是一种文档主题生成模型,也称为一个三层贝叶斯概率模型,包含词、主题和文档三层结构。所谓生成模型,就是说,我们认为一篇文章的每个词都是通过“以一定概率选择了某个主题,并从这个主题中以一定概率选择某个词语”这样一个过程得到。文档到主题服从多项式分布,主题到词服从多项式分布.
LDA是一种非监督机器学习技术,可以用来识别大规模文档集(document collection)或语料库(corpus)中潜藏的主题信息。它采用了词袋(bag of words)的方法,这种方法将每一篇文档视为一个词频向量,从而将文本信息转化为了易于建模的数字信息。但是词袋方法没有考虑词与词之间的顺序,这简化了问题的复杂性,同时也为模型的改进提供了契机。每一篇文档代表了一些主题所构成的一个概率分布,而每一个主题又代表了很多单词所构成的一个概率分布。

生成过程:

  • 对每一篇文档,从主题分布中抽取一个主题;
  • 从上述被抽到的主题所对应的单词分布中抽取一个单词;
  • 重复上述过程直至遍历文档中的每一个单词。

贝叶斯 & 二项式分布 & Beta分布 & Dirichlet分布

https://www.cnblogs.com/pinard/p/6831308.html

贝叶斯学派

先验分布 + 数据(似然)= 后验分布  

因为这符合我们人的思维方式,比如你对好人和坏人的认知,先验分布为:100个好人和100个的坏人,即你认为好人坏人各占一半,现在你被2个好人(数据)帮助了和1个坏人骗了,于是你得到了新的后验分布为:102个好人和101个的坏人。现在你的后验分布里面认为好人比坏人多了。这个后验分布接着又变成你的新的先验分布,当你被1个好人(数据)帮助了和3个坏人(数据)骗了后,你又更新了你的后验分布为:103个好人和104个的坏人。依次继续更新下去。

二项分布与Beta分布

对于上一节的贝叶斯模型和认知过程,假如用数学和概率的方式该如何表达呢?

    对于我们的数据(似然),这个好办,用一个二项分布就可以搞定,即对于二项分布:

    其中p我们可以理解为好人的概率,k为好人的个数,n为好人坏人的总数。

    虽然数据(似然)很好理解,但是对于先验分布,我们就要费一番脑筋了,为什么呢?因为我们希望这个先验分布和数据(似然)对应的二项分布集合后,得到的后验分布在后面还可以作为先验分布!就像上面例子里的“102个好人和101个的坏人”,它是前面一次贝叶斯推荐的后验分布,又是后一次贝叶斯推荐的先验分布。也即是说,我们希望先验分布和后验分布的形式应该是一样的,这样的分布我们一般叫共轭分布。在我们的例子里,我们希望找到和二项分布共轭的分布。

    和二项分布共轭的分布其实就是Beta分布。Beta分布的表达式为:

    其中Γ是Gamma函数,满足Γ(x)=(x−1)!     仔细观察Beta分布和二项分布,可以发现两者的密度函数很相似,区别仅仅在前面的归一化的阶乘项。那么它如何做到先验分布和后验分布的形式一样呢?后验分布P(p|n,k,α,β)推导如下:

    将上面最后的式子归一化以后,得到我们的后验概率为:

    可见我们的后验分布的确是Beta分布,而且我们发现:

    这个式子完全符合我们在上一节好人坏人例子里的情况,我们的认知会把数据里的好人坏人数分别加到我们的先验分布上,得到后验分布。 

    我们在来看看Beta分布Beta(p α,β)的期望:
    由于上式最右边的乘积对应Beta分布Beta(p α+1,β),因此有:

    这样我们的期望可以表达为:

    这个结果也很符合我们的思维方式。     

多项分布与Dirichlet 分布

现在我们回到上面好人坏人的问题,假如我们发现有第三类人,不好不坏的人,这时候我们如何用贝叶斯来表达这个模型分布呢?之前我们是二维分布,现在是三维分布。由于二维我们使用了Beta分布和二项分布来表达这个模型,则在三维时,以此类推,我们可以用三维的Beta分布来表达先验后验分布,三项的多项分布来表达数据(似然)。

    三项的多项分布好表达,我们假设数据中的第一类有m1个好人,第二类有m2个坏人,第三类为m3=n−m1−m2个不好不坏的人,对应的概率分别为p1,p2,p3=1−p1−p2,则对应的多项分布为:

    那三维的Beta分布呢?超过二维的Beta分布我们一般称之为狄利克雷(以下称为Dirichlet )分布。也可以说Beta分布是Dirichlet 分布在二维时的特殊形式。从二维的Beta分布表达式,我们很容易写出三维的Dirichlet分布如下:

    同样的方法,我们可以写出4维,5维,。。。以及更高维的Dirichlet 分布的概率密度函数。为了简化表达式,我们用向量来表示概率和计数,这样多项分布可以表示为:Dirichlet(p⃗ α⃗ ),而多项分布可以表示为:multi(m⃗ n,p⃗ )。

    一般意义上的K维Dirichlet 分布表达式为:

    而多项分布和Dirichlet 分布也满足共轭关系,这样我们可以得到和上一节类似的结论:

    对于Dirichlet 分布的期望,也有和Beta分布类似的性质:

生成& 学习过程

https://blog.csdn.net/zxm1306192988/article/details/78551575
标准版解释,一篇文章的每个词都是以一定概率选择了某个主题,并从这个主题中以一定概率选择某个词语组成的

**P(单词 文档)=P(单词 主题)XP(主题 文档)**

LDA生成过程:

对于语料库中的每篇文档,LDA定义了如下生成过程(generative process):

  • 1、对每一篇文档,从主题分布中抽取一个主题;
  • 2、从上述被抽到的主题所对应的单词分布中抽取一个单词;
  • 3、重复上述过程直至遍历文档中的每一个单词。

具体来讲:

w代表单词;d代表文档;t代表主题;大写代表总集合,小写代表个体

输入
D中每个文档d看作一个单词序列 < w1,w2,…,wn> ,wi 表示第 i 个单词。
D中涉及的所有不同单词组成一个词汇表大集合V(vocabulary)

目的
LDA以文档集合D作为输入,希望训练出的两个结果向量(假设形成k个topic,V中一共m个词):

  • 对每个D中的文档d,对应到不同Topic的概率 θd< pt1,…,ptk>,其中,pti 表示d对应T中第i个topic的概率。计算方法是直观的,pti=nti/n,其中nti表示d中对应第 i 个topic 的词的数目,n是d中所有词的总数。
  • 每个T中的topict,生成不同单词的概率 φt< pw1,…,pwm>,其中,pwi 表示 t 生成 V 中第 i 个单词的概率。计算方法同样很直观,pwi=Nwi/N,其中Nwi表示对应到topict的V 中第 i 个单词的数目,N表示所有对用到topict的单词总数。 所以LDA的核心公式如下: P(w | d)=P(w | t)*P(t | d)

直观的看这个公式,就是以Topic作为中间层,可以通过当前的 θd 和 φt 给出了文档 d 中出现单词 w 的概率。其中 p(t | d)利用 θd 计算得到,P(w | t)利用φt计算得到。

实际上,利用当前的 θd 和 φt,我们可以为一个文档中的一个单词计算它对应任意一个Topic时的 p(w | d),然后根据这些结果来更新这个词应该对应的topic。然后,如果这个更新改变了这个单词所对应的Topic,就会反过来影响 θd 和 φt。

LDA学习过程:

LDA算法开始时,先随机地给 θd 和 φt 赋值(对所有的d和t)。然后:

  • 1,针对一个特定的文档ds中的第i单词wi,如果令该单词对应的topic为tj,可以把上述公式改写为:Pj(wi|ds)=P(wi|tj)∗P(tj|ds)
  • 2,现在我们可以枚举T中topic,得到所有的 Pj(wi|ds)。然后可以根据这些概率值结果为ds中的第i个单词wi选择一个topic。最简单的想法是取令 Pj(wi|ds) 最大的 tj(注意,这个式子里只有j是变量)
  • 3 然后,如果ds中的第i个单词wi在这里选择了一个与原先不同的topic(也就是说,这个时候i在遍历ds中所有的单词,而tj理当不变),就会对 θd 和 φt 有影响了。它们的影响又会反过来影响对下面提到的 P(w | d)的计算。对D中所有的d中的所有w进行一次P(w d)的计算并重新选择topic看作一次迭代。这样进行n次循环迭代之后,就会收敛到LDA所需要的结果了。

在LDA模型中,一篇文档生成的方式如下:

从狄利克雷分布 α 中取样生成文档 i 的主题分布 θi 从主题的多项式分布θi中取样生成文档 i 第 j 个词的主题 zi,j 从狄利克雷分布 β中取样生成主题 zi,j 对应的词语分布 φzi,j 从词语的多项式分布 φzi,j 中采样最终生成词语 wi,j 其中,类似Beta分布是二项式分布的共轭先验概率分布,而狄利克雷分布(Dirichlet分布)是多项式分布的共轭先验概率分布。此外,LDA的图模型结构如下图所示(类似贝叶斯网络结构):

https://segmentfault.com/a/1190000012215533

LDA Train

目标:

  • 1.估计模型中的参数 $\vec \varphi_1, \cdots, \vec \varphi_K$和 $\theta_1, \cdots, \theta_M $
  • 2.对于新来的一篇文档,我们能够计算这篇文档的 topic 分布$\vec \theta$。

训练过程:

  • 1.对语料库中的每篇文档中的每个词汇ω,随机的赋予一个topic编号z
  • 2.重新扫描语料库,对每个词ω,使用Gibbs Sampling公式对其采样,求出它的topic,在语料中更新
  • 3.重复步骤2,直到Gibbs Sampling收敛
  • 4.统计语料库的topic-word共现频率矩阵,该矩阵就是LDA的模型;

https://segmentfault.com/a/1190000012215533

Genuis model

from nltk.tokenize import RegexpTokenizer
from stop_words import get_stop_words
from nltk.stem.porter import PorterStemmer
from gensim import corpora, models
import gensim
 
tokenizer = RegexpTokenizer(r'\w+')
 
# create English stop words list
en_stop = get_stop_words('en')
 
# Create p_stemmer of class PorterStemmer
p_stemmer = PorterStemmer()
    
# create sample documents
doc_a = "Brocolli is good to eat. My brother likes to eat good brocolli, but not my mother."
doc_b = "My mother spends a lot of time driving my brother around to baseball practice."
doc_c = "Some health experts suggest that driving may cause increased tension and blood pressure."
doc_d = "I often feel pressure to perform well at school, but my mother never seems to drive my brother to do better."
doc_e = "Health professionals say that brocolli is good for your health." 
 
# compile sample documents into a list
doc_set = [doc_a, doc_b, doc_c, doc_d, doc_e]
 
# list for tokenized documents in loop
texts = []
 
# loop through document list
for i in doc_set:
    
    # clean and tokenize document string
    raw = i.lower()
    tokens = tokenizer.tokenize(raw)
 
    # remove stop words from tokens
    stopped_tokens = [i for i in tokens if not i in en_stop]
    
    # stem tokens
    stemmed_tokens = [p_stemmer.stem(i) for i in stopped_tokens]
    
    # add tokens to list
    texts.append(stemmed_tokens)
 
# turn our tokenized documents into a id <-> term dictionary
dictionary = corpora.Dictionary(texts)
    
# convert tokenized documents into a document-term matrix
corpus = [dictionary.doc2bow(text) for text in texts]
 
# generate LDA model
ldamodel = gensim.models.ldamodel.LdaModel(corpus, num_topics=2, id2word = dictionary, passes=20)