查看全课大纲(第 33 / 69 节)
  1. 1.NLP 的研究领域、应用与学习路径
  2. 2.自然语言的基本术语
  3. 3.信息论基础:信息熵与交叉熵
  4. 4.语言的进化与语义变迁
  5. 5.主观概率、客观概率与条件概率
  6. 6.贝叶斯原理与推理
  7. 7.随机变量:二项式、期望方差与联合概率
  8. 8.常见概率分布
  9. 9.辛普森悖论与贝叶斯解题实例
  10. 10.语言模型:语言概率与词袋模型
  11. 11.n-gram 语言模型(二元 / 三元 / N-Gram)
  12. 12.语言模型评价:交叉熵、困惑度与插值
  13. 13.朴素贝叶斯文本分类:垃圾邮件
  14. 14.拼写检查
  15. 15.中文分词:构词法、最大匹配、最优路径与 Jieba
  16. 16.词表示问题与语义相似度
  17. 17.TF-IDF 权重处理
  18. 18.One-Hot 表达与词表示的局限
  19. 19.Word2Vec 核心思想
  20. 20.Word2Vec 训练过程
  21. 21.Skip-gram 模型详解
  22. 22.词性标注任务与标注方法(规则 + N-Gram)
  23. 23.EM 算法与混合模型
  24. 24.高斯混合模型 GMM
  25. 25.从混合模型到隐马尔可夫模型
  26. 26.隐马尔可夫模型(HMM)推导与维特比解码
  27. 27.线性链条件随机场 CRF
  28. 28.贝叶斯网络与图模型
  29. 29.隐马尔可夫链与 HMM 推导
  30. 30.PLSA 主题模型
  31. 31.LSI 与 SVD 向量表达
  32. 32.LDA 与狄利克雷分布
  33. 33.主题模型实验复现(困惑度 / 分类 / 相似度 / 延展)
  34. 34.上下文无关文法(CFG)与句法分析
  35. 35.概率上下文无关文法 PCFG
  36. 36.统计机器翻译:词对齐与翻译模型
  37. 37.统计机器翻译中的对齐模型与参数估计
  38. 38.从统计机器翻译到神经机器翻译
  39. 39.神经元与反向传播速览(指向深度学习课)
  40. 40.CNN 原理及其在 NLP 中的应用
  41. 41.循环神经网络的基本原理与 Elman / Jordan 网络
  42. 42.LSTM:长短期记忆网络的核心思想与分步实现
  43. 43.Encoder-Decoder 框架
  44. 44.Seq2Seq 与早期注意力机制
  45. 45.注意力机制的背景与动机
  46. 46.注意力模型实现原理与匹配函数
  47. 47.记忆网络:组成与计算
  48. 48.注意力机制的应用与历史延展
  49. 49.Transformer 结构:Encoder-Decoder 栈与 Self-Attention
  50. 50.Multi-Head Attention、FFNN 与位置编码
  51. 51.层归一化、损失函数与 Universal Transformer 延展
  52. 52.预训练范式:从 ELMo 到 BERT
  53. 53.BERT 与掩码语言模型
  54. 54.BERT 下游任务微调与 GLUE 口径
  55. 55.GPT 与自回归语言建模
  56. 56.GPT-1 到 GPT-4 的演进
  57. 57.指令微调与 RLHF
  58. 58.分词器:BPE、WordPiece 与中文词表
  59. 59.上下文窗口与长文本处理
  60. 60.能力边界、成本与适用场景
  61. 61.文本分类实战:从朴素贝叶斯到逻辑回归
  62. 62.命名实体识别实战
  63. 63.文本生成与摘要实战
  64. 64.语义检索与向量库实战
  65. 65.RAG 知识库问答
  66. 66.NLP 评估指标:BLEU / ROUGE / BERTScore 与人工评估
  67. 67.幻觉与事实性问题
  68. 68.文本与知识结合(知识图谱压缩)
  69. 69.跨媒体信息检索与未来展望

