循环神经网络与序列模型¶
RNN 系列网络专为序列数据设计——文本、语音、时间序列等。它们的核心能力是"记住过去",让当前的输出受到之前输入的影响。
1. 为什么需要 RNN?¶
前馈网络(MLP、CNN)的输入和输出是固定大小的,且每次预测独立,不考虑顺序关系。但很多任务天然是序列性的:
- 文本:"我 喜欢 深度 学习" → 词的顺序决定含义
- 语音:音频帧按时间排列,前后帧高度关联
- 股票:今天的价格与过去的走势相关
关键需求
我们需要一种网络,能接收变长序列输入,且对序列中的每个元素都能参考之前的上下文信息。
2. RNN 的基本结构¶
核心思想¶
RNN 在处理序列时,维护一个隐藏状态(Hidden State) \(\mathbf{h}_t\),它像一个"记忆体",将之前时间步的信息传递给当前时间步。
其中:
- \(\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 \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) 缓解:当梯度范数超过阈值时,按比例缩小。
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):决定从细胞状态中丢弃哪些信息
输入门(Input Gate):决定哪些新信息写入细胞状态
更新细胞状态:
输出门(Output Gate):决定细胞状态中哪些信息输出为隐藏状态
其中 \(\sigma\) 是 Sigmoid 函数(输出 0~1,表示"保留多少"),\(\odot\) 是逐元素乘法。
直觉类比
LSTM 就像一个带橡皮擦的笔记本:
- 遗忘门 = 橡皮擦:擦掉不再重要的旧笔记
- 输入门 = 笔:决定写下哪些新内容
- 输出门 = 选择性阅读:决定现在需要用到笔记本中的哪些内容
为什么 LSTM 能解决梯度消失?¶
关键在于细胞状态的更新方式:
这是一个加法操作(不是连乘!)。当 \(\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):决定忘记多少历史信息
更新门(Update Gate):决定新旧信息的混合比例
候选隐藏状态:
最终隐藏状态:
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 分别从正向和反向处理序列,再拼接输出:
适用场景:文本分类、命名实体识别等可以看到完整序列的任务(不适用于实时生成)。
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 层堆叠,下一层的输入是上一层的输出序列:
通常 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 已经是默认选择。