📑 本页目录(点开跳转)
13 / 先看直觉,再看细节
读到下一个词时,
还带着前面的记忆。
RNN 把上一步的隐藏状态与当前输入合在一起,形成新的状态。
先抓住一个具体结果
“好”和“不好”,差在前文
读到“好”时,如果状态保留了前面的“不”,模型才有机会区分两种意思。隐藏状态是压缩后的信息,不是完整保存原文。
简单 tanh RNN 的状态更新
接下来,按需要选一项
你现在想看什么?
这些入口是选择,不是必须按顺序完成的任务。
可以停在这里
RNN 把上一步的隐藏状态与当前输入合在一起,形成新的状态。
需要更多细节时,继续看完整正文 →原有正文、图解和例子都在下方。按需跳转,不必一次读完。
13 · RNN 与序列建模
⏱ 26 分钟 | 🎁 可快速过,但要理解它「为什么被淘汰」
🎯 一句话
RNN 用一个不断更新的记忆状态来处理序列。 它的思路是对的,但串行和长距离遗忘两个硬伤让它输给了 Transformer——理解这两个硬伤,你才真正懂 Transformer 赢在哪。
🤔 零、先问:序列数据到底特殊在哪
结果对照
前面学过的模型为什么处理不了序列:
| 模型 | 为什么不行 |
|---|---|
| MLP | 输入维度固定,句子长度可变;而且打乱词序结果不变(它看不到顺序) |
| CNN | 能看局部顺序(卷积窗口内),但看不到远距离依赖 |
| 树模型 | 每个特征独立看,完全没有顺序概念 |
🔑 所以需要一个新机制:能吃变长输入、能记住之前看过什么。 RNN 就是这个问题的第一个像样的答案。
🔄 一、RNN:把上一步的输出喂回来
💡 人话:h 是一个「记忆本」,每读一个新词就更新一次。
同一套权重 W、U 在每个时间步复用——这是 RNN 版的「权重共享」(对比第 12 章 CNN)。
💡 权重共享带来两个好处(和 CNN 完全同构的道理):
操作步骤
🔨 五行看懂 RNN
# 🧩 骨架:`hidden` 来自你自己的代码,这一段只看写法
import numpy as np
h = np.zeros(hidden)
for x_t in sequence: # ⭐ 注意这个 for 循环
h = np.tanh(W @ h + U @ x_t + b) # 更新记忆
y_t = V @ h # 输出
⚠️ 就是这个
for循环毁了 RNN。记住它,第二节会回来。
💀 二、两个致命硬伤
硬伤 1:梯度消失 → 记不住长距离
关键信息
后果:「我在法国长大……所以我说流利的 __」——隔了 50 个词,RNN 已经忘了「法国」。
🔗 这和第 8 章的梯度消失是同一件事,但更严重: 深度网络是"层数"次连乘,RNN 是"序列长度"次连乘—— 而且乘的是同一个 W(权重共享的代价),所以要么一起消失,要么一起爆炸。
⚠️ 梯度爆炸在 RNN 里比在 CNN 里常见得多,标准解法是梯度裁剪:
# 🧩 骨架:`model` 来自你自己的代码,这一段只看写法
import torch
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # ⭐ RNN 几乎必加
LSTM / GRU 的补救:加一条门控的记忆通道
关键信息
- LSTM 的核心是「细胞状态」C,它像一条传送带:
- 遗忘门 f:决定丢掉多少旧记忆
- 输入门 i:决定写入多少新信息
- 输出门 o:决定输出多少
- 关键的一行: Cₜ = f ⊙ Cₜ₋₁ + i ⊙ C̃ₜ
- ↑ 加法!不是反复相乘 ⭐
- → 当遗忘门 f ≈ 1 时,梯度沿 C 【几乎原样】传回去
- → 和 ResNet 残差连接同一个思想
📐 为什么"加法更新"能救梯度(想看再点)
普通 RNN:$\frac{\partial h_t}{\partial h_{t-1}} = W^\top\text{diag}(\tanh')$ —— 纯乘法,连乘 T 次必然指数衰减。
LSTM:$\frac{\partial C_t}{\partial C_{t-1}} = f_t$ —— 如果遗忘门 $f_t \approx 1$, 连乘 T 次还是 ≈ 1,梯度原样通过。
🔗 这和 ResNet 的 ∂a^l/∂a^{l-1} = f'W + I 里那个 +I 是同一个技巧:
给梯度留一条不经过乘法的直通路径。
💡 记住这个模式,它在深度学习里出现了至少四次: LSTM 的细胞状态、ResNet 的残差、Transformer 的残差、Highway Network 的门控。
GRU 是 LSTM 的简化版(两个门),参数少约 25%、更快,效果通常相当。
| LSTM | GRU | |
|---|---|---|
| 门的数量 | 3(遗忘/输入/输出) | 2(重置/更新) |
| 状态 | h 和 C 两条 | 只有 h |
| 参数量 | 多 ~33% | 少 |
| 效果 | 长序列上略好 | 中短序列相当,训练更快 ⭐ |
✅ LSTM/GRU 缓解了梯度消失,但没根治——几百步以上仍然吃力。
硬伤 2:串行,无法并行 ⭐ 这才是致命的
结果对照
一个具体的对比:
关键信息
🔑 这是 Transformer 取代 RNN 的根本原因。 不是因为 Transformer 更"聪明",而是因为它能并行,所以能训得更大、喂更多数据。 可扩展性(scalability)胜过了精巧的结构设计——这是深度学习史上反复出现的教训。
💡 同样的教训还出现在:CNN 打败手工特征、Transformer 打败 CNN 在视觉上(ViT)、 大模型的"scaling law"。一个能吃下更多算力的笨办法,往往赢过吃不下算力的聪明办法。
⚖️ 但 Transformer 也付出了代价
对照
RNN: 时间 O(T), 内存 O(1) ← 可以流式处理无限长序列
Transformer: 时间 O(T²), 内存 O(T²) ← 序列一长就爆 💀
💡 这就是为什么"长上下文"是大模型的核心难题—— 也是为什么 Mamba 这类"类 RNN"架构最近又被重新拾起。 RNN 输的是并行,赢的是复杂度。
🌉 三、通往注意力:Seq2Seq 的瓶颈
结果对照
💡 有多严重:实验显示 Seq2Seq 的翻译质量在句子超过 30 个词后急剧下降—— 因为那个固定向量装不下了。
注意力机制的诞生(2014-2015):
信息关系
💡 注意力的三个立刻可见的好处:
| 好处 | 说明 |
|---|---|
| 打破信息瓶颈 | 不再压成一个向量,所有位置的信息都还在 |
| 梯度捷径 ⭐ | 解码器到编码器任意位置只隔 1 步,不用穿过 T 个时间步 |
| 可解释 | 注意力权重能画成热力图,看到"翻译这个词时在看哪里" |
💡 然后 2017 年有人问了一个关键问题:
既然注意力这么好用,我们还需要 RNN 吗?
答案是那篇论文的标题:《Attention Is All You Need》—— 去掉 RNN,只留注意力,就是 Transformer。
🔗 完整的 Transformer 讲解在全景导论主线 2 · 模型怎样看懂一句话, 而下一章会把这条演化线补完。 现在你知道它是怎么来的了。
🗺️ 四、序列任务的四种形态
| 形态 | 典型任务 | 关键点 |
|---|---|---|
| 一对多 | 图像描述 | 一张图 → 一句话 |
| 多对一 | 文本分类、情感分析 | 只用最后一个 h |
| 多对多 | 机器翻译 | 长度可不同,需要 encoder-decoder |
| 同步多对多 | 词性标注、命名实体识别 | 输入输出一一对应 |
⚠️ 一个实践细节:多对一任务里,用最后一个 h 不一定最好—— 更常见的做法是对所有时间步的 h 做池化(mean/max),或者加一层注意力。 因为最后一个 h 天然偏向记住结尾的内容。
🔗 Kaggle 第 7 章命名实体识别讲的 CRF / GlobalPointer 就是最后一类的实战方案。
🤔 五、RNN 今天还有用吗
| 场景 | 用什么 |
|---|---|
| NLP 几乎所有任务 | Transformer(RNN 已基本退场) |
| 超长序列 + 极低延迟 + 小设备 | RNN/GRU 仍有一席之地(O(1) 内存,流式处理天然友好) |
| 时间序列预测 | 树模型常更强;LSTM 可作为对比基线 |
| 在线/流式场景(如实时推荐) | GRU 的增量更新很自然 |
| 状态空间模型(Mamba 等) | 新一代"类RNN"架构,试图兼得并行与线性复杂度 ⭐ |
💡 学 RNN 的价值不在于用它,而在于: ① 理解 Transformer 解决了什么问题 ② 理解「门控 + 加法更新」这个防梯度消失的通用思想 (LSTM 的 C、ResNet 的残差、Transformer 的残差,全是它) ③ 理解「并行性是架构设计的一等公民」这个教训
🔗 和站内其他章的关系
| 相关的地方 | 这里的对应 |
|---|---|
| 第 8 章梯度消失 | RNN 版更严重(连乘次数 = 序列长度,且乘同一个 W) |
| 第 12 章 CNN 的权重共享 | RNN 跨时间步共享,CNN 跨空间位置共享 |
| 第 12 章 ResNet 残差 | 和 LSTM 的加法式记忆更新是同一思想 |
| 推荐算法第 9 章 GRU4Rec | 就是本章的 GRU 用在序列推荐上 |
| 推荐算法第 9 章 SASRec 用 Transformer 取代 RNN | 本章讲的正是"为什么要取代" |
| 全景导论主线 2 的注意力 | 本章第三节讲的是它的起源 |
✅ 检查点
- 为什么 MLP 和 CNN 处理不了序列?
- RNN 的核心公式和直觉是什么?权重共享带来哪两个好处?
- RNN 的梯度消失为什么比普通深度网络更严重?梯度爆炸怎么处理?
- LSTM 靠什么缓解梯度消失?关键的那一行公式是什么?它和 ResNet 有什么共同点?
- RNN 的两个硬伤哪个更致命?为什么?
- Transformer 相比 RNN 赢在哪、输在哪?
- 注意力机制最初是为了解决什么问题?它带来了哪三个好处?
- 多对一任务里,为什么"用最后一个 h"不一定最好?
- 今天还有哪些场景 RNN 有优势?
👀 答案
- MLP 输入维度固定、打乱词序结果不变(看不到顺序);CNN 只能看卷积窗口内的局部顺序,看不到远距离依赖。
- hₜ = tanh(W·hₜ₋₁ + U·xₜ + b),一个不断更新的记忆本。权重共享带来:①参数量和序列长度无关 ②学到的规律能跨位置迁移。
- 因为连乘次数是序列长度(可达几百上千)而不是层数(几十),而且每次乘的是同一个 W——要么一起消失要么一起爆炸。爆炸用梯度裁剪
clip_grad_norm_(..., max_norm=1.0)。 - 门控的细胞状态 C。关键行:Cₜ = f ⊙ Cₜ₋₁ + i ⊙ C̃ₜ,是加法不是反复相乘;f≈1 时 ∂Cₜ/∂Cₜ₋₁ = f ≈ 1,梯度原样通过。和 ResNet 的
+I是同一技巧:给梯度留一条不经过乘法的直通路径。 - 串行更致命。梯度消失还能靠 LSTM 缓解,但串行限制了可扩展性——GPU 并行能力完全浪费,训不大。
- 赢在并行(时间 O(T) 串行 → 一次矩阵乘法),输在复杂度(O(T²) 时间和内存 vs RNN 的 O(1) 内存)。这就是长上下文难、Mamba 被重拾的原因。
- 解决 Seq2Seq 把整句压成一个固定向量的信息瓶颈(超过 30 词质量急剧下降)。三个好处:①打破瓶颈 ②梯度捷径(解码器到编码器任意位置只隔 1 步)③注意力权重可解释。
- 最后一个 h 天然偏向记住结尾内容。更好的做法是对所有时间步的 h 做 mean/max 池化,或加一层注意力。
- 超长序列+低延迟+小设备(O(1) 内存,流式友好)、在线增量更新场景;时间序列上树模型常更强,LSTM 只作基线。
🛑 可以停在这里
⚡ 走神救援
先记住这几件事
- RNN 在各时刻复用参数,把历史压进隐藏状态。
- LSTM、GRU 用门控制保留与更新,缓解长序列的信息和梯度问题。
- 注意批次、时间和特征维度;序列输出与最终状态不是同一个对象。
下一节 👉 14-通往Transformer.md