主题模型实验复现(困惑度 / 分类 / 相似度 / 延展)

小象实战讲义 · 自然语言处理

本节我们将通过一组示例分析,梳理主题模型(Topic Model)的实际应用与评估方法。我们将聚焦于 LDA(Latent Dirichlet Allocation)模型,探讨其在中文处理中的特殊考量,例如基于字(character)与基于词(word)的建模差异,以及如何通过引入字词关系来提升模型性能。学完本节,你将能够设计并解读主题模型的实验,理解困惑度、文本分类准确度、语义相似度等核心评估指标,并了解模型延展的基本思路。

💡 核心导读

  • 实验评估指标:掌握困惑度(Perplexity)、精确率(Precision)、召回率(Recall)、F1 分数等用于衡量主题模型和文本分类性能的核心指标。
  • 中文字 vs. 词建模:对比分析以汉字为基本单元和以汉语词为基本单元的 LDA 模型在性能、计算效率和语义表示上的差异。
  • 跨语言语义关联:探索双语料库中,不同语言(如中英文)在主题模型下的语义空间关系是否得以保持。
  • 模型延展:字词关系主题模型:了解如何将汉字与汉语词之间的构成关系引入主题模型,以处理未登录词并提升语义建模能力。

主题模型评估指标回顾

在深入实验前,我们先回顾几个关键的评估概念,它们将贯穿后续的实验分析。

困惑度(Perplexity)

困惑度是衡量概率模型(如语言模型、主题模型)预测能力的重要指标。对于一个主题模型,其困惑度定义为模型对未见过的测试文档集 DtestD_{test} 的预测概率的几何平均的倒数,通常取对数形式方便计算:

Perplexity(Dtest)=exp⁡(−∑d=1Mlog⁡p(wd)∑d=1MNd)\text{Perplexity}(D_{test}) = \exp \left( -\frac{\sum_{d=1}^{M} \log p(\mathbf{w}_d)}{\sum_{d=1}^{M} N_d} \right)

其中,MM 是测试文档数,NdN_d 是文档 dd 中的词数,p(wd)p(\mathbf{w}_d) 是模型生成文档 dd 中所有词的概率。困惑度越低,表明模型对数据的拟合和泛化能力越好。在主题模型中,我们通过比较不同模型(如不同主题数 KK)在验证集上的困惑度来选择超参数。

精确率、召回率与 F1 分数

当我们将主题模型应用于文本分类等下游任务时,需要用到分类任务的评估指标。

  • 精确率(Precision):在所有被模型预测为正例的样本中,真正为正例的比例。Precision=TPTP+FP\text{Precision} = \frac{TP}{TP + FP}。
  • 召回率(Recall):在所有真实为正例的样本中,被模型正确预测为正例的比例。Recall=TPTP+FN\text{Recall} = \frac{TP}{TP + FN}。
  • F1 分数(F1-Score):精确率和召回率的调和平均数,用于综合评估模型性能。F1=2⋅Precision⋅RecallPrecision+Recall\text{F1} = 2 \cdot \frac{\text{Precision} \cdot \text{Recall}}{\text{Precision} + \text{Recall}}。

其中,TPTP(True Positive)、FPFP(False Positive)、FNFN(False Negative)分别代表真阳性、假阳性和假阴性。

实验一:中文字 vs. 词的 LDA 模型对比

汉语文本处理面临一个基础选择:以“字”还是“词”作为语义的基本单元?下面用示意数据对比这两种建模方式在评估口径上的差异。

实验设置与困惑度分析

真实研究通常使用搜狗实验室和译言网的双语料库(Chinese-English parallel corpus),分别构建:

  1. Character-based LDA:将每个汉字视为一个独立的“词”。
  2. Word-based LDA:使用分词工具将文本切分为词。

随后比较两种模型在不同主题数(Number of Topics)下的困惑度;本节代码用预设示意值演示这一比较流程。

# 模拟不同主题数下,基于字和基于词的LDA模型的困惑度对比
# 注意:此为模拟数据,用于展示分析逻辑。真实实验需使用gensim、sklearn等库。
import numpy as np

