LDA 困惑度 vs 主题一致性:到底该信哪个指标选 K 值?
LDA 主题数 K 选多少,是老板们问得最多的问题。答案是没有”标准答案”——但有 2 个客观指标可以参考:困惑度(perplexity) 和 主题一致性(coherence)。
这两个指标经常打架——困惑度说 K=8 最好,一致性说 K=15 最好。到底听谁的?
我们的 LDA 主题一致性和困惑度评价软件 用 双指标联合判断 + 折线图可视化 解决了这个矛盾。这篇博客讲清楚原理和代码。
一、困惑度(Perplexity):越低越好?
困惑度衡量模型对未见文档的”预测难度”,公式:
1
| perplexity(D) = exp(-1/N * Σ log p(w_d))
|
直觉:困惑度越低,模型越”自信”,聚类效果越好。但 LDA 场景下有个著名的坑——困惑度会随主题数 K 一直下降(因为参数多了总能更好地拟合训练集),所以单看困惑度最小值会选出过大的 K。
1 2 3 4 5
| from gensim.models import LdaModel
lda = LdaModel(corpus=corpus, id2word=dictionary, num_topics=8, passes=15) perp = lda.log_perplexity(corpus) print(perp)
|
踩坑:困惑度的 绝对值没意义,只在同一语料上比较不同 K 才有意义。
二、主题一致性(Coherence):越高越好
一致性衡量主题内 Top N 词的”语义相关程度”——好的主题里 Top 词应该互相能解释(比如主题1的 Top 词是”小米/手机/续航/充电/发热”,这些词明显在聊”手机续航”)。
gensim 提供 4 种 Coherence 算法:
| 算法 |
含义 |
计算量 |
u_mass |
用文档共现计数,-1~1 之间 |
最快 |
c_v |
用滑动窗口 + NPMI + 余弦相似度 |
最准,推荐 |
c_uci |
滑动窗口 + PMI |
中 |
c_npmi |
归一化 PMI |
中 |
1 2 3 4 5 6 7 8 9 10
| from gensim.models import CoherenceModel
coh = CoherenceModel( model=lda, texts=texts, dictionary=dictionary, coherence='c_v' ) score = coh.get_coherence() print(score)
|
c_v 的核心思想:每个主题取 Top N 词(默认 N=10),对每对词算 NPMI(归一化点互信息)——这俩词在同一篇文档里同现的概率,比随机同现高出多少。NPMI 高 = 这俩词强相关 = 主题内聚。
三、双指标联合判断的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import matplotlib.pyplot as plt from gensim.models import LdaModel, CoherenceModel import numpy as np
def evaluate_lda(corpus, dictionary, texts, k_range=range(2, 21)): perplexities, coherences = [], [] for k in k_range: lda = LdaModel( corpus=corpus, id2word=dictionary, num_topics=k, passes=15, random_state=42, alpha='auto', eta='auto' ) perp = lda.log_perplexity(corpus) perplexities.append(perp) coh = CoherenceModel( model=lda, texts=texts, dictionary=dictionary, coherence='c_v' ).get_coherence() coherences.append(coh) print(f'K={k:2d} | Perplexity={perp:8.3f} | Coherence={coh:.4f}') return list(k_range), perplexities, coherences
ks, perps, cohs = evaluate_lda(corpus, dictionary, texts, range(2, 16))
best_k = ks[np.argmax(cohs)] print(f'\n推荐主题数 K = {best_k} (Coherence={max(cohs):.4f})')
|
四、双指标折线图:一眼看出”肘部”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| fig, ax1 = plt.subplots(figsize=(10, 6))
color1 = '#6e5cff' ax1.set_xlabel('主题数 K', fontsize=12) ax1.set_ylabel('困惑度(越低越好)', color=color1, fontsize=12) ax1.plot(ks, perps, 'o-', color=color1, linewidth=2, markersize=8, label='Perplexity') ax1.tick_params(axis='y', labelcolor=color1) ax1.grid(True, alpha=.3)
ax2 = ax1.twinx() color2 = '#00d4ff' ax2.set_ylabel('一致性(越高越好)', color=color2, fontsize=12) ax2.plot(ks, cohs, 's-', color=color2, linewidth=2, markersize=8, label='Coherence') ax2.tick_params(axis='y', labelcolor=color2)
best_k = ks[np.argmax(cohs)] ax2.axvline(x=best_k, color='#ff5cb3', linestyle='--', alpha=.6) ax2.annotate( f'最佳 K = {best_k}\nCoherence = {max(cohs):.3f}', xy=(best_k, max(cohs)), xytext=(best_k + 2, max(cohs) - 0.05), fontsize=11, color='#ff5cb3', arrowprops=dict(arrowstyle='->', color='#ff5cb3') )
plt.title('LDA 主题数寻优:困惑度 vs 一致性', fontsize=14, pad=15) fig.tight_layout() plt.savefig('lda_evaluation.png', dpi=150, bbox_inches='tight')
|
怎么看这张图:
- 困惑度曲线:找”肘部”——曲线从陡降变缓的那个拐点(类似手肘的弯折)。肘部之后继续加主题带来的困惑度收益就很小了。
- 一致性曲线:找最高点——这是数学最优解。
- 如果俩打架:以一致性最高点为主,困惑度肘部为辅。理由——一致性是直接反映主题质量的指标(Top 词是否相关),困惑度只是间接反映(预测能力)。
五、踩坑大全
踩坑 1:一致性计算很慢,K 范围别太大。建议先粗搜 range(2, 21) 找到大致区间,再细搜。
踩坑 2:texts 参数必须传原始分词后的 list of list,不能传 corpus。否则 c_v 算不出 NPMI。
踩坑 3:random_state 必须固定!gensim LDA 每次训练结果有随机性,不固定 random_state 两次跑出来的 K 评估不一样。
踩坑 4:一致性高不代表主题”可解释”。最好人工抽查 Top 词。我们见过 Coherence=0.6 但 Top 词是”今天/明天/后天”这种没意义的主题。
六、典型输出
我们的软件在 1 万条淘宝评论上的典型输出:
| K |
Perplexity |
Coherence |
| 5 |
-7.83 |
0.421 |
| 8 |
-7.95 |
0.512 |
| 10 |
-8.02 |
0.587 ← 最佳 |
| 12 |
-8.07 |
0.561 |
| 15 |
-8.11 |
0.523 |
困惑度一直在降,但一致性在 K=10 处达到峰值。用一致性选 K = 10。
我们的 LDA 主题一致性和困惑度评价软件 把这套流程做成了一键式界面:导入 Excel 语料 → 选最大 K → 自动跑完所有评估 → 出一张双指标折线图 + 推荐 K 值。无需编程基础,3 分钟得到专业级模型评估报告。