前置讲 P2:机器学习与 NLP 基础回顾

Large Language Models: Methods and Applications · 预备知识 2。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

定位:可选的前置讲。原课要求机器学习达到 CMU 10-301 / 10-601 的水平、NLP 达到 11-411 / 11-611 的水平。这一篇把后面反复用到的概念串讲一遍:最大似然与交叉熵、梯度下降、生成式与判别式分类、过拟合与正则化、文本的统计规律、词向量、循环神经网络。 建议阅读时间:第 1 讲之前(可选),最晚第 2 讲之前。第 1 讲已经细讲的 n-gram 和平滑,这里只一笔带过。 配套代码:code/prereq2/(2 个模块 + 5 个脚本,每个都在 15 秒内跑完) 数据:除了第 1 讲的”花园世界”,这一篇还用到两份不需要下载的数据:Python 标准库的文档字符串(约 22 万个真实英文单词,用来看真实语言的统计规律),以及一个生成过程已知的情感分类小世界”花园日记”(能算出理论上最好的分类器)。所有实验数字都来自配套代码的实际输出。


目录


0. 导读

LLM 的论文里满是这些词:交叉熵损失、困惑度、过拟合、权重衰减、验证集、嵌入、梯度消失……它们都不是 LLM 发明的,而是机器学习和 NLP 几十年积累下来的基本概念。LLM 的训练,说穿了就是”在海量文本上,用梯度下降最小化下一个词的交叉熵,再用各种手段防止它学歪”。

这一篇按”一个语言模型从零到训练好”需要的顺序回顾这些概念。每个概念都配一个能在几秒内跑完的小实验,并且尽量用知道标准答案的数据,好让你看清楚”学到了多少""离理论最优还有多远”。

学完这一篇你应该能:

  1. 解释最大似然、交叉熵、KL 散度之间的关系,说明为什么语言模型用对数损失训练;
  2. 说清楚学习率、批大小对梯度下降的影响;
  3. 比较生成式(朴素贝叶斯)和判别式(逻辑回归)模型,知道各自适合什么情况;
  4. 识别过拟合,用验证集选择正则化强度、用提前停止;
  5. 描述自然语言的 Zipf 定律和 Heaps 定律,以及它们对 NLP 系统的影响;
  6. 解释词向量从哪里来(分布假设、PPMI + SVD、word2vec);
  7. 解释 RNN 怎样处理任意长的历史,以及为什么它难学长距离依赖。

1. 监督学习的框架

几乎所有机器学习问题都由四件事组成:

组成含义语言模型里是什么
数据输入 xx 和目标 yy 的样本xx = 前面的文字,yy = 下一个 token
模型一个带参数 θ\theta 的函数族 fθf_\thetan-gram、窗口神经网络、RNN、Transformer
损失衡量预测和目标差多少负对数似然(交叉熵)
优化找让损失最小的 θ\theta数数(n-gram),梯度下降(神经网络)

我们真正关心的不是模型在训练数据上的表现,而是在没见过的数据上的表现(泛化)。所以数据要分成三份:

  • 训练集:用来拟合参数;
  • 验证集(开发集):用来做选择——选模型、选超参数(学习率、正则化强度、训练多少步);
  • 测试集:所有选择都做完之后,只用一次,报告最终成绩。

为什么要单独的验证集?因为”选择”本身也是一种拟合。如果你在测试集上试了 20 种学习率、挑最好的那个报告,这个成绩就偏乐观了——你已经让测试集参与了决策。第 5 讲会看到,LLM 时代这个问题以”基准泄漏、反复刷榜”的形式变得更加严重。

这一切的前提是:训练、验证、测试数据来自同一个分布(独立同分布,i.i.d.)。一旦测试数据的分布和训练不同(换了领域、换了时间),泛化就没有保证。


2. 最大似然与交叉熵

2.1 最大似然:选让数据最可能出现的参数

抛一枚硬币 nn 次,看到 kk 次正面。正面概率 θ\theta 是多少?最大似然估计(MLE)的回答是:选让”看到这些数据”的概率最大的 θ\theta。

L(θ)=θk(1−θ)n−k,log⁡L(θ)=klog⁡θ+(n−k)log⁡(1−θ)L(\theta) = \theta^k (1 - \theta)^{n-k}, \qquad \log L(\theta) = k \log\theta + (n-k)\log(1-\theta)

求导令其为 0,得 θ^=k/n\hat\theta = k/n——就是正面的比例。推广到 VV 个类别(例如”下一个词是哪个”),MLE 就是每个类别出现的频率。这正是第 1 讲 n-gram”数数”的理论依据。

代码 01 §1(真实 θ=0.3\theta = 0.3):

nn正面数MLE k/nk/n加一平滑 (k+1)/(n+2)(k+1)/(n+2)
320.6670.600
1020.2000.250
100250.2500.255
1000030080.3010.301

数据越多,MLE 越准。数据少时,它可能离谱,而且会过度自信:如果前 3 次都是反面,MLE 断言 θ=0\theta = 0,“永远不会出现正面”;下一次真的出现正面时,对数损失是 −log⁡0=∞-\log 0 = \infty。

加一平滑(拉普拉斯平滑)相当于”先假装看到过 1 次正面、1 次反面”,三次反面时估计 θ=1/5\theta = 1/5。从贝叶斯的角度看,这是给 θ\theta 加了一个先验;数据越多,先验的影响越小(n=10000n = 10000 时两者相同)。第 1 讲的加 k 平滑就是它的多类别版本。

2.2 交叉熵、熵与 KL 散度

