查看全课大纲(第 63 / 82 节)
  1. 1.函数与数据泛化
  2. 2.向量:从几何直觉到高维空间
  3. 3.矩阵与张量
  4. 4.特征值与特征向量:矩阵变换的不动方向
  5. 5.凸函数:从向量求导的矩阵记号到好优化的函数形状
  6. 6.概率与统计:机器学习的数学基础
  7. 7.独立性与随机变量:贝叶斯公式与二项分布
  8. 8.期望、方差与协方差:随机变量的数字画像
  9. 9.联合概率:当一条轴不够用的时候
  10. 10.概率分布(一):从伯努利到伽马函数
  11. 11.概率分布(二):贝塔、泊松、正态与两个贝叶斯故事
  12. 12.机器学习的模型:找输入与输出的关系
  13. 13.机器学习的应用
  14. 14.过拟合
  15. 15.机器学习的分类
  16. 16.有监督学习:从房价预测说起
  17. 17.损失函数
  18. 18.梯度下降(上):更新规则与学习速率
  19. 19.梯度下降(2)
  20. 20.牛顿方法
  21. 21.KNN简介
  22. 22.距离测算
  23. 23.朴素贝叶斯
  24. 24.朴素贝叶斯的实际应用
  25. 25.信息熵:决策树构建的度量基础
  26. 26.信息增益与决策树的构建
  27. 27.ID3算法
  28. 28.决策树节点的划分
  29. 29.CART树
  30. 30.Bagging
  31. 31.Boosting:自适应提升算法
  32. 32.Gradient Boosting
  33. 33.线性模型:回归与分类的同一副骨架
  34. 34.最小二乘:把最优参数直接解出来
  35. 35.最小二乘法的几何与概率视角
  36. 36.极大似然:给最小二乘一个概率出身
  37. 37.逻辑回归(1):把线性模型接到分类上
  38. 38.逻辑回归(2):损失函数与多分类
  39. 39.---- 手造数据: 5 个样本, 2 个特征; x2 几乎 = 2*x1 (偏差手工指定, 无随机) ----
  40. 40.讲师例: 向量 "23014" -- 三把范数尺子量同一个向量
  41. 41.优化方法(1):坐标下降与前向选择
  42. 42.优化方法(2):前向策略与最小角回归
  43. 43.拉格朗日乘子法(1):从无约束到等式约束
  44. 44.拉格朗日乘子法(2):等式约束下的平行条件与拉格朗日函数
  45. 45.拉格朗日乘子法(3):不等式约束与 KKT 条件
  46. 46.最大间隔:把分类问题变成优化问题
  47. 47.拉格朗日对偶问题:把 w 和 b 全部换成 α
  48. 48.支持向量:边界上的少数派
  49. 49.SVM 计算过程:一笔到底算完,再走进核方法
  50. 50.贝叶斯公式:概率即信念
  51. 51.隐含变量和概率模型:给看不见的原因建模
  52. 52.EM 算法:期望与最大化的迭代
  53. 53.琴生不等式:EM 的数学钥匙
  54. 54.三个硬币问题:EM 的复盘与衔接
  55. 55.高斯混合模型:用隐变量解释双峰数据
  56. 56.隐马尔可夫模型
  57. 57.隐马尔可夫模型(2):前向算法与动态混合模型
  58. 58.隐马尔可夫模型在 NLP 中的运用
  59. 59.EM 算法:把「为什么收敛」推到底
  60. 60.HMM 在 NLP 中的运用:从分词到机器翻译
  61. 61.HMM 参数估计:Baum-Welch 的三块拉格朗日
  62. 62.高维下的高斯模型
  63. 63.主题模型:从词袋到隐含主题
  64. 64.Dirichlet 分布(1)
  65. 65.Dirichlet 分布(2)
  66. 66.统计学习内容回顾
  67. 67.K-means 算法
  68. 68.PCA 降维
  69. 69.SVD 降维
  70. 70.从聚类到流形
  71. 71.局部线性嵌入
  72. 72.随机邻域嵌入:SNE 与 t-SNE
  73. 73.无参模型:让数据自己说话
  74. 74.高斯过程:函数空间里的正态分布
  75. 75.高斯过程(2):噪声、正则化与核的线性组合
  76. 76.神经网络简介:从感知器到自编码器
  77. 77.反向传播:把误差一层层传回去
  78. 78.卷积神经网络(CNN):让特征自己学出来
  79. 79.循环神经网络(RNN):给网络装上记忆
  80. 80.深度学习回顾:静态一类,动态一类
  81. 81.增强学习(1):蒙特卡洛、树搜索与 Q-learning
  82. 82.强化学习(2):从乌鸦吃核桃到井字棋的 value 回传

