第 6 讲:梯度消失、LSTM、seq2seq 与注意力

Natural Language Processing with Deep Learning · Lecture 06。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

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

本篇目录

对应: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 学完本讲,你应该能够

  1. 从矩阵连乘的角度解释梯度消失和梯度爆炸,说明它们分别带来什么后果;会用梯度裁剪对付梯度爆炸。
  2. 写出 LSTM 的全部公式,解释遗忘门、输入门、输出门和细胞状态各自的作用,以及”加法更新”为什么能缓解梯度消失;知道 GRU 的结构。
  3. 知道梯度消失是所有深层网络的问题,以及残差连接等通用的解决思路;理解双向和多层 RNN。
  4. 说出机器翻译从规则到统计再到神经网络的发展,描述 seq2seq(编码器—解码器)模型及其训练方式。
  5. 比较贪心解码、穷举搜索、束搜索,解释束搜索的停止条件和长度归一化。
  6. 计算 BLEU:修正的 n-gram 精确率、几何平均、简短惩罚,并说出 BLEU 的局限。
  7. 解释 seq2seq 的信息瓶颈,写出注意力机制的计算步骤(打分、softmax、加权求和),比较点积、乘性、加性三种打分方式。
  8. 用”查询—键值”的语言给注意力一个一般性的定义,为第 7 讲的 Transformer 做准备。

0.2 先修知识

知识点用在哪里最低要求
第 5 讲全讲RNN、BPTT、teacher forcing、语言模型、解码策略
第 3 讲第 2、3 节雅可比矩阵、链式法则;sigmoid 的取值在 0~1 之间
特征值第 2.2 节知道 Wv=λvWv = \lambda v 的含义即可;不熟悉也能看懂结论

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 语言模型有两个问题:

  1. 梯度消失 / 爆炸:反向传播时梯度要穿过很多次矩阵连乘,可能指数衰减或指数增长。第 5 讲代码 03 演示 4 看到,WhW_h 偏小时 40 步之前的梯度只剩 10−1310^{-13};偏大时放大了上万倍。第 5 讲代码 04 演示 3(b) 看到,训练时只见过短依赖的 RNN,推广到长距离时准确率逐渐下降。
  2. 信息瓶颈:所有前文都要塞进一个固定大小的隐藏状态向量里。

本讲的前半部分(第 2–4 节)对付第一个问题,后半部分(第 5–9 节)在机器翻译的场景中对付第二个问题。


2. 梯度消失与梯度爆炸

2.1 梯度是一串雅可比矩阵的乘积

RNN 的隐藏状态更新是 h(t)=σ(Whh(t−1)+Wxx(t)+b)h^{(t)} = \sigma(W_h h^{(t-1)} + W_x x^{(t)} + b)。根据链式法则,第 jj 步的损失对更早的第 ii 步隐藏状态的梯度是:

∂J(j)∂h(i)=∂J(j)∂h(j)∏i<t≤j∂h(t)∂h(t−1)\frac{\partial J^{(j)}}{\partial h^{(i)}} = \frac{\partial J^{(j)}}{\partial h^{(j)}} \prod_{i < t \le j} \frac{\partial h^{(t)}}{\partial h^{(t-1)}}

而每一个因子都是