设真实分布是 pp,模型是 qq。

  • 熵 H(p)=−∑ipilog⁡piH(p) = -\sum_i p_i \log p_i:真实分布本身的不确定性;
  • 交叉熵 H(p,q)=−∑ipilog⁡qiH(p, q) = -\sum_i p_i \log q_i:用模型 qq 去”编码 / 预测”来自 pp 的数据,平均每个样本付出的代价;
  • KL 散度 KL(p∥q)=∑ipilog⁡piqi\mathrm{KL}(p \| q) = \sum_i p_i \log \frac{p_i}{q_i}。

它们满足 H(p,q)=H(p)+KL(p∥q)H(p, q) = H(p) + \mathrm{KL}(p \| q),而 KL≥0\mathrm{KL} \ge 0,等号当且仅当 q=pq = p。代码 01 §2(p=[0.5,0.3,0.2]p = [0.5, 0.3, 0.2],H(p)=1.0297H(p) = 1.0297):

模型 qq交叉熵KL
[0.5, 0.3, 0.2]1.02970.0000
[0.4, 0.4, 0.2]1.05490.0253
[1/3, 1/3, 1/3]1.09860.0690
[0.7, 0.2, 0.1]1.12170.0920
[0.98, 0.01, 0.01]2.31271.2830

把 pp 换成”训练数据的经验分布”,交叉熵就是训练集上的平均负对数似然。所以:最小化平均负对数似然 = 最大似然 = 最小化交叉熵 = 让模型在 KL 意义下逼近数据分布。这四句话说的是同一件事,也是所有 LLM 预训练的目标。第 1 讲的困惑度就是交叉熵的指数。

2.3 为什么用对数损失,而不是准确率

一枚正面概率 0.7 的硬币,模型预测”正面的概率是 rr“。代码 01 §3 按两种标准算期望得分:

rr期望对数损失期望准确率
0.50.69310.50
0.60.63250.70
0.70.61090.70
0.80.63900.70
0.90.76450.70
0.991.38860.70

对数损失在 r=0.7r = 0.7(如实报告)时最小——它是一个恰当的评分规则(proper scoring rule):只有说真话才能得到最好的分。准确率对 r=0.6r = 0.6 和 r=0.99r = 0.99 一视同仁,完全不在乎概率准不准;而且它是阶梯函数,梯度处处为 0,没法用梯度下降优化。

语言模型输出的是概率分布,后面要拿它来采样(第 1 讲 §7)、给句子打分、做校准的判断,所以必须训练”概率准确”——用对数损失。


3. 梯度下降

3.1 没有闭式解时怎么办

抛硬币的 MLE 可以直接写出来。但大多数模型没有闭式解,例如逻辑回归:

p(y=1∣x)=σ(w⋅x+b),σ(z)=11+e−zp(y = 1 \mid x) = \sigma(w \cdot x + b), \qquad \sigma(z) = \frac{1}{1 + e^{-z}}

平均对数损失对参数的梯度是

∇wL=1n∑i(σ(w⋅xi+b)−yi) xi\nabla_w \mathcal{L} = \frac{1}{n}\sum_i \big(\sigma(w \cdot x_i + b) - y_i\big)\, x_i

又是”预测减真实”(和第 1 讲 softmax 的 p−yp - y 是同一个形式,逻辑回归就是两个类别的 softmax 回归)。没有闭式解,就沿着负梯度方向一步一步走:θ←θ−η∇θL\theta \leftarrow \theta - \eta \nabla_\theta \mathcal{L}。

代码 01 §4 用 2000 个样本(真实参数 w=(2,−1)w = (2, -1),b=0.5b = 0.5),梯度下降 500 步得到 (1.923,−0.921,0.444)(1.923, -0.921, 0.444),接近真实值(有限样本的 MLE 本身就有误差)。

3.2 学习率

学习率第 1 步第 10 步第 100 步
0.010.69310.68440.6159
0.10.69310.62080.4636
10.69310.46400.4414
100.69310.44140.4414
600.69311.17800.9415

学习率太小,100 步几乎没动;合适的学习率很快收敛;太大(60)则每一步都跳过了最优点,100 步后的损失比起点还高。直觉:梯度只告诉你”局部往哪边是下坡”,步子迈得太大,就会越过谷底爬到对面的坡上。前置讲 P1 §5.3 在神经网络版的 bigram 上看到了同样的现象,还看到了”不同参数需要不同步长”——那是 Adam 的用武之地。

3.3 小批量随机梯度下降

全批量梯度要扫过全部数据才走一步。随机梯度下降(SGD)每步只用一小批样本估计梯度:估计有噪声,但便宜得多,同样的计算量能走更多步。代码 01 §5 让每种设置都只”看”2 万个样本(10 遍数据):

批大小更新步数最终训练损失最后 50 步损失的标准差
1200000.43070.00194
1612500.42960.00114
1281560.42780.00031
2000(全批量)100.4487—(仍在下降)

(最优点的损失是 0.4272。)批越小,步数越多,但损失在最优点附近抖得越厉害;全批量每步最准,但同样的数据量只够走 10 步。实践中取中间值。LLM 训练的批大小通常是几百万个 token:GPU 并行算一大批几乎和算一个样本一样快(P3),而大批量的梯度噪声小,允许用更大的学习率。

逻辑回归的损失是凸的(只有一个谷底),梯度下降只要学习率合适就一定能找到最优。神经网络的损失不是凸的,但实践表明梯度下降(配合合适的初始化、学习率和优化器)在大模型上同样有效——这在理论上仍是一个活跃的研究问题。


