chapter 05 / sequence-models · 预计学习时间 120 分钟 · Transformer 前最后一站
前四章的模型有个隐含假设:输入是定长的,且各维之间没有顺序(图像有空间结构,但尺寸固定)。语言、语音、时间序列打破了这两点:
CNN 的答案是空间上的权重共享,序列模型的答案如出一辙:时间上的权重共享——同一组参数在每个时间步重复使用。这就是循环神经网络。
RNN 维护一个隐藏状态 $\mathbf{h}_t$(网络的「工作记忆」),每读入一个词就更新一次:
$$\mathbf{h}_t = \tanh\big(W_h \mathbf{h}_{t-1} + W_x \mathbf{x}_t + \mathbf{b}\big)$$同一组 $W_h, W_x$ 用于所有时间步——处理 3 个词和 300 个词用的都是这点参数(变长问题解决),且「位置 7 学到的规律」自动适用于位置 70(时间平移共享)。把循环按时间展开,RNN 就是一个深度等于序列长度、且每层共享权重的特殊深网络——这个视角是理解下一节灾难的钥匙。
训练 RNN 用的还是反向传播,只是沿展开的时间轴回传,得名 BPTT(Backpropagation Through Time)。问题藏在「远距离信用分配」里:第 $t$ 步的损失对 $k$ 步之前隐藏状态的梯度,由链式法则:
$$\frac{\partial \mathbf{h}_t}{\partial \mathbf{h}_k} = \prod_{i=k+1}^{t} \frac{\partial \mathbf{h}_i}{\partial \mathbf{h}_{i-1}} = \prod_{i=k+1}^{t} W_h^\top\, \text{diag}\big(\tanh'(\mathbf{z}_i)\big)$$同一个矩阵 $W_h$ 连乘 $t-k$ 次。设其最大奇异值为 $\sigma_{\max}$:$\sigma_{\max} < 1$ 时梯度随距离指数消失——50 步外的「小明」对当前梯度的影响约等于零,长程依赖学不到;$\sigma_{\max} > 1$ 时指数爆炸——损失变 NaN。和第 3 章深度方向的梯度消失同源,但更恶劣:深网络各层是不同矩阵,运气可以互相抵消;RNN 是同一个矩阵自乘,命运完全由 $W_h$ 的谱决定,没有侥幸。
LSTM(1997)的手术方案:在 $\mathbf{h}_t$ 之外增设一条细胞状态 $\mathbf{c}_t$——专用的长期记忆传送带,并用三个可学习的「门」控制读写。门就是一个 sigmoid 输出的 0~1 向量,逐元素相乘实现「软开关」:
$$\mathbf{f}_t = \sigma(W_f [\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_f) \qquad \text{遗忘门:旧记忆保留多少}$$ $$\mathbf{i}_t = \sigma(W_i [\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_i), \qquad \tilde{\mathbf{c}}_t = \tanh(W_c [\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_c) \qquad \text{输入门:新信息写入多少}$$ $$\boxed{\ \mathbf{c}_t = \mathbf{f}_t \odot \mathbf{c}_{t-1} + \mathbf{i}_t \odot \tilde{\mathbf{c}}_t\ } \qquad \text{细胞状态更新}$$ $$\mathbf{o}_t = \sigma(W_o [\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_o), \qquad \mathbf{h}_t = \mathbf{o}_t \odot \tanh(\mathbf{c}_t) \qquad \text{输出门:暴露多少给本步输出}$$盯住方框里的更新式:$\mathbf{c}_t$ 的主干是加法。求梯度 $\frac{\partial \mathbf{c}_t}{\partial \mathbf{c}_{t-1}} = \text{diag}(\mathbf{f}_t)$——不再有 $W_h$ 连乘!只要遗忘门学会保持开启($f \approx 1$),梯度就能沿细胞状态无衰减地流过几百步。
2014 年的 Seq2Seq 把两个 LSTM 接力,解决「输入输出都是变长序列」的任务(翻译、摘要):编码器读完源句,把全部理解压进最后一个隐藏状态 $\mathbf{h}_{enc}$;解码器以它为初始状态,逐词生成目标句。优雅,但有个先天残疾:
Bahdanau 注意力(2015)就是给解码器这个权利。编码器保留每个位置的隐藏状态 $\mathbf{h}_1,\dots,\mathbf{h}_n$(不再只留最后一个);解码器生成第 $t$ 个词之前,做三步:
① 打分——拿当前解码状态 $\mathbf{s}_{t-1}$ 去问每个源位置「你和我现在要生成的东西相关吗」:
$$e_{tj} = \mathbf{v}^\top \tanh\big(W_s \mathbf{s}_{t-1} + W_h \mathbf{h}_j\big) \qquad j = 1,\dots,n$$② 归一化——softmax 把分数变成权重(和为 1):
$$\alpha_{tj} = \frac{\exp(e_{tj})}{\sum_{k} \exp(e_{tk})}$$③ 加权汇总——按权重混合所有源位置的信息,得到本步专属的上下文向量:
$$\mathbf{c}_t = \sum_{j=1}^{n} \alpha_{tj}\, \mathbf{h}_j$$瓶颈消失了:每生成一个词,解码器都现场定制一份源句摘要。生成「yesterday」时 $\alpha$ 集中在「昨天」,生成「park」时集中在「公园」——对齐是从翻译数据里自己学出来的,没有任何人工词典。
下面是一个模拟训练好的注意力模型(中→英)。必做实验:① 悬停「yesterday」——它的注意力越过整个句子对齐到第 2 个词「昨天」(中英语序不同,这正是注意力优于「按位置对齐」的地方);② 悬停「the」——虚词没有明确对应,注意力弥散——模型表达「不确定」的方式;③ 把温度拉到 0.1——软对齐退化成硬指针;拉到 5——退化成均匀平均(≈ 没有注意力的 Seq2Seq)。温度就是第 2 章 softmax 那个 $T$,在第 9 章 LLM 采样里你会第三次遇到它。
悬停/点击英文词 · 中文词的高亮深浅 = 注意力权重 α · 柱状图显示精确数值
训练 Seq2Seq 时有个微妙选择:解码器第 $t$ 步的输入用什么?用模型上一步自己的输出——错一个词,后面全在垃圾上训练,且无法并行;所以实践用教师强制(teacher forcing):训练时永远喂真实的上一个词。代价是曝光偏差(exposure bias):模型在训练中从未见过自己的错误,推理时一旦走错就进入完全陌生的状态分布,错误滚雪球。
这个 2015 年的老问题在 LLM 时代依然活着:GPT 预训练就是大规模教师强制(每个位置都以真实前文为条件),幻觉的雪球效应与之相关;而第 8 章的 RLHF/RLVR 之所以有效,部分原因恰恰是强化学习让模型在自己生成的轨迹上接受训练——可以视为对曝光偏差的系统性修复。一根线索,牵了十年。
import torch, torch.nn as nn
class LSTMCell(nn.Module):
"""四组门计算合并成一个大矩阵乘(工程标准做法),逐行对应 §4 公式"""
def __init__(self, d_in, d_h):
super().__init__()
self.W = nn.Linear(d_in + d_h, 4 * d_h) # f, i, c̃, o 一次算完
self.d_h = d_h
def forward(self, x, h, c):
z = self.W(torch.cat([x, h], dim=-1))
f, i, c_tilde, o = z.chunk(4, dim=-1)
f = torch.sigmoid(f) # 遗忘门
i = torch.sigmoid(i) # 输入门
c_tilde = torch.tanh(c_tilde) # 候选记忆
o = torch.sigmoid(o) # 输出门
c = f * c + i * c_tilde # ★ 加法主干:时间维的残差连接
h = o * torch.tanh(c)
return h, c
# 跑一个变长序列,验证状态形状
cell = LSTMCell(d_in=32, d_h=64)
h = torch.zeros(1, 64); c = torch.zeros(1, 64)
for t in range(100): # 100 步后梯度依然能流回 t=0(拜加法主干所赐)
h, c = cell(torch.randn(1, 32), h, c)
print(h.shape, c.shape) # torch.Size([1, 64]) ×2
# 生产中直接用 nn.LSTM(内部 cuDNN 融合实现,快一个数量级):
# rnn = nn.LSTM(input_size=32, hidden_size=64, num_layers=2, batch_first=True)