主题模型:从词袋到隐含主题

约 22 分钟

📺 正在播放小象官方高清录播(支持倍速与清晰度调节)

小象实战讲义 · 机器学习算法精讲

上一节我们看到,生成式模型的参数不见得是一个数:高维数据 xx 的生成由高维参数控制——均值是一个向量、协方差是一个矩阵。带着这个认识,我们进入本章的重点:主题模型(Topic Model),学习如何从一堆看似无序的词袋(Bag of Words)中,自动发现隐藏的”主题”,并用概率生成模型刻画文档与词之间的关系。这一节我们重点理解 PLSA 的基本思想,建立”文档-主题-词”的生成直觉。

💡 核心导读

  • 从词袋到主题:超越简单的词频统计,引入”主题”作为中间隐含变量,连接文档与词语。
  • PLSA模型:概率潜在语义分析,用概率图模型清晰定义文档、主题、词三者的生成关系。
  • “硬币”类比:用”作者选择硬币(主题混合)-> 硬币掷出词语”的生动比喻,理解主题模型的生成机制。
  • 模型表示:学习概率图模型中的”盘(plate)“表示法,用它简洁描述重复的生成过程。
  • 迈向LDA:本节只是最简单的 latent topic 概念引入,真正的 LDA(潜在狄利克雷分配)及其完整数学描述在接下来的几节展开。

文本表示:从序列到词袋

为什么这个问题叫”主题”?这要从自然语言处理的历史说起。语言能力是人类智能最重要的标志之一——小猫小狗也能看见东西、被打时也会躲,但它们的语言能力与人类语言的复杂程度和背后的语义相比,完全不在一个量级。因此早期的人工智能研究很多都从自然语言开始:计算机科学家和语言学家一起,依靠语法、语义、句法规则来构建理解语言的系统。但后来发现语言学家在这件事上往往帮倒忙,业内甚至流传一句名言:每开除一位语言学家,语音识别系统的性能就提高一点。

于是人们渐渐放弃”让机器像人脑一样理解语言”,转向统计与概率的方法:机器处理语言的方式和人本来就不一样,完全可以把词看成数字和符号。当读到一句话时,人脑中会有神经层面的变化从而”理解”了它;而机器可以把每个字直接变成一个编号——这个词是 101,那个词是 372,另一个是 41145——面对这些数字和符号,它依然能完成处理。在这条路上,一个关键的简化就是词袋模型(Bag of Words, BoW):我们并不关心词谁先谁后、词性是什么、代表什么语义,只关心每个词和每个词是不一样的——每个文档就是装着许多词的一个”袋子”。

例如两篇极简的文档:“I love this movie” 和 “I love China”,可以把它们转化成一个词-文档矩阵(Term-Document Matrix):

# 示例:构建一个极简的词-文档矩阵
# 文档1: "I love this movie"
# 文档2: "I love China"
vocabulary = {'I': 0, 'love': 1, 'this': 2, 'movie': 3, 'China': 4}
# 矩阵表示 (行为词,列为文档)
term_doc_matrix = [
    [1, 1],  # 'I' 在文档1和2中各出现1次
    [1, 1],  # 'love'
    [1, 0],  # 'this' 只在文档1出现
    [1, 0],  # 'movie'
    [0, 1],  # 'China' 只在文档2出现
]

print("词-文档矩阵(行为词,列为文档):")
for word, idx in vocabulary.items():
    print(f"{word:>6}: {term_doc_matrix[idx]}")