4. 文本分类:生成式 vs 判别式

4.1 花园日记:一个知道标准答案的分类世界

text_data.ReviewWorld 生成”花园日记”:每篇日记有一个标签(1 = 开心,0 = 沮丧),由 5~15 个”单元”组成。每个单元以 75% 的概率是中性词(the、garden、rain……),以 25% 的概率是情感表达:开心的日记里 80% 的情感表达是正面的(good、lovely……),20% 是负面的(bad、rotten……);沮丧的日记反过来。

[开心] morning rain fresh the the i good the the the
[沮丧] wilted were is sick garden

因为生成过程已知,我们能精确算出每篇日记在两个类别下的似然,得到贝叶斯最优分类器——理论上最好的分类器。它的准确率是 0.825(代码 03 §1),不是 100%:短日记里可能一个情感词都没有,情感词的极性也可能和标签相反。

还可以打开”否定”开关(q=0.4q = 0.4):40% 的情感表达写成 “not + 相反极性的词”,“not bad” 表示正面。

[开心] not bad the garden with garden it not great the garden the lovely

否定世界的贝叶斯最优准确率同样是 0.825:信息没有减少,只是换了一种写法。

4.2 词袋与朴素贝叶斯

最简单的文本表示是词袋(bag of words):只记录每个词出现了几次,丢掉顺序。

朴素贝叶斯是一个生成式模型:它假设”给定标签后,每个词独立地从一个类别专属的分布里抽出来”,

p(日记∣y)=∏词 wp(w∣y)p(\text{日记} \mid y) = \prod_{\text{词 } w} p(w \mid y)

每个类别的 p(w∣y)p(w \mid y) 用最大似然估计——就是数数(加上平滑)。分类时用贝叶斯公式比较 p(y=1∣日记)p(y = 1 \mid \text{日记}) 和 p(y=0∣日记)p(y = 0 \mid \text{日记})。取对数后,打分是

∑wcount(w)⋅log⁡p(w∣y=1)p(w∣y=0)⏟每个词的权重+log⁡p(y=1)p(y=0)\sum_w \text{count}(w) \cdot \underbrace{\log\frac{p(w \mid y=1)}{p(w \mid y=0)}}_{\text{每个词的权重}} + \log\frac{p(y=1)}{p(y=0)}

——一个线性模型,权重是数出来的。在普通世界里,真实的权重是正面词 log⁡(0.8/0.2)=+1.386\log(0.8/0.2) = +1.386、负面词 −1.386-1.386、中性词 0。代码 03 §2 从 4000 篇日记数出来的权重:good +1.375、lovely +1.429、bad −1.471、rotten −1.470、garden −0.023、the +0.007。数数确实恢复了真相。

4.3 逻辑回归:直接优化分类

逻辑回归是判别式模型:同样是”计数 × 权重”的线性打分,但权重不是数出来的,而是直接用梯度下降最小化分类的对数损失。它不对”日记是怎样生成的”做任何假设,只关心 p(y∣日记)p(y \mid \text{日记})。

两者的经典对比(Ng & Jordan, 2002):生成式模型收敛得快(需要的数据少),判别式模型的渐近误差不比它高(数据足够多时更好,特别是生成式模型的假设不成立时)。代码 03 §3 在普通世界里(3 个随机训练集取平均):

训练日记数朴素贝叶斯准确率逻辑回归准确率NB 对数损失LR 对数损失
200.6880.6520.7320.976
500.7720.7180.4930.756
2000.8150.7900.4060.470
10000.8240.8220.3670.379
40000.8220.8200.3640.371

(贝叶斯最优 0.825。)普通世界恰好满足朴素贝叶斯的独立性假设,所以它在数据少时明显领先,数据多时两者都到达天花板。

4.4 假设被打破时:否定世界

在否定世界里,“bad” 不再只代表负面——它常常出现在 “not bad” 里。代码 03 §4:

只用单个词作特征时,两者都只比瞎猜好一点(4000 篇时约 0.56):词袋丢掉了顺序,“not bad” 和 “bad … not” 无法区分。

加上相邻词对(bigram,“not_bad” 作为一个特征)之后:

训练日记数朴素贝叶斯准确率逻辑回归准确率NB 对数损失LR 对数损失
200.5500.5421.5511.204
500.5910.5951.0450.881
2000.6760.7110.7850.598
10000.7380.7860.5670.444
40000.7860.8180.4570.404

逻辑回归在 4000 篇时接近贝叶斯最优(0.818 vs 0.825),朴素贝叶斯停在 0.786,并且对数损失明显更差。原因是朴素贝叶斯把 “not”、“bad”、“not_bad” 当作三条独立的证据,而它们其实来自同一个表达——重复计算证据让它过度自信。逻辑回归联合地优化权重,学到了 not_bad +2.08(正面)、not_sweet −1.95(负面),并把单独的 “bad” 调到 −1.02、“not” 调到几乎为 0;朴素贝叶斯则给 “bad” −0.30、“not_bad” +1.33,两者叠加的结果不对。

带走的结论:模型的假设和数据的真实结构是否吻合,决定了它的天花板;数据量决定了你能多快接近这个天花板。LLM 走的是”假设尽量少(判别式、灵活的神经网络)+ 数据尽量多”这条路。


5. 过拟合与正则化

5.1 参数比数据多时会发生什么

否定世界里只有 100 篇训练日记,却有 516 个特征(单个词加 bigram)。代码 03 §5 训练不同 L2 正则化强度的逻辑回归(损失 + λ∥w∥2\lambda \|w\|^2):

