跳转至

循环神经网络与序列模型

RNN 系列网络专为序列数据设计——文本、语音、时间序列等。它们的核心能力是"记住过去",让当前的输出受到之前输入的影响。


1. 为什么需要 RNN?

前馈网络(MLP、CNN)的输入和输出是固定大小的,且每次预测独立,不考虑顺序关系。但很多任务天然是序列性的:

  • 文本:"我 喜欢 深度 学习" → 词的顺序决定含义
  • 语音:音频帧按时间排列,前后帧高度关联
  • 股票:今天的价格与过去的走势相关

关键需求

我们需要一种网络,能接收变长序列输入,且对序列中的每个元素都能参考之前的上下文信息


2. RNN 的基本结构

核心思想

RNN 在处理序列时,维护一个隐藏状态(Hidden State) \(\mathbf{h}_t\),它像一个"记忆体",将之前时间步的信息传递给当前时间步。

\[ \mathbf{h}_t = \tanh(\mathbf{W}_{hh}\mathbf{h}_{t-1} + \mathbf{W}_{xh}\mathbf{x}_t + \mathbf{b}_h) \]
\[ \mathbf{y}_t = \mathbf{W}_{hy}\mathbf{h}_t + \mathbf{b}_y \]

其中:

  • \(\mathbf{x}_t\):时间步 \(t\) 的输入
  • \(\mathbf{h}_{t-1}\):上一步的隐藏状态("记忆")
  • \(\mathbf{h}_t\):当前的隐藏状态
  • \(\mathbf{y}_t\):当前步的输出

直觉理解

RNN 就像一个边读边记的人:读到第 5 个字时,脑子里不仅有第 5 个字的信息,还保留着前 4 个字的"印象"。

展开视图

RNN 按时间展开后,可以看成一个很深的前馈网络,但每一层用的是相同的参数

graph LR
    X1[x₁] --> H1[h₁]
    H1 --> H2[h₂]
    X2[x₂] --> H2
    H2 --> H3[h₃]
    X3[x₃] --> H3
    H3 --> H4[h₄]
    X4[x₄] --> H4
    H1 --> Y1[y₁]
    H2 --> Y2[y₂]
    H3 --> Y3[y₃]
    H4 --> Y4[y₄]

RNN 的输入/输出模式

模式 说明 示例
一对多 一个输入,序列输出 图像描述生成
多对一 序列输入,一个输出 情感分类
多对多(等长) 序列输入,等长序列输出 词性标注
多对多(不等长) 序列输入,不等长序列输出 机器翻译(Seq2Seq)

3. RNN 的致命问题:梯度消失

数学推导

通过时间反向传播(BPTT)时,梯度需要沿时间步连乘:

\[ \frac{\partial L}{\partial \mathbf{h}_1} = \frac{\partial L}{\partial \mathbf{h}_T} \cdot \prod_{t=2}^{T} \frac{\partial \mathbf{h}_t}{\partial \mathbf{h}_{t-1}} \]

