资料来源
本文的资料来源主要是Introduction to Probabilistic Topic Models、LDA数学八卦等资料,有兴趣的同学可以看相关资料。
LDA简介
LDA是一种概率主题模型,其目标是自动从文档集中发现主题。主题建模的核心问题是用观测到的文档来推测隐含的主题结构,这可被视为逆向生成过程,即寻找是什么样的隐含结构有可能生成这样的观测的集合。
LDA的步骤
第1步:随机产生主题分布
第2步:对于文档中的每一个单词
(a)从第一步产生的主题中随机选择一个
(b)从主题对应的单词分布中随机选择一个
Latent Dirichlet Allocation的名字来源
Latent:文档是显性的,但是主题结构(主题集合、文档与主题的关联、文档、主题和词之间的分配)都是未知的、隐含的
Dirichlet:因为第一步中使用的文档主题分布是Dirichlet distribution
Allocation:因为在LDA过程中,Dirichlet的结果被用于将文档中的单词 Allocation(分配) 给各个主题
数学模型
LDA和其他主体模型都属于概率建模,生成过程定义了显变量和隐变量的联合概率分布。在给定了显变量的情况下,借助于联合分布,使用数据分析来计算隐变量的条件分布(即后验分布)。在LDA中,显变量是文档中的单词,隐变量是主题结构,推断文档中的主题结构其实就是在给定文档的情况下计算隐变量额条件分布或后验分布。
变量定义:
变量名 | 含义 |
---|---|
β 1 : K \beta_{1:K} β1:K | 主题集合 |
β K \beta_K βK | 第K个主题的词分布 |
θ d \theta_d θd | 第d篇文档的主题分布 |
θ d , k \theta_{d,k} θd,k | 第d篇文档中第k个主题的概率 |
z d z_d zd | 第d篇文档的主题分配 |
z d , n z_{d,n} zd,n | 第d篇文档中的第n个词的主题 |
ω d \omega_d ωd | 第d篇文档中观测到的单词 |
ω d , n \omega_{d,n} ωd,n | 第d篇文档中的第n个单词 |
联合分布
LDA的生成过程对应于下面显变量和隐变量的联合分布
p
(
β
1
:
K
,
θ
1
:
D
,
z
1
:
D
,
w
1
:
D
)
=
∏
i
=
1
K
p
(
β
i
)
∏
d
=
1
D
p
(
θ
d
)
(
∏
n
=
1
N
p
(
z
d
,
n
∣
θ
d
)
p
(
ω
d
,
n
∣
β
1
:
K
,
z
d
,
n
)
)
p(\beta_{1:K}, \theta_{1:D}, z_{1:D}, w_{1:D}) = \prod_{i=1}^Kp(\beta_i)\prod_{d=1}^Dp(\theta_d)\left(\prod_{n=1}^Np(z_{d,n}|\theta_d)p(\omega_{d,n}|\beta_{1:K},z_{d,n})\right)
p(β1:K,θ1:D,z1:D,w1:D)=i=1∏Kp(βi)d=1∏Dp(θd)(n=1∏Np(zd,n∣θd)p(ωd,n∣β1:K,zd,n))
以上公式指定了很多依赖关系,也正是这些依赖关系定义了LDA,下面用概率图更形象的说明公式中的依赖
存在依赖关系已经都用有向线段进行了连接,空心的表示隐变量,实心的表示显变量。
后验分布
在上文给出的联合分布的基础上,计算在给定文档情况下隐含的主题结构的条件分布,也就是后验分布。
p
(
β
1
:
K
,
θ
1
:
D
,
z
1
:
D
∣
ω
1
:
D
)
=
p
(
β
1
:
K
,
θ
1
:
D
,
z
1
:
D
,
w
1
:
D
)
p
(
ω
1
:
D
)
p(\beta_{1:K}, \theta_{1:D}, z_{1:D}|\omega_{1:D}) = \frac{p(\beta_{1:K}, \theta_{1:D}, z_{1:D}, w_{1:D}) }{p(\omega_{1:D})}
p(β1:K,θ1:D,z1:D∣ω1:D)=p(ω1:D)p(β1:K,θ1:D,z1:D,w1:D)
对于任何隐变量的设定,分子都很容易计算。
分母其实是观测值的边缘分布,从理论上讲可以通过对每一种情况进行加和求得,但在大数据集情况下,对于一个主题,这种累加包括了将每个词的所有可能的主题配置,而且文档集合通常有数量级达百万的词,复杂度过高,就像现在众多的概率模型(如贝叶斯统计)那样由于分母的原因导致后验概率无法计算,因此现在的概率建模的一个核心研究目标就是使用快速的方法对分母进行估计。
主题建模方法通过在潜在主题结构中构建接近真实后验的分布来估计上式。主题建模算法通常可以分为两类:基于采样的算法和变分算法。
基于采样算法通过从后验分布中收集样本来近似它的经验分布。最常用的采样算法是Gibbs采样,在该方法中,我们构建一个马尔可夫链,而马尔可夫链的极限分布就是后验分布。马尔可夫链是一组独立于之前的随机变量组成的串,对主题模型而言,随机变量就是定义在一个特定语料库上的隐含主题,采样算法从马尔可夫链的极限分布上收集样本,再用这些样本来近似分布,只有概率最高的样本会被收集作为主题结构的近似。
变分算法的确定性比采样算法高。变分方法假定在隐含结构上的一簇参数化的分布,并寻找最接近于后验的分布。因此,推断问题被转化为了优化问题,这是一个巨大的创新。
LDA实现的python代码
代码部分参考了https://blog.csdn.net/selinda001/article/details/80446766
# 官方文档https://radimrehurek.com/gensim/models/ldamodel.html
from nltk.corpus import stopwords
from nltk.stem.wordnet import WordNetLemmatizer
import string
import gensim
from gensim import corpora
from gensim.models.callbacks import PerplexityMetric
from gensim.models.callbacks import CoherenceMetric
doc1 = "Sugar is bad to consume. My sister likes to have sugar, but not my father."
doc2 = "My father spends a lot of time driving my sister around to dance practice."
doc3 = "Doctors suggest that driving may cause increased stress and blood pressure."
doc4 = "Sometimes I feel pressure to perform well at school, but my father never seems to drive my sister to do better."
doc5 = "Health experts say that Sugar is not good for your lifestyle."
# 整合文档数据
doc_complete = [doc1, doc2, doc3, doc4, doc5]
# 需要先在命令行中执行nltk.download('punkt')、nltk.download('stopwords')和nltk.download('wordnet'),否则会报错
# 数据清洗和预处理
stop = set(stopwords.words('english'))
exclude = set(string.punctuation)
lemma = WordNetLemmatizer()
def clean(doc):
stop_free = " ".join([i for i in doc.lower().split() if i not in stop])
punc_free = ''.join(ch for ch in stop_free if ch not in exclude)
normalized = " ".join(lemma.lemmatize(word) for word in punc_free.split())
return normalized
doc_clean = [clean(doc).split() for doc in doc_complete]
# 创建语料的词语词典,每个单独的词语都会被赋予一个索引
dictionary = corpora.Dictionary(doc_clean)
# 使用上面的词典,将转换文档列表(语料)变成 DT 矩阵
doc_term_matrix = [dictionary.doc2bow(doc) for doc in doc_clean]
# 使用 gensim 来创建 LDA 模型对象
Lda = gensim.models.ldamodel.LdaModel
perplexity_logger = PerplexityMetric(corpus=doc_term_matrix, logger='visdom')
# 在 DT 矩阵上运行和训练 LDA 模型
# 如果报错,查阅我的博客https://blog.csdn.net/weixin_42690752/article/details/103936259
ldamodel = Lda(doc_term_matrix, num_topics=100, id2word=dictionary, passes=50, callbacks=[perplexity_logger])
# 输出主题内词的构成
print(ldamodel.print_topics(num_topics=100, num_words=3))
# 输出每个文档的主题
for doc in doc_clean:
print(ldamodel.get_document_topics(bow=dictionary.doc2bow(doc)))