L2 强度 λ\lambda训练准确率验证准确率测试准确率训练损失测试损失∥w∥\|w\|
01.0000.5780.5790.0001.67430.16
1e-41.0000.6330.6100.0071.24712.64
1e-31.0000.6350.6120.0420.9097.69
1e-21.0000.6230.6080.1940.7053.45
3e-20.9900.5970.5940.3300.6781.95
1e-10.9500.5650.5500.4840.6810.89

不加正则时,训练准确率 100%、训练损失 0——模型把 100 篇日记”背”了下来,包括只出现过一次的偶然词对;测试准确率只有 0.579,测试损失 1.674(比瞎猜的 0.693 还差:它对错误的答案非常自信)。这就是过拟合:在训练集上表现极好,在新数据上表现差。

L2 正则化(权重衰减)在损失里加上 λ∥w∥2\lambda\|w\|^2,惩罚大权重,迫使模型只在证据足够强时才给某个特征大权重。λ\lambda 适中时测试准确率和损失都变好;太大则”欠拟合”,连训练集都拟合不好。

用验证集选 λ\lambda:按验证集损失选出 λ=0.03\lambda = 0.03(测试准确率 0.594),按验证集准确率选出 λ=0.001\lambda = 0.001(测试准确率 0.612)。用哪个指标来选,结论可能不同——这也是一个需要提前想清楚的决定。

5.2 提前停止

另一种防止过拟合的方法:训练过程中盯着验证集,在验证损失最低时停下。代码 03 §6(不加正则,学习率 0.005):

步数01020305080120200400600
训练损失0.6930.5200.3980.3130.2070.1290.0810.0430.0160.009
验证损失0.6930.6840.6730.6720.6820.7100.7510.8280.9721.074

训练损失一路下降,验证损失先降后升,第 30 步最低。之后模型学到的”规律”只对训练集成立。P2 §8 训练 RNN 时也用了提前停止:按验证集挑出最好的一遍。

5.3 LLM 里的过拟合

大语言模型的预训练通常只把数据过一遍(或几遍),数据量远大于”背下来”的能力,所以经典意义上的过拟合不太常见;但微调时数据少、参数多,过拟合非常常见(第 6 讲)。权重衰减(AdamW)、dropout、提前停止在 LLM 训练中都还在用。另一种”过拟合”更隐蔽:在基准测试上反复调参、或者测试数据混进了训练数据,分数虚高(第 5、16 讲)。


6. 文本的统计规律

前面的分类实验用的是人造数据。真实的文字长什么样?用 Python 标准库的文档字符串作为一份真实英文语料(6568 段、221,796 个词、8,122 个不同的词,代码 02)。

几个术语:token 是文本中词的每一次出现,type 是不同的词。“the cat saw the dog” 有 5 个 token、4 个 type。

6.1 Zipf 定律

把词按出现次数从高到低排序,第 rr 名的频率 ff 大约正比于 1/r1/r:

排名 rr频率 ffr⋅f/Nr \cdot f / N
1(the)141360.064
2(a)73820.067
1024170.109
1003200.144
1000290.131
500020.045

在排名 10~1000 之间拟合 log⁡f=a+slog⁡r\log f = a + s \log r,斜率 s=−0.982s = -0.982,几乎正好是 Zipf 定律预言的 −1-1。这意味着:

  • 头部极重:最常见的 10 个词覆盖了 22.8% 的文本,100 个词覆盖 50.7%,1000 个词覆盖 84.4%;
  • 尾巴极长:只出现一次的词(hapax legomena)有 2616 个,占不同词数的 32.2%,却只占文本的 1.18%。

Zipf 定律在几乎所有语言、几乎所有规模的语料上都成立,是 NLP 里最可靠的经验规律之一。

6.2 Heaps 定律

读的文字越多,见过的不同词就越多,但增长越来越慢:

读过的词数 nn1,00010,000100,000221,796
不同词数 V(n)V(n)3641,2675,2808,122

拟合 V(n)=KnβV(n) = K n^\beta,β=0.567\beta = 0.567。β<1\beta < 1:越往后新词越少,但永远有新词。对照第 1 讲的花园世界:读到 1000 个词时已经见过 81 个不同的词,10000 个词时见过全部 87 个,之后不再增长。人造语言是封闭的,真实语言是开放的。

6.3 后果:没见过的词永远存在

把文档字符串交替划分成训练和测试,用”训练集里最常见的 KK 个词”作为词表,测试集里有多少 token 不在词表里(OOV):

词表测试集 OOV 比例
最常见的 100 个词48.77%
最常见的 1,000 个词15.57%
最常见的 3,000 个词5.52%
训练集全部 7,518 个词1.84%

即使收录训练集里的每一个词,仍有 1.84% 的 OOV(训练集减半时是 3.23%)。而且 OOV 往往是信息量最大的词:专有名词、新术语、拼写变体。这就是 LLM 用子词分词(第 1 讲 §5.1)的根本原因:BPE 能把任何新词拆成见过的片段,OOV 从此不存在。

Zipf 的长尾还是 n-gram 稀疏性(第 1 讲 §3.2)、解码”长尾问题”(第 1 讲 §7.4)、预训练数据去重与配比(第 3 讲)背后的共同原因。


7. 词向量:分布假设

7.1 one-hot 的问题

