定位:可选的前置讲。原课要求机器学习达到 CMU 10-301 / 10-601 的水平、NLP 达到 11-411 / 11-611 的水平。这一篇把后面反复用到的概念串讲一遍:最大似然与交叉熵、梯度下降、生成式与判别式分类、过拟合与正则化、文本的统计规律、词向量、循环神经网络。 建议阅读时间:第 1 讲之前(可选),最晚第 2 讲之前。第 1 讲已经细讲的 n-gram 和平滑,这里只一笔带过。 配套代码:code/prereq2/(2 个模块 + 5 个脚本,每个都在 15 秒内跑完) 数据:除了第 1 讲的”花园世界”,这一篇还用到两份不需要下载的数据:Python 标准库的文档字符串(约 22 万个真实英文单词,用来看真实语言的统计规律),以及一个生成过程已知的情感分类小世界”花园日记”(能算出理论上最好的分类器)。所有实验数字都来自配套代码的实际输出。
目录
- 0. 导读
- 1. 监督学习的框架
- 2. 最大似然与交叉熵
- 3. 梯度下降
- 4. 文本分类:生成式 vs 判别式
- 5. 过拟合与正则化
- 6. 文本的统计规律
- 7. 词向量:分布假设
- 8. 从固定窗口到循环神经网络
- 9. 小结、常见误区、练习
0. 导读
LLM 的论文里满是这些词:交叉熵损失、困惑度、过拟合、权重衰减、验证集、嵌入、梯度消失……它们都不是 LLM 发明的,而是机器学习和 NLP 几十年积累下来的基本概念。LLM 的训练,说穿了就是”在海量文本上,用梯度下降最小化下一个词的交叉熵,再用各种手段防止它学歪”。
这一篇按”一个语言模型从零到训练好”需要的顺序回顾这些概念。每个概念都配一个能在几秒内跑完的小实验,并且尽量用知道标准答案的数据,好让你看清楚”学到了多少""离理论最优还有多远”。
学完这一篇你应该能:
- 解释最大似然、交叉熵、KL 散度之间的关系,说明为什么语言模型用对数损失训练;
- 说清楚学习率、批大小对梯度下降的影响;
- 比较生成式(朴素贝叶斯)和判别式(逻辑回归)模型,知道各自适合什么情况;
- 识别过拟合,用验证集选择正则化强度、用提前停止;
- 描述自然语言的 Zipf 定律和 Heaps 定律,以及它们对 NLP 系统的影响;
- 解释词向量从哪里来(分布假设、PPMI + SVD、word2vec);
- 解释 RNN 怎样处理任意长的历史,以及为什么它难学长距离依赖。
1. 监督学习的框架
几乎所有机器学习问题都由四件事组成:
| 组成 | 含义 | 语言模型里是什么 |
|---|---|---|
| 数据 | 输入 和目标 的样本 | = 前面的文字, = 下一个 token |
| 模型 | 一个带参数 的函数族 | n-gram、窗口神经网络、RNN、Transformer |
| 损失 | 衡量预测和目标差多少 | 负对数似然(交叉熵) |
| 优化 | 找让损失最小的 | 数数(n-gram),梯度下降(神经网络) |
我们真正关心的不是模型在训练数据上的表现,而是在没见过的数据上的表现(泛化)。所以数据要分成三份:
- 训练集:用来拟合参数;
- 验证集(开发集):用来做选择——选模型、选超参数(学习率、正则化强度、训练多少步);
- 测试集:所有选择都做完之后,只用一次,报告最终成绩。
为什么要单独的验证集?因为”选择”本身也是一种拟合。如果你在测试集上试了 20 种学习率、挑最好的那个报告,这个成绩就偏乐观了——你已经让测试集参与了决策。第 5 讲会看到,LLM 时代这个问题以”基准泄漏、反复刷榜”的形式变得更加严重。
这一切的前提是:训练、验证、测试数据来自同一个分布(独立同分布,i.i.d.)。一旦测试数据的分布和训练不同(换了领域、换了时间),泛化就没有保证。
2. 最大似然与交叉熵
2.1 最大似然:选让数据最可能出现的参数
抛一枚硬币 次,看到 次正面。正面概率 是多少?最大似然估计(MLE)的回答是:选让”看到这些数据”的概率最大的 。
求导令其为 0,得 ——就是正面的比例。推广到 个类别(例如”下一个词是哪个”),MLE 就是每个类别出现的频率。这正是第 1 讲 n-gram”数数”的理论依据。
代码 01 §1(真实 ):
| 正面数 | MLE | 加一平滑 | |
|---|---|---|---|
| 3 | 2 | 0.667 | 0.600 |
| 10 | 2 | 0.200 | 0.250 |
| 100 | 25 | 0.250 | 0.255 |
| 10000 | 3008 | 0.301 | 0.301 |
数据越多,MLE 越准。数据少时,它可能离谱,而且会过度自信:如果前 3 次都是反面,MLE 断言 ,“永远不会出现正面”;下一次真的出现正面时,对数损失是 。
加一平滑(拉普拉斯平滑)相当于”先假装看到过 1 次正面、1 次反面”,三次反面时估计 。从贝叶斯的角度看,这是给 加了一个先验;数据越多,先验的影响越小( 时两者相同)。第 1 讲的加 k 平滑就是它的多类别版本。
2.2 交叉熵、熵与 KL 散度
设真实分布是 ,模型是 。
- 熵 :真实分布本身的不确定性;
- 交叉熵 :用模型 去”编码 / 预测”来自 的数据,平均每个样本付出的代价;
- KL 散度 。
它们满足 ,而 ,等号当且仅当 。代码 01 §2(,):
| 模型 | 交叉熵 | KL |
|---|---|---|
| [0.5, 0.3, 0.2] | 1.0297 | 0.0000 |
| [0.4, 0.4, 0.2] | 1.0549 | 0.0253 |
| [1/3, 1/3, 1/3] | 1.0986 | 0.0690 |
| [0.7, 0.2, 0.1] | 1.1217 | 0.0920 |
| [0.98, 0.01, 0.01] | 2.3127 | 1.2830 |
把 换成”训练数据的经验分布”,交叉熵就是训练集上的平均负对数似然。所以:最小化平均负对数似然 = 最大似然 = 最小化交叉熵 = 让模型在 KL 意义下逼近数据分布。这四句话说的是同一件事,也是所有 LLM 预训练的目标。第 1 讲的困惑度就是交叉熵的指数。
2.3 为什么用对数损失,而不是准确率
一枚正面概率 0.7 的硬币,模型预测”正面的概率是 “。代码 01 §3 按两种标准算期望得分:
| 期望对数损失 | 期望准确率 | |
|---|---|---|
| 0.5 | 0.6931 | 0.50 |
| 0.6 | 0.6325 | 0.70 |
| 0.7 | 0.6109 | 0.70 |
| 0.8 | 0.6390 | 0.70 |
| 0.9 | 0.7645 | 0.70 |
| 0.99 | 1.3886 | 0.70 |
对数损失在 (如实报告)时最小——它是一个恰当的评分规则(proper scoring rule):只有说真话才能得到最好的分。准确率对 和 一视同仁,完全不在乎概率准不准;而且它是阶梯函数,梯度处处为 0,没法用梯度下降优化。
语言模型输出的是概率分布,后面要拿它来采样(第 1 讲 §7)、给句子打分、做校准的判断,所以必须训练”概率准确”——用对数损失。
3. 梯度下降
3.1 没有闭式解时怎么办
抛硬币的 MLE 可以直接写出来。但大多数模型没有闭式解,例如逻辑回归:
平均对数损失对参数的梯度是
又是”预测减真实”(和第 1 讲 softmax 的 是同一个形式,逻辑回归就是两个类别的 softmax 回归)。没有闭式解,就沿着负梯度方向一步一步走:。
代码 01 §4 用 2000 个样本(真实参数 ,),梯度下降 500 步得到 ,接近真实值(有限样本的 MLE 本身就有误差)。
3.2 学习率
| 学习率 | 第 1 步 | 第 10 步 | 第 100 步 |
|---|---|---|---|
| 0.01 | 0.6931 | 0.6844 | 0.6159 |
| 0.1 | 0.6931 | 0.6208 | 0.4636 |
| 1 | 0.6931 | 0.4640 | 0.4414 |
| 10 | 0.6931 | 0.4414 | 0.4414 |
| 60 | 0.6931 | 1.1780 | 0.9415 |
学习率太小,100 步几乎没动;合适的学习率很快收敛;太大(60)则每一步都跳过了最优点,100 步后的损失比起点还高。直觉:梯度只告诉你”局部往哪边是下坡”,步子迈得太大,就会越过谷底爬到对面的坡上。前置讲 P1 §5.3 在神经网络版的 bigram 上看到了同样的现象,还看到了”不同参数需要不同步长”——那是 Adam 的用武之地。
3.3 小批量随机梯度下降
全批量梯度要扫过全部数据才走一步。随机梯度下降(SGD)每步只用一小批样本估计梯度:估计有噪声,但便宜得多,同样的计算量能走更多步。代码 01 §5 让每种设置都只”看”2 万个样本(10 遍数据):
| 批大小 | 更新步数 | 最终训练损失 | 最后 50 步损失的标准差 |
|---|---|---|---|
| 1 | 20000 | 0.4307 | 0.00194 |
| 16 | 1250 | 0.4296 | 0.00114 |
| 128 | 156 | 0.4278 | 0.00031 |
| 2000(全批量) | 10 | 0.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%:短日记里可能一个情感词都没有,情感词的极性也可能和标签相反。
还可以打开”否定”开关():40% 的情感表达写成 “not + 相反极性的词”,“not bad” 表示正面。
[开心] not bad the garden with garden it not great the garden the lovely
否定世界的贝叶斯最优准确率同样是 0.825:信息没有减少,只是换了一种写法。
4.2 词袋与朴素贝叶斯
最简单的文本表示是词袋(bag of words):只记录每个词出现了几次,丢掉顺序。
朴素贝叶斯是一个生成式模型:它假设”给定标签后,每个词独立地从一个类别专属的分布里抽出来”,
每个类别的 用最大似然估计——就是数数(加上平滑)。分类时用贝叶斯公式比较 和 。取对数后,打分是
——一个线性模型,权重是数出来的。在普通世界里,真实的权重是正面词 、负面词 、中性词 0。代码 03 §2 从 4000 篇日记数出来的权重:good +1.375、lovely +1.429、bad −1.471、rotten −1.470、garden −0.023、the +0.007。数数确实恢复了真相。
4.3 逻辑回归:直接优化分类
逻辑回归是判别式模型:同样是”计数 × 权重”的线性打分,但权重不是数出来的,而是直接用梯度下降最小化分类的对数损失。它不对”日记是怎样生成的”做任何假设,只关心 。
两者的经典对比(Ng & Jordan, 2002):生成式模型收敛得快(需要的数据少),判别式模型的渐近误差不比它高(数据足够多时更好,特别是生成式模型的假设不成立时)。代码 03 §3 在普通世界里(3 个随机训练集取平均):
| 训练日记数 | 朴素贝叶斯准确率 | 逻辑回归准确率 | NB 对数损失 | LR 对数损失 |
|---|---|---|---|---|
| 20 | 0.688 | 0.652 | 0.732 | 0.976 |
| 50 | 0.772 | 0.718 | 0.493 | 0.756 |
| 200 | 0.815 | 0.790 | 0.406 | 0.470 |
| 1000 | 0.824 | 0.822 | 0.367 | 0.379 |
| 4000 | 0.822 | 0.820 | 0.364 | 0.371 |
(贝叶斯最优 0.825。)普通世界恰好满足朴素贝叶斯的独立性假设,所以它在数据少时明显领先,数据多时两者都到达天花板。
4.4 假设被打破时:否定世界
在否定世界里,“bad” 不再只代表负面——它常常出现在 “not bad” 里。代码 03 §4:
只用单个词作特征时,两者都只比瞎猜好一点(4000 篇时约 0.56):词袋丢掉了顺序,“not bad” 和 “bad … not” 无法区分。
加上相邻词对(bigram,“not_bad” 作为一个特征)之后:
| 训练日记数 | 朴素贝叶斯准确率 | 逻辑回归准确率 | NB 对数损失 | LR 对数损失 |
|---|---|---|---|---|
| 20 | 0.550 | 0.542 | 1.551 | 1.204 |
| 50 | 0.591 | 0.595 | 1.045 | 0.881 |
| 200 | 0.676 | 0.711 | 0.785 | 0.598 |
| 1000 | 0.738 | 0.786 | 0.567 | 0.444 |
| 4000 | 0.786 | 0.818 | 0.457 | 0.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 正则化强度的逻辑回归(损失 + ):
| L2 强度 | 训练准确率 | 验证准确率 | 测试准确率 | 训练损失 | 测试损失 | |
|---|---|---|---|---|---|---|
| 0 | 1.000 | 0.578 | 0.579 | 0.000 | 1.674 | 30.16 |
| 1e-4 | 1.000 | 0.633 | 0.610 | 0.007 | 1.247 | 12.64 |
| 1e-3 | 1.000 | 0.635 | 0.612 | 0.042 | 0.909 | 7.69 |
| 1e-2 | 1.000 | 0.623 | 0.608 | 0.194 | 0.705 | 3.45 |
| 3e-2 | 0.990 | 0.597 | 0.594 | 0.330 | 0.678 | 1.95 |
| 1e-1 | 0.950 | 0.565 | 0.550 | 0.484 | 0.681 | 0.89 |
不加正则时,训练准确率 100%、训练损失 0——模型把 100 篇日记”背”了下来,包括只出现过一次的偶然词对;测试准确率只有 0.579,测试损失 1.674(比瞎猜的 0.693 还差:它对错误的答案非常自信)。这就是过拟合:在训练集上表现极好,在新数据上表现差。
L2 正则化(权重衰减)在损失里加上 ,惩罚大权重,迫使模型只在证据足够强时才给某个特征大权重。 适中时测试准确率和损失都变好;太大则”欠拟合”,连训练集都拟合不好。
用验证集选 :按验证集损失选出 (测试准确率 0.594),按验证集准确率选出 (测试准确率 0.612)。用哪个指标来选,结论可能不同——这也是一个需要提前想清楚的决定。
5.2 提前停止
另一种防止过拟合的方法:训练过程中盯着验证集,在验证损失最低时停下。代码 03 §6(不加正则,学习率 0.005):
| 步数 | 0 | 10 | 20 | 30 | 50 | 80 | 120 | 200 | 400 | 600 |
|---|---|---|---|---|---|---|---|---|---|---|
| 训练损失 | 0.693 | 0.520 | 0.398 | 0.313 | 0.207 | 0.129 | 0.081 | 0.043 | 0.016 | 0.009 |
| 验证损失 | 0.693 | 0.684 | 0.673 | 0.672 | 0.682 | 0.710 | 0.751 | 0.828 | 0.972 | 1.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 定律
把词按出现次数从高到低排序,第 名的频率 大约正比于 :
| 排名 | 频率 | |
|---|---|---|
| 1(the) | 14136 | 0.064 |
| 2(a) | 7382 | 0.067 |
| 10 | 2417 | 0.109 |
| 100 | 320 | 0.144 |
| 1000 | 29 | 0.131 |
| 5000 | 2 | 0.045 |
在排名 10~1000 之间拟合 ,斜率 ,几乎正好是 Zipf 定律预言的 。这意味着:
- 头部极重:最常见的 10 个词覆盖了 22.8% 的文本,100 个词覆盖 50.7%,1000 个词覆盖 84.4%;
- 尾巴极长:只出现一次的词(hapax legomena)有 2616 个,占不同词数的 32.2%,却只占文本的 1.18%。
Zipf 定律在几乎所有语言、几乎所有规模的语料上都成立,是 NLP 里最可靠的经验规律之一。
6.2 Heaps 定律
读的文字越多,见过的不同词就越多,但增长越来越慢:
| 读过的词数 | 1,000 | 10,000 | 100,000 | 221,796 |
|---|---|---|---|---|
| 不同词数 | 364 | 1,267 | 5,280 | 8,122 |
拟合 ,。:越往后新词越少,但永远有新词。对照第 1 讲的花园世界:读到 1000 个词时已经见过 81 个不同的词,10000 个词时见过全部 87 个,之后不再增长。人造语言是封闭的,真实语言是开放的。
6.3 后果:没见过的词永远存在
把文档字符串交替划分成训练和测试,用”训练集里最常见的 个词”作为词表,测试集里有多少 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。它的问题是所有词两两正交:。模型无法把关于 pear 的经验借给 apple——这正是 n-gram 不能泛化的原因(第 1 讲 §3.4)。
我们想要的是”意思相近的词,向量也相近”。意思从哪里来?
7.2 分布假设
语言学家 Firth(1957)有一句名言:“You shall know a word by the company it keeps”——看一个词和哪些词一起出现,就知道它的意思。Harris(1954)更早提出了同样的分布假设:出现在相似上下文中的词,意思相似。
最直接的实现:统计共现矩阵 = 词 出现在词 前后若干个位置以内的次数,把第 行当作词 的向量。
原始计数有一个问题:被高频词主导。代码 04 §2:花园世界里 apple 前后两个位置内最常出现的是 the(2271 次)、.(1348)、from(822)……这些词和谁都经常一起出现。解决办法是点互信息(PMI):
它衡量” 和 一起出现的次数,比它们各自独立随机出现时的预期多多少倍”,把”本来就常见”的部分除掉了。负的 PMI 估计得很不可靠,通常截断成 0,叫 PPMI。按 PPMI 排序,apple 的上下文变成了 sweet、red、ripe(只修饰水果的形容词)、eat、pick……
PPMI 的行向量维度等于词表大小,而且大部分是 0。用 SVD(奇异值分解)把它压缩成几十到几百维的稠密向量:SVD 找到最能解释矩阵结构的少数几个方向,“在相似上下文中出现”的词被压到相近的位置。
7.3 word2vec:用预测代替计数
Mikolov 等人(2013)的 word2vec 换了一个角度:不数数,而是训练一个模型去预测上下文。最常用的变体是 skip-gram + 负采样(SGNS):对每一对真实出现在一起的(中心词 ,上下文词 ),以及 个随机抽出的”负样本”词 ,最大化
即:让真实词对的点积大,随机词对的点积小。负样本按”频率的 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 维,输入和输出共用一套嵌入,而且目标是”预测下一个词”而不是”刻画上下文相似性”。专门的目标得到专门的结构。
类比:用向量偏移 回答 ""(代码 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):
| 词 | 原始共现计数 | PPMI | PPMI + SVD |
|---|---|---|---|
| file | given, path, message, object, value | files, open, a, object, stream | open, opened, files, archive, zip |
| error | exception, integer, element, archive, application | errors, exception, unexpected, message, failure | unexpected, failure, exception, errors, raised |
| true | symbol, none, otherwise, needed, false | false, if, is, none, default | false, none, bool, if, default |
| thread | module, given, directory, process, file | process, waiting, lock, threads, blocked | process, worker, threading, acquire, blocked |
| socket | file, stream, message, connection, given | af, sock, family, connection, sockets | sock, 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)换了一种思路:维护一个”记忆”向量 (隐藏状态),每读一个词就更新一次:
- 理论上总结了从开头到第 个词的全部历史;
- 、、 在每个位置共享:不管句子多长,参数量都一样;
- 训练时把计算图沿时间展开,用反向传播计算梯度(“通过时间的反向传播”,BPTT;原理就是前置讲 P1 §4 的自动微分)。
GRU(门控循环单元)和 LSTM(长短期记忆网络)是改进版:它们用”门”(取值在 0~1 之间的向量)控制保留多少旧记忆、写入多少新信息。
8.3 结果
代码 05 训练普通 RNN 和 GRU(嵌入 32 维、隐藏状态 64 维,各 10 遍数据,按验证集挑最好的一遍——两者都是第 5 遍,之后验证集困惑度回升,开始过拟合):
| 模型 | 参数量 | 测试困惑度 | 物主代词(主语在 5 词内) | 物主代词(主语在 5 词外) |
|---|---|---|---|---|
| 第 1 讲固定窗口(看 5 个词) | 27,706 | 3.846 | 0.988 | 0.700 |
| 普通 RNN | 15,002 | 3.807 | 0.963 | 0.939 |
| GRU | 27,546 | 3.824 | 0.988 | 0.988 |
(表中是模型给正确物主代词的平均概率,真实分布永远是 1。)RNN 看得到整个历史,窗口外的物主代词从 0.700 提高到 0.939;GRU 窗口内外一样好(0.988)。前面那个例子,RNN 给 their 0.985,GRU 给 0.994。普通 RNN 参数最少,困惑度却最低——它不需要为每个窗口位置单独准备参数。
8.4 为什么普通 RNN 难学长距离依赖
反向传播时,损失对 步之前的隐藏状态的梯度,要乘上 个雅可比矩阵:
代码 05 §3 在随机初始化下计算它的大小( = 尺度 × 一个随机正交矩阵):
| 激活 | |||||
|---|---|---|---|---|---|
| 无 | 0.5 | 0.5 | 9.8e-04 | 9.5e-07 | 9.1e-13 |
| 无 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 |
| 无 | 1.5 | 1.5 | 57.7 | 3.3e+03 | 1.1e+07 |
| tanh | 0.9 | 0.9 | 7.4e-02 | 2.8e-03 | 4.2e-06 |
| tanh | 1.0 | 1.0 | 0.16 | 1.3e-02 | 1.3e-04 |
| tanh | 1.5 | 1.5 | 2.56 | 2.17 | 2.39 |
| tanh | 3.0 | 2.95 | 117 | 3.6e+03 | 6.6e+05 |
没有激活函数时,范数恰好是 : 指数消失, 指数爆炸。tanh 的导数不超过 1,会让梯度再缩小一些; 时放大和压缩大致抵消,范数停在 2 左右; 又会爆炸。训练过程中 不断变化,很难一直停在”不消失也不爆炸”的窄缝里。
- 梯度消失:远处的信息对当前损失几乎没有梯度,模型学不会长距离依赖;
- 梯度爆炸:一步更新就把参数推得很远,训练崩溃。常用补救是梯度裁剪(把梯度范数限制在某个上限,代码 05 用的是 1)。
LSTM / GRU 的门控给信息留了一条”直通”路径:当更新门接近”保留”时,,梯度也能几乎原样地往回传。这是它们能学到更长依赖的原因(本仓库 CMU10-423 第 2 讲 有专门的”遗忘”实验)。
我也试过直接测量训练好的模型里”预测对 k 步前输入的梯度”,结果 GRU 的梯度反而衰减得更快——因为训练好的 GRU 对答案非常有把握(概率 0.99),梯度 ∝ 本来就很小,而且它学会了忽略中间无关的词。这种测量混淆了太多因素,所以讲义改用上面这个”只看矩阵连乘”的干净实验。
8.5 RNN 的两个根本限制
- 瓶颈:全部历史必须挤进一个固定大小的向量。句子越长,越难保留所有需要的信息。
- 顺序计算:第 步必须等第 步算完,无法在长序列上并行。在 GPU 上,这意味着很难高效地训练海量数据。
第 2 讲的注意力机制同时解决了这两点:每个位置直接查看所有之前的位置(没有瓶颈,也没有长长的梯度链),并且所有位置可以并行计算。这就是 Transformer 取代 RNN、成为所有 LLM 基础的原因。
9. 小结、常见误区、练习
9.1 小结
- 监督学习 = 数据 + 模型 + 损失 + 优化;用训练集拟合、验证集选择、测试集只用一次。
- 最大似然 = 最小化交叉熵 = 最小化 KL;对类别分布就是”数数”。数据少时 MLE 过度自信,平滑即先验。对数损失是恰当的评分规则,语言模型用它训练。
- 梯度下降:学习率太小慢、太大跳过最优点;小批量 SGD 用噪声换步数。
- 生成式 vs 判别式:朴素贝叶斯(数数)数据少时更好,假设成立时也能到天花板;假设被打破时(否定、重叠特征),逻辑回归在数据多时明显更好,朴素贝叶斯还会过度自信。
- 过拟合:参数多、数据少时,训练集满分而测试集差。L2 正则和提前停止都能缓解,强度和停止点都用验证集选。
- 文本的统计规律:Zipf 定律(频率 ∝ 1/排名)、Heaps 定律(词表永不饱和)→ 长尾、OOV → 子词分词。
- 词向量:分布假设;PPMI 去掉高频词的影响,SVD 降维;word2vec 用预测达到同样的效果。分布相似 ≠ 意思相同,静态向量无法区分多义词。
- RNN:隐藏状态总结全部历史、参数共享;但梯度要穿过一长串雅可比矩阵,指数消失或爆炸;门控缓解,注意力取而代之。
9.2 常见误区
- “准确率高的模型概率也准”:§4.4 的朴素贝叶斯准确率和逻辑回归相差不大时,对数损失可能差很多(过度自信)。
- “训练损失越低越好”:§5 不加正则时训练损失为 0,测试损失 1.674,比瞎猜还差。
- “在测试集上调参没关系,只要最后报告测试集成绩”:调参用测试集,测试集就不再是”没见过的数据”。
- “生成式模型过时了”:数据少时它往往更好;而且语言模型本身就是生成式模型。
- “词向量把同义词放在一起”:它把出现在相似上下文的词放在一起,反义词(true / false)也在其中。
- “RNN 理论上能记住任意长的历史,所以实践中也能”:梯度消失 / 爆炸让长距离依赖很难学到。
9.3 练习
练习 1(MLE):一个 类的类别分布,观察到第 类 次()。证明最大似然估计是 。(提示:在约束 下最大化 ,用拉格朗日乘子。)
参考答案
拉格朗日函数 。对 求导:,得 。代入约束 得 ,所以 。这就是”数数”。 的类别得到概率 0——零概率问题的根源。
练习 2(交叉熵):,。手算 和 。 等于 吗?
参考答案
;;(与代码 01 §2 一致)。,不相等。KL 散度不对称,所以它不是一个”距离”。
练习 3(Zipf 定律):假设一门语言恰好服从 ,词表有 10,000 个词。最常见的 100 个词覆盖多少比例的文本?和 §6.1 的实测值比较。
参考答案
覆盖比例 ,其中 。,,比例 。实测 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)比 时(0.032)略小;50 篇时两者是 0.634 和 0.622,朴素贝叶斯仍然领先。规律不变:数据少时生成式模型领先,假设被打破、数据又多时判别式模型领先。
练习 6(PPMI):为什么 PMI 要截断成非负(PPMI),而不保留负值?
参考答案
负的 PMI 表示”一起出现的次数比独立时的预期少”。对罕见词,预期的共现次数本来就小于 1,“一次都没一起出现”太正常了,无法区分”真的互相排斥”和”只是数据不够”——负值的估计极不可靠(而且从未共现时 PMI = −∞)。截断成 0 等于说”我们只相信正的关联”。经验上 PPMI 比原始 PMI 好得多。
练习 7(梯度消失):线性 RNN ,其中 , 是正交矩阵。证明 。这说明了为什么 §8.4 表格里 linear 那几行是精确的 。
参考答案
,所以 。正交矩阵的乘积仍是正交矩阵,谱范数为 1,所以范数是 。一般的 不是正交的,但大致规律相同: 由 的最大特征值的模(谱半径)的 次方主导。
练习 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 讲得更细