∂h(t)∂h(t−1)=diag(σ′(Whh(t−1)+Wxx(t)+b)) Wh\frac{\partial h^{(t)}}{\partial h^{(t-1)}} = \text{diag}\Big(\sigma'\big(W_h h^{(t-1)} + W_x x^{(t)} + b\big)\Big)\, W_h

所以梯度里包含 WhW_h 的 j−ij - i 次方(再乘上一串激活函数导数组成的对角阵)。距离越远,乘的次数越多。

为了看清本质,先假设 σ\sigma 是恒等函数(σ′=1\sigma' = 1),那么 ∂h(j)∂h(i)=Wh j−i\frac{\partial h^{(j)}}{\partial h^{(i)}} = W_h^{\,j-i}。

2.2 特征值决定一切

设 WhW_h 有特征值 λ1,…,λn\lambda_1, \dots, \lambda_n(按绝对值从大到小排)和对应的特征向量 q1,…,qnq_1, \dots, q_n。任何向量都可以写成特征向量的组合 v=∑kckqkv = \sum_k c_k q_k,于是

Wh ℓv=∑kckλkℓ qkW_h^{\,\ell} v = \sum_k c_k \lambda_k^{\ell}\, q_k

ℓ\ell 很大时,这个结果的大小由 ∣λ1∣ℓ|\lambda_1|^\ell 决定:

  • ∣λ1∣<1|\lambda_1| < 1:指数衰减到 0 → 梯度消失;
  • ∣λ1∣>1|\lambda_1| > 1:指数增长 → 梯度爆炸。

Pascanu、Mikolov 和 Bengio(2013)证明了更一般的结论:如果 WhW_h 的最大特征值小于 1/γ1/\gamma(γ\gamma 是激活函数导数的上界;tanh 的 γ=1\gamma = 1,sigmoid 的 γ=1/4\gamma = 1/4),梯度一定会指数消失。激活函数的导数小于 1,只会让衰减更快。

动手:代码 01_vanishing_gradients.py 演示 1 计算了 64 维随机矩阵的幂作用在单位向量上的长度 ∥Wkv∥\|W^k v\|:

谱半径 ρ\rhok=1k = 1k=10k = 10k=20k = 20k=50k = 50k=100k = 100
0.90.890.250.0581.8e-39.2e-6
1.00.990.710.470.360.35
1.11.091.853.18424,790

(谱半径就是最大特征值的绝对值。)0.9100≈2.7×10−50.9^{100} \approx 2.7 \times 10^{-5},1.1100≈1.4×1041.1^{100} \approx 1.4 \times 10^{4}。

2.3 梯度消失的后果

梯度消失为什么是个问题?

第一,远处的依赖”学不到”。 损失对参数 WhW_h 的梯度是所有时间步贡献之和;远处时间步的贡献被指数衰减,近处的贡献占了绝对主导。模型只会根据近处的依赖调整参数。

动手:代码 01 演示 2:一个谱半径 0.9 的 RNN,序列长 40,最后一步的损失对 WhW_h 的梯度中,最近 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。如果梯度传不回去,模型就学不会利用这种远距离的线索。

第二,无法区分”没有依赖”和”没学好”。 梯度很小时,我们分不清是第 ii 步和第 jj 步之间真的没有关系,还是有关系但参数还没学好、梯度传不过来。

还有一个语言学的例子(同样来自课件):“The writer of the books ___“(is 还是 are?)。正确答案取决于远处的 writer(单数)而不是近处的 books(复数)。由于梯度消失,RNN 语言模型更擅长学习顺序上的近邻(books → are)而不是句法上的依存(writer → is),于是会犯这类错误。这正是第 5 讲主谓一致实验中”干扰名词”的作用。

2.4 梯度爆炸与梯度裁剪

梯度爆炸的问题更直接:梯度很大时,SGD 的一步更新 θnew=θold−α∇θJ\theta^{\text{new}} = \theta^{\text{old}} - \alpha \nabla_\theta J 会走得非常远,把参数推到一个很糟糕的位置,损失突然飙升,甚至变成 Inf 或 NaN。

Pascanu 等人指出,RNN 的损失曲面上经常有”悬崖”:大部分地方很平缓,但某些地方有一道陡壁,梯度在那里突然变得极大。

动手:代码 01 演示 3 构造了一个只有一个参数 ww 的 RNN:ht=tanh⁡(wht−1−0.2)h_t = \tanh(w h_{t-1} - 0.2),跑 40 步,损失 L=(h40+0.8)2L = (h_{40} + 0.8)^2。w<1.5w < 1.5 时损失很小;越过 w≈1.498w \approx 1.498 后系统变成双稳态,从正的初值出发会收敛到正的固定点,损失突然跳到 2 以上。在这道陡壁上,∣dL/dw∣|dL/dw| 最大达到约 4,500,而其他地方都小于 3。

梯度裁剪(gradient clipping)是标准的解决办法:如果梯度的范数超过某个阈值,就把它按比例缩小。

if ∥g^∥≥threshold:g^←threshold∥g^∥ g^\text{if } \|\hat{g}\| \ge \text{threshold}: \quad \hat{g} \leftarrow \frac{\text{threshold}}{\|\hat{g}\|}\, \hat{g}

它只改变一步的长度,不改变方向。直觉是:梯度方向仍然是”往下走”的正确方向,只是不要一步跨得太远。

动手:代码 01 演示 4 从悬崖壁上的 w=1.498w = 1.498 出发(学习率 0.1):

第 0 步第 1 步第 50 步
不裁剪w=1.498w = 1.498,损失 0.061w=−170.8w = -170.8,损失 3.24w=−170.8w = -170.8,损失 3.24
裁剪(阈值 1.0)w=1.498w = 1.498,损失 0.061w=1.398w = 1.398,损失 0.0093w=1.200w = 1.200,损失 0.0011

不裁剪时,一步就被约 1700 的梯度”弹飞”到 w≈−171w \approx -171;那里的损失曲面是平的,再也回不来了。裁剪后步长被限制在 学习率 × 阈值 = 0.1 以内,平稳地滑下悬崖。(悬崖壁非常窄,梯度下降很少恰好落在上面,所以演示直接从壁上出发;但在有上百万个参数的网络里,“某个方向恰好落在悬崖上”的机会要多得多。)

梯度裁剪简单有效,至今仍是训练 RNN 和 Transformer 的标准做法(第 5 讲代码 04 的训练就用了它)。但它只能对付梯度爆炸,对付不了梯度消失——那需要改变网络的结构。


3. LSTM 与 GRU:用”门”控制信息流

3.1 想法:一个可以”保持不变”的记忆

普通 RNN 的问题在于:隐藏状态每一步都被整个重写——h(t)=σ(Whh(t−1)+⋯ )h^{(t)} = \sigma(W_h h^{(t-1)} + \cdots),旧信息必须穿过一次矩阵乘法和一次非线性函数才能留到下一步。要想信息(和梯度)能长时间保留,需要一个可以几乎原样传下去的记忆。

LSTM(Long Short-Term Memory,长短期记忆网络)由 Hochreiter 和 Schmidhuber 在 1997 年提出(今天通用的版本中,“遗忘门”是 Gers 等人在 2000 年加入的)。它的想法是:

  • 除了隐藏状态 h(t)h^{(t)},再维护一个细胞状态(cell state)c(t)c^{(t)},专门用来存放长期信息;
  • 用三个门(gate)来控制信息的擦除、写入和读出。门是取值在 0 到 1 之间的向量(用 sigmoid 算出来),逐元素地乘在别的向量上:1 表示”全部通过”,0 表示”全部挡住”。门的取值是动态的,由当前输入和上一步的隐藏状态决定。

3.2 LSTM 的公式

给定输入 x(t)x^{(t)},每一步计算:

f(t)=σ(Wfh(t−1)+Ufx(t)+bf)遗忘门:旧的细胞状态保留多少i(t)=σ(Wih(t−1)+Uix(t)+bi)输入门:新内容写入多少o(t)=σ(Woh(t−1)+Uox(t)+bo)输出门:细胞状态读出多少c~(t)=tanh⁡(Wch(t−1)+Ucx(t)+bc)候选的新内容c(t)=f(t)∘c(t−1)+i(t)∘c~(t)擦掉一部分旧的,写进一部分新的h(t)=o(t)∘tanh⁡(c(t))读出一部分作为隐藏状态\begin{aligned} f^{(t)} &= \sigma\big(W_f h^{(t-1)} + U_f x^{(t)} + b_f\big) && \text{遗忘门:旧的细胞状态保留多少} \\ i^{(t)} &= \sigma\big(W_i h^{(t-1)} + U_i x^{(t)} + b_i\big) && \text{输入门:新内容写入多少} \\ o^{(t)} &= \sigma\big(W_o h^{(t-1)} + U_o x^{(t)} + b_o\big) && \text{输出门:细胞状态读出多少} \\[4pt] \tilde{c}^{(t)} &= \tanh\big(W_c h^{(t-1)} + U_c x^{(t)} + b_c\big) && \text{候选的新内容} \\ c^{(t)} &= f^{(t)} \circ c^{(t-1)} + i^{(t)} \circ \tilde{c}^{(t)} && \text{擦掉一部分旧的,写进一部分新的} \\ h^{(t)} &= o^{(t)} \circ \tanh\big(c^{(t)}\big) && \text{读出一部分作为隐藏状态} \end{aligned}

(∘\circ 是逐元素乘法。四组权重形状相同,实现时通常把它们叠在一起,一次矩阵乘法算出 4H4H 个数再切开。)

理解 LSTM 的关键是第五行:细胞状态的更新是加法。如果遗忘门 f(t)=1f^{(t)} = 1、输入门 i(t)=0i^{(t)} = 0,那么 c(t)=c(t−1)c^{(t)} = c^{(t-1)},信息原封不动地保留下来——无论经过多少步。

3.3 LSTM 为什么能缓解梯度消失?

看细胞状态的反向传播:

∂c(t)∂c(t−1)=diag(f(t))+(经过门间接依赖 c(t−1) 的项)\frac{\partial c^{(t)}}{\partial c^{(t-1)}} = \text{diag}\big(f^{(t)}\big) + (\text{经过门间接依赖 } c^{(t-1)} \text{ 的项})

主要部分是逐元素地乘以遗忘门,而不是像普通 RNN 那样反复乘同一个矩阵 WhW_h 再乘激活函数的导数。只要网络学会让需要保留的维度的遗忘门接近 1,梯度就能沿着细胞状态这条”高速公路”几乎无损地传回很远的地方。

Manning 的说法是:LSTM 让模型更容易学会保留很多步之前的信息。普通 RNN 也能保留(第 5 讲的主谓一致实验中,训练充分的 RNN 也全对了),但需要非常精确地调好 WhW_h;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 和 9×10−89 \times 10^{-8}。输入 5 维、隐藏 4 维时,RNN、GRU、LSTM 的参数量分别是 44、132、176——GRU 是 RNN 的 3 倍,LSTM 是 4 倍。

3.4 实验:梯度流动和长距离记忆

动手:代码 02 演示 2 比较了未训练的网络中,最后的隐藏状态对第一个输入的梯度大小 ∥∂hT/∂x1∥\|\partial h_T / \partial x_1\|(5 个随机初始化取平均):

模型T=5T = 5T=10T = 10T=25T = 25T=50T = 50T=100T = 100
RNN0.135.9e-34.1e-71.7e-130
GRU(更新门偏置 0)0.108.1e-36.1e-64.7e-111.6e-20
GRU(更新门偏置 3)0.0920.0730.0370.0131.7e-3
LSTM(遗忘门偏置 1)0.200.0766.3e-39.1e-53.4e-8
LSTM(遗忘门偏置 3)0.500.470.740.610.77

RNN 的梯度随 TT 指数衰减,100 步后变成 0。门控模型的表现取决于记忆门的初始偏置:门的初始值约为 σ(偏置)\sigma(\text{偏置}),偏置 1 时约 0.73,偏置 3 时约 0.95。遗忘门偏置为 3 的 LSTM,100 步之后梯度几乎没有衰减。

演示 3 是一个最纯粹的长距离依赖任务:输入是 [信号, 噪声, 噪声, ……],第一个词是 8 种信号之一,后面跟 TT 个随机的噪声词,要求在最后一步说出信号是哪一种(随机猜 12.5%)。每个模型训练 2000 步:

模型T=20T = 20T=50T = 50T=100T = 100
RNN100.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%

几个发现:

  1. 距离较短(T=20T = 20)时,普通 RNN 也学得会;距离一长,它的准确率停在随机水平——第一个词带来的梯度穿过几十次矩阵连乘,早就消失了。
  2. 门控本身并不自动解决问题:遗忘门偏置为 1 的 LSTM 在 T=50T = 50 上同样学不会。它的遗忘门初始约为 0.73,50 步之后信息只剩 0.7350≈10−70.73^{50} \approx 10^{-7}。
  3. 把记忆门的偏置调到 3,LSTM 和 GRU 都很快学会了 T=50T = 50,GRU 甚至学会了 T=100T = 100。门控结构让”几乎原样地保留信息”成为可能,合适的初始化让模型一开始就处在这种状态,梯度能传回去,模型才学得会。(后来有工作根据任务需要的记忆长度来设置遗忘门偏置,叫”chrono 初始化”。)

演示 4 用探针(第 5 讲第 8.4 节)检查信号在状态中能保留多久:在每个时间步,训练一个线性分类器从状态向量中读出信号(T=50T = 50):

时间步0125102050
LSTM 细胞状态 ctc_t100%100%100%100%100%100%100%
RNN 隐藏状态 hth_t100%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 个随机种子取平均),测试更长距离上的主谓一致:

修饰语个数123468
平均距离3.77.511.214.822.329.7
RNN100.0%99.0%96.2%92.6%85.1%80.8%
GRU100.0%100.0%99.4%96.6%88.6%83.5%
LSTM100.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 的一个简化版本:没有单独的细胞状态,只有两个门。

u(t)=σ(Wuh(t−1)+Uux(t)+bu)更新门r(t)=σ(Wrh(t−1)+Urx(t)+br)重置门h~(t)=tanh⁡(Wh(r(t)∘h(t−1))+Uhx(t)+bh)候选的新状态h(t)=(1−u(t))∘h(t−1)+u(t)∘h~(t)新旧状态的插值\begin{aligned} u^{(t)} &= \sigma\big(W_u h^{(t-1)} + U_u x^{(t)} + b_u\big) && \text{更新门} \\ r^{(t)} &= \sigma\big(W_r h^{(t-1)} + U_r x^{(t)} + b_r\big) && \text{重置门} \\ \tilde{h}^{(t)} &= \tanh\big(W_h (r^{(t)} \circ h^{(t-1)}) + U_h x^{(t)} + b_h\big) && \text{候选的新状态} \\ h^{(t)} &= (1 - u^{(t)}) \circ h^{(t-1)} + u^{(t)} \circ \tilde{h}^{(t)} && \text{新旧状态的插值} \end{aligned}

更新门 uu 同时扮演了 LSTM 中遗忘门和输入门的角色:uu 接近 0 时,h(t)≈h(t−1)h^{(t)} \approx h^{(t-1)},信息(和梯度)原样保留。重置门 rr 决定计算候选状态时用多少旧状态。(PyTorch 的 nn.GRU 中更新门的约定正好相反,h′=(1−z)n+zhh' = (1 - z) n + z h,zz 接近 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):y=x+F(x)y = x + F(x)。反向传播时 ∂y∂x=I+∂F∂x\frac{\partial y}{\partial x} = I + \frac{\partial F}{\partial x},那个 II 保证梯度至少能原样传回去。这让训练上百层的网络成为可能,也是第 7 讲 Transformer 的关键组件。
  • 稠密连接(DenseNet):每一层都直接连到后面所有层。
  • 高速公路网络(Highway connections):用一个门控制”直接通过”和”经过变换”的比例——几乎就是把 LSTM 的思想用在了深度方向上。

4.2 双向 RNN

做分类、标注时,一个词的含义往往取决于两边的上下文。Manning 课件的例子:“the movie was terribly exciting!”——terribly 通常是负面的,但这里修饰 exciting,意思是”非常”。只从左往右读的 RNN 在读到 terribly 时还没看到 exciting。

双向 RNN(bidirectional RNN)用两个独立的 RNN:一个从左往右读,一个从右往左读,把每个位置的两个隐藏状态拼接起来:

h→(t)=RNNFW(h→(t−1),x(t)),h←(t)=RNNBW(h←(t+1),x(t)),h(t)=[h→(t); h←(t)]\overrightarrow{h}^{(t)} = \text{RNN}_\text{FW}\big(\overrightarrow{h}^{(t-1)}, x^{(t)}\big), \quad \overleftarrow{h}^{(t)} = \text{RNN}_\text{BW}\big(\overleftarrow{h}^{(t+1)}, x^{(t)}\big), \quad h^{(t)} = \big[\overrightarrow{h}^{(t)};\ \overleftarrow{h}^{(t)}\big]

双向 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 的研究:要找到最好的英语译文 yy,就是求
arg⁡max⁡yP(y∣x)=arg⁡max⁡yP(x∣y)⏟翻译模型 P(y)⏟语言模型\arg\max_y P(y \mid x) = \arg\max_y \underbrace{P(x \mid y)}_{\text{翻译模型}}\ \underbrace{P(y)}_{\text{语言模型}}

(贝叶斯公式)。翻译模型从大量平行语料(比如加拿大议会的英法对照记录)中学习”哪些词、短语互相对应”,语言模型保证译文通顺。为了学习翻译模型,还要引入一个隐变量——对齐(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 直接对 P(y∣x)P(y \mid x) 建模:

P(y∣x)=P(y1∣x) P(y2∣y1,x) P(y3∣y1,y2,x)⋯P(yT∣y1,…,yT−1,x)P(y \mid x) = P(y_1 \mid x)\, P(y_2 \mid y_1, x)\, P(y_3 \mid y_1, y_2, x) \cdots P(y_T \mid y_1, \dots, y_{T-1}, x)

每一项都是”给定源句子和已经生成的目标词,预测下一个目标词”——这就是”条件语言模型”的含义:它是语言模型(预测下一个词),而且以源句子 xx 为条件。

训练:在平行语料上,最小化目标句子每个词的交叉熵之和(和第 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 句):

介词短语个数01234(训练时没见过)
源句平均长度6.29.612.916.119.7
BLEU1.0000.9860.9810.9430.689
完全正确100.0%97.0%95.0%82.3%14.7%

源句子越长,翻译越差;比训练时更长的句子更是一落千丈(只有 14.7% 完全正确)。第 8 节会解释原因,并用注意力解决它。


6. 解码:贪心、穷举、束搜索

训练好的 seq2seq 模型给出 P(y∣x)P(y \mid x)。翻译时,我们想找到概率最高的译文:

y^=arg⁡max⁡yP(y∣x)\hat{y} = \arg\max_y P(y \mid x)

6.1 贪心解码

最简单的办法是贪心解码:每一步都选当前概率最高的词,作为下一步的输入。问题是无法回头:一旦某一步选错(比如概率最高的词其实会把后面的句子带进死胡同),后面就只能将错就错。

6.2 穷举搜索

理论上可以枚举所有可能的译文,算出每个的概率,选最高的。但长度为 TT 的译文有 ∣V∣T|V|^T 种可能——完全不可行。

6.3 束搜索

束搜索(beam search)是折中:每一步只保留 kk 个最好的部分译文(叫”假设”,hypothesis),kk 叫束宽(beam size),实践中通常取 5~10。每个假设的得分是它的对数概率:

score(y1,…,yt)=log⁡P(y1,…,yt∣x)=∑i=1tlog⁡P(yi∣y1,…,yi−1,x)\text{score}(y_1, \dots, y_t) = \log P(y_1, \dots, y_t \mid x) = \sum_{i=1}^{t} \log P(y_i \mid y_1, \dots, y_{i-1}, x)

得分都是负数,越大(越接近 0)越好。每一步:

  1. 对束中的每个假设,算出下一个词的分布,扩展出 kk 个最好的后继(共 k2k^2 个候选);
  2. 从这 k2k^2 个候选中选出得分最高的 kk 个,作为新的束。

束搜索不保证找到全局最优解,但比贪心高效得多地探索了更多可能,也比穷举可行得多。

停止条件:不同的假设可能在不同的时间生成 </s>。生成 </s> 的假设被放进”已完成”列表,继续搜索其他假设,直到达到预设的最大长度、或者已完成的假设达到 nn 个。

长度归一化:最后要从已完成的假设中选一个。问题是:每多一个词就多加一个负数,长假设的得分天然更低,模型会偏爱过短的译文。解决办法是按长度归一化:

1t∑i=1tlog⁡P(yi∣y1,…,yi−1,x)\frac{1}{t} \sum_{i=1}^{t} \log P(y_i \mid y_1, \dots, y_{i-1}, x)

动手:代码 05_attention.py 的 beam_search 函数实现了带长度归一化的束搜索(演示 4 比较了不同束宽,结果见第 8.4 节)。


7. 评估:BLEU

怎样评价一个翻译系统?最可靠的是人工评估(流畅度、忠实度),但又慢又贵。最常用的自动指标是 BLEU(Bilingual Evaluation Understudy,Papineni 等,2002),它比较机器译文和一个或多个人工参考译文:

  1. 修正的 n-gram 精确率 pnp_n(n=1,2,3,4n = 1, 2, 3, 4):机器译文中的 n-gram,有多少也出现在参考译文里。“修正”指每个 n-gram 的匹配次数不能超过它在参考译文中出现的最多次数——否则一个只输出 “the the the the” 的系统也能拿高分。
  2. 几何平均:exp⁡(∑n=1414log⁡pn)\exp\big(\sum_{n=1}^{4} \frac{1}{4} \log p_n\big)。
  3. 简短惩罚(brevity penalty,BP):只看精确率的话,输出越短越”安全”。所以候选总长度 cc 小于参考总长度 rr 时乘以 e1−r/ce^{1 - r/c}。
BLEU=BP×exp⁡(∑n=1414log⁡pn),BP={1c>re1−r/cc≤r\text{BLEU} = \text{BP} \times \exp\left(\sum_{n=1}^{4} \frac{1}{4} \log p_n\right), \qquad \text{BP} = \begin{cases} 1 & c > r \\ e^{1 - r/c} & c \le r \end{cases}

BLEU 通常在整个测试集上计算(语料级):先把所有句子的匹配数、n-gram 总数、长度分别加总,再算一次。句子级 BLEU 噪声很大(短句子很容易某个 pnp_n 为 0)。

动手:代码 bleu.py 从零实现了 BLEU。直接运行它,会用两个参考译文(“the cat is on the mat” 和 “there is a cat on the mat”)给几个候选打分:

候选p1p_1p2p_2p3p_3p4p_4BP句子 BLEU(平滑)
the cat is on the mat1.001.001.001.001.001.000
the cat sat on the mat0.830.600.250.001.000.420
mat the on is cat the1.000.000.000.001.000.302
the the the the the the the0.290.000.000.001.000.192
the cat1.001.000.000.000.140.135
a feline rests upon the rug0.330.000.000.001.000.230

(句子 BLEU 用了加一平滑:高阶 pnp_n 为 0 时分子分母各加 1;不平滑的话,p4=0p_4 = 0 的行 BLEU 都是 0。)

观察:

  • 重复的 the 在参考中最多出现 2 次,修正后 p1=2/7p_1 = 2/7;
  • 语序打乱时 p1p_1 满分但高阶 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)正是这个想法:解码的每一步,都用一条直接连到编码器的通路,专注于源句子中与当前这一步最相关的部分。

设编码器的隐藏状态为 h1,…,hN∈Rhh_1, \dots, h_N \in \mathbb{R}^h(源句子每个位置一个),解码器在第 tt 步的隐藏状态为 st∈Rhs_t \in \mathbb{R}^h。第 tt 步的计算:

  1. 打分:解码器状态和每个编码器状态算一个”相关程度”
et=[st⊤h1,…,st⊤hN]∈RNe^t = \big[s_t^\top h_1, \dots, s_t^\top h_N\big] \in \mathbb{R}^N
  1. 归一化:用 softmax 把分数变成一个概率分布,叫注意力分布
αt=softmax(et)∈RN\alpha^t = \text{softmax}(e^t) \in \mathbb{R}^N
  1. 加权求和:用注意力分布对编码器状态加权平均,得到注意力输出(也叫上下文向量)
at=∑i=1Nαit hi∈Rha_t = \sum_{i=1}^{N} \alpha^t_i\, h_i \in \mathbb{R}^h
  1. 组合:把注意力输出和解码器状态拼接起来 [at;st]∈R2h[a_t; s_t] \in \mathbb{R}^{2h},像普通 seq2seq 一样用它预测下一个词。

注意力分布是”软”的:它不是硬性地选一个源位置,而是给每个位置一个权重。因为每一步都是可导的运算,整个模型仍然可以端到端地用反向传播训练。

8.3 注意力的几种变体

打分函数 eie_i 有几种常见的选择(s∈Rd2s \in \mathbb{R}^{d_2} 是解码器状态,hi∈Rd1h_i \in \mathbb{R}^{d_1} 是编码器状态):

名称公式说明
点积注意力ei=s⊤hie_i = s^\top h_i最简单,要求 d1=d2d_1 = d_2
乘性注意力(Luong 2015)ei=s⊤Whie_i = s^\top W h_i,W∈Rd2×d1W \in \mathbb{R}^{d_2 \times d_1}多一个可学习的矩阵,让两个空间可以”对上”
降秩乘性注意力ei=s⊤(U⊤V)hi=(Us)⊤(Vhi)e_i = s^\top (U^\top V) h_i = (U s)^\top (V h_i),U,VU, V 是”瘦”矩阵先把两边各自投影到低维空间再点积——这正是第 7 讲 Transformer 的注意力
加性注意力(Bahdanau 2015)ei=v⊤tanh⁡(W1hi+W2s)e_i = v^\top \tanh(W_1 h_i + W_2 s)用一个小的前馈网络打分;“加性”的说法有点误导,其实是一个单隐藏层的网络

代码 05(和作业 3)用的是乘性注意力。作业 3 的版本还有一个”输入喂入”(input feeding)的细节:解码器每一步的输入除了上一个词的嵌入,还拼接上上一步的组合输出 ot−1o_{t-1},让模型在决定这一步看哪里时,知道上一步看了哪里。

8.4 实验:注意力的效果

动手:代码 05_attention.py 的模型和代码 04 完全相同(同样的双向 LSTM 编码器和 LSTM 解码器),唯一的区别是加了乘性注意力和组合输出层(参数从 67.5 万增加到 113 万)。训练 10 轮后,测试集 BLEU 1.000,99.9% 的句子完全正确(seq2seq 是 0.969 和 94.4%)。按源句子长度分组:

介词短语个数01234(训练时没见过)
seq2seq 的 BLEU1.0000.9860.9810.9430.689
注意力的 BLEU1.0001.0001.0000.9990.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。在这个简单的任务上,模型已经非常确定,贪心本身就不会走错;束搜索的代价是解码时间大约增加到 kk 倍。在真实的机器翻译中,模型远没有这么确定,束搜索(束宽 4~10)通常能带来明显的提升。

8.5 注意力为什么这么好?

  1. 大幅提升神经机器翻译的效果:让解码器每一步都能专注于源句子的特定部分。
  2. 解决了瓶颈问题:解码器可以直接查看源句子的每个位置,不再需要把一切塞进一个向量。
  3. 缓解了梯度消失:注意力在解码器和每个编码器状态之间建立了一条”捷径”,梯度可以直接传过去,不用穿过很多个时间步——这和第 4.1 节的残差连接是同一个思想。
  4. 提供了一定的可解释性:查看注意力分布,就能看到解码器在每一步关注了什么。模型自己学会了对齐,而统计机器翻译需要专门训练一个对齐模型。(不过,注意力权重能在多大程度上”解释”模型的决策,后来也有很多争论,第 17 讲会讨论。)

9. 注意力的一般形式:查询、键、值

注意力不只能用在 seq2seq 里。Manning 给出了一个更一般的定义:

给定一组向量值(values)和一个向量查询(query),注意力就是根据查询计算这组值的加权和。

在 seq2seq 中,每个解码器隐藏状态(查询)“关注”所有编码器隐藏状态(值)。更一般地,可以把每个值配上一个键(key),用查询和键算打分,再对值加权求和:

ei=score(q,ki),α=softmax(e),output=∑iαi vie_i = \text{score}(q, k_i), \qquad \alpha = \text{softmax}(e), \qquad \text{output} = \sum_i \alpha_i\, v_i

(seq2seq 中键和值是同一组编码器状态。)

几个直观的理解:

  • 加权和是对值所包含信息的一个选择性摘要,查询决定了要关注哪些值;
  • 注意力是一种从任意多个向量(值)中得到固定大小表示的方法,而且取决于另一个向量(查询);
  • 它像一个**“软”的查表**:普通的查表(比如 Python 的字典)是用查询精确匹配一个键,返回对应的值;注意力是用查询和所有键算相似度,返回所有值的加权混合。

下一步的问题:既然注意力这么强大,能不能完全不用 RNN,只用注意力来处理序列?让句子中的每个词都作为查询,去关注句子中的所有词(包括它自己)——这叫自注意力(self-attention)。它没有 RNN 的串行计算问题(所有位置可以并行计算),任意两个词之间的路径长度都是 1(没有梯度消失)。这就是 2017 年的 Transformer,第 7 讲的主题。


10. 本讲小结

  1. 梯度消失 / 爆炸:RNN 的梯度是一串雅可比矩阵 diag(σ′)Wh\text{diag}(\sigma') W_h 的乘积,由 WhW_h 的最大特征值决定是指数衰减还是增长。梯度消失让模型学不到远距离的依赖、无法区分”没有依赖”和”没学好”;梯度爆炸让训练不稳定,用梯度裁剪解决。
  2. LSTM:细胞状态 + 遗忘门、输入门、输出门。细胞状态加法更新,∂c(t)/∂c(t−1)\partial c^{(t)} / \partial c^{(t-1)} 的主要部分是逐元素乘以遗忘门,信息和梯度可以沿细胞状态长距离流动。GRU 用更新门在新旧状态之间插值,更简单。
  3. 梯度消失是所有深层网络的问题;残差连接等”直接连接”是通用的解决思路。双向 RNN 适合编码(不能用于语言模型);多层 RNN 学到更高级的特征。
  4. 机器翻译:规则 → 统计(翻译模型 × 语言模型、对齐)→ 神经网络(2014 年起,2016 年 Google 翻译换成神经网络)。
  5. seq2seq:编码器 RNN 读源句子,解码器 RNN 作为条件语言模型生成目标句子,端到端训练;适用于各种序列到序列的任务。
  6. 解码:贪心无法回头,穷举不可行,束搜索每步保留 kk 个最好的假设;要用长度归一化。
  7. BLEU:修正的 n-gram 精确率的几何平均 × 简短惩罚;便宜、可重复,但只看字面匹配。
  8. 注意力:每一步用解码器状态给所有编码器状态打分 → softmax → 加权求和 → 和解码器状态一起预测。解决了信息瓶颈、缓解了梯度消失、自动学到了对齐。一般形式:查询对一组键值做加权求和——通往 Transformer 的关键一步。

11. 与作业的关系

Winter 2025 及更早的作业 3(Neural Machine Translation with RNNs)几乎就是本讲第 5–8 节:

  1. 实现一个带乘性注意力的 seq2seq 翻译模型(历年用过 Cherokee → 英语、汉语 → 英语等语言对):双向 LSTM 编码器;用编码器最后状态的投影初始化解码器;解码器每步的输入是上一个词的嵌入拼接上一步的组合输出(输入喂入);乘性注意力 et,i=htdec⊤WattProjhience_{t,i} = h_t^{\text{dec}\top} W_{\text{attProj}} h_i^{\text{enc}};组合输出 ot=dropout(tanh⁡(Wu[at;htdec]))o_t = \text{dropout}(\tanh(W_u [a_t; h_t^{\text{dec}}]))。代码 05 的 AttentionSeq2Seq 是同样的结构,只是词表和数据都小得多。
  2. 实现束搜索解码,在测试集上报告 BLEU。
  3. 书面题:注意力中 mask 的作用(为什么填充位置的打分要设为 −∞-\infty);比较点积、乘性、加性注意力的优缺点;分析模型的翻译错误;手算 BLEU 并讨论它的局限。

Winter 2026 的作业 3 直接从自注意力和 Transformer 开始,但本讲的注意力是理解它的前提。

作业的具体题目以当年官方发布的版本为准。


12. 练习

练习 1(梯度消失)★:设隐藏状态是标量,h(t)=σ(wh(t−1)+x(t))h^{(t)} = \sigma(w h^{(t-1)} + x^{(t)}),σ\sigma 是 sigmoid。(a) 写出 ∂h(t)/∂h(t−k)\partial h^{(t)} / \partial h^{(t-k)}。(b) 已知 sigmoid 的导数不超过 1/4,∣w∣|w| 要满足什么条件才能保证梯度一定指数消失?

参考答案

(a) ∏j=t−k+1tw σ′(wh(j−1)+x(j))\prod_{j=t-k+1}^{t} w\, \sigma'(w h^{(j-1)} + x^{(j)})。 (b) 每个因子的绝对值不超过 ∣w∣/4|w|/4。只要 ∣w∣<4|w| < 4,每个因子都小于 1,乘积随 kk 指数衰减。这就是 Pascanu 等人结论”最大特征值小于 1/γ1/\gamma“在标量情形下的样子(γ=1/4\gamma = 1/4)。

练习 2(梯度裁剪):梯度向量 g=(6,8)g = (6, 8),裁剪阈值为 5。裁剪后的梯度是多少?如果阈值是 20 呢?

参考答案

∥g∥=10>5\|g\| = 10 > 5,裁剪后 g=510(6,8)=(3,4)g = \frac{5}{10}(6, 8) = (3, 4),方向不变、长度变为 5。阈值 20 时 ∥g∥=10<20\|g\| = 10 < 20,不变。

练习 3(LSTM 的门)★:在 LSTM 中,(a) 如果某一步 f(t)=1f^{(t)} = 1、i(t)=0i^{(t)} = 0,细胞状态会怎样?(b) 如果 f(t)=0f^{(t)} = 0 呢?(c) 输出门 o(t)=0o^{(t)} = 0 意味着什么?细胞状态里的信息丢了吗?

参考答案

(a) c(t)=c(t−1)c^{(t)} = c^{(t-1)},信息原样保留,不写入任何新内容。 (b) c(t)=i(t)∘c~(t)c^{(t)} = i^{(t)} \circ \tilde{c}^{(t)},旧的记忆被完全擦除。 (c) h(t)=0h^{(t)} = 0,这一步不对外”输出”任何记忆内容;但细胞状态 c(t)c^{(t)} 仍然保存着信息,以后输出门打开时还能读出来。这让 LSTM 可以”默默地记住”一件事,等到需要的时候再用。

练习 4(LSTM 的梯度)★★:忽略门对 c(t−1)c^{(t-1)} 的间接依赖,∂c(t)/∂c(t−k)\partial c^{(t)} / \partial c^{(t-k)} 是什么?和普通 RNN 的 ∂h(t)/∂h(t−k)\partial h^{(t)} / \partial h^{(t-k)} 相比,关键的区别在哪里?

参考答案

∂c(t)∂c(t−k)≈∏j=t−k+1tdiag(f(j))\frac{\partial c^{(t)}}{\partial c^{(t-k)}} \approx \prod_{j=t-k+1}^{t} \text{diag}(f^{(j)}),即各步遗忘门的逐元素乘积。区别:(1) 它是对角阵的乘积,每个维度独立,不会因为矩阵 WhW_h 的特征值而统一地衰减或爆炸;(2) 遗忘门是由网络根据输入动态决定的,网络可以学会对需要保留的维度把 ff 开到接近 1,这样乘积几乎不衰减;而普通 RNN 的 WhW_h 是固定的,对所有时间步都一样。

练习 5(参数量):输入维度 dd,隐藏维度 hh。写出普通 RNN、GRU、LSTM 的参数量(含偏置)。d=100d = 100,h=200h = 200 时各是多少?

参考答案

每组”门 / 变换”有 h×d+h×h+hh \times d + h \times h + h 个参数。RNN 1 组,GRU 3 组,LSTM 4 组。d=100d = 100、h=200h = 200 时一组是 20000+40000+200=6020020000 + 40000 + 200 = 60200:RNN 60,200,GRU 180,600,LSTM 240,800。(PyTorch 每组用两个偏置,会再多 hh 个。)

练习 6(束搜索)★:束宽 k=2k = 2。第 1 步:P(a)=0.5P(a) = 0.5,P(b)=0.4P(b) = 0.4,P(c)=0.1P(c) = 0.1。第 2 步:在 a 之后 P(a)=0.1,P(b)=0.1,P(⟨/s⟩)=0.8P(a) = 0.1, P(b) = 0.1, P(\langle/s\rangle) = 0.8;在 b 之后 P(a)=0.9,P(b)=0.05,P(⟨/s⟩)=0.05P(a) = 0.9, P(b) = 0.05, P(\langle/s\rangle) = 0.05。(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” 之后以较高概率结束(比如 P(⟨/s⟩)=0.9P(\langle/s\rangle) = 0.9,总概率 0.324),按长度归一化的对数概率比较:“a ” 为 log⁡0.4/2≈−0.458\log 0.4 / 2 \approx -0.458,“b a ” 为 log⁡0.324/3≈−0.376\log 0.324 / 3 \approx -0.376,更长的 “b a ” 反而胜出——这说明了长度归一化的作用。

练习 7(BLEU 手算)★:参考译文 “the cat sat on the mat”,候选 “the cat on the mat”。计算 p1,p2,p3,p4p_1, p_2, p_3, p_4、BP 和 BLEU。

参考答案

候选 1-gram:the×2、cat、on、mat,共 5 个,全部匹配(参考中 the 出现 2 次),p1=5/5=1p_1 = 5/5 = 1。 2-gram:the cat ✓、cat on ✗、on the ✓、the mat ✓,p2=3/4p_2 = 3/4。 3-gram:the cat on ✗、cat on the ✗、on the mat ✓,p3=1/3p_3 = 1/3。 4-gram:the cat on the ✗、cat on the mat ✗,p4=0p_4 = 0。 c=5<r=6c = 5 < r = 6,BP =e1−6/5=e−0.2≈0.819= e^{1 - 6/5} = e^{-0.2} \approx 0.819。因为 p4=0p_4 = 0,不平滑的 BLEU = 0。这说明单句 BLEU 很不稳定,所以要在整个语料上计算。

练习 8(注意力手算):编码器状态 h1=(1,0)h_1 = (1, 0)、h2=(0,1)h_2 = (0, 1)、h3=(1,1)h_3 = (1, 1),解码器状态 s=(2,0)s = (2, 0)。用点积注意力计算 ee、α\alpha 和注意力输出 aa。

参考答案

e=(s⋅h1,s⋅h2,s⋅h3)=(2,0,2)e = (s \cdot h_1, s \cdot h_2, s \cdot h_3) = (2, 0, 2)。α=softmax(2,0,2)=(e2,1,e2)/(2e2+1)≈(0.468,0.063,0.468)\alpha = \text{softmax}(2, 0, 2) = (e^2, 1, e^2) / (2e^2 + 1) \approx (0.468, 0.063, 0.468)。a=0.468(1,0)+0.063(0,1)+0.468(1,1)≈(0.937,0.532)a = 0.468 (1, 0) + 0.063 (0, 1) + 0.468 (1, 1) \approx (0.937, 0.532)。

练习 9(mask):一批源句子被填充到相同长度。如果计算注意力时不对填充位置做 mask,会发生什么?为什么要把填充位置的打分设为 −∞-\infty 而不是 0?

参考答案

不做 mask 时,注意力可能分配一部分权重给 <pad> 位置的编码器状态,这些状态不包含真实信息,会污染注意力输出;而且同一个句子填充多少会影响结果。把打分设为 0 不行:e0=1e^0 = 1,softmax 后仍有正的权重。设为 −∞-\infty 时 e−∞=0e^{-\infty} = 0,填充位置的权重恰好为 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 能并行训练、能处理长距离依赖,又有什么代价(O(n2)O(n^2) 的计算量)。

下一讲之前,建议你:跑通 code/lecture06/ 下的脚本(先运行 04,05 会复用它保存的模型);手算练习 6、7、8;仔细看代码 05 演示 3 的注意力热图。

← 返回课程目录