# 模拟主题数列表
topic_numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

# 模拟基于字的LDA困惑度(数值较小,集中在600-1500)
perplexity_char = np.array([1500, 1200, 1000, 900, 800, 750, 720, 700, 680, 670])

# 模拟基于词的LDA困惑度(数值较大,集中在2800-4000)
perplexity_word = np.array([4000, 3800, 3500, 3200, 3000, 2900, 2850, 2820, 2800, 2790])

print("主题数\t字符LDA困惑度\t词语LDA困惑度")
for i in range(len(topic_numbers)):
    print(f"{topic_numbers[i]}\t{perplexity_char[i]}\t\t{perplexity_word[i]}")
主题数	字符LDA困惑度	词语LDA困惑度
10	1500		4000
20	1200		3800
30	1000		3500
40	900		3200
50	800		3000
60	750		2900
70	720		2850
80	700		2820
90	680		2800
100	670		2790

结果分析:

  • 两列数字不能直接比大小。这是本节最需要警惕的一处:基于字的模型把每个汉字算一个 token,基于词的模型把每个词算一个 token,同一段文本在两者眼里的 NdN_d 完全不同(“自然语言处理”是 6 个 token 还是 1 个 token),而困惑度定义里的 ∑d∑i=1Nd\sum_d \sum_{i=1}^{N_d} 正是按 token 求平均。分母都不一样,两个困惑度就不在同一个量纲上——670 与 2790 的差距主要来自 tokenization,而不是模型优劣。要比较,必须先把预测事件对齐(都按词、或都按字),并把 NdN_d 的计数口径统一后再算。
  • 表里的数字是示意值(脚本只是打印预设数组),用来说明分析逻辑,不是真实训练结果。自己跑实验时不要拿这两列下”字模型更好”的结论。
  • 随着主题数增加,两种模型的困惑度都呈下降趋势,说明增加主题复杂度能提升对训练数据的表示能力;但困惑度在训练集上继续下降往往意味着过拟合,故选 KK 要在留出集上比,而不是在训练集上取最小。

文本分类性能对比

既然两列困惑度不在同一量纲(见上文),我们换个问法:它们各自在真实下游任务上有没有用?这里看文本分类。在真实研究中,会在一个新闻文本分类数据集(如 news_one_w,包含 IT、旅游、教育、经济、健康等 10 个类别)上训练分类;本节用示意数值演示比较口径。

标准流程是:将每个文档通过 LDA 模型转换为其主题分布(即文档-主题矩阵),再用简单的分类器(如基于 KL 散度或余弦相似度的最近邻分类)进行分类。


# —— 重新构建上文用到的微型数据(本节各代码块相互独立,可单独运行)
import numpy as np
topic_numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
perplexity_char = np.array([1500, 1200, 1000, 900, 800, 750, 720, 700, 680, 670])
perplexity_word = np.array([4000, 3800, 3500, 3200, 3000, 2900, 2850, 2820, 2800, 2790])

# 模拟基于字和基于词的LDA模型在10个新闻类别上的F1分数对比
# 此为模拟数据,展示分析框架。
categories = ['IT', 'Travel', 'Education', 'Economy', 'Health', 'Sports', 'Job', 'Auto', 'Entertainment', 'Politics']

# 模拟F1分数(范围0-1)
f1_char_based = np.array([0.85, 0.78, 0.82, 0.80, 0.88, 0.75, 0.83, 0.79, 0.81, 0.77])
f1_word_based = np.array([0.86, 0.79, 0.83, 0.81, 0.87, 0.76, 0.84, 0.80, 0.82, 0.78])

print("类别\t\t字符LDA F1\t词语LDA F1")
for i in range(len(categories)):
    print(f"{categories[i]:15s}\t{round(f1_char_based[i], 3)}\t\t{round(f1_word_based[i], 3)}")