最朴素的词表示是 one-hot 向量:长度为词表大小,只有自己的位置是 1。它的问题是所有词两两正交:cos⁡(apple,pear)=cos⁡(apple,cat)=0\cos(\text{apple}, \text{pear}) = \cos(\text{apple}, \text{cat}) = 0。模型无法把关于 pear 的经验借给 apple——这正是 n-gram 不能泛化的原因(第 1 讲 §3.4)。

我们想要的是”意思相近的词,向量也相近”。意思从哪里来?

7.2 分布假设

语言学家 Firth(1957)有一句名言:“You shall know a word by the company it keeps”——看一个词和哪些词一起出现,就知道它的意思。Harris(1954)更早提出了同样的分布假设:出现在相似上下文中的词,意思相似。

最直接的实现:统计共现矩阵 C[i,j]C[i, j] = 词 jj 出现在词 ii 前后若干个位置以内的次数,把第 ii 行当作词 ii 的向量。

原始计数有一个问题:被高频词主导。代码 04 §2:花园世界里 apple 前后两个位置内最常出现的是 the(2271 次)、.(1348)、from(822)……这些词和谁都经常一起出现。解决办法是点互信息(PMI):

PMI(i,j)=log⁡P(i,j)P(i) P(j)\text{PMI}(i, j) = \log \frac{P(i, j)}{P(i)\, P(j)}

它衡量”ii 和 jj 一起出现的次数,比它们各自独立随机出现时的预期多多少倍”,把”本来就常见”的部分除掉了。负的 PMI 估计得很不可靠,通常截断成 0,叫 PPMI。按 PPMI 排序,apple 的上下文变成了 sweet、red、ripe(只修饰水果的形容词)、eat、pick……

PPMI 的行向量维度等于词表大小,而且大部分是 0。用 SVD(奇异值分解)把它压缩成几十到几百维的稠密向量:SVD 找到最能解释矩阵结构的少数几个方向,“在相似上下文中出现”的词被压到相近的位置。

7.3 word2vec:用预测代替计数

Mikolov 等人(2013)的 word2vec 换了一个角度:不数数,而是训练一个模型去预测上下文。最常用的变体是 skip-gram + 负采样(SGNS):对每一对真实出现在一起的(中心词 cc,上下文词 oo),以及 kk 个随机抽出的”负样本”词 n1,…,nkn_1, \dots, n_k,最大化

log⁡σ(uc⋅vo)+∑j=1klog⁡σ(−uc⋅vnj)\log\sigma(u_c \cdot v_o) + \sum_{j=1}^{k} \log\sigma(-u_c \cdot v_{n_j})

即:让真实词对的点积大,随机词对的点积小。负样本按”频率的 0.75 次方”抽取(比按频率抽更照顾罕见词)。Levy & Goldberg(2014)证明,SGNS 其实在隐式地分解一个”移位的 PMI 矩阵”——计数法和预测法殊途同归。

wordvec.py 用纯 NumPy 从零实现了 SGNS。一个实现细节:原版 word2vec 每次只更新一对词;我们用小批量,而高频词(比如句号)在一批里会出现几百次,把它们的梯度直接加起来,步子就会大几百倍而发散(第一版代码真的得到了 nan)。解决办法是对每个词的梯度取批内平均。

7.4 结果:花园世界

“最近邻是否同类”——对每个词,在所有参与比较的词中找最近的一个,看是否同类(代码 04 §3):

表示4 类名词8 类词(加上形容词、动词、物主代词)
原始共现计数行(87 维)100%100%
PPMI 行(87 维)100%93%
PPMI + SVD(16 维)100%97%
word2vec SGNS(16 维)100%97%
第 1 讲语言模型的嵌入(32 维)69%55%
随机猜的期望26%13%

花园世界太干净了:每个类别的词出现在几乎完全相同的上下文里,连原始计数都能完美分开。第 1 讲语言模型顺带学到的嵌入差得多:它只有 32 维,输入和输出共用一套嵌入,而且目标是”预测下一个词”而不是”刻画上下文相似性”。专门的目标得到专门的结构。

类比:用向量偏移 vb−va+vcv_b - v_a + v_c 回答 "a:b=c:?a : b = c : ?"(代码 04 §4)。在花园世界里,SGNS 答对 5/5(she : her = he : his,I : my = we : our,eat : eats = pick : picks……),PPMI+SVD 答对 4/5(I : my = we : ? 给出了 his)。这门小语言的语法关系由非常固定的上下文决定,所以偏移很好用。在真实语言里,类比远没有这么可靠——它曾被当作词向量的招牌演示,但后来的研究表明结果对评测方式很敏感,不要把它当成词向量的”标准能力”。

7.5 结果:真实英文

在标准库文档字符串上(保留出现 ≥ 10 次的 2103 个词,窗口 ±2,SVD 64 维),同一个词用三种表示找 5 个最近邻(代码 04 §5):

词原始共现计数PPMIPPMI + SVD
filegiven, path, message, object, valuefiles, open, a, object, streamopen, opened, files, archive, zip
errorexception, integer, element, archive, applicationerrors, exception, unexpected, message, failureunexpected, failure, exception, errors, raised
truesymbol, none, otherwise, needed, falsefalse, if, is, none, defaultfalse, none, bool, if, default
threadmodule, given, directory, process, fileprocess, waiting, lock, threads, blockedprocess, worker, threading, acquire, blocked
socketfile, stream, message, connection, givenaf, sock, family, connection, socketssock, af, reuse, datagram, connection

在真实语言里,差别一目了然:原始计数的近邻是 given、object、value 这类”到处都有”的词;PPMI 和 SVD 之后才出现有意义的关系。只用几行代码、二十多万个词、不需要任何标注,就学到了 thread ≈ process / lock / acquire 这样的知识。