词-文档矩阵(行为词,列为文档):
     I: [1, 1]
  love: [1, 1]
  this: [1, 0]
 movie: [1, 0]
 China: [0, 1]

矩阵的行是词表(vocabulary,大小记为 VV),列是文档(可以不断往下加,加到 KK 篇),整个矩阵的大小是 V×KV \times K。对这个矩阵做奇异值分解(SVD),就得到传统文本分析的经典技术——潜在语义分析(LSA):把词和文档映射到一个低维语义空间。SVD 具体怎么分解,后面的课会跟 PCA 一起专门讲,这里只需知道它是直接的代数运算。LSA 的问题是它完全没用到概率和生成模型的概念——这正是 PLSA 的切入点。

概率潜在语义分析(PLSA)

概率潜在语义分析(Probabilistic Latent Semantic Analysis, PLSA)在词袋的基础上引入了概率生成框架和隐含变量。它的基本假设很朴素:如果某些词经常在一起出现,就认为它们的语义相关——以此为语义建模。

基本思想与”硬币”类比

PLSA 的核心思想是:文档中的词并非直接由文档产生,而是通过一个隐含的”主题(Topic)“中介生成的。在图模型里,词 ww 和文档 dd 都是可观测的数据(图中阴影节点),中间引入一个不可直接观测的隐含变量 zz(也叫 cc),它就是我们说的 topic。

主题是什么?一个主题就是词的一种组合、词上的一个分布。比如”计算机”主题——它没有一个具体的标签叫 computer,但你会看到 Bill Gates、Word、PPT、machine learning 这些词在它之下出现的概率很大;而 Bible、God、Jesus 这些词则明显属于另一个偏宗教的话题。我们希望在整个大词典里把词分成这样不同的部分,每个 topic 就看成词的一个分布。

理解这个模型最好的方式还是硬币类比——它和我们学三硬币模型时一脉相承:

  1. 主题即”硬币”:每个主题好比一枚特殊的硬币,掷出不同面的概率就是不同词的概率。计算机硬币掷出的都是计算机词汇,宗教硬币掷出的都是宗教词汇,还可以有物理硬币、化学硬币……普通的硬币只有正反两面,而这些”主题硬币”可以掷出各自词表里的不同词。
  2. 文档即”硬币集合”:一篇文档不会只属于一个主题,它是很多硬币的混合。好比一位同学手里拿了不同的硬币,代表他的喜好:如果拿的基本都是计算机硬币、物理硬币、天文硬币,他大概是一位科学爱好者;如果拿的是艺术硬币、文学硬币、美学硬币,那显然是一位文科同学。文档里 topic 的混合比例,就代表它背后语义上的差别。
  3. 观察与推断:硬币(隐含变量)不能直接观察到,但我们能看到硬币掷出的结果——这位同学每次说出的词。说的都是技术词汇,就是理工科同学;说的都是文学美学,就是文科同学。于是我们不需要观察别的,只要看到这些词,就能对文档进行分类。

这与以前的模型唯一的区别是:以前文档直接被词所替代,直接观察给定文档的词汇;而现在假设这些词汇之上还有一个更高阶的特征——由”主题”这枚硬币掷出来。观察到的词由硬币控制,硬币是隐含变量,隐含变量又跟更上层的 document 相关——一个清晰的三层结构。

下面我们用一段简化的代码来模拟这个”作者-硬币-词语”的生成过程:作者 A 是科技爱好者(80% 科技硬币 + 20% 体育硬币),作者 B 是体育爱好者(20% 科技 + 80% 体育)。

import random

random.seed(42)

# 定义两个主题(两枚"硬币")及其词分布
# 主题0:科技
topic_tech = {'算法': 0.4, '数据': 0.4, '学习': 0.2}
# 主题1:体育
topic_sports = {'比赛': 0.5, '进球': 0.3, '运动员': 0.2}

# 两位作者(文档)的主题混合比例
author_a_mix = [0.8, 0.2]  # 作者A:80%科技 + 20%体育
author_b_mix = [0.2, 0.8]  # 作者B:20%科技 + 80%体育