# 计算平均F1
avg_f1_char = np.mean(f1_char_based)
avg_f1_word = np.mean(f1_word_based)
print(f"\n平均F1分数 - 字符LDA: {round(avg_f1_char, 3)}")
print(f"平均F1分数 - 词语LDA: {round(avg_f1_word, 3)}")
类别		字符LDA F1	词语LDA F1
IT             	0.85		0.86
Travel         	0.78		0.79
Education      	0.82		0.83
Economy        	0.8		0.81
Health         	0.88		0.87
Sports         	0.75		0.76
Job            	0.83		0.84
Auto           	0.79		0.8
Entertainment  	0.81		0.82
Politics       	0.77		0.78

平均F1分数 - 字符LDA: 0.808
平均F1分数 - 词语LDA: 0.816

结果分析:

  • 两套模型在分类任务的平均分数非常接近(0.808 vs 0.816),差 0.008。先把口径说清:这里的数字是逐类 F1 的算术平均,即宏平均 F1(macro-F1),不是准确率;要和准确率比必须另算(准确率按样本加权,宏 F1 按类别等权,类别不平衡时两者会差出不少)。
  • 0.008 的差距不足以支持任何结论:这是示意数值,没有做重复实验,也没有任何显著性检验。要下”字模型同样可用”的判断,至少得跑多次不同随机种子、给出均值与标准差,再做配对检验;单次的 0.008 很可能落在噪声范围内。本节只能说”两者在同一量级”。
  • 顺带纠正一个常见误解:词表小 ≠ 训练和推断更快。LDA 的每轮迭代成本随语料 token 数增长,而字模型把同一段文本切成更多 token(“自然语言处理”6 个 token 而不是 1 个),单篇文档的 token 数反而更多。词表小影响的是参数矩阵的宽度,不等于整体更快——真实速度要实测,不能凭直觉断言。

实验二:跨语言语义关联保持性

在真实研究中,会利用中英双语平行语料库,探究主题模型能否捕捉并保持跨语言的语义关联。核心问题是:在一种语言的语义空间中成立的文档间相似性关系,在其翻译对另一种语言的语义空间中是否依然成立?本节以下用示意比例演示分析口径。

实验设计

  1. 语料:真实研究常用 Bell 3200 双语料库,包含语义等价的中文文档和英文文档对。
  2. 建模:
    • 英文侧:仅使用词(EW)。
    • 中文侧:分别使用词(CW)和字(CC)建立 LDA 模型。
  3. 验证:在每种语言的语义空间(即文档-主题分布空间)中,计算文档三元组 (A,B,C)(A, B, C) 之间的距离。例如,AA(计算机)、BB(IT)、CC(宗教)。我们检查关系 D(A,B)<D(A,C)D(A, B) < D(A, C) 是否在英文空间和中文空间中都成立。统计所有测试三元组中该关系得以保持的比例。
# 模拟跨语言语义关联保持性的实验结果
# 关系保持比例:在一种语言空间中A与B比A与C更相似,在另一种语言空间中也成立的比例。
relationships = {
    'CC vs CW': 0.73,  # 中文字 vs 中文词
    'CW vs EW': 0.70,  # 中文词 vs 英文词
    'CC vs EW': 0.67,  # 中文字 vs 英文词
    'All Three': 0.55  # 三种表示下关系同时成立
}

print("对比组\t\t语义关联保持比例")
for key, value in relationships.items():
    print(f"{key:10s}\t{value:.2%}")
对比组		语义关联保持比例
CC vs CW  	73.00%
CW vs EW  	70.00%
CC vs EW  	67.00%
All Three 	55.00%

结果分析:

  1. CC vs CW (73%):同一种语言(中文)下,用字和用词建模的语义空间最为接近,关联保持性最高。
  2. CW vs EW (70%):跨语言但均以“词”为单位建模时,关联保持性次之。这说明翻译通常以词为单元,使得词级别的语义关系得以较好传递。
  3. CC vs EW (67%):中文字与英文词之间的关联保持性相对最弱。因为字的语义颗粒度太细,且与英文词的翻译对应关系不直接。
  4. All Three (55%):在这组示意数据上,超过一半的语义三元关系在三种表示下均保持一致;这只能作为主题模型捕捉跨语言、跨表示单元语义关联的一个示例线索,真实结论需在真实语料上重复验证。

