05-序列模型与注意力 核心 预计 30 分钟

序列建模与循环神经网络 RNN

循环神经网络(Recurrent Neural Network, RNN)用一个跨时间步共享参数的隐状态 h_t 压缩历史信息——h_t = φ(W_h h_{t-1} + W_x x_t + b)——使同一套权重能处理任意长度的序列。

一句话定义

循环神经网络(Recurrent Neural Network, RNN)用一个跨时间步共享参数的隐状态 h_t 压缩历史信息——h_t = φ(W_h h_{t-1} + W_x x_t + b)——使同一套权重能处理任意长度的序列。

直觉

RNN 像「读长文做摘要的人」:每读一个词,脑子(隐状态)就更新一次对全文的理解;读到句尾时,脑子里的状态已浓缩了整段历史——只是这个「脑子」容量有限(固定维向量),词一多就记不住开头的伏笔。

为什么重要

文本、语音、股价、传感器读数都是变长序列,CNN 的固定结构先验不适用,需要「参数共享沿时间轴」的新机制。RNN 是这条路线的原点:kp-023 的 LSTM/GRU、kp-025 的截断训练、乃至 kp-026 的注意力(最初为改进 RNN 编码器而生),全部是围绕它缺陷的修补史。理解 RNN 的机制与病根,才能理解后续每一步「为什么要改」。

前置知识

  • kp-007(梯度回传——RNN 是它沿时间的展开)、kp-005(共享权重的层结构)。

核心概念

  • 隐状态(hidden state) h_t:序列到当前步为止的信息压缩摘要。
  • 时间展开(unrolling):把循环按时间步展开成深网络,参数跨层共享。
  • 共享参数:W_h、W_x 在所有时间步复用——「同一套语法规则用于任何位置」。
  • 序列到序列形态:一对多(图像描述)、多对一(情感分类)、多对多(词性标注、翻译)。
  • BPTT(沿时间反向传播):展开图上做普通反向传播(细节在 kp-025)。

原理与机制

单步方程:h_t = φ(W_h h_{t-1} + W_x x_t + b)。三个机制要点。其一,参数共享与变长能力:无论序列 10 步还是 1000 步,权重只有一份——这与 CNN 的空间共享(kp-017)同构,是「时间轴上的参数共享」。其二,隐状态的马尔可夫式压缩:h_t 只直接依赖 h_{t-1} 与 x_t,全部历史经递归链隐式传递;信息要活过 T 步就得在递归链上「存活」T 次——这正是 kp-023 长程依赖难题的结构根源。其三,沿时间展开后,RNN 就是一张很深的共享权重网络:梯度既沿层回传也沿时间回传,梯度消失/爆炸在时间维度上被指数放大(T 步链式相乘),50 步以外梯度常衰减到 1e−5 量级以下——模型「记不住」的本质是「学不到」(梯度到不了远方)。展开视角还解释了输入/输出的多种对齐形态:只要在对应时间步接入输入或读出输出,同一套方程即可支持分类(末步读出)、生成(输出作下一步输入)等任务。RNN 的现代地位:在自然语言的生成主干上已被注意力架构(kp-026 之后的发展,指路 Transformer 站点)取代,但它仍是时序传感器、轻量嵌入式序列任务与「理解注意力为何胜出」的必修对照组。

公式或模型

RNN 单步方程与多对一分类读出:

h_t = φ( W_h h_{t-1} + W_x x_t + b )      h_0 初始化为零向量
ŷ   = softmax( W_o h_T + b_o )            (多对一:末状态读出)

变量说明:x_t 为 t 步输入向量,h_t ∈ R^d 为隐状态,W_h 为跨步转移矩阵(重复使用 T 次),W_x 为输入映射。梯度沿时间的连乘因子正是 W_h 的幂:W_h^T——其谱半径决定记忆寿命(kp-023 展开)。

图示

        y_1     y_2     y_3     y_4        (按需读出)
        ▲       ▲       ▲       ▲
      [O]     [O]     [O]     [O]
        ▲       ▲       ▲       ▲
h_0 ►[A]──►  [A]──►  [A]──►  [A]──► h_4      (同一个 A:参数共享)
        ▲       ▲       ▲       ▲
        x_1     x_2     x_3     x_4        (时间展开图)
[A] = h_t = φ(W_h h_{t-1} + W_x x_t + b)

直观类比

隐状态像「游戏中角色的内存条」:每帧(时间步)用最新事件覆盖更新一次——近期事件清晰、久远事件不断被冲淡;W_h^T 的谱半径就是「内存条的保质期」。

实例或案例

字符级语言模型的最小实现(kp-024 将给出其损失与评估口径):

class TinyRNN(nn.Module):
    def __init__(self, vocab, d=64):
        super().__init__()
        self.emb = nn.Embedding(vocab, d)
        self.rnn = nn.RNN(d, d, batch_first=True)
        self.out = nn.Linear(d, vocab)
    def forward(self, x, h=None):
        e, h = self.rnn(self.emb(x), h)   # 循环内部即本节方程
        return self.out(e), h

在莎士比亚文本上训练后采样文本:前 30 个字符连贯、越往后越语无伦次——「短期像样、长期失忆」正是朴素 RNN 的签名症状,也是 kp-023 门控改进的出发点。

常见误区

  • 「RNN 每个时间步有一套独立参数」:恰恰相反,全程共享一份 W_h/W_x,这正是它能处理变长序列的原因。
  • 「隐状态存储了完整历史」:它是固定维压缩,本质有损;长程信息能否保留取决于梯度能否教会它(kp-023)。
  • 「RNN 已完全无用」:作为概念基线与轻量时序方案仍有价值;学习它的最大意义是理解注意力改良的对象。
  • 混淆 h_t 的「深度」(层间)与「时间」(步间):展开图上二者都是回传方向,但参数只有一份。

与其他知识点的关系

kp-023 治其梯度病、kp-025 讲其训练工程(截断与裁剪)、kp-024 提供其文本任务的表示层与损失;kp-026 的注意力最初作为 RNN 编码-解码的补充出现;kp-007 的时间展开版本就是本节。

自测题

  1. 写出 RNN 单步方程,指出哪部分实现了「参数共享」。

答案要点:W_h/W_x/b 全时间步复用;h_t=φ(W_h h_{t-1}+W_x x_t+b)。

  1. 为什么朴素 RNN 难以记住 100 步之前的信息?

答案要点:梯度沿时间连乘 W_h 与 φ',谱半径偏离 1 时指数消失/爆炸,远方梯度到不了(kp-023 量化)。

  1. 多对一分类在 RNN 中如何实现读出?

答案要点:取末时间步 h_T 经线性层+softmax 输出类别。

延伸阅读

  • Elman《Finding Structure in Time» Cognitive Science 1990(RNN 作为认知模型的开山作)。
  • Goodfellow 等《Deep Learning》第 10 章「序列建模:循环和递归网络」。

学习状态

状态保存在浏览器本地,用于首页与路径页的进度统计。