def generate_words(num_words, topic_mix):
    """根据主题混合比例生成词语:先选硬币,再掷硬币"""
    words = []
    topics = [0, 1]  # 主题索引
    for _ in range(num_words):
        # 1. 根据混合比例选择一枚硬币(主题)
        chosen_topic = random.choices(topics, weights=topic_mix, k=1)[0]
        # 2. 根据选定主题的分布掷出词语
        if chosen_topic == 0:
            word = random.choices(list(topic_tech.keys()), weights=list(topic_tech.values()), k=1)[0]
        else:
            word = random.choices(list(topic_sports.keys()), weights=list(topic_sports.values()), k=1)[0]
        words.append(word)
    return words

# 生成观察数据
words_from_a = generate_words(10, author_a_mix)
words_from_b = generate_words(10, author_b_mix)

print("作者A(科技爱好者)生成的10个词:", words_from_a)
print("作者B(体育爱好者)生成的10个词:", words_from_b)
作者A(科技爱好者)生成的10个词: ['算法', '算法', '数据', '比赛', '算法', '数据', '算法', '数据', '数据', '比赛']
作者B(体育爱好者)生成的10个词: ['进球', '比赛', '比赛', '算法', '进球', '进球', '运动员', '进球', '进球', '进球']

注意一个细节:作者 A 的袋子里偶尔也会蹦出”比赛”,作者 B 也会蹦出”算法”——因为混合比例不是非 0 即 1,每生成一个词都要重新”抽一次硬币”。这正是主题模型与”一个文档只属于一个主题”的本质区别。

PLSA 的概率图模型

PLSA 用概率图模型来形式化上述生成过程。模型涉及三个变量:

  • dd:文档(可观测)
  • ww:词语(可观测)
  • zz:主题(隐含变量,即类比中的”硬币”)

其生成过程为:

  1. 以概率 P(d)P(d) 选择一篇文档 dd。
  2. 对于文档 dd 中的每一个词的位置: a. 根据条件分布 P(z∣d)P(z|d) 选择一个主题 zz。 b. 根据条件分布 P(w∣z)P(w|z) 从主题 zz 中生成一个词 ww。

为了简洁地表示重复结构,我们使用盘表示法(Plate Notation):图中的方块(plate)就代表”重复”。如果只画一个隐含变量 zz 对应一个观测 xx、外面框一个标着 NN 的方块,它展开就是 z1→x1,z2→x2,…,zN→xNz_1 \to x_1, z_2 \to x_2, \ldots, z_N \to x_N——同一对结构重复 NN 次。两个长得像的图模型,差别就看重复的方块把谁圈了进来:圈的位置不同,含义就完全不同。

按这个记法,PLSA 的完整图模型是:外层一个标着 MM 的盘代表 MM 篇文档(d1,d2,…,dMd_1, d_2, \ldots, d_M,文档参数控制着主题的选择),每篇文档内嵌一个标着 NN 的盘,代表这篇文档的 NN 个词;内盘中对每个词位置 nn:dd 指向隐含主题 znz_n,znz_n 再指向观测词 wnw_n。一大套复杂的概率关系,用几层”盘子”就简洁地表示出来了。

这个模型想建模的是 ww 与 dd 的联合概率。推导从引入隐含变量开始:我们想知道”第三个文本里出现某个词的概率”,就要先构造隐含变量 zz,写出

P(d,w)=P(d) P(w∣d)P(d, w) = P(d) \, P(w|d)

而 P(w∣d)P(w|d) 无法直接得到——词并不是从文档直接生成的。注意到 zz 跟 dd 相关(dd 是 zz 的条件),ww 又直接跟 zz 相关,于是用贝叶斯公式把 P(d∣z)P(d|z) 换成 P(z∣d)⋅P(d)P(z|d) \cdot P(d),再对所有的 topic 求和,就得到:

P(d,w)=P(d)∑zP(w∣z)P(z∣d)P(d, w) = P(d) \sum_{z} P(w|z) P(z|d)