实验三:引入字词关系的主题模型(CWTM)

汉语中,词由字构成,这种结构关系蕴含语义线索(如“学生”、“学习”共享“学”字)。我们能否利用这种关系提升主题模型,尤其是处理未登录词(Out-of-Vocabulary, OOV)的能力?

模型思想:Character-Word Topic Model (CWTM)

CWTM 在标准 LDA 的基础上,引入了一个显式的字词关系矩阵 RR。其中 Rc,wR_{c,w} 表示汉字 cc 在词 ww 中出现的次数(一个稀疏矩阵)。模型的生成过程关键修改在于:生成一个词 ww 的概率,不仅依赖于该词在其所属主题下的分布 ϕw\phi_w,还依赖于构成该词的各个汉字在其所属主题下的分布 ϕc\phi_c 以及关系矩阵 RR。

简化的思想是:一个词的主题倾向,会受到其构成汉字主题倾向的“拉动”。例如,即使“主客场”这个词未在训练语料中出现,但“主”、“客”、“场”这些字在“体育”主题下出现概率高,那么模型也会倾向于将“主客场”分配到“体育”主题。

实验结果示例

我们对比了标准 LDA 和 CWTM 在文本分类任务上的性能,以及它们对未登录词的主题分配能力。

# 模拟CWTM与标准LDA在文本分类宏平均F1上的对比
# 此为模拟数据,代码只打印预设数组,并未真正训练分类器;这里的指标是逐类 F1 的算术平均(macro-F1),不是准确率。
models = ['Standard LDA (Word)', 'Standard LDA (Char)', 'CWTM']
macro_f1 = [0.816, 0.808, 0.832] # 对应宏平均 F1

print("模型\t\t\t宏平均F1")
for i in range(len(models)):
    print(f"{models[i]:20s}\t{macro_f1[i]:.3f}")

improvement = macro_f1[2] - macro_f1[0]
print(f"\nCWTM 相较于 词语LDA 的提升: {improvement:.3f}")
模型			宏平均F1
Standard LDA (Word) 	0.816
Standard LDA (Char) 	0.808
CWTM                	0.832

CWTM 相较于 词语LDA 的提升: 0.016

结果分析:

  • 在这组示意数值上,CWTM 的分数高于两种标准 LDA 模型;但代码并未真正训练分类器,只能作为分析逻辑演示,不能据此断言真实性能。
  • 提升幅度(本例中 0.016,即 1.6 个百分点,不是 1.6% 的相对提升)可能不是特别巨大,原因在于字词关系是一把“双刃剑”:
    • 正面:共享字的确能提示语义关联(如“钱包”、“书包”)。
    • 负面:也可能引入噪声关联(如“甘肃”、“甘蔗”;“包公”、“钱包”)。
  • 尽管有噪声,这组示意分数上整体仍有提升;但由于没有重复实验、方差或显著性检验,不能声称字所携带的语义共享信号在统计意义上强于噪声,超参数、数据划分和随机波动都可能影响结果。

📝 动手练一练

  1. 概念辨析:在基于字的 LDA 模型中,词汇表大小约为 3000;在基于词的 LDA 模型中,词汇表大小约为 10000。假设主题数 K=50K=50,请估算两种模型中需要学习的“主题-词”分布参数 ϕ\phi 的数量级差异。这如何从参数复杂度的角度解释两者困惑度的差异?
