跳转至

CS231N Lecture 7: Recurrent Neural Networks

LaTeX 源码 · 备用 PDF · 观看视频

字段 内容
作者/整理 基于 Zane Durante 授课内容整理
来源 Stanford University
日期 2025

CS231N Lecture 7: Recurrent Neural Networks

引言:从固定输入到序列建模

在之前的课程中,我们一直讨论的是标准的非循环(non-recurrent)神经网络:固定大小的输入映射到固定大小的输出。这种范式适合图像分类等任务,但真实世界中存在大量变长序列数据——语言、视频、音乐、时间序列等。本讲将引入循环神经网络(Recurrent Neural Networks, RNNs),这是一种专门为序列建模设计的神经网络架构。

序列建模的多种范式:从左到右分别为 one-to-one(固定输入输出)、one-to-many(如图像描述)、many-to-one(如视频分类)、many-to-many(输入输出长度可不同,如机器翻译),以及同步的 many-to-many(如逐帧视频分类)

来源:Slides 第5页。

序列建模的五种范式

  • One-to-one:固定输入 \(\rightarrow\) 固定输出(如图像分类)
  • One-to-many:固定输入 \(\rightarrow\) 变长输出(如图像描述生成)
  • Many-to-one:变长输入 \(\rightarrow\) 固定输出(如视频分类、情感分析)
  • Many-to-many(异步):变长输入 \(\rightarrow\) 变长输出(如机器翻译)
  • Many-to-many(同步):每个输入对应一个输出(如逐帧视频标注)

课程前期回顾

讲者首先对上节课的内容进行了几点澄清:

  • Dropout 的缩放:超参数 \(p\) 在不同实现中可能表示丢弃概率或保留概率。核心原则是测试时的期望输出应与训练时一致——若训练时丢弃 25% 的激活值,测试时需乘以 0.75 进行缩放。
  • Layer Normalization 与权重初始化:Layer Norm 可以缓解不良初始化带来的问题,但无法完全替代良好的权重初始化。在某些需要精确空间信息的任务中,Layer Norm 可能反而损害性能。

本章小结

神经网络不再局限于固定大小的输入输出。通过引入序列建模,我们能够处理语言、视频、对话等变长数据。RNN 正是为这类任务设计的核心架构。

循环神经网络基础

RNN 的核心思想

RNN 的核心特征是引入了一个隐藏状态(hidden state),该状态在处理序列时不断更新。每接收一个新输入,RNN 都会基于当前输入和上一步的隐藏状态,计算新的隐藏状态。

RNN 的两种表示:左侧为折叠图(带自环箭头),右侧为展开图(explicitly showing 时间步之间的依赖关系)

来源:Slides 第7页。

RNN 的数学公式

RNN 在每个时间步 \(t\) 执行以下计算:

隐藏状态更新

\[ h_t = f_W(h_{t-1}, x_t) \]

输出计算

\[ y_t = f_Y(h_t) \]

其中:

  • \(h_t\):时间步 \(t\) 的隐藏状态向量
  • \(x_t\):时间步 \(t\) 的输入向量
  • \(f_W\):带参数 \(W\) 的状态转移函数(每个时间步共享相同的参数)
  • \(f_Y\):将隐藏状态映射到输出的函数

Vanilla RNN 的具体形式

最常见的 RNN 形式(称为 Vanilla RNN)使用 \(\tanh\) 作为激活函数:

\[ h_t = \tanh(W_{hh} h_{t-1} + W_{xh} x_t + b) \]
\[ y_t = W_{hy} h_t \]

Vanilla RNN 的数学公式:隐藏状态通过 激活函数更新,输出通过线性变换从隐藏状态计算得到

来源:Slides 第10页。

为什么使用 激活函数

\(\tanh\) 的输出范围为 \([-1, 1]\),具有以下优势:

  • 有界性:反复应用不会导致隐藏状态爆炸
  • 零中心:能表示正负值,比 sigmoid 更适合作为隐藏状态的激活函数
  • 非线性:提供必要的表达能力

\(\tanh\) 的导数最大值为 1(在 \(x=0\) 处),这意味着梯度在反向传播时几乎总是在衰减——这是导致梯度消失的重要原因之一。

三组权重矩阵

RNN 中有三组关键的权重矩阵:

权重矩阵 作用 维度
\(W_xh\) 将输入 \(x_t\) 变换到隐藏状态维度 \(d_h × d_x\)
\(W_hh\) 将上一步隐藏状态变换为当前步的贡献 \(d_h × d_h\)
\(W_hy\) 将隐藏状态映射到输出 \(d_y × d_h\)
Vanilla RNN 的三组权重矩阵

所有时间步共享同一组权重

RNN 在每个时间步使用完全相同的权重矩阵 \(W_{hh}\)\(W_{xh}\)\(W_{hy}\)。这意味着:

  1. 模型大小不随序列长度增长
  2. 梯度计算时需要将各时间步的梯度累加
  3. 模型对序列中不同位置施加相同的变换规则

本章小结

RNN 通过引入隐藏状态和参数共享的循环结构,实现了对变长序列的建模。其核心计算包含两个矩阵乘法(输入到隐藏、隐藏到隐藏)加一个非线性激活函数,以及一个从隐藏状态到输出的线性映射。

手工构造 RNN:一个玩具示例

为了深入理解 RNN 的前向传播过程,讲者通过一个手工设计的玩具示例,展示了如何构造权重矩阵来实现特定功能。

任务定义

目标:给定一个 0 和 1 的序列,当连续出现两个 1 时输出 1,否则输出 0。

玩具示例任务:检测连续的两个 1。输入序列 \([0,1,0,1,1,1,0,1]\),对应输出 \([0,0,0,0,1,1,0,0]\)

来源:Slides 第12页。

隐藏状态设计

为了完成这个任务,隐藏状态需要记录两条信息:当前输入值上一步的输入值。讲者将隐藏状态设计为三维向量 \(h_t = [\text{current}, \text{previous}, 1]^T\),其中第三个维度固定为 1(用于输出阶段的偏置计算)。

隐藏状态设计:三维向量分别记录当前值、前一步的值和常数 1

来源:Slides 第13页。

权重矩阵构造

输入权重 \(W_{xh}\)

\[ W_{xh} = \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix} \]

\(x_t = 1\) 时,\(W_{xh} x_t = [1, 0, 0]^T\)(记录当前值为 1);当 \(x_t = 0\) 时,结果为零向量。

隐藏状态权重 \(W_{hh}\)

\[ W_{hh} = \begin{bmatrix} 0 & 0 & 0 \\ 1 & 0 & 0 \\ 0 & 0 & 1 \end{bmatrix} \]
  • 第一行全零:当前值完全由 \(W_{xh} x_t\) 决定
  • 第二行 \([1,0,0]\):将上一步的“当前值”复制为这一步的“前一步值”
  • 第三行 \([0,0,1]\):保持常数 1 不变

输出权重 \(W_{hy}\)

\[ W_{hy} = \begin{bmatrix} 1 & 1 & -1 \end{bmatrix} \]

输出 \(y_t = \text{ReLU}(W_{hy} h_t) = \text{ReLU}(\text{current} + \text{previous} - 1)\)。只有当 current \(= 1\) 且 previous \(= 1\) 时,\(y_t = 1\)

手工构造的完整 RNN 前向传播过程:通过精心设计的权重矩阵,RNN 能够正确检测连续的两个 1

来源:Slides 第16页。

从手工构造到梯度学习

这个玩具示例的目的是帮助理解 RNN 的计算过程。在实际应用中:

  • 我们不需要手工设计权重矩阵——通过梯度下降自动学习
  • 隐藏状态的维度通常远大于 3(可以是数百到数千维)
  • 隐藏状态中学到的“特征”往往是高度抽象且不易解释的

本章小结

通过手工构造 RNN 的权重矩阵,我们直观地看到了 RNN 如何通过隐藏状态在时间步之间传递信息。两组权重矩阵分别负责“处理当前输入”和“继承历史信息”,它们的加和经过激活函数后形成新的隐藏状态。

RNN 的训练:时间反向传播

计算图与损失函数

在 many-to-many 设置中,RNN 在每个时间步都产生输出并计算损失。总损失为各时间步损失之和:

\[ L = \sum_{t=1}^{T} L_t \]

RNN 的计算图:展示了输入、隐藏状态、输出和损失之间的依赖关系。注意所有时间步共享相同的权重 \(W\)