有了这个联合概率,我们就可以根据已观测的 DD 和 WW 去估计参数(极大似然),进而对未来做预测。具体的求解不需要手推公式——有现成的模型和算法(如期望最大化 EM 算法,正是我们学 GMM 时用过的),而且 topic model 的实现代码有很多不同版本,都可以拿来实验。

展望:从 PLSA 到 LDA

到目前为止,我们其实还没有讲”真正的”主题模型——本节只是最简单地引入了 latent topic 的概念。PLSA 的参数 P(z∣d)P(z|d) 与训练文档绑定,不是一个完备的生成式模型。接下来两节我们先学习它的数学基石狄利克雷分布(Dirichlet Distribution),然后正式进入 LDA(Latent Dirichlet Allocation,潜在狄利克雷分配)——那里才有完整的数学描述,希望大家跟得上。

📝 动手练一练

  1. 概念辨析:在PLSA模型中,如果我们已知一篇文档的主题分布 P(z∣d)P(z|d) 和每个主题的词分布 P(w∣z)P(w|z),请描述如何计算该文档中出现某个特定词 ww 的概率 P(w∣d)P(w|d)。这与我们直接统计文档中词频得到的概率有何本质区别?
👉 点击查看参考答案

根据PLSA的生成过程,文档 dd 中生成词 ww 的概率,需要对所有可能的隐含主题 zz 进行求和: P(w∣d)=∑zP(w∣z)P(z∣d)P(w|d) = \sum_{z} P(w|z) P(z|d) 这个概率是基于模型推断的,它融合了文档的主题倾向和主题本身的用词特点。例如,即使某篇科技文档中从未出现”神经网络”这个词,但如果”神经网络”在”计算机”主题中概率很高,而该文档的”计算机”主题占比也很高,那么模型仍然会赋予 P("神经网络"∣d)P(\text{"神经网络"}|d) 一个可观的概率。

而直接统计词频得到的概率 Pcount(w∣d)=词w在文档d中的出现次数文档d的总词数P_{\text{count}}(w|d) = \frac{\text{词w在文档d中的出现次数}}{\text{文档d的总词数}},是基于观测数据的。对于未在文档中出现的词,其统计概率为0。主题模型的核心优势就在于能够通过共享的主题信息,实现不同文档间信息的平滑与迁移,对未观测到的词-文档组合进行概率估计。

  1. 动手实验:下面的代码观察一段词,用贝叶斯推断它更可能来自哪位作者。请你修改 words_manual 列表里的词语,观察当生成的词语更偏向体育或更混合时,推断结果如何变化。
import math

# 为保证本块可独立运行,重新定义两枚硬币与两位作者的混合比例
topic_tech = {'算法': 0.4, '数据': 0.4, '学习': 0.2}
topic_sports = {'比赛': 0.5, '进球': 0.3, '运动员': 0.2}
author_a_mix = [0.8, 0.2]  # 作者A:80%科技 + 20%体育
author_b_mix = [0.2, 0.8]  # 作者B:20%科技 + 80%体育

def likelihood_of_words(words, topic_mix):
    """计算一段词由某混合比例生成的对数似然(对数空间防止下溢)"""
    log_likelihood = 0.0
    for w in words:
        # P(w) = P(w|主题0)*P(主题0) + P(w|主题1)*P(主题1)
        p_w_given_t0 = topic_tech.get(w, 0.0)
        p_w_given_t1 = topic_sports.get(w, 0.0)
        p_w = p_w_given_t0 * topic_mix[0] + p_w_given_t1 * topic_mix[1]
        # 使用很小的epsilon防止概率为0导致对数无穷大
        log_likelihood += math.log(p_w + 1e-10)
    return log_likelihood

# 修改观察到的词,试试不同组合
words_manual = ['比赛', '进球', '运动员']
print("观察到的词语:", words_manual)

like_a = likelihood_of_words(words_manual, author_a_mix)
like_b = likelihood_of_words(words_manual, author_b_mix)

# 假设先验概率相等 P(A)=P(B)=0.5,计算后验概率
# 先做限幅,防止指数溢出
like_a_safe = max(min(like_a, 500), -500)
like_b_safe = max(min(like_b, 500), -500)
posterior_a = math.exp(like_a_safe) / (math.exp(like_a_safe) + math.exp(like_b_safe))
posterior_b = math.exp(like_b_safe) / (math.exp(like_a_safe) + math.exp(like_b_safe))