👉 点击查看参考答案
  • 基于字的模型:参数 ϕ\phi 的维度为 K×VcharK \times V_{char},其中 Vchar≈3000V_{char} \approx 3000。因此参数数量级约为 50×3000=150,00050 \times 3000 = 150,000。
  • 基于词的模型:参数 ϕ\phi 的维度为 K×VwordK \times V_{word},其中 Vword≈10000V_{word} \approx 10000。因此参数数量级约为 50×10000=500,00050 \times 10000 = 500,000。
  • 解释:基于词的模型需要估计的参数数量约为基于字模型的 3.3 倍(50×1000050\times10000 对 50×300050\times3000)。参数多确实意味着在同量数据下更难充分训练、更容易过拟合,但这不能用来解释”字模型困惑度更低”——两者的困惑度按不同的 token 口径计算(一个是字、一个是词),数值本身不可比(见上文关于 NdN_d 的说明)。这个参数规模的差异只能在同一 tokenization 下才有比较意义。
  1. 手算评估指标:在一个关于“体育”新闻的二分类任务中,测试集共有 100 篇文档,其中 60 篇是真正的体育新闻。你的分类模型预测了 80 篇文档为体育新闻,在这 80 篇预测中,有 50 篇确实是体育新闻。请计算该模型在此任务上的精确率(Precision)、召回率(Recall)和 F1 分数。
👉 点击查看参考答案

根据定义:

  • 真正例(TP):模型预测为体育且确实是体育的文档数 = 50。
  • 假正例(FP):模型预测为体育但实际不是体育的文档数 = 80 - 50 = 30。
  • 假反例(FN):实际是体育但模型预测不是体育的文档数 = 60 - 50 = 10。

计算:

  • 精确率(Precision)= TP/(TP+FP)=50/(50+30)=50/80=0.625TP / (TP + FP) = 50 / (50 + 30) = 50 / 80 = 0.625
  • 召回率(Recall)= TP/(TP+FN)=50/(50+10)=50/60≈0.833TP / (TP + FN) = 50 / (50 + 10) = 50 / 60 \approx 0.833
  • F1 分数 = 2⋅(Precision⋅Recall)/(Precision+Recall)=2⋅(0.625⋅0.833)/(0.625+0.833)≈2⋅0.520/1.458≈0.7142 \cdot (Precision \cdot Recall) / (Precision + Recall) = 2 \cdot (0.625 \cdot 0.833) / (0.625 + 0.833) \approx 2 \cdot 0.520 / 1.458 \approx 0.714

因此,精确率为 0.625,召回率为 0.833,F1 分数约为 0.714。

本章小结

本节通过一系列模拟演示(脚本只打印预设数组,未真正训练模型),梳理了主题模型应用与评估的分析逻辑:

  1. 评估是导航仪:困惑度用于衡量模型本身的生成质量,而精确率、召回率、F1 分数则用于评估模型在具体下游任务(如分类)上的效用。
  2. 中文处理的特殊性:对于 LDA 这类主题模型,基于字的建模在保证与基于词建模相近的分类性能时,字词表较小通常可降低主题-词参数矩阵的存储开销;但实际运行成本还受 token 总量、主题数、迭代次数和推断算法影响,不能仅凭词表小就断言整体更快或大幅降低计算复杂度,速度与内存需在相同条件下实测。
  3. 主题模型捕捉深层语义:跨语言实验表明,主题模型能够在一定程度上抽取出超越具体语言形式和词汇单元的语义关联。
  4. 模型可延展性:通过引入领域知识(如汉语的字词构成关系),可以设计像 CWTM 这样的改进模型,以处理未登录词等特定问题,提升模型性能。但改进需权衡引入新信息带来的收益与可能增加的噪声。

📋 行动清单

  • 使用一个中文文本数据集(如搜狗新闻语料),分别用 gensim 库训练基于字和基于词的 LDA 模型,并绘制主题数-困惑度曲线进行对比。
  • 在上述数据集上,尝试将 LDA 得到的文档主题分布作为特征,使用 scikit-learn 中的简单分类器(如 SVM)进行文本分类,比较字/词两种特征的效果。
  • 思考并调研:除了引入字词关系,还有哪些方法可以改进主题模型对未登录词或稀有词的处理能力?

—— 小象教研组