来源:Slides 第18页。

时间反向传播(BPTT)

由于所有时间步共享相同的权重矩阵 \(W\),计算 \(\frac{\partial L}{\partial W}\) 时需要:

  1. 分别计算每个时间步对 \(W\) 的梯度贡献
  2. 将所有时间步的梯度累加

这个过程称为Backpropagation Through Time (BPTT)。可以将其理解为:在概念上将 \(W\) 视为每个时间步使用了不同的“副本”,分别计算各副本的梯度,最后因为它们实际上是同一个 \(W\) 而将梯度求和。

BPTT 的内存问题

对于长序列,BPTT 需要在内存中保存所有时间步的激活值和梯度。这会导致 GPU 内存迅速耗尽。

标准 BPTT 需要将整个序列的计算图保存在内存中

来源:Slides 第20页。

截断 BPTT

解决方案:将长序列分割为固定长度的“块”(chunk),在每个块内独立执行前向传播和反向传播。

截断 BPTT(Truncated BPTT):将序列分块处理。每个块的初始隐藏状态来自上一块的最终隐藏状态,但梯度不再跨块传播

来源:Slides 第21页。

具体流程:

  1. 处理第一个块:从 \(h_0\) 开始,计算隐藏状态和损失,执行反向传播,更新权重
  2. 处理第二个块:用第一个块的最终隐藏状态作为起始 \(h_0\),但不传播梯度
  3. 重复直到序列结束

截断 BPTT 的信息损失

截断 BPTT 是一种近似方法:

  • 前向传播:隐藏状态在块之间传递,信息得以保留
  • 反向传播:梯度在块边界被截断,长距离依赖的梯度信号会丢失

在 many-to-many 设置中,信息损失更为明显,因为每个块只看到局部的损失。理想情况下当然是将整个序列放入内存一次性计算,但这对长序列不可行。

本章小结

RNN 的训练通过 BPTT 实现,本质上是在展开的计算图上执行标准反向传播。截断 BPTT 通过将序列分块来控制内存消耗,但代价是丢失了跨块的梯度信息。

字符级语言模型

自回归生成

RNN 可以训练为字符级语言模型:给定前面的字符,预测下一个字符。这本质上是一个逐时间步的分类问题。

字符级语言模型:输入字符的 one-hot 编码,隐藏层更新状态,输出层产生下一个字符的概率分布

来源:Slides 第23页。

语言模型的训练与推理