print("推断结果(假设先验相等):")
print(f"  来自作者A的后验概率: {posterior_a:.4f}")
print(f"  来自作者B的后验概率: {posterior_b:.4f}")
if posterior_a > posterior_b:
    print("  结论:这段文字更可能来自作者A(科技爱好者)。")
else:
    print("  结论:这段文字更可能来自作者B(体育爱好者)。")
👉 点击查看参考答案
观察到的词语: ['比赛', '进球', '运动员']
推断结果(假设先验相等):
  来自作者A的后验概率: 0.0154
  来自作者B的后验概率: 0.9846
  结论:这段文字更可能来自作者B(体育爱好者)。

这三个词都只出现在”体育”硬币里,作者A(80%科技)生成它们的概率是 0.23×0.5×0.3×0.2=0.000240.2^3 \times 0.5 \times 0.3 \times 0.2 = 0.00024,而作者B是 0.83×0.5×0.3×0.2=0.015360.8^3 \times 0.5 \times 0.3 \times 0.2 = 0.01536——相差 64 倍,后验一边倒。

再加入科技词:如果我们让 words_manual = ['算法', '数据', '比赛'],即两个科技词加一个体育词:

观察到的词语: ['算法', '数据', '比赛']
推断结果(假设先验相等):
  来自作者A的后验概率: 0.8000
  来自作者B的后验概率: 0.2000
  结论:这段文字更可能来自作者A(科技爱好者)。

此时,尽管出现了体育词”比赛”,但两个强科技词(在 A 下各 0.32、在 B 下各 0.08)使得整体似然 0.01024 对 0.00256,仍以 4:1 偏向作者A。

全是体育词:如果设置 words_manual = ['比赛', '比赛', '比赛'],即使作者A有 20% 的体育硬币,其连续生成三个”比赛”的概率(0.13=0.0010.1^3 = 0.001)也远低于作者B(0.43=0.0640.4^3 = 0.064),系统仍会坚定地判断为作者B(后验与第一组相同:0.0154 对 0.9846)。

要让系统”严重误判”,需要构造一种情况:观察到的词在作者B的强势主题下概率很低,但在作者A的弱势主题下却有相对较高的概率。在本例的简单设定下(每个词只属于一个主题)这很难实现,但在真实的、更复杂的主题分布中,这种交叉情况是可能发生的——这也说明了模型推断的不确定性。

本章小结

本节我们迈出了从简单词袋模型走向深层语义建模的关键一步。从”每开除一位语言学家”的统计主义转向,到词袋与词-文档矩阵的符号化表示,再到 PLSA 引入隐含主题:主题模型通过”主题”这一隐含变量,为文档生成词语的过程提供了优雅的概率解释。其”文档-主题-词”的三层结构、“硬币与同学”的类比、盘表示法与 P(d,w)=P(d)∑zP(w∣z)P(z∣d)P(d, w) = P(d) \sum_{z} P(w|z) P(z|d) 的联合概率公式,是我们理解后续 LDA 的坚实基础。

📋 行动清单

  • 理解核心类比:确保能用”作者选择硬币(主题混合)-> 硬币掷出词语”的比喻,向他人解释主题模型的基本思想。
  • 复现关键公式:在纸上写出PLSA的联合概率公式 P(d,w)=P(d)∑zP(w∣z)P(z∣d)P(d, w) = P(d) \sum_{z} P(w|z) P(z|d),并说明每一项的含义,特别是贝叶斯公式在哪一步介入。
  • 运行实验代码:动手修改并运行讲义中的代码,观察不同词语组合如何影响对文档来源(作者)的贝叶斯推断,直观感受主题混合与词语生成的关系。

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 第15章课件:统计学习(高维高斯/主题模型/Dirichlet)
    下载
  • 第15章笔记:高斯混合模型(GMM)
    下载
  • 第15章代码包(LDA 主题模型实验)
    下载