两个要注意的地方:

  • true 的近邻是 false:反义词出现在几乎相同的上下文里(“returns true if…”、“returns false if…”)。分布相似 ≠ 意思相同。
  • 一个词只有一个向量:bank(银行 / 河岸)只能有一个表示,两种意思被混在一起。LLM 的表示是上下文相关的:同一个词在不同句子里得到不同的向量(第 2 讲)。

word2vec 是”预训练表示”思想的起点:先在大量无标注文本上学表示,再拿去做别的任务。LLM 把这个思路推到了极致——第 1 讲的嵌入矩阵、以及 Transformer 每一层的隐藏状态,都是这样学出来的。


8. 从固定窗口到循环神经网络

8.1 固定窗口的极限

第 1 讲的固定窗口神经语言模型只看最近 5 个词。花园世界里,“from ___ own garden” 中的物主代词要和句首主语一致;主语滑出窗口后,模型只能猜(the children grow the fresh pumpkin from ___,它给 our 0.477,正确答案 their 只有 0.104)。加大窗口?参数随窗口线性增长,每个位置的权重都要单独学,第 1 讲练习 7 显示窗口加到 8 反而过拟合。

8.2 循环神经网络

循环神经网络(RNN)换了一种思路:维护一个”记忆”向量 hth_t(隐藏状态),每读一个词就更新一次:

ht=tanh⁡(Wht−1+Ue(xt)+b),p(xt+1∣x≤t)=softmax(Vht)h_t = \tanh(W h_{t-1} + U e(x_t) + b), \qquad p(x_{t+1} \mid x_{\le t}) = \text{softmax}(V h_t)
  • hth_t 理论上总结了从开头到第 tt 个词的全部历史;
  • WW、UU、VV 在每个位置共享:不管句子多长,参数量都一样;
  • 训练时把计算图沿时间展开,用反向传播计算梯度(“通过时间的反向传播”,BPTT;原理就是前置讲 P1 §4 的自动微分)。

GRU(门控循环单元)和 LSTM(长短期记忆网络)是改进版:它们用”门”(取值在 0~1 之间的向量)控制保留多少旧记忆、写入多少新信息。

8.3 结果

代码 05 训练普通 RNN 和 GRU(嵌入 32 维、隐藏状态 64 维,各 10 遍数据,按验证集挑最好的一遍——两者都是第 5 遍,之后验证集困惑度回升,开始过拟合):

模型参数量测试困惑度物主代词(主语在 5 词内)物主代词(主语在 5 词外)
第 1 讲固定窗口(看 5 个词)27,7063.8460.9880.700
普通 RNN15,0023.8070.9630.939
GRU27,5463.8240.9880.988

(表中是模型给正确物主代词的平均概率,真实分布永远是 1。)RNN 看得到整个历史,窗口外的物主代词从 0.700 提高到 0.939;GRU 窗口内外一样好(0.988)。前面那个例子,RNN 给 their 0.985,GRU 给 0.994。普通 RNN 参数最少,困惑度却最低——它不需要为每个窗口位置单独准备参数。

8.4 为什么普通 RNN 难学长距离依赖

反向传播时,损失对 kk 步之前的隐藏状态的梯度,要乘上 kk 个雅可比矩阵:

