初始化
- 先为各个文档里的单词随机分配主题
- guidedLDA在初始化阶段改变了[文档:主题]的随机分布
- seed_topics-字典格式{在词袋中的位置:种子词的列表索引}
# 这是有种子词的初始化
# 遍历所有单词
for i in range(N):
# WS[k] 包含语料库中的第k个单词
# DS[k] 包含第k个单词的文档索引
w, d = WS[i], DS[i]
if w not in seed_topics:
continue
# check if seeded initialization
# 判断是否在种子词出现过
# 初始化新的主题
if w in seed_topics and random.random() < seed_confidence:
# 使用自定义的主题编号
z_new = seed_topics[w]
else:
# 否则,随机分配
z_new = i % n_topics
ZS[i] = z_new
# 矩阵对应元素+1
ndz_[d, z_new] += 1
nzw_[z_new, w] += 1
nz_[z_new] += 1
开始迭代
核心公式,这里源码是用cpython搞的
_guidedlda.cpython-36m-darwin.so
log p(w,z) = log p(w|z) + log p(z)
nzw_: 记录最终迭代中主题词分配的计数矩阵
ndz_: 记录最终迭代中文档主题分配的计数矩阵
nz_: 主题赋值数组在最终迭代中计数
z:主题
d:文档
w:单词
def _fit(self, X, seed_topics, seed_confidence):
"""Fit the model to the data X
Parameters
----------
X: array-like, shape (n_samples, n_features)
Training vector, where n_samples in the number of samples and
n_features is the number of features. Sparse matrix allowed.
"""
random_state = guidedlda.utils.check_random_state(self.random_state)
rands = self._rands.copy()
self._initialize(X, seed_topics, seed_confidence)
# 迭代
for it in range(self.n_iter):
# FIXME: using numpy.roll with a random shift might be faster
random_state.shuffle(rands)
if it % self.refresh == 0:
ll = self.loglikelihood()
logger.info("<{}> log likelihood: {:.0f}".format(it, ll))
# keep track of loglikelihoods for monitoring convergence
self.loglikelihoods_.append(ll)
self._sample_topics(rands)
# 这里python代码是看不出返回的值是啥玩意
# 用c写的,编译成so文件的
# 计算可能性
ll = self.loglikelihood()
logger.info("<{}> log likelihood: {:.0f}".format(self.n_iter - 1, ll))
# eta: Dirichlet parameter for distribution over words 词分布
# alpha: Dirichlet parameter for distribution over topics 主题分布
self.components_ = (self.nzw_ + self.eta).astype(float)
# sum之后再增加一维得到形状(主题数,1)
self.components_ /= np.sum(self.components_, axis=1)[:, np.newaxis]
# 主题t生成V中第i个单词的概率
self.topic_word_ = self.components_
self.word_topic_ = (self.nzw_ + self.eta).astype(float)
self.word_topic_ /= np.sum(self.word_topic_, axis=0)[np.newaxis, :]
self.word_topic_ = self.word_topic_.T
# 文档d对应主题T中第i个主题的概率
self.doc_topic_ = (self.ndz_ + self.alpha).astype(float)
self.doc_topic_ /= np.sum(self.doc_topic_, axis=1)[:, np.newaxis]
# delete attributes no longer needed after fitting to save memory and reduce clutter
del self.WS
del self.DS
del self.ZS
return self
总结
- 对于lDA来说,p(w|d) = p(w|t)p(t|d),词在文档的分布 = 词在主题的分布*主题在文档的分布,有两个超参数,θd和αt,分别表示d文档对于主题的概率分布和t主题生成单词的概率分布
- 首先随机初始化θd和αt,然后枚举主题T,对于每一个主题,都可以计算出每一篇文档d和文档d对应所有单词w的p(w|d),取最大值,此时的主题t就是对应d文档w单词
- 然后不断更新超参,最后收敛
- python包源码不涉及LDA公式的实现,或者说不涉及串联一些变量,核心变量的更新在py代码里没有体现,是用的c(或者C++)实现的
- 目前已经掌握变量的定义、形状、数据预处理
- 目前能看到的是guidedlda在有种子词的情况下会影响初始化的主题分布和词分布的矩阵,这个原本在lda中是完全随机的,我在代码上做了注释