对应:Stanford CS224N Winter 2026 Lecture 4 后半;Winter 2025 Lecture 6(Advanced Variants of RNNs, Attention)与作业 3(神经机器翻译);Manning 主讲的 Winter 2021 / 2023 版第 6 讲(Simple and LSTM RNNs)和第 7 讲(Translation, Seq2Seq, Attention),录像在 YouTube 上。 配套代码:
code/lecture06/(03 号文件导入第 5 讲的代码) 预计阅读时间:5–6 小时(这一讲内容较多:第一遍通读 2.5 小时,第二遍对照代码 3 小时)
0. 本讲导航
0.1 学完本讲,你应该能够
- 从矩阵连乘的角度解释梯度消失和梯度爆炸,说明它们分别带来什么后果;会用梯度裁剪对付梯度爆炸。
- 写出 LSTM 的全部公式,解释遗忘门、输入门、输出门和细胞状态各自的作用,以及”加法更新”为什么能缓解梯度消失;知道 GRU 的结构。
- 知道梯度消失是所有深层网络的问题,以及残差连接等通用的解决思路;理解双向和多层 RNN。
- 说出机器翻译从规则到统计再到神经网络的发展,描述 seq2seq(编码器—解码器)模型及其训练方式。
- 比较贪心解码、穷举搜索、束搜索,解释束搜索的停止条件和长度归一化。
- 计算 BLEU:修正的 n-gram 精确率、几何平均、简短惩罚,并说出 BLEU 的局限。
- 解释 seq2seq 的信息瓶颈,写出注意力机制的计算步骤(打分、softmax、加权求和),比较点积、乘性、加性三种打分方式。
- 用”查询—键值”的语言给注意力一个一般性的定义,为第 7 讲的 Transformer 做准备。
0.2 先修知识
| 知识点 | 用在哪里 | 最低要求 |
|---|---|---|
| 第 5 讲 | 全讲 | RNN、BPTT、teacher forcing、语言模型、解码策略 |
| 第 3 讲 | 第 2、3 节 | 雅可比矩阵、链式法则;sigmoid 的取值在 0~1 之间 |
| 特征值 | 第 2.2 节 | 知道 的含义即可;不熟悉也能看懂结论 |
0.3 本讲地图
第1节 回顾:RNN 的两个问题
第2节 梯度消失与梯度爆炸 ← 代码 01
第3节 LSTM 与 GRU:用"门"控制信息流 ← 代码 02、03(本讲重点之一)
第4节 更一般的视角:残差连接;双向与多层 RNN
第5节 机器翻译与 seq2seq ← 代码 04
第6节 解码:贪心、穷举、束搜索 ← 代码 05
第7节 评估:BLEU ← bleu.py
第8节 信息瓶颈与注意力机制 ← 代码 05(本讲重点之二)
第9节 注意力的一般形式:查询、键、值
第10–14节 小结、与作业的关系、练习、延伸阅读、下一讲预告
本讲是从 RNN 走向 Transformer 的”桥梁”。最后得到的注意力机制,在第 7 讲会变成 Transformer 的核心——到那时,RNN 本身都不需要了。
1. 回顾:RNN 的两个问题
第 5 讲的 RNN 语言模型有两个问题:
- 梯度消失 / 爆炸:反向传播时梯度要穿过很多次矩阵连乘,可能指数衰减或指数增长。第 5 讲代码 03 演示 4 看到, 偏小时 40 步之前的梯度只剩 ;偏大时放大了上万倍。第 5 讲代码 04 演示 3(b) 看到,训练时只见过短依赖的 RNN,推广到长距离时准确率逐渐下降。
- 信息瓶颈:所有前文都要塞进一个固定大小的隐藏状态向量里。
本讲的前半部分(第 2–4 节)对付第一个问题,后半部分(第 5–9 节)在机器翻译的场景中对付第二个问题。
2. 梯度消失与梯度爆炸
2.1 梯度是一串雅可比矩阵的乘积
RNN 的隐藏状态更新是 。根据链式法则,第 步的损失对更早的第 步隐藏状态的梯度是:
而每一个因子都是
所以梯度里包含 的 次方(再乘上一串激活函数导数组成的对角阵)。距离越远,乘的次数越多。
为了看清本质,先假设 是恒等函数(),那么 。
2.2 特征值决定一切
设 有特征值 (按绝对值从大到小排)和对应的特征向量 。任何向量都可以写成特征向量的组合 ,于是
很大时,这个结果的大小由 决定:
- :指数衰减到 0 → 梯度消失;
- :指数增长 → 梯度爆炸。
Pascanu、Mikolov 和 Bengio(2013)证明了更一般的结论:如果 的最大特征值小于 ( 是激活函数导数的上界;tanh 的 ,sigmoid 的 ),梯度一定会指数消失。激活函数的导数小于 1,只会让衰减更快。
动手:代码
01_vanishing_gradients.py演示 1 计算了 64 维随机矩阵的幂作用在单位向量上的长度 :
谱半径 0.9 0.89 0.25 0.058 1.8e-3 9.2e-6 1.0 0.99 0.71 0.47 0.36 0.35 1.1 1.09 1.85 3.18 42 4,790 (谱半径就是最大特征值的绝对值。),。
2.3 梯度消失的后果
梯度消失为什么是个问题?
第一,远处的依赖”学不到”。 损失对参数 的梯度是所有时间步贡献之和;远处时间步的贡献被指数衰减,近处的贡献占了绝对主导。模型只会根据近处的依赖调整参数。
动手:代码 01 演示 2:一个谱半径 0.9 的 RNN,序列长 40,最后一步的损失对 的梯度中,最近 5 步的贡献占了 95.4%;距离 10 步的贡献只占 0.06%,距离 20 步的几乎为 0。
Manning 课件里有一个语言模型的例子:“When she tried to print her tickets, she found that the printer was out of toner. She went to the stationery store to buy more ___“。要预测 toner,模型需要记住很久以前出现的 toner。如果梯度传不回去,模型就学不会利用这种远距离的线索。
第二,无法区分”没有依赖”和”没学好”。 梯度很小时,我们分不清是第 步和第 步之间真的没有关系,还是有关系但参数还没学好、梯度传不过来。
还有一个语言学的例子(同样来自课件):“The writer of the books ___“(is 还是 are?)。正确答案取决于远处的 writer(单数)而不是近处的 books(复数)。由于梯度消失,RNN 语言模型更擅长学习顺序上的近邻(books → are)而不是句法上的依存(writer → is),于是会犯这类错误。这正是第 5 讲主谓一致实验中”干扰名词”的作用。
2.4 梯度爆炸与梯度裁剪
梯度爆炸的问题更直接:梯度很大时,SGD 的一步更新 会走得非常远,把参数推到一个很糟糕的位置,损失突然飙升,甚至变成 Inf 或 NaN。
Pascanu 等人指出,RNN 的损失曲面上经常有”悬崖”:大部分地方很平缓,但某些地方有一道陡壁,梯度在那里突然变得极大。
动手:代码 01 演示 3 构造了一个只有一个参数 的 RNN:,跑 40 步,损失 。 时损失很小;越过 后系统变成双稳态,从正的初值出发会收敛到正的固定点,损失突然跳到 2 以上。在这道陡壁上, 最大达到约 4,500,而其他地方都小于 3。
梯度裁剪(gradient clipping)是标准的解决办法:如果梯度的范数超过某个阈值,就把它按比例缩小。
它只改变一步的长度,不改变方向。直觉是:梯度方向仍然是”往下走”的正确方向,只是不要一步跨得太远。
动手:代码 01 演示 4 从悬崖壁上的 出发(学习率 0.1):
第 0 步 第 1 步 第 50 步 不裁剪 ,损失 0.061 ,损失 3.24 ,损失 3.24 裁剪(阈值 1.0) ,损失 0.061 ,损失 0.0093 ,损失 0.0011 不裁剪时,一步就被约 1700 的梯度”弹飞”到 ;那里的损失曲面是平的,再也回不来了。裁剪后步长被限制在 学习率 × 阈值 = 0.1 以内,平稳地滑下悬崖。(悬崖壁非常窄,梯度下降很少恰好落在上面,所以演示直接从壁上出发;但在有上百万个参数的网络里,“某个方向恰好落在悬崖上”的机会要多得多。)
梯度裁剪简单有效,至今仍是训练 RNN 和 Transformer 的标准做法(第 5 讲代码 04 的训练就用了它)。但它只能对付梯度爆炸,对付不了梯度消失——那需要改变网络的结构。
3. LSTM 与 GRU:用”门”控制信息流
3.1 想法:一个可以”保持不变”的记忆
普通 RNN 的问题在于:隐藏状态每一步都被整个重写——,旧信息必须穿过一次矩阵乘法和一次非线性函数才能留到下一步。要想信息(和梯度)能长时间保留,需要一个可以几乎原样传下去的记忆。
LSTM(Long Short-Term Memory,长短期记忆网络)由 Hochreiter 和 Schmidhuber 在 1997 年提出(今天通用的版本中,“遗忘门”是 Gers 等人在 2000 年加入的)。它的想法是:
- 除了隐藏状态 ,再维护一个细胞状态(cell state),专门用来存放长期信息;
- 用三个门(gate)来控制信息的擦除、写入和读出。门是取值在 0 到 1 之间的向量(用 sigmoid 算出来),逐元素地乘在别的向量上:1 表示”全部通过”,0 表示”全部挡住”。门的取值是动态的,由当前输入和上一步的隐藏状态决定。
3.2 LSTM 的公式
给定输入 ,每一步计算:
( 是逐元素乘法。四组权重形状相同,实现时通常把它们叠在一起,一次矩阵乘法算出 个数再切开。)
理解 LSTM 的关键是第五行:细胞状态的更新是加法。如果遗忘门 、输入门 ,那么 ,信息原封不动地保留下来——无论经过多少步。
3.3 LSTM 为什么能缓解梯度消失?
看细胞状态的反向传播:
主要部分是逐元素地乘以遗忘门,而不是像普通 RNN 那样反复乘同一个矩阵 再乘激活函数的导数。只要网络学会让需要保留的维度的遗忘门接近 1,梯度就能沿着细胞状态这条”高速公路”几乎无损地传回很远的地方。
Manning 的说法是:LSTM 让模型更容易学会保留很多步之前的信息。普通 RNN 也能保留(第 5 讲的主谓一致实验中,训练充分的 RNN 也全对了),但需要非常精确地调好 ;LSTM 只需要把遗忘门开到接近 1。
几点说明:
- LSTM 不保证没有梯度消失或爆炸,只是让模型更容易学会长距离依赖。
- 一个常见的技巧是把遗忘门的偏置初始化为正数(常见的取值是 1),让模型一开始就倾向于”记住”。第 3.4 节的实验会看到,要记住的距离很长时,偏置 1 可能还不够。
- 实践中,一个 LSTM 能有效利用的距离大约是普通 RNN 的几倍(Manning 课件中的经验数字:普通 RNN 约 7 步,LSTM 约 100 步)。
动手:代码
02_lstm_gru.py演示 1 从零实现了 LSTM 单元(MyLSTMCell)和 GRU 单元(MyGRUCell),把 PyTorch 的nn.LSTMCell、nn.GRUCell的权重复制过来后,两者输出的最大误差分别为 0 和 。输入 5 维、隐藏 4 维时,RNN、GRU、LSTM 的参数量分别是 44、132、176——GRU 是 RNN 的 3 倍,LSTM 是 4 倍。
3.4 实验:梯度流动和长距离记忆
动手:代码 02 演示 2 比较了未训练的网络中,最后的隐藏状态对第一个输入的梯度大小 (5 个随机初始化取平均):
模型 RNN 0.13 5.9e-3 4.1e-7 1.7e-13 0 GRU(更新门偏置 0) 0.10 8.1e-3 6.1e-6 4.7e-11 1.6e-20 GRU(更新门偏置 3) 0.092 0.073 0.037 0.013 1.7e-3 LSTM(遗忘门偏置 1) 0.20 0.076 6.3e-3 9.1e-5 3.4e-8 LSTM(遗忘门偏置 3) 0.50 0.47 0.74 0.61 0.77 RNN 的梯度随 指数衰减,100 步后变成 0。门控模型的表现取决于记忆门的初始偏置:门的初始值约为 ,偏置 1 时约 0.73,偏置 3 时约 0.95。遗忘门偏置为 3 的 LSTM,100 步之后梯度几乎没有衰减。
演示 3 是一个最纯粹的长距离依赖任务:输入是 [信号, 噪声, 噪声, ……],第一个词是 8 种信号之一,后面跟 个随机的噪声词,要求在最后一步说出信号是哪一种(随机猜 12.5%)。每个模型训练 2000 步:
模型 RNN 100.0% 12.9% 13.5% LSTM(遗忘门偏置 1) 100.0% 11.7% 11.9% LSTM(遗忘门偏置 3) 100.0% 100.0% 61.6% GRU(更新门偏置 3) 100.0% 100.0% 100.0%
几个发现:
- 距离较短()时,普通 RNN 也学得会;距离一长,它的准确率停在随机水平——第一个词带来的梯度穿过几十次矩阵连乘,早就消失了。
- 门控本身并不自动解决问题:遗忘门偏置为 1 的 LSTM 在 上同样学不会。它的遗忘门初始约为 0.73,50 步之后信息只剩 。
- 把记忆门的偏置调到 3,LSTM 和 GRU 都很快学会了 ,GRU 甚至学会了 。门控结构让”几乎原样地保留信息”成为可能,合适的初始化让模型一开始就处在这种状态,梯度能传回去,模型才学得会。(后来有工作根据任务需要的记忆长度来设置遗忘门偏置,叫”chrono 初始化”。)
演示 4 用探针(第 5 讲第 8.4 节)检查信号在状态中能保留多久:在每个时间步,训练一个线性分类器从状态向量中读出信号():
时间步 0 1 2 5 10 20 50 LSTM 细胞状态 100% 100% 100% 100% 100% 100% 100% RNN 隐藏状态 100% 100% 100% 91.3% 10.0% 12.0% 14.0% LSTM 的平均遗忘门在每一步都保持在 0.96 左右。
LSTM 的细胞状态从头到尾都保存着信号;RNN 的隐藏状态在读到信号后几步之内还能读出来,到第 10 步就已经接近随机猜——信号被后面的噪声词一步步”冲掉”了。RNN 在前向传播中保不住信息,在反向传播中梯度也传不回去,这是同一件事的两面。
3.5 回到主谓一致
动手:代码
03_agreement_lstm.py用第 5 讲第 8.3 节完全相同的设置——训练语料中只有 0~1 个修饰语——分别训练 RNN、GRU、LSTM 语言模型(每种 2 个随机种子取平均),测试更长距离上的主谓一致:
修饰语个数 1 2 3 4 6 8 平均距离 3.7 7.5 11.2 14.8 22.3 29.7 RNN 100.0% 99.0% 96.2% 92.6% 85.1% 80.8% GRU 100.0% 100.0% 99.4% 96.6% 88.6% 83.5% LSTM 100.0% 100.0% 100.0% 100.0% 99.9% 100.0% GRU 一行的两个种子差别很大:8 个修饰语时一个是 99%,另一个只有 68%。
LSTM 在比训练时远得多的距离上几乎全对:它学到的”记住主语单复数”的机制是稳健的。普通 RNN 随距离逐渐下降。GRU 平均比 RNN 好,但取决于随机种子——有的种子学到了稳健的机制,有的没有。这再次提醒我们:**只跑一次实验很容易得出片面的结论。**三种模型的测试集困惑度很接近(5.35、5.25、5.27),差别集中在少数需要长距离信息的位置上——这也是为什么要专门设计主谓一致这样的测试。
3.6 GRU
GRU(Gated Recurrent Unit,门控循环单元;Cho 等,2014)是 LSTM 的一个简化版本:没有单独的细胞状态,只有两个门。
更新门 同时扮演了 LSTM 中遗忘门和输入门的角色: 接近 0 时,,信息(和梯度)原样保留。重置门 决定计算候选状态时用多少旧状态。(PyTorch 的 nn.GRU 中更新门的约定正好相反,, 接近 1 时保留旧状态;含义相同。)
LSTM 还是 GRU? 两者效果通常差不多。GRU 参数更少(3 组权重而不是 4 组)、更快;LSTM 有单独的细胞状态,表达能力略强。在 Transformer 出现之前(2013–2017),LSTM 是 NLP 中的默认选择;今天 Transformer 已经在大多数任务上取代了它们,但 RNN 的思想在一些新架构(比如线性 RNN、状态空间模型,见 CS336 第 4 讲)中又回来了。
4. 更一般的视角:残差连接;双向与多层 RNN
4.1 梯度消失不只是 RNN 的问题
任何很深的网络都会遇到梯度消失:反向传播时梯度要穿过很多层,每层都乘一个雅可比矩阵。RNN 只是特别严重,因为它在时间维度上”非常深”(每个时间步一层),而且每一层都乘同一个矩阵。
几种通用的解决办法,思路都和 LSTM 类似——增加”直接连接”,让梯度有一条绕过非线性变换的捷径:
- 残差连接(residual connection,ResNet;He 等,2016):。反向传播时 ,那个 保证梯度至少能原样传回去。这让训练上百层的网络成为可能,也是第 7 讲 Transformer 的关键组件。
- 稠密连接(DenseNet):每一层都直接连到后面所有层。
- 高速公路网络(Highway connections):用一个门控制”直接通过”和”经过变换”的比例——几乎就是把 LSTM 的思想用在了深度方向上。
4.2 双向 RNN
做分类、标注时,一个词的含义往往取决于两边的上下文。Manning 课件的例子:“the movie was terribly exciting!”——terribly 通常是负面的,但这里修饰 exciting,意思是”非常”。只从左往右读的 RNN 在读到 terribly 时还没看到 exciting。
双向 RNN(bidirectional RNN)用两个独立的 RNN:一个从左往右读,一个从右往左读,把每个位置的两个隐藏状态拼接起来:
双向 RNN 只能用在能看到整个输入的场景(编码、分类、标注),不能用于语言模型(预测下一个词时不能偷看后面的词)。代码 04、05 的编码器就是双向 LSTM。第 9 讲的 BERT 本质上就是一个强大的”双向”编码器。
4.3 多层 RNN
把 RNN 堆叠起来:第 1 层的隐藏状态序列作为第 2 层的输入,依此类推。低层学到较低级的特征(比如词形、局部搭配),高层学到较高级的特征(比如句法、语义)。
实践中的经验(Britz 等,2017,“Massive Exploration of Neural Machine Translation Architectures”):神经机器翻译中编码器 2~4 层、解码器 4 层效果较好;更深的 RNN(比如 8 层)需要残差连接才训练得动。相比之下,Transformer 通常有 12 层、24 层甚至更多——因为它从设计上就包含了残差连接。
5. 机器翻译与 seq2seq
5.1 机器翻译简史
机器翻译(Machine Translation,MT)是把一种语言(源语言)的句子翻译成另一种语言(目标语言)。它是 NLP 最古老的任务之一(第 1 讲第 3.1 节):
- 1950 年代:冷战推动了俄译英的研究。系统主要是双语词典 + 少量规则,效果很差。
- 1990s–2010s:统计机器翻译(SMT)。核心想法来自 IBM 的研究:要找到最好的英语译文 ,就是求
(贝叶斯公式)。翻译模型从大量平行语料(比如加拿大议会的英法对照记录)中学习”哪些词、短语互相对应”,语言模型保证译文通顺。为了学习翻译模型,还要引入一个隐变量——对齐(alignment):源句子和目标句子中哪些词互相对应。对齐可能是一对多、多对一、多对多,还有语序的调整,非常复杂。最好的系统是基于短语的统计机器翻译,由成百上千个人工设计的组件组成,需要大量特征工程和额外的资源(比如对照短语表),由很大的团队维护。
- 2014 年:神经机器翻译(NMT)。用一个端到端的神经网络完成翻译。仅仅两年后(2016 年),Google 翻译从统计方法换成了神经网络。Manning 的评价是:一个由数百名工程师多年打造的统计系统,被几位工程师几个月训练出的神经系统超越了。
5.2 seq2seq:编码器 + 解码器
神经机器翻译的架构叫序列到序列(sequence-to-sequence,seq2seq)模型(Sutskever、Vinyals 和 Le,2014;Cho 等,2014),由两个 RNN 组成:
- 编码器(encoder)RNN 读完整个源句子,得到源句子的一个编码(最后的隐藏状态);
- 解码器(decoder)RNN 是一个条件语言模型:以编码器的最后状态作为初始隐藏状态,一个词一个词地生成目标句子。
编码器(读源句子) 解码器(生成目标句子)
[the] → [dog] → [sees] → [the] → [cat] ══╗
╚══> [<s>] → wufo → miro → tavek → </s>
↓ ↓ ↓ ↓
wufo miro tavek </s>
seq2seq 直接对 建模:
每一项都是”给定源句子和已经生成的目标词,预测下一个目标词”——这就是”条件语言模型”的含义:它是语言模型(预测下一个词),而且以源句子 为条件。
训练:在平行语料上,最小化目标句子每个词的交叉熵之和(和第 5 讲的语言模型一样,用 teacher forcing),梯度从解码器一路反向传播到编码器,端到端地同时训练两个 RNN。
seq2seq 的用途远不止翻译:摘要(长文 → 短文)、对话(上一句 → 下一句)、句法分析(句子 → 线性化的句法树)、代码生成(自然语言 → Python)……任何”输入一个序列、输出另一个序列”的任务都可以用它。
5.3 神经机器翻译的优缺点
| 优点(相对于统计机器翻译) | 缺点 |
|---|---|
| 译文更流畅 | 不可解释:很难说清模型为什么这样翻译,也很难调试 |
| 更好地利用上下文 | 难以控制:不能简单地加一条”这个词必须这样翻译”的规则 |
| 更好地利用短语相似性(词向量) | 训练需要大量平行语料 |
| 端到端优化一个系统,没有一堆需要单独调的组件 | 仍有安全问题(如幻觉、偏见) |
| 人工工作量小得多:不需要特征工程,同一个方法适用于所有语言对 |
5.4 我们的玩具翻译任务
动手:代码
toy_translation.py设计了一门人造的目标语言 Zhoric,和英语有几处系统性的差别:词汇不同(dog → wufo);没有冠词;形容词放在名词后面;复数用一个单独的词 ka 标记;介词变成后置词(in the garden → pelin nai);动词放在句末(主—宾—动语序)。例如:英语 :the red dogs help the small kings in a city Zhoric:wufo ka rosa rega ka tini urba nai juvo训练集 20,000 对,测试集 1,000 对;英语词表 60 个词、Zhoric 36 个(各含 4 个特殊符号)。最关键的是最后一条:英语里动词在第 3~5 个位置,Zhoric 里在最后,中间隔着长短不一的内容。
动手:代码
04_seq2seq.py训练了一个不带注意力的 seq2seq:双向 LSTM 编码器(每个方向 128 维),把前向、后向的最后状态拼接后经线性层变成解码器的初始状态;LSTM 解码器 256 维。训练 10 轮(在编写讲义的电脑上约 2.7 分钟)后,测试集语料 BLEU 0.969,94.4% 的句子完全译对。按源句子中介词短语的个数分组(每组 300 句):
介词短语个数 0 1 2 3 4(训练时没见过) 源句平均长度 6.2 9.6 12.9 16.1 19.7 BLEU 1.000 0.986 0.981 0.943 0.689 完全正确 100.0% 97.0% 95.0% 82.3% 14.7% 源句子越长,翻译越差;比训练时更长的句子更是一落千丈(只有 14.7% 完全正确)。第 8 节会解释原因,并用注意力解决它。
6. 解码:贪心、穷举、束搜索
训练好的 seq2seq 模型给出 。翻译时,我们想找到概率最高的译文:
6.1 贪心解码
最简单的办法是贪心解码:每一步都选当前概率最高的词,作为下一步的输入。问题是无法回头:一旦某一步选错(比如概率最高的词其实会把后面的句子带进死胡同),后面就只能将错就错。
6.2 穷举搜索
理论上可以枚举所有可能的译文,算出每个的概率,选最高的。但长度为 的译文有 种可能——完全不可行。
6.3 束搜索
束搜索(beam search)是折中:每一步只保留 个最好的部分译文(叫”假设”,hypothesis), 叫束宽(beam size),实践中通常取 5~10。每个假设的得分是它的对数概率:
得分都是负数,越大(越接近 0)越好。每一步:
- 对束中的每个假设,算出下一个词的分布,扩展出 个最好的后继(共 个候选);
- 从这 个候选中选出得分最高的 个,作为新的束。
束搜索不保证找到全局最优解,但比贪心高效得多地探索了更多可能,也比穷举可行得多。
停止条件:不同的假设可能在不同的时间生成 </s>。生成 </s> 的假设被放进”已完成”列表,继续搜索其他假设,直到达到预设的最大长度、或者已完成的假设达到 个。
长度归一化:最后要从已完成的假设中选一个。问题是:每多一个词就多加一个负数,长假设的得分天然更低,模型会偏爱过短的译文。解决办法是按长度归一化:
动手:代码
05_attention.py的beam_search函数实现了带长度归一化的束搜索(演示 4 比较了不同束宽,结果见第 8.4 节)。
7. 评估:BLEU
怎样评价一个翻译系统?最可靠的是人工评估(流畅度、忠实度),但又慢又贵。最常用的自动指标是 BLEU(Bilingual Evaluation Understudy,Papineni 等,2002),它比较机器译文和一个或多个人工参考译文:
- 修正的 n-gram 精确率 ():机器译文中的 n-gram,有多少也出现在参考译文里。“修正”指每个 n-gram 的匹配次数不能超过它在参考译文中出现的最多次数——否则一个只输出 “the the the the” 的系统也能拿高分。
- 几何平均:。
- 简短惩罚(brevity penalty,BP):只看精确率的话,输出越短越”安全”。所以候选总长度 小于参考总长度 时乘以 。
BLEU 通常在整个测试集上计算(语料级):先把所有句子的匹配数、n-gram 总数、长度分别加总,再算一次。句子级 BLEU 噪声很大(短句子很容易某个 为 0)。
动手:代码
bleu.py从零实现了 BLEU。直接运行它,会用两个参考译文(“the cat is on the mat” 和 “there is a cat on the mat”)给几个候选打分:
候选 BP 句子 BLEU(平滑) the cat is on the mat 1.00 1.00 1.00 1.00 1.00 1.000 the cat sat on the mat 0.83 0.60 0.25 0.00 1.00 0.420 mat the on is cat the 1.00 0.00 0.00 0.00 1.00 0.302 the the the the the the the 0.29 0.00 0.00 0.00 1.00 0.192 the cat 1.00 1.00 0.00 0.00 0.14 0.135 a feline rests upon the rug 0.33 0.00 0.00 0.00 1.00 0.230 (句子 BLEU 用了加一平滑:高阶 为 0 时分子分母各加 1;不平滑的话, 的行 BLEU 都是 0。)
观察:
- 重复的 the 在参考中最多出现 2 次,修正后 ;
- 语序打乱时 满分但高阶 n-gram 几乎没有匹配——高阶 n-gram 衡量的是流畅度和语序;
- “the cat” 精确率很高,但简短惩罚把分数压到了 0.135;
- “a feline rests upon the rug” 意思几乎正确,BLEU 却很低——BLEU 只看字面匹配,不懂同义词和改写。
BLEU 的局限:一个好的译文可能因为和参考的措辞不同而得分很低;它和人类判断的相关性在系统级别上还可以,在句子级别上很弱。尽管如此,因为它便宜、可重复,BLEU 至今仍是机器翻译论文的标准报告指标(通常还会配合人工评估,以及基于预训练模型的指标如 COMET、BERTScore,见第 13 讲)。
8. 信息瓶颈与注意力机制
8.1 信息瓶颈
回头看 seq2seq 的结构:编码器读完整个源句子之后,交给解码器的只有最后一个隐藏状态。这个固定大小的向量必须装下源句子的全部信息——每个词是什么、它们的顺序、谁修饰谁……这叫信息瓶颈(information bottleneck)。
句子越长,瓶颈越严重。而且翻译时,解码器生成某个词往往只需要源句子中一两个相关的词(生成 wufo 时只需要看 dog),却不得不从一个”混合了所有内容”的向量里把它解读出来。第 5.4 节的实验正说明了这一点:源句子越长,seq2seq 翻译得越差。
人类翻译时是怎么做的?我们不会读一遍原文就合上书凭记忆翻译,而是一边翻译一边回头看原文中相关的部分。
8.2 注意力:每一步都回头看
注意力机制(attention;Bahdanau、Cho 和 Bengio,2015;Luong、Pham 和 Manning,2015)正是这个想法:解码的每一步,都用一条直接连到编码器的通路,专注于源句子中与当前这一步最相关的部分。
设编码器的隐藏状态为 (源句子每个位置一个),解码器在第 步的隐藏状态为 。第 步的计算:
- 打分:解码器状态和每个编码器状态算一个”相关程度”
- 归一化:用 softmax 把分数变成一个概率分布,叫注意力分布
- 加权求和:用注意力分布对编码器状态加权平均,得到注意力输出(也叫上下文向量)
- 组合:把注意力输出和解码器状态拼接起来 ,像普通 seq2seq 一样用它预测下一个词。
注意力分布是”软”的:它不是硬性地选一个源位置,而是给每个位置一个权重。因为每一步都是可导的运算,整个模型仍然可以端到端地用反向传播训练。
8.3 注意力的几种变体
打分函数 有几种常见的选择( 是解码器状态, 是编码器状态):
| 名称 | 公式 | 说明 |
|---|---|---|
| 点积注意力 | 最简单,要求 | |
| 乘性注意力(Luong 2015) | , | 多一个可学习的矩阵,让两个空间可以”对上” |
| 降秩乘性注意力 | , 是”瘦”矩阵 | 先把两边各自投影到低维空间再点积——这正是第 7 讲 Transformer 的注意力 |
| 加性注意力(Bahdanau 2015) | 用一个小的前馈网络打分;“加性”的说法有点误导,其实是一个单隐藏层的网络 |
代码 05(和作业 3)用的是乘性注意力。作业 3 的版本还有一个”输入喂入”(input feeding)的细节:解码器每一步的输入除了上一个词的嵌入,还拼接上上一步的组合输出 ,让模型在决定这一步看哪里时,知道上一步看了哪里。
8.4 实验:注意力的效果
动手:代码
05_attention.py的模型和代码 04 完全相同(同样的双向 LSTM 编码器和 LSTM 解码器),唯一的区别是加了乘性注意力和组合输出层(参数从 67.5 万增加到 113 万)。训练 10 轮后,测试集 BLEU 1.000,99.9% 的句子完全正确(seq2seq 是 0.969 和 94.4%)。按源句子长度分组:
介词短语个数 0 1 2 3 4(训练时没见过) seq2seq 的 BLEU 1.000 0.986 0.981 0.943 0.689 注意力的 BLEU 1.000 1.000 1.000 0.999 0.765 seq2seq 完全正确 100.0% 97.0% 95.0% 82.3% 14.7% 注意力完全正确 100.0% 100.0% 100.0% 99.3% 0.0%
在训练见过的长度上,瓶颈消失了:注意力模型几乎全对,seq2seq 则随长度明显下降。
但在比训练时更长的句子上,出现了一个有意思的现象:注意力模型的 BLEU 更高(0.765 vs 0.689),“完全正确”却是 0。看具体的译文:
参考 :medi rega ka silo ka nai vasi ka ven vasi ka ven domo rosa pos kurt
seq2seq:medi rega ka silo ka nai vasi ka ven domo rosa ven vasi ka pos kurt
注意力 :medi rega ka silo ka nai vasi ka ven vasi rosa pos kurt
注意力模型的译文非常流畅,但平均比参考短 2.6 个词——它总是漏掉一个介词短语:训练时最多只见过 3 个,模型学到了”3 个之后就该输出动词了”。seq2seq 的译文长度大致对(平均只短 0.4 个词),但内容和顺序乱了。注意力解决了信息瓶颈,但不会自动推广到训练分布之外的长度。 长度泛化至今仍是序列模型(包括 Transformer)的一个难题。
动手:代码 05 演示 3 画出了注意力热图,并列出了每个生成词注意力最大的源词:
英语:the kings see the cats in an old city near the gardens behind the gardens 模型:rega ka miro ka urba gron nai pelin ka ven pelin ka pos tavek 对齐:rega→kings,ka→the,miro→cats,ka→the,urba→in,gron→old,nai→in, pelin→near,ka→the,ven→near,pelin→behind,ka→the,pos→behind,tavek→</s>
几点观察:
- 实词基本对齐到对应的英语词:rega → kings、miro → cats、gron → old。没有任何人告诉模型哪个词对应哪个词,它在学习翻译的过程中自己学会了这种”对齐”。
- 有些”对齐”和人的直觉不同:生成地点名词和后置词时,注意力常常落在前面的介词(in、near、behind)上;生成句末的动词 tavek 时,注意力没有跳回英语的动词 see,而是落在源句子末尾的
</s>上。
原因是注意力看的是编码器的隐藏状态,而双向 LSTM 的每个状态都已经混入了上下文:前向 LSTM 在 </s> 处已经读完了整个句子(包括动词),介词位置的后向状态里也有后面那个名词的信息。模型只要能从某个位置取到需要的信息就行,不一定非要看”对应”的那个词。所以注意力权重可以给我们一些直观的线索,但不一定是模型决策的忠实解释——这在研究界有过不少争论(第 17 讲)。
动手:代码 05 演示 4 比较了束搜索和贪心解码(测试集前 300 句):束宽 1(贪心)、3、5 的 BLEU 都是 1.000。在这个简单的任务上,模型已经非常确定,贪心本身就不会走错;束搜索的代价是解码时间大约增加到 倍。在真实的机器翻译中,模型远没有这么确定,束搜索(束宽 4~10)通常能带来明显的提升。
8.5 注意力为什么这么好?
- 大幅提升神经机器翻译的效果:让解码器每一步都能专注于源句子的特定部分。
- 解决了瓶颈问题:解码器可以直接查看源句子的每个位置,不再需要把一切塞进一个向量。
- 缓解了梯度消失:注意力在解码器和每个编码器状态之间建立了一条”捷径”,梯度可以直接传过去,不用穿过很多个时间步——这和第 4.1 节的残差连接是同一个思想。
- 提供了一定的可解释性:查看注意力分布,就能看到解码器在每一步关注了什么。模型自己学会了对齐,而统计机器翻译需要专门训练一个对齐模型。(不过,注意力权重能在多大程度上”解释”模型的决策,后来也有很多争论,第 17 讲会讨论。)
9. 注意力的一般形式:查询、键、值
注意力不只能用在 seq2seq 里。Manning 给出了一个更一般的定义:
给定一组向量值(values)和一个向量查询(query),注意力就是根据查询计算这组值的加权和。
在 seq2seq 中,每个解码器隐藏状态(查询)“关注”所有编码器隐藏状态(值)。更一般地,可以把每个值配上一个键(key),用查询和键算打分,再对值加权求和:
(seq2seq 中键和值是同一组编码器状态。)
几个直观的理解:
- 加权和是对值所包含信息的一个选择性摘要,查询决定了要关注哪些值;
- 注意力是一种从任意多个向量(值)中得到固定大小表示的方法,而且取决于另一个向量(查询);
- 它像一个**“软”的查表**:普通的查表(比如 Python 的字典)是用查询精确匹配一个键,返回对应的值;注意力是用查询和所有键算相似度,返回所有值的加权混合。
下一步的问题:既然注意力这么强大,能不能完全不用 RNN,只用注意力来处理序列?让句子中的每个词都作为查询,去关注句子中的所有词(包括它自己)——这叫自注意力(self-attention)。它没有 RNN 的串行计算问题(所有位置可以并行计算),任意两个词之间的路径长度都是 1(没有梯度消失)。这就是 2017 年的 Transformer,第 7 讲的主题。
10. 本讲小结
- 梯度消失 / 爆炸:RNN 的梯度是一串雅可比矩阵 的乘积,由 的最大特征值决定是指数衰减还是增长。梯度消失让模型学不到远距离的依赖、无法区分”没有依赖”和”没学好”;梯度爆炸让训练不稳定,用梯度裁剪解决。
- LSTM:细胞状态 + 遗忘门、输入门、输出门。细胞状态加法更新, 的主要部分是逐元素乘以遗忘门,信息和梯度可以沿细胞状态长距离流动。GRU 用更新门在新旧状态之间插值,更简单。
- 梯度消失是所有深层网络的问题;残差连接等”直接连接”是通用的解决思路。双向 RNN 适合编码(不能用于语言模型);多层 RNN 学到更高级的特征。
- 机器翻译:规则 → 统计(翻译模型 × 语言模型、对齐)→ 神经网络(2014 年起,2016 年 Google 翻译换成神经网络)。
- seq2seq:编码器 RNN 读源句子,解码器 RNN 作为条件语言模型生成目标句子,端到端训练;适用于各种序列到序列的任务。
- 解码:贪心无法回头,穷举不可行,束搜索每步保留 个最好的假设;要用长度归一化。
- BLEU:修正的 n-gram 精确率的几何平均 × 简短惩罚;便宜、可重复,但只看字面匹配。
- 注意力:每一步用解码器状态给所有编码器状态打分 → softmax → 加权求和 → 和解码器状态一起预测。解决了信息瓶颈、缓解了梯度消失、自动学到了对齐。一般形式:查询对一组键值做加权求和——通往 Transformer 的关键一步。
11. 与作业的关系
Winter 2025 及更早的作业 3(Neural Machine Translation with RNNs)几乎就是本讲第 5–8 节:
- 实现一个带乘性注意力的 seq2seq 翻译模型(历年用过 Cherokee → 英语、汉语 → 英语等语言对):双向 LSTM 编码器;用编码器最后状态的投影初始化解码器;解码器每步的输入是上一个词的嵌入拼接上一步的组合输出(输入喂入);乘性注意力 ;组合输出 。代码 05 的
AttentionSeq2Seq是同样的结构,只是词表和数据都小得多。 - 实现束搜索解码,在测试集上报告 BLEU。
- 书面题:注意力中 mask 的作用(为什么填充位置的打分要设为 );比较点积、乘性、加性注意力的优缺点;分析模型的翻译错误;手算 BLEU 并讨论它的局限。
Winter 2026 的作业 3 直接从自注意力和 Transformer 开始,但本讲的注意力是理解它的前提。
作业的具体题目以当年官方发布的版本为准。
12. 练习
练习 1(梯度消失)★:设隐藏状态是标量,, 是 sigmoid。(a) 写出 。(b) 已知 sigmoid 的导数不超过 1/4, 要满足什么条件才能保证梯度一定指数消失?
参考答案
(a) 。 (b) 每个因子的绝对值不超过 。只要 ,每个因子都小于 1,乘积随 指数衰减。这就是 Pascanu 等人结论”最大特征值小于 “在标量情形下的样子()。
练习 2(梯度裁剪):梯度向量 ,裁剪阈值为 5。裁剪后的梯度是多少?如果阈值是 20 呢?
参考答案
,裁剪后 ,方向不变、长度变为 5。阈值 20 时 ,不变。
练习 3(LSTM 的门)★:在 LSTM 中,(a) 如果某一步 、,细胞状态会怎样?(b) 如果 呢?(c) 输出门 意味着什么?细胞状态里的信息丢了吗?
参考答案
(a) ,信息原样保留,不写入任何新内容。 (b) ,旧的记忆被完全擦除。 (c) ,这一步不对外”输出”任何记忆内容;但细胞状态 仍然保存着信息,以后输出门打开时还能读出来。这让 LSTM 可以”默默地记住”一件事,等到需要的时候再用。
练习 4(LSTM 的梯度)★★:忽略门对 的间接依赖, 是什么?和普通 RNN 的 相比,关键的区别在哪里?
参考答案
,即各步遗忘门的逐元素乘积。区别:(1) 它是对角阵的乘积,每个维度独立,不会因为矩阵 的特征值而统一地衰减或爆炸;(2) 遗忘门是由网络根据输入动态决定的,网络可以学会对需要保留的维度把 开到接近 1,这样乘积几乎不衰减;而普通 RNN 的 是固定的,对所有时间步都一样。
练习 5(参数量):输入维度 ,隐藏维度 。写出普通 RNN、GRU、LSTM 的参数量(含偏置)。, 时各是多少?
参考答案
每组”门 / 变换”有 个参数。RNN 1 组,GRU 3 组,LSTM 4 组。、 时一组是 :RNN 60,200,GRU 180,600,LSTM 240,800。(PyTorch 每组用两个偏置,会再多 个。)
练习 6(束搜索)★:束宽 。第 1 步:,,。第 2 步:在 a 之后 ;在 b 之后 。(a) 贪心解码的前两个词是什么?概率是多少?(b) 束搜索保留哪两个两词假设?
参考答案
(a) 贪心:先选 a(0.5),再选 </s>(0.8),得到 “a “,概率 0.4。
(b) 第 1 步保留 a(0.5)和 b(0.4)。第 2 步候选:“a a” 0.05,“a b” 0.05,“a ” 0.40,“b a” 0.36,“b b” 0.02,“b ” 0.02。最好的两个是 “a “(0.40,已完成)和 “b a”(0.36,继续扩展)。如果 “b a” 之后以较高概率结束(比如 ,总概率 0.324),按长度归一化的对数概率比较:“a ” 为 ,“b a ” 为 ,更长的 “b a ” 反而胜出——这说明了长度归一化的作用。
练习 7(BLEU 手算)★:参考译文 “the cat sat on the mat”,候选 “the cat on the mat”。计算 、BP 和 BLEU。
参考答案
候选 1-gram:the×2、cat、on、mat,共 5 个,全部匹配(参考中 the 出现 2 次),。 2-gram:the cat ✓、cat on ✗、on the ✓、the mat ✓,。 3-gram:the cat on ✗、cat on the ✗、on the mat ✓,。 4-gram:the cat on the ✗、cat on the mat ✗,。 ,BP 。因为 ,不平滑的 BLEU = 0。这说明单句 BLEU 很不稳定,所以要在整个语料上计算。
练习 8(注意力手算):编码器状态 、、,解码器状态 。用点积注意力计算 、 和注意力输出 。
参考答案
。。。
练习 9(mask):一批源句子被填充到相同长度。如果计算注意力时不对填充位置做 mask,会发生什么?为什么要把填充位置的打分设为 而不是 0?
参考答案
不做 mask 时,注意力可能分配一部分权重给 <pad> 位置的编码器状态,这些状态不包含真实信息,会污染注意力输出;而且同一个句子填充多少会影响结果。把打分设为 0 不行:,softmax 后仍有正的权重。设为 时 ,填充位置的权重恰好为 0。
练习 10(动手):
(a) 在代码 02 的记忆任务中,把训练步数从 1500 增加到 5000,普通 RNN 在 T = 50 上能学会吗?
(b) 在代码 04 中,把源句子倒过来输入编码器(Sutskever 等人 2014 年的技巧),BLEU 会变化吗?为什么倒序可能有帮助?
(c) 在代码 05 中把乘性注意力换成点积注意力(去掉 W_att)或加性注意力,比较结果。
(d) 在代码 05 中去掉”输入喂入”(解码器输入只用词嵌入),注意力热图和 BLEU 有什么变化?
练习 11(思考):注意力分布可以看作一种”对齐”。在我们的玩具任务中,复数标记 ka 对应英语中名词词尾的 s,而 s 并不是一个单独的词。模型的注意力会落在哪里?这对真实的翻译(比如英语 → 有丰富词形变化的语言)意味着什么?
参考答案
ka 的注意力很可能落在复数名词本身(dogs、kings)上,因为在词级别的输入中,“复数”这个信息就编码在 dogs 这个词里。真实翻译中,一个词常常对应目标语言的多个语素(或者反过来),所以对齐往往是”一对多""多对一”的,注意力分布也会分散在多个位置。这也是为什么今天的系统使用子词(BPE)作为翻译的基本单位:词尾、词缀可以成为单独的 token,对齐更细致(第 16 讲)。
13. 延伸阅读
- CS224N 课程笔记 Part V(RNN, GRU and LSTM)、Part VI(Neural Machine Translation, Seq2seq and Attention)。
- Christopher Olah,Understanding LSTM Networks(博客,2015):LSTM 最好懂的图解。
- Hochreiter & Schmidhuber,Long Short-Term Memory(1997);Cho et al.,Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation(2014,GRU)。
- Pascanu, Mikolov & Bengio,On the Difficulty of Training Recurrent Neural Networks(2013):梯度消失 / 爆炸的分析与梯度裁剪。
- Sutskever, Vinyals & Le,Sequence to Sequence Learning with Neural Networks(2014)。
- Bahdanau, Cho & Bengio,Neural Machine Translation by Jointly Learning to Align and Translate(2015):加性注意力。
- Luong, Pham & Manning,Effective Approaches to Attention-based Neural Machine Translation(2015):乘性注意力、输入喂入。
- Papineni et al.,BLEU: a Method for Automatic Evaluation of Machine Translation(2002)。
- Koehn,Statistical Machine Translation(2009)和 Neural Machine Translation(2020):机器翻译的两本教科书。
14. 下一讲预告
本讲最后提出了一个问题:既然注意力这么有效,能不能完全不用 RNN?下一讲(自注意力与 Transformer)的回答是:能,而且效果更好。
- 自注意力:句子中的每个词都作为查询,关注句子中所有的词;查询、键、值从同一个序列中投影出来;
- 为什么要缩放点积?为什么需要位置编码?什么是多头注意力?
- Transformer 块:自注意力 + 前馈网络 + 残差连接 + 层归一化;
- 编码器、解码器、编码器—解码器三种结构,以及解码器中的因果掩码;
- 为什么 Transformer 能并行训练、能处理长距离依赖,又有什么代价( 的计算量)。
下一讲之前,建议你:跑通 code/lecture06/ 下的脚本(先运行 04,05 会复用它保存的模型);手算练习 6、7、8;仔细看代码 05 演示 3 的注意力热图。