∂hT∂hT−k=∏t=T−k+1Tdiag(tanh⁡′(⋅)) W\frac{\partial h_T}{\partial h_{T-k}} = \prod_{t=T-k+1}^{T} \mathrm{diag}\big(\tanh'(\cdot)\big)\, W

代码 05 §3 在随机初始化下计算它的大小(WW = 尺度 ρ\rho × 一个随机正交矩阵):

激活ρ\rhok=1k = 1k=10k = 10k=20k = 20k=40k = 40
无0.50.59.8e-049.5e-079.1e-13
无1.01.01.01.01.0
无1.51.557.73.3e+031.1e+07
tanh0.90.97.4e-022.8e-034.2e-06
tanh1.01.00.161.3e-021.3e-04
tanh1.51.52.562.172.39
tanh3.02.951173.6e+036.6e+05

没有激活函数时,范数恰好是 ρk\rho^k:ρ<1\rho < 1 指数消失,ρ>1\rho > 1 指数爆炸。tanh 的导数不超过 1,会让梯度再缩小一些;ρ=1.5\rho = 1.5 时放大和压缩大致抵消,范数停在 2 左右;ρ=3\rho = 3 又会爆炸。训练过程中 WW 不断变化,很难一直停在”不消失也不爆炸”的窄缝里。

  • 梯度消失:远处的信息对当前损失几乎没有梯度,模型学不会长距离依赖;
  • 梯度爆炸:一步更新就把参数推得很远,训练崩溃。常用补救是梯度裁剪(把梯度范数限制在某个上限,代码 05 用的是 1)。

LSTM / GRU 的门控给信息留了一条”直通”路径:当更新门接近”保留”时,ht≈ht−1h_t \approx h_{t-1},梯度也能几乎原样地往回传。这是它们能学到更长依赖的原因(本仓库 CMU10-423 第 2 讲 有专门的”遗忘”实验)。

我也试过直接测量训练好的模型里”预测对 k 步前输入的梯度”,结果 GRU 的梯度反而衰减得更快——因为训练好的 GRU 对答案非常有把握(概率 0.99),梯度 ∝ 1−p1 - p 本来就很小,而且它学会了忽略中间无关的词。这种测量混淆了太多因素,所以讲义改用上面这个”只看矩阵连乘”的干净实验。

8.5 RNN 的两个根本限制

  1. 瓶颈:全部历史必须挤进一个固定大小的向量。句子越长,越难保留所有需要的信息。
  2. 顺序计算:第 tt 步必须等第 t−1t-1 步算完,无法在长序列上并行。在 GPU 上,这意味着很难高效地训练海量数据。

第 2 讲的注意力机制同时解决了这两点:每个位置直接查看所有之前的位置(没有瓶颈,也没有长长的梯度链),并且所有位置可以并行计算。这就是 Transformer 取代 RNN、成为所有 LLM 基础的原因。


9. 小结、常见误区、练习

9.1 小结

  1. 监督学习 = 数据 + 模型 + 损失 + 优化;用训练集拟合、验证集选择、测试集只用一次。
  2. 最大似然 = 最小化交叉熵 = 最小化 KL;对类别分布就是”数数”。数据少时 MLE 过度自信,平滑即先验。对数损失是恰当的评分规则,语言模型用它训练。
  3. 梯度下降:学习率太小慢、太大跳过最优点;小批量 SGD 用噪声换步数。
  4. 生成式 vs 判别式:朴素贝叶斯(数数)数据少时更好,假设成立时也能到天花板;假设被打破时(否定、重叠特征),逻辑回归在数据多时明显更好,朴素贝叶斯还会过度自信。
  5. 过拟合:参数多、数据少时,训练集满分而测试集差。L2 正则和提前停止都能缓解,强度和停止点都用验证集选。
  6. 文本的统计规律:Zipf 定律(频率 ∝ 1/排名)、Heaps 定律(词表永不饱和)→ 长尾、OOV → 子词分词。
  7. 词向量:分布假设;PPMI 去掉高频词的影响,SVD 降维;word2vec 用预测达到同样的效果。分布相似 ≠ 意思相同,静态向量无法区分多义词。
  8. RNN:隐藏状态总结全部历史、参数共享;但梯度要穿过一长串雅可比矩阵,指数消失或爆炸;门控缓解,注意力取而代之。

9.2 常见误区

  • “准确率高的模型概率也准”:§4.4 的朴素贝叶斯准确率和逻辑回归相差不大时,对数损失可能差很多(过度自信)。
  • “训练损失越低越好”:§5 不加正则时训练损失为 0,测试损失 1.674,比瞎猜还差。
  • “在测试集上调参没关系,只要最后报告测试集成绩”:调参用测试集,测试集就不再是”没见过的数据”。
  • “生成式模型过时了”:数据少时它往往更好;而且语言模型本身就是生成式模型。
  • “词向量把同义词放在一起”:它把出现在相似上下文的词放在一起,反义词(true / false)也在其中。
  • “RNN 理论上能记住任意长的历史,所以实践中也能”:梯度消失 / 爆炸让长距离依赖很难学到。

9.3 练习

练习 1(MLE):一个 VV 类的类别分布,观察到第 ii 类 nin_i 次(∑ini=N\sum_i n_i = N)。证明最大似然估计是 p^i=ni/N\hat p_i = n_i / N。(提示:在约束 ∑ipi=1\sum_i p_i = 1 下最大化 ∑inilog⁡pi\sum_i n_i \log p_i,用拉格朗日乘子。)

参考答案

拉格朗日函数 L=∑inilog⁡pi−λ(∑ipi−1)\mathcal{L} = \sum_i n_i \log p_i - \lambda(\sum_i p_i - 1)。对 pip_i 求导:ni/pi−λ=0n_i / p_i - \lambda = 0,得 pi=ni/λp_i = n_i / \lambda。代入约束 ∑ini/λ=1\sum_i n_i / \lambda = 1 得 λ=N\lambda = N,所以 p^i=ni/N\hat p_i = n_i / N。这就是”数数”。ni=0n_i = 0 的类别得到概率 0——零概率问题的根源。

练习 2(交叉熵):p=[0.5,0.3,0.2]p = [0.5, 0.3, 0.2],q=[0.7,0.2,0.1]q = [0.7, 0.2, 0.1]。手算 H(p,q)H(p, q) 和 KL(p∥q)\mathrm{KL}(p \| q)。KL(q∥p)\mathrm{KL}(q \| p) 等于 KL(p∥q)\mathrm{KL}(p \| q) 吗?

参考答案

H(p,q)=−(0.5ln⁡0.7+0.3ln⁡0.2+0.2ln⁡0.1)=0.1783+0.4828+0.4605=1.1217H(p, q) = -(0.5 \ln 0.7 + 0.3 \ln 0.2 + 0.2 \ln 0.1) = 0.1783 + 0.4828 + 0.4605 = 1.1217;H(p)=1.0297H(p) = 1.0297;KL(p∥q)=0.0920\mathrm{KL}(p\|q) = 0.0920(与代码 01 §2 一致)。KL(q∥p)=0.7ln⁡(0.7/0.5)+0.2ln⁡(0.2/0.3)+0.1ln⁡(0.1/0.2)=0.2355−0.0811−0.0693=0.0851\mathrm{KL}(q\|p) = 0.7\ln(0.7/0.5) + 0.2\ln(0.2/0.3) + 0.1\ln(0.1/0.2) = 0.2355 - 0.0811 - 0.0693 = 0.0851,不相等。KL 散度不对称,所以它不是一个”距离”。

练习 3(Zipf 定律):假设一门语言恰好服从 f∝1/rf \propto 1/r,词表有 10,000 个词。最常见的 100 个词覆盖多少比例的文本?和 §6.1 的实测值比较。

参考答案

覆盖比例 =H100/H10000= H_{100} / H_{10000},其中 Hn=∑r=1n1/r≈ln⁡n+0.577H_n = \sum_{r=1}^{n} 1/r \approx \ln n + 0.577。H100=5.187H_{100} = 5.187,H10000=9.788H_{10000} = 9.788,比例 ≈0.530\approx 0.530。实测 50.7%(词表 8122),非常接近。Zipf 定律的一个惊人推论:词表再大,最常见的一百个词也总是覆盖大约一半的文本。

练习 4(朴素贝叶斯的过度自信):一篇日记里出现了 “not bad”。在含 bigram 特征的朴素贝叶斯里,这一个表达贡献了哪几条”证据”?为什么这会让它过度自信?

参考答案

三条:unigram “not”、unigram “bad”、bigram “not_bad”。朴素贝叶斯假设它们给定标签后相互独立,于是把三者的对数似然比直接相加。但它们其实是同一个表达的三个”影子”,信息高度重叠。重复计算让总的对数几率被放大,预测的概率被推向 0 或 1——判错时的对数损失就很大。逻辑回归联合拟合权重,会自动把重叠的部分”分摊”掉(代码里 “not” 的权重被压到几乎为 0)。

练习 5(动手:否定比例):把 ReviewWorld(q=0.4) 改成 q=0.2,重新跑 §4.4 的学习曲线(50、200、4000 篇)。预测:只用单个词时准确率会在 0.56 和 0.82 之间的什么位置?加 bigram 后两个模型的差距会变大还是变小?

参考答案(作者机器上的实际结果)

贝叶斯最优 0.828。只用单个词:4000 篇时朴素贝叶斯 0.700、逻辑回归 0.702,介于两个世界之间(否定越少,单个词越可靠)。加 bigram:4000 篇时朴素贝叶斯 0.779、逻辑回归 0.809,差距(0.030)比 q=0.4q = 0.4 时(0.032)略小;50 篇时两者是 0.634 和 0.622,朴素贝叶斯仍然领先。规律不变:数据少时生成式模型领先,假设被打破、数据又多时判别式模型领先。

练习 6(PPMI):为什么 PMI 要截断成非负(PPMI),而不保留负值?

参考答案

负的 PMI 表示”一起出现的次数比独立时的预期少”。对罕见词,预期的共现次数本来就小于 1,“一次都没一起出现”太正常了,无法区分”真的互相排斥”和”只是数据不够”——负值的估计极不可靠(而且从未共现时 PMI = −∞)。截断成 0 等于说”我们只相信正的关联”。经验上 PPMI 比原始 PMI 好得多。

练习 7(梯度消失):线性 RNN ht=Wht−1+Uxth_t = W h_{t-1} + U x_t,其中 W=ρQW = \rho Q,QQ 是正交矩阵。证明 ∥∂hT/∂hT−k∥2=ρk\|\partial h_T / \partial h_{T-k}\|_2 = \rho^k。这说明了为什么 §8.4 表格里 linear 那几行是精确的 ρk\rho^k。

参考答案

∂ht/∂ht−1=W\partial h_t / \partial h_{t-1} = W,所以 ∂hT/∂hT−k=Wk=ρkQk\partial h_T / \partial h_{T-k} = W^k = \rho^k Q^k。正交矩阵的乘积仍是正交矩阵,谱范数为 1,所以范数是 ρk\rho^k。一般的 WW 不是正交的,但大致规律相同:∥Wk∥\|W^k\| 由 WW 的最大特征值的模(谱半径)的 kk 次方主导。

练习 8(思考:为什么 RNN 的参数更少却更好):§8.3 里普通 RNN 只有 15,002 个参数,困惑度却比 27,706 个参数的窗口模型低。为什么?

参考答案

窗口模型给窗口里的每个位置单独准备一组权重(输入层是 5 × 32 = 160 维),“主语在第 1 个位置”和”主语在第 3 个位置”要分别学;而且窗口外的信息它根本看不到。RNN 在每个位置共享同一组参数,相当于把”读一个词、更新记忆”这件事学一次、用到所有位置,既省参数,又能利用任意远的上下文。参数共享是一种很强的归纳偏置:它假设”处理语言的规则不依赖于位置”。注意力机制也继承了这一点。

9.4 延伸阅读

  • Jurafsky & Martin, Speech and Language Processing(第 3 版草稿):朴素贝叶斯与情感分类、逻辑回归、向量语义与嵌入、RNN 与 LSTM 各章
  • Ng & Jordan (2002), On Discriminative vs. Generative Classifiers: A comparison of logistic regression and naive Bayes
  • Mikolov et al. (2013), Distributed Representations of Words and Phrases and their Compositionality(word2vec 与负采样)
  • Levy & Goldberg (2014), Neural Word Embedding as Implicit Matrix Factorization
  • Bengio, Simard & Frasconi (1994), Learning Long-Term Dependencies with Gradient Descent is Difficult;Pascanu, Mikolov & Bengio (2013), On the difficulty of training recurrent neural networks(梯度消失 / 爆炸与梯度裁剪)
  • Hochreiter & Schmidhuber (1997), Long Short-Term Memory;Cho et al. (2014)(GRU)
  • 本仓库 CS224N 对词向量和 RNN 讲得更细

9.5 接下来

  • 读 第 1 讲:语言模型的完整流水线,以及 n-gram、窗口神经网络和各种解码策略的实验。
  • 想在云上做实验、或者想知道”训练一个 7B 模型要多少显存”,读 前置讲 P3。
← 返回课程目录