其中每一项 \(\frac{\partial \mathbf{h}_t}{\partial \mathbf{h}_{t-1}} = \text{diag}(\tanh'(\cdot)) \cdot \mathbf{W}_{hh}\)

\(T\) 很大时:

  • 如果 \(\|\mathbf{W}_{hh}\|\) 的特征值 < 1 → 连乘趋近于 0 → 梯度消失
  • 如果 \(\|\mathbf{W}_{hh}\|\) 的特征值 > 1 → 连乘趋近于 ∞ → 梯度爆炸

后果

梯度消失 意味着网络无法学到长距离依赖——读到句子第 50 个词时,已经"记不住"第 1 个词了。
梯度爆炸 可以通过梯度裁剪(Gradient Clipping) 缓解:当梯度范数超过阈值时,按比例缩小。

\[ \mathbf{g} \leftarrow \frac{\theta}{\|\mathbf{g}\|} \cdot \mathbf{g} \quad \text{if } \|\mathbf{g}\| > \theta \]

4. LSTM(长短期记忆网络)

LSTM(Long Short-Term Memory)由 Hochreiter & Schmidhuber 在 1997 年提出,专门解决 RNN 的长期依赖问题。

核心设计:细胞状态 + 三个门

LSTM 的关键是引入了一条独立的细胞状态(Cell State) \(\mathbf{C}_t\),它像一条信息传送带,信息可以几乎不受干扰地在上面流动。三个门控制信息的进出:

graph TD
    subgraph LSTM 单元
        FG[遗忘门 f_t] --> CT[细胞状态 C_t]
        IG[输入门 i_t] --> CT
        CT --> OG[输出门 o_t]
        OG --> HT[隐藏状态 h_t]
    end

数学公式

遗忘门(Forget Gate):决定从细胞状态中丢弃哪些信息

\[ \mathbf{f}_t = \sigma(\mathbf{W}_f [\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_f) \]

输入门(Input Gate):决定哪些新信息写入细胞状态

\[ \mathbf{i}_t = \sigma(\mathbf{W}_i [\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_i) \]
\[ \tilde{\mathbf{C}}_t = \tanh(\mathbf{W}_C [\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_C) \]

更新细胞状态

\[ \mathbf{C}_t = \mathbf{f}_t \odot \mathbf{C}_{t-1} + \mathbf{i}_t \odot \tilde{\mathbf{C}}_t \]

输出门(Output Gate):决定细胞状态中哪些信息输出为隐藏状态

\[ \mathbf{o}_t = \sigma(\mathbf{W}_o [\mathbf{h}_{t-1}, \mathbf{x}_t] + \mathbf{b}_o) \]
\[ \mathbf{h}_t = \mathbf{o}_t \odot \tanh(\mathbf{C}_t) \]

其中 \(\sigma\) 是 Sigmoid 函数(输出 0~1,表示"保留多少"),\(\odot\) 是逐元素乘法。

直觉类比

LSTM 就像一个带橡皮擦的笔记本

  • 遗忘门 = 橡皮擦:擦掉不再重要的旧笔记
  • 输入门 = 笔:决定写下哪些新内容
  • 输出门 = 选择性阅读:决定现在需要用到笔记本中的哪些内容

为什么 LSTM 能解决梯度消失?

关键在于细胞状态的更新方式

\[ \mathbf{C}_t = \mathbf{f}_t \odot \mathbf{C}_{t-1} + \mathbf{i}_t \odot \tilde{\mathbf{C}}_t \]

这是一个加法操作(不是连乘!)。当 \(\mathbf{f}_t \approx 1\)\(\mathbf{i}_t \approx 0\) 时,\(\mathbf{C}_t \approx \mathbf{C}_{t-1}\),信息几乎无损传递。梯度梯度沿这条路径回传时也不会消失——这就是"短路连接"的原理,与 ResNet 的残差连接异曲同工。


5. GRU(门控循环单元)

GRU(Gated Recurrent Unit)由 Cho 等人在 2014 年提出,是 LSTM 的简化版本,只有两个门

数学公式

重置门(Reset Gate):决定忘记多少历史信息

\[ \mathbf{r}_t = \sigma(\mathbf{W}_r [\mathbf{h}_{t-1}, \mathbf{x}_t]) \]

更新门(Update Gate):决定新旧信息的混合比例

\[ \mathbf{z}_t = \sigma(\mathbf{W}_z [\mathbf{h}_{t-1}, \mathbf{x}_t]) \]

候选隐藏状态

\[ \tilde{\mathbf{h}}_t = \tanh(\mathbf{W} [\mathbf{r}_t \odot \mathbf{h}_{t-1}, \mathbf{x}_t]) \]

最终隐藏状态

\[ \mathbf{h}_t = (1 - \mathbf{z}_t) \odot \mathbf{h}_{t-1} + \mathbf{z}_t \odot \tilde{\mathbf{h}}_t \]

LSTM vs GRU

对比 LSTM GRU
门的数量 3 个(遗忘、输入、输出) 2 个(重置、更新)
状态 细胞状态 \(\mathbf{C}_t\) + 隐藏状态 \(\mathbf{h}_t\) 只有隐藏状态 \(\mathbf{h}_t\)
参数量 更多 更少(约 LSTM 的 3/4)
效果 长序列通常更好 短/中序列效果接近,训练更快
使用建议 默认选择,尤其是长序列 数据量小或序列不太长时

实际应用中两者差距不大。如果不确定用哪个,默认 LSTM;如果追求训练速度,试试 GRU。


6. 双向 RNN(Bidirectional RNN)

标准 RNN 只能看到过去的信息。但很多任务需要同时参考上下文:

"他说他会__在周五。" → 需要看后面的"周五"才能填空

双向 RNN 用两个 RNN 分别从正向和反向处理序列,再拼接输出:

\[ \overrightarrow{\mathbf{h}_t} = \text{RNN}(\mathbf{x}_t, \overrightarrow{\mathbf{h}_{t-1}}) \]
\[ \overleftarrow{\mathbf{h}_t} = \text{RNN}(\mathbf{x}_t, \overleftarrow{\mathbf{h}_{t+1}}) \]
\[ \mathbf{h}_t = [\overrightarrow{\mathbf{h}_t}; \overleftarrow{\mathbf{h}_t}] \]

适用场景:文本分类、命名实体识别等可以看到完整序列的任务(不适用于实时生成)。


7. Seq2Seq 模型

Seq2Seq(Sequence to Sequence)是处理输入和输出长度不同的序列任务的经典框架,典型应用是机器翻译。

编码器-解码器架构

graph LR
    subgraph 编码器 Encoder
        E1[h₁] --> E2[h₂] --> E3[h₃] --> E4[h₄]
    end
    subgraph 解码器 Decoder
        D1[h'₁] --> D2[h'₂] --> D3[h'₃]
    end
    E4 -->|上下文向量 c| D1
    X1[我] --> E1
    X2[爱] --> E2
    X3[深度] --> E3
    X4[学习] --> E4
    D1 --> Y1[I]
    D2 --> Y2[love]
    D3 --> Y3[DL]

编码器:将输入序列编码为一个固定长度的上下文向量 \(\mathbf{c}\)(通常是最后一个隐藏状态)

解码器:以 \(\mathbf{c}\) 为初始状态,逐步生成输出序列

瓶颈问题

所有输入信息被压缩到一个固定长度的向量 \(\mathbf{c}\) 中——对于长句子,这个向量根本装不下所有信息。

这就是注意力机制被发明的直接动机

Attention 机制让解码器在生成每个词时,都能"回头看"编码器的所有隐藏状态,而不只是最后一个。详见 注意力机制


8. RNN 的实际应用技巧

多层 RNN(Stacked RNN)

将多个 RNN 层堆叠,下一层的输入是上一层的输出序列:

输入序列 → RNN 层 1 → RNN 层 2 → RNN 层 3 → 输出

通常 2~4 层效果最好,过深反而难训练。

Teacher Forcing

训练 Seq2Seq 时的常用技巧:

  • 没有 Teacher Forcing:解码器用自己上一步的预测作为下一步输入 → 一步错,步步错
  • 有 Teacher Forcing:解码器用真实标签作为下一步输入 → 训练更稳定,收敛更快

暴露偏差(Exposure Bias)

训练时用真实标签,推理时用自己的预测——训练和推理的分布不一致。解决方案:Scheduled Sampling(训练时逐渐从真实标签过渡到自己的预测)。

RNN vs CNN vs Transformer

特性 RNN / LSTM CNN (1D) Transformer
长距离依赖 困难(即使 LSTM 也有上限) 需要很多层 ⭐ 天然擅长
并行化 ❌ 必须逐步计算 ✅ 完全并行 ✅ 完全并行
训练速度
适用场景 短-中序列,实时流数据 局部模式提取 长序列,大规模预训练

RNN 还有用吗?

在 Transformer 统治 NLP 的今天,RNN 在某些场景仍有价值:实时流处理(无需完整序列)、小模型部署(参数少)、状态空间模型(Mamba 等新架构融合了 RNN 的思想)。但对于大多数序列任务,Transformer 已经是默认选择。