训练时

  • 输入真实序列,目标是下一个字符
  • 每个时间步计算交叉熵损失
  • 无需人工标注——文本本身就是监督信号(自监督学习

推理时(生成)

  • 给定一个起始字符(或起始 token)
  • 从输出概率分布中采样一个字符
  • 将采样结果作为下一步的输入
  • 重复直到生成结束 token 或达到最大长度

Embedding 层

在实际实现中,通常不直接使用 one-hot 编码作为输入,而是使用Embedding 层:一个可学习的查找表,将每个离散 token 映射为一个稠密向量。

One-hot vs Embedding

  • One-hot:稀疏、高维(维度 = 词表大小),不包含语义信息
  • Embedding:稠密、低维(通常 64--512 维),通过训练自动学习语义关系
  • 数学上,用 one-hot 向量做矩阵乘法等价于从 Embedding 矩阵中取出对应行
  • Embedding 在优化上更高效,且能学到有意义的表示

采样策略

贪心解码的问题

如果每次都选择概率最大的字符(贪心解码),模型会反复生成相同的序列。实际做法是:

  • 随机采样:按概率分布采样
  • 温度调节\(p_i = \frac{e^{z_i / T}}{\sum_j e^{z_j / T}}\),温度 \(T\) 控制分布的“尖锐度”
  • Beam Search:同时维护多个候选序列,选择整体概率最高的
  • Top-k / Top-p 采样:只从概率最高的 \(k\) 个(或累积概率达到 \(p\) 的)token 中采样

生成效果展示

用仅 112 行 Python 代码实现的字符级 RNN,训练在莎士比亚十四行诗上,可以生成风格相似的文本。随着训练的进行,生成质量逐步提升:

训练不同阶段的生成结果:从随机乱码逐步变为有结构的、类似莎士比亚风格的文本

来源:Slides 第26页。

在 Linux 源代码上训练的字符级 RNN 生成的 C 代码:看起来结构合理,虽然无法编译,但已经学会了代码的基本格式

来源:Slides 第27页。

这正是当今大语言模型的雏形——从字符级预测发展到 token 级预测,从 RNN 发展到 Transformer,但自回归预测下一个 token 的核心思想始终未变

本章小结

字符级语言模型展示了 RNN 在文本生成方面的潜力。其核心思想——自回归地预测下一个 token——直接启发了现代大语言模型的设计。从 Karpathy 2015 年的“The Unreasonable Effectiveness of Recurrent Neural Networks”到 GPT 系列,这一范式延续至今。

RNN 隐藏状态的可解释性

RNN 的一个有趣特性是,我们可以直接观察隐藏状态中各个神经元的激活值,并发现其中一些具有高度可解释的语义。

RNN 隐藏状态可视化:使用 激活函数,值域为 \([-1, 1]\),红色表示接近 \(-1\),蓝色表示接近 \(+1\)

来源:Slides 第29页。

讲者展示了在代码和文本数据上训练的 RNN 中发现的几种可解释的隐藏单元:

引号检测器:该神经元在引号开始时激活,引号结束时关闭,准确追踪引号的开闭状态

来源:Slides 第30页。

行长度追踪器:该神经元的激活值随行的进展逐渐变化,在接近换行符时达到极值

来源:Slides 第31页。

if 语句检测器:在代码中,该神经元在进入 if 语句块时激活

来源:Slides 第32页。

代码缩进深度追踪器:随着代码嵌套层次加深,该神经元的激活值逐步增大

来源:Slides 第34页。

RNN 的自发特征学习

这些可解释的隐藏单元从未被明确设计或监督——它们完全是模型在训练过程中自发学会的。这与我们手工构造的玩具 RNN 形成了有趣的对比:

  • 玩具示例中,我们手动指定隐藏状态追踪“当前值”和“前一步值”
  • 训练好的 RNN 自动学会追踪引号状态、代码深度等更复杂的概念
  • 但并非所有隐藏单元都可解释——大部分单元的激活模式看起来是随机的

本章小结

RNN 隐藏状态中的某些神经元能够自发地学习到有意义的特征,如引号追踪、行长度计数、代码嵌套深度等。这种可解释性是 RNN 架构的一个独特优势。

RNN 的优势与局限

优势

RNN 的核心优势

来源:Slides 第35页。

  1. 可处理任意长度的输入:没有上下文长度限制(与 Transformer 形成对比)
  2. 理论上可利用任意远的历史信息:如果隐藏状态有效地编码了历史,计算可以依赖很久以前的输入
  3. 模型大小不随输入长度增长:参数数量固定,只由隐藏状态维度决定
  4. 每个时间步应用相同的变换:对称性和概念简洁性

局限

  1. 训练时难以并行化:计算 \(h_t\) 必须先计算 \(h_{t-1}\),这是一个严格的顺序依赖
  2. 实际上难以访问远距离信息:固定大小的隐藏状态无法无损地编码任意长的历史
  3. 梯度消失/爆炸:长序列的反向传播中,梯度会指数级衰减或增长

训练的不可并行性是 RNN 的致命弱点

现代深度学习的成功很大程度上依赖于 GPU 的大规模并行计算能力。RNN 的顺序依赖结构使得它无法充分利用 GPU 的并行性——这正是 Transformer 最终取代 RNN 的核心原因之一。在训练时,Transformer 可以同时处理序列中的所有位置,而 RNN 必须逐步计算。

本章小结

RNN 在理论上具有处理无限长序列的能力,但在实践中受到训练效率(不可并行)和梯度流动(消失/爆炸)的严重限制。这些局限性推动了 LSTM、GRU 以及最终 Transformer 的诞生。

RNN 在计算机视觉中的应用

图像描述生成(Image Captioning)

最经典的 CNN + RNN 应用是图像描述生成:用 CNN 提取图像特征,用 RNN 生成自然语言描述。

CNN + RNN 图像描述模型:CNN(如 ImageNet 预训练的 ResNet)提取图像特征作为 RNN 的初始输入,RNN 逐步生成描述文字

来源:Slides 第38页。

更具体的 CNN+RNN 架构:使用 CNN 倒数第二层的特征向量作为 RNN 隐藏状态的附加输入

来源:Slides 第39页。

图像描述的成功案例和失败案例

来源:Slides 第41页。

共现偏置与幻觉

早期的图像描述模型存在严重的共现偏置问题:

  • 看到海滩就假设有冲浪板
  • 看到手持物体就猜测是常见搭配
  • 看到棒球运动就默认是“投球”而非“接球”

这种幻觉(hallucination)问题在今天的视觉语言模型中依然普遍存在。其根源在于模型学习了训练数据中的统计关联,而非真正理解场景内容。

视觉问答(Visual Question Answering)

视觉问答(VQA):给定图像和问题,模型输出答案。可以通过序列生成或多选分类来实现

来源:Slides 第43页。

其他应用

RNN 在计算机视觉中的其他应用包括:

  • 视觉对话(Visual Dialogue):围绕图像进行多轮对话
  • 视觉导航(Visual Navigation):根据视觉输入生成移动指令序列

本章小结

RNN 与 CNN 的结合开创了视觉-语言多模态模型的先河。尽管这些方法已被 Transformer 基础的模型所取代,但 CNN+RNN 的架构思想——将视觉编码器与序列生成器组合——在今天的视觉语言模型(如 LLaVA、GPT-4V)中以不同形式延续。

多层 RNN

多层 RNN 结构:每一层有自己的权重矩阵,层内时间步共享权重,层间通过输出传递信息

来源:Slides 第47页。

多层 RNN 的组织方式如下:

  • 层内:同一层的所有时间步共享权重,隐藏状态沿时间维度传递
  • 层间:第 \(l\) 层的输出作为第 \(l+1\) 层的输入
  • 每一层有独立的权重矩阵

这形成了一个二维的计算网格(时间 \(\times\) 深度),进一步加剧了训练时的计算瓶颈。

本章小结

多层 RNN 通过堆叠 RNN 层来增加模型的表达能力,但也使得计算图更加复杂,训练更加困难。

梯度消失/爆炸与 LSTM

梯度消失问题的根源

反向传播中,隐藏状态之间的梯度为:

\[ \frac{\partial h_t}{\partial h_{t-1}} = \text{diag}(\tanh'(\cdot)) \cdot W_{hh} \]

这个乘积在时间步之间反复累乘,导致:

梯度消失与爆炸的两个来源: 导数的最大值为 1(几乎总是小于 1),以及 \(W_hh\) 矩阵的最大奇异值可能大于或小于 1

来源:Slides 第49页。

梯度消失/爆炸的数学分析

  1. \(\tanh\) 导数\(\tanh'(x) \in [0, 1]\),只有在 \(x = 0\) 时取最大值 1。多次乘以小于 1 的值 \(\rightarrow\) 指数衰减(梯度消失)。
  2. \(W_{hh}\) 的奇异值

  3. 最大奇异值 \(\sigma_{\max} > 1\):梯度爆炸

  4. 最大奇异值 \(\sigma_{\max} < 1\):梯度消失

梯度爆炸可以通过梯度裁剪(gradient clipping)解决:当梯度范数超过阈值时进行缩放。但梯度消失更加棘手——这正是 LSTM 要解决的问题。

LSTM 的核心思想

LSTM(Long Short-Term Memory)通过引入门控机制细胞状态(cell state)来缓解梯度消失问题。

LSTM 结构图:四个门(input gate, forget gate, output gate, gate gate)控制信息的写入、遗忘和读出。顶部的细胞状态通道(cell state highway)提供了无激活函数干扰的信息传递路径

来源:Slides 第50页。

LSTM 的四个门:

  1. Gate Gate(候选值):计算要写入的候选信息 \(\tilde{c}_t = \tanh(W_g [h_{t-1}, x_t])\)
  2. Input Gate:决定是否写入 \(i_t = \sigma(W_i [h_{t-1}, x_t])\)
  3. Forget Gate:决定遗忘多少历史信息 \(f_t = \sigma(W_f [h_{t-1}, x_t])\)
  4. Output Gate:决定输出多少 \(o_t = \sigma(W_o [h_{t-1}, x_t])\)

细胞状态更新:\(c_t = f_t \odot c_{t-1} + i_t \odot \tilde{c}_t\)

隐藏状态输出:\(h_t = o_t \odot \tanh(c_t)\)

LSTM 解决梯度消失的关键:Cell State Highway

细胞状态 \(c_t\) 的更新路径(图中顶部通道)不经过 \(\tanh\) 激活函数,只涉及逐元素乘法(forget gate)和加法(input gate)。这意味着:

  • 只要 forget gate 不为零,梯度就能相对无损地沿时间反向传播
  • 类似于 ResNet 中的跳跃连接——提供了梯度的“高速公路”
  • LSTM 并不保证解决梯度消失,但使得模型更容易学习长距离依赖

LSTM 与 ResNet 的联系

LSTM 的 cell state highway 与 ResNet 的跳跃连接在概念上高度相似:都通过“绕过”非线性层来改善梯度流动

来源:Slides 第52页。

跳跃连接的普适原理

ResNet 和 LSTM 解决的问题在本质上是相同的:

  • ResNet:随着网络深度增加,梯度消失使得深层难以训练 \(\rightarrow\) 引入跳跃连接
  • LSTM:随着序列长度增加,梯度消失使得远距离信息难以利用 \(\rightarrow\) 引入 cell state highway

核心思想是一致的:提供一条不经过非线性变换的“直达路径”,让信息和梯度能够更自由地流动。

GRU 简介

GRU(Gated Recurrent Unit)是 LSTM 的一个简化变体,将 forget gate 和 input gate 合并为一个 update gate,减少了参数量,同时保持了类似的性能。

本章小结

梯度消失是 Vanilla RNN 难以处理长序列的根本原因。LSTM 通过引入门控机制和无激活函数的 cell state 通道来缓解这一问题。这与 ResNet 中的跳跃连接异曲同工——都通过提供梯度“高速公路”来改善深层/长距离的训练。

现代回归:状态空间模型

RNN 思想的复兴

尽管 Transformer 在过去几年统治了深度学习,但 RNN 的核心思想正在以状态空间模型(State Space Models, SSMs)的形式回归。

状态空间模型的代表:RWKV 和 Mamba 等模型结合了 RNN 的线性复杂度和 Transformer 的并行训练能力

来源:Slides 第53页。

状态空间模型相比 Transformer 的关键优势:

  1. 无限上下文长度:像 RNN 一样,没有固定的上下文窗口限制
  2. 线性时间推理:计算量与序列长度成线性关系,而非 Transformer 的二次方
  3. 固定内存占用:推理时只需维护固定大小的状态,不需要 KV cache 的线性增长

RNN vs Transformer 的核心权衡

  • Transformer:训练可并行(快),推理时 KV cache 随序列增长(慢),\(O(n^2)\) 注意力开销
  • RNN/SSM:训练难以并行(慢),推理时固定状态(快),\(O(n)\) 线性开销
  • 当前研究热点:如何兼得两者优势——训练时的并行性 + 推理时的效率

本章小结

以 Mamba 和 RWKV 为代表的状态空间模型正在挑战 Transformer 的统治地位,特别是在长上下文场景中。RNN 的核心概念——循环状态更新、线性时间推理——正以更现代的形式焕发新生。

拓展阅读

总结与延伸

讲者的核心总结

Zane Durante 在课程结尾总结了三个要点:

  1. Vanilla RNN 概念简洁但性能有限
  2. LSTM 等变体通过门控机制选择性传递信息,缓解了梯度消失问题
  3. RNN 的核心思想(循环状态、线性复杂度)正在通过状态空间模型回归研究前沿

全课知识图谱

TikZ diagram

关键 Takeaways

五条核心原则

  1. 隐藏状态是 RNN 的灵魂:它在时间步之间传递信息,编码序列的“记忆”
  2. 参数共享使模型泛化:所有时间步共享权重,使 RNN 能处理任意长度的输入
  3. 梯度消失是长序列的天敌\(\tanh\) 和权重矩阵的反复累乘导致远距离梯度信号衰减
  4. LSTM 的 cell state 是解药:无激活函数的直通路径,类似 ResNet 的跳跃连接
  5. RNN 并未过时:状态空间模型正在以线性复杂度挑战 Transformer 的二次方开销