LDA主题模型主题数的确定
通过折肘法+困惑度折线确定lda模型的主题个数
前言
如题,
LDA
(Latent Dirichlet Allocation)是主题模型中极具代表性的一种,常用于文本分类,推测文本(文档)的主题分布。简而言之:LDA算法可以将文档集中的每篇文章所对应的主题以概率分布的形式给出。
-
给定一些文档集,可通过LDA算法获得这些文档的主题分布,后续可根据主题分布做
文本聚类
或
文本分类
;
-
LDA 模型涉及很多数学先验知识,也是LDA晦涩难懂的主要原因,本文不会推导LDA涉及的数学公式,感兴趣的朋友可以参考:
rickjin
的《
LDA数学八卦
》;
http://www.flickering.cn/tag/lda/
-
LDA模型通常需要确定从每篇文章中提取多少个主题(关键词),本文重点介绍通过
折肘法
+
困惑度
法确定
LDA主题模型
的
主题数
。
理论
对于主题模型而言,选定的算法一旦确定, 需要
人为确定的选项
(超参)通常是主题数量,LDA算法也不例外:主题数量通常视不同场景进行调整,简单点儿说,通过评估不同主题数模型的困惑度来选择最优的模型主题数。在本文中,采用
计算困惑度
(
Perplexity
)的方法来衡量选取的主题数量的优劣:
其中,
M
是测试语料的大小(文档的数量),
Nd
是第
d
篇文本大小(
word
或
token
个数)
其中,
z
是主题,
w
是文档,
r
是基于训练集学习的
文本-主题分布
,简而言之,
Perplexity
对数函数的分子部分是生成整个文档集的
似然估计
(表示训练集训练出的参数的生成能力)的负数,由于概率取值范围为[0,1],按照对数函数的定义,分子值是一个正值且与文本生成能力正相关;而分母是整个文档集的的单词数目。那么,也就是说,模型生成能力越强,
Perplexity
值越小。
代码
1. 加载第三方
gensim.LdaModel
from gensim import corpora, models
import math
import matplotlib.pyplot as plt
def ldamodel(num_topics, pwd):
cop = open(pwd, 'r', encoding='UTF-8')
train = []
for line in cop.readlines():
line = [word.strip() for word in line.split(' ')]
train.append(line)
dictionary = corpora.Dictionary(train)
corpus = [dictionary.doc2bow(text) for text in train]
corpora.MmCorpus.serialize('corpus.mm', corpus)
lda = models.LdaModel(corpus=corpus, id2word=dictionary, random_state=1,
num_topics=num_topics)
topic_list = lda.print_topics(num_topics, 10)
return lda, dictionary
2. 计算给定文档集的
Perplexity
def perplexity(ldamodel, testset, dictionary, size_dictionary, num_topics):
print('the info of this ldamodel: \n')
print('num of topics: %s' % num_topics)
prep = 0.0
prob_doc_sum = 0.0
topic_word_list = []
for topic_id in range(num_topics):
topic_word = ldamodel.show_topic(topic_id, size_dictionary)
dic = {}
for word, probability in topic_word:
dic[word] = probability
topic_word_list.append(dic)
doc_topics_ist = []
for doc in testset:
doc_topics_ist.append(ldamodel.get_document_topics(doc, minimum_probability=0))
testset_word_num = 0
for i in range(len(testset)):
prob_doc = 0.0
doc = testset[i]
doc_word_num = 0
for word_id, num in dict(doc).items():
prob_word = 0.0
doc_word_num += num
word = dictionary[word_id]
for topic_id in range(num_topics):
# cal p(w) : p(w) = sumz(p(z)*p(w|z))
prob_topic = doc_topics_ist[i][topic_id][1]
prob_topic_word = topic_word_list[topic_id][word]
prob_word += prob_topic * prob_topic_word
prob_doc += math.log(prob_word) # p(d) = sum(log(p(w)))
prob_doc_sum += prob_doc
testset_word_num += doc_word_num
prep = math.exp(-prob_doc_sum / testset_word_num) # perplexity = exp(-sum(p(d)/sum(Nd))
print("模型困惑度为 : %s" % prep)
return prep
3. 绘制
主题数
与
困惑度
的折线图
def graph_draw(topic, perplexity):
x = topic
y = perplexity
plt.plot(x, y, color="red", linewidth=2)
plt.xlabel("Number of Topic")
plt.ylabel("Perplexity")
plt.savefig("Perplexity-Topics")
plt.show()
4. 主函数入口
if __name__ == '__main__':
pwd = '~/*_summary.txt'
for i in range(20,21,1):
print("抽样为"+str(i)+"时的perplexity")
a=range(1,50,1) # 主题个数
for num_topics in a:
lda, dictionary = ldamodel(num_topics, pwd)
corpus = corpora.MmCorpus('corpus.mm')
testset = []
for c in range(int(corpus.num_docs/i)):
testset.append(corpus[c*i])
prep = perplexity(lda, testset, dictionary, len(dictionary.keys()), num_topics)
p.append(prep)
graph_draw(a,p)
备注
-
第4步
中的
pwd
,要求将给定的语料集合成一个文本文件。原因是在测试使用LDA模型提取单篇文档时报bug,至今未解决[2022.04.28];
-
不会写
latex
公式,后面的学习内容;
图像
使用随机抽取语料采用本文方法的结果
结论
-
在上图中,随着
K
(
Number of Topic
)值的增大,困惑度(
Perplexity
)逐渐减小。根据手肘法,并且当
K
约为5的时候,存在一个显著的拐点:
-
当
K
属于(1, 5)时,曲线急剧下降;
-
当
K
属于(5,10)时,曲线基本趋于平稳;
-
故拐点5即为K的最佳值,因此在本文所选定的语料集中,LDA主题的生成数量选定为5。
-
值得注意的是,当K>10时,训练困惑度出现小幅度的上升,这与样本空间和算法本身都有关联,属正常现象。
-
在使用LDA算法提取关键词时,通常会采样如下方案:使用
tf-idf
对数据集中的每个词进行加权,得到加权后的向量表示,通过词空间构建和向量化方法,得到数据集的主题-词分布,最后计算词的分布和文档的分布的相似度,取相似度最高的
keyword num
个词作为关键词。具体见下一篇文章:LDA主题模型提取文本中的关键词
完结撒花 🎉🎉