RNN 与 LSTM 入门
这一篇可以跳。如果你只想读 Transformer,直接翻下一篇就行。 但是理解 Transformer之前是什么样子、它具体解决了哪些痛点, 是理解「Transformer 为什么是个那么剧烈断裂」的最干净的方式。 三个短主题:RNN,一次看一个 token 的基础循环网络;LSTM / GRU,带门控、专门对付长程依赖的变种; 以及Transformer 为什么把它们全部干掉了 —— 并行化、真正的长距离记忆、以及 encoder-decoder 信息瓶颈的终结。
RNN —— 一次一个 token
沿序列携带的一个定长「记忆」。
文本 primer §4 在 Transformer 之前的图景里列过 RNN 一类模型。这里我们打开一台看看。 基础的 RNN(Recurrent Neural Network,循环神经网络)是处理变长输入 最简单的网络:从左到右走一遍序列,每一步把新 token 和「迄今为止看过的所有东西的记忆」 组合起来。
配方就一行更新规则,每个 timestep 用一次:
h_t = tanh(W_h · h_{t−1} + W_x · x_t + b)
h_t 是第 t 步的 hidden state —— 一个定长向量、网络对「目前 为止整个序列」的全部总结。x_t 是新 token 的 embedding。W_h 是循环之前的 hidden state、W_x 是把新输入投影一下,tanh 把两边压到一起。关键的一点:W_h、W_x、b 在每一个 timestep 上都一模一样 —— 这就是「跨时间的 参数共享」,让同一套权重能处理任意长度的序列。
对于「最后只要一个输出」的任务(情感分类、下一 token 预测),取最后的 hidden state 投影一下就行:
往前走一遍: h_0 = tanh( 0 + W_x · x_0 + b ) ← "The" h_1 = tanh( W_h · h_0 + W_x · x_1 + b ) ← "dog" h_2 = tanh( W_h · h_1 + W_x · x_2 + b ) ← "runs" h_3 = tanh( W_h · h_2 + W_x · x_3 + b ) ← "fast" 读答案: y = W_o · h_3 + b_o ← 预测
RNN 做对了的事:
- 变长。同一套权重,循环跑多少次都行。5 个 token 的句子和 5000 个 token 的文档用的是完全一样的参数。
- 顺序天生就被尊重。token
x_t只能影响t时刻以后的 hidden state;循环强制了「严格从左到右读」。 - 参数量小。每个「角色」一个矩阵(
W_h、W_x、W_o),哪都用同一个。一个训练过的语言 RNN 总共可能只有几百万参数。
RNN 做错了的事:
- 顺序的。没算出
h_4之前没法算h_5。 一个样本内部跨时间没法并行,这对 GPU 是灾难。 - 沿时间的梯度消失 / 爆炸。算
∂L/∂h_0就是沿着整条链反向走一遍、每步都乘一次W_h。要么‖W_h‖ < 1(梯度衰到 0)、要么‖W_h‖ > 1(梯度爆 NaN)。反向传播 primer §3 讲过的那种失败模式。 - 容量有限的记忆。hidden state 是定长向量。把一段 500 token 的话 的意思塞进 1024 个浮点数是有损压缩;旧信息会被新信息覆盖。
第一个缺点对 GPU 来说是杀手锏。第二个和第三个让即便小一点的 RNN 也搞不定几十个 token 以上的东西。LSTM 和 GRU(§2)正面解梯度问题; Transformer(下一篇 primer)干脆把循环扔了、一次性把三件事都解了。
LSTM & GRU —— 带门控的记忆
在「高速通道」上独立流动的 cell state,由可学的门控制。
基础 RNN 最大的弱点是 §1 说的那个「沿时间的梯度」问题:往回走 20 步, 就乘了 20 次 W_h。信号要么变尘埃,要么变 NaN。 Hochreiter 和 Schmidhuber(1997)给了一个聪明的架构修法:多带一个独立的 cell state c_t,沿时间线大致不被打扰地流过去,然后让可学的门在每一步决定:丢什么、加什么、暴露什么。这就是 LSTM(Long Short-Term Memory)。
一个 timestep 的完整更新 —— 看起来吓人其实没那么吓人:
Forget 门: f_t = σ(W_f · [h_{t-1}, x_t] + b_f)
Input 门: i_t = σ(W_i · [h_{t-1}, x_t] + b_i)
候选项: ĉ_t = tanh(W_c · [h_{t-1}, x_t] + b_c)
更新 cell: c_t = f_t · c_{t-1} + i_t · ĉ_t
Output 门: o_t = σ(W_o · [h_{t-1}, x_t] + b_o)
Hidden 状态: h_t = o_t · tanh(c_t)每个门都是 sigmoid(值在 [0, 1]),按元素乘到它控制的东西上。每一行像一句话来读:
- Forget(忘记) —— 「旧的 cell state,留这么多比例」。
f = 1全留;f = 0抹平。 - Input + candidate(写入 + 候选) —— 「往 cell 里写多少(i)、写什么(ĉ)」。
i = 0什么都不写;候选项本身是一坨 tanh 限幅的「新信息」。 - Output(输出) —— 「(已更新的)cell state,暴露这么多比例作为新的 hidden state h_t」。hidden state 是流向下一 timestep 的门、以及流向 LSTM 上层的东西。
这一招为什么能解梯度消失 —— 道理细腻又漂亮。看 cell state 的更新:c_t = f_t · c_{t-1} + i_t · ĉ_t。c_t 对 c_{t-1} 的导数就恰好是 f_t。 如果网络在某个 cell 维度上学到「连续多个 timestep 的 f_t ≈ 1」, 那梯度反向走过这些步基本就不衰 —— 1 乘 20 次还是 1。对比原版 RNN, 反向每一步都不管三七二十一地乘一次 W_h。LSTM 给网络一个「自己选什么时候忘」 的旋钮;原版 RNN 没有这个旋钮。
GRU(Gated Recurrent Unit,2014)是一种流行的简化版: 只有两个门(reset 和 update),没有独立的 cell state。参数比 LSTM 少 25% 左右、稍快、 实战上准确率经常分不出来。你的框架里有谁的绑定就用谁。
LSTM / GRU 比原版 RNN 多搞定的事:
- 长距离依赖变得可行。100、500,甚至 1000 个 token 的上下文都能训。 只要 forget 门维持在 1 附近,cell state 可以保留很长一段距离的信息。
- 训练稳定。长序列上不再容易爆 NaN。 原版 RNN 需要各种 trick(梯度裁剪、小心初始化);LSTM 基本上拿来就能用。
没解决的事:
- 还是顺序的。第
t步的门值依赖h_{t-1}、 再依赖h_{t-2}、以此类推。GPU 仍然讨厌这件事。 - 记忆依然有限。cell state 是定长向量 —— 更大的上下文窗口 只意味着更多信息挤同一些浮点数。
- 处理 n 个 token 依然是 O(n) 个顺序操作。1024 维 hidden state 的 LSTM 每步很快,但每一步都堵下一步。
2014 到 2017 年,LSTM 是序列建模的主流架构 —— 神经机器翻译、语言建模、语音识别。 Google translate 2016 年的重写是一个深层 LSTM。ELMo(2018)是第一个被广泛使用的 上下文化 embedding、用的是双向 LSTM。然后 attention 接管了一切。
Transformer 凭啥把它们都干掉了
Transformer 一次性带来、而 LSTM 怎么都搞不到的三个结构性优势。
大约从 2017 年起,LSTM 在 benchmark 上慢慢消失。到 2020 年,你已经很难找到一篇 最新的 NLP 论文还把它当主架构在用。Transformer(Vaswani 等,2017) 把它们替换得如此彻底,以至于整个领域在三年内重新装备了一次。为什么? 三个性质 —— 每一个都是对 §1 和 §2 里 RNN 系某个具体弱点的正面回应。
1. 并行化。
RNN 的第 t 步必须等 t − 1 步结束才能开始。 hidden state 只能按顺序算,没商量。现代 GPU 有几千个并行核; 在上面跑 LSTM 几乎让所有核闲着。即使有 batch 维度的并行(多个序列并行),序列内部的工作仍然是顺序的。
Transformer 的 self-attention 层正相反:每个位置的输出并行计算。 位置 1、位置 5、位置 5000 都在同一个瞬间处理。一次 Transformer 前向 在一个 batch_size=64 × seq_len=2048 的数据上,GPU 完全填满; 同样的 batch 跑 LSTM,即使在 batch 维度上充分利用 GPU, 每个序列也要慢 2048 倍。这就是「Transformer 在规模上吃掉 LSTM」的最大原因。
2. 真正的长距离记忆。
就算有 LSTM 那个聪明的 forget 门,从 token 1 来的信息要影响 token 1000, 也得过 999 个 timestep 的门控算术。门可以让它原样穿过去 (理论上每步都让 forget ≈ 1 是可以的),但实战中网络得学到对每一个 要保留的维度都这么做,这是个很难的优化问题。真实的 LSTM 在距离上是平滑衰减的。
在 Transformer 里,位置 1 和位置 1000 通过 attention直接相连。 模型把它们的 query 和 key 相乘、得到相似度、过 softmax 加权进来。 位置 1 对位置 1000 的贡献,不用过 999 个中间门 —— 而是过一条 attention 边。 任意两个 token 之间的「有效路径长度」是 O(1),不是 O(n)。
3. 没有信息瓶颈。
对于 encoder-decoder 任务(翻译、摘要),RNN encoder 读完整个源序列, 产生一个最终 hidden state。这个定长向量被一次性交给 decoder, decoder 必须从这一个向量里生成整段输出。翻译一个 200 词的段落: 整段意思必须塞进一个几千维的浮点向量里、然后 decoder 再一个词一个词地解开。 Bahdanau 等(2014)意识到这是个瓶颈,提出了一个 attention 机制, 让 decoder 能看到所有 encoder hidden state、不光是最后那一个。 这本身就给翻译质量带来了巨大提升 —— 也埋下了那个伏笔: 也许「循环」本身就是个错误的原语?
Vaswani 2017 给的答案是「对」。RNN 干脆扔掉。 attention 在 encoder 和 decoder 的每一层都用。 现在每一个输出位置都能直接关注每一个输入位置 —— 没有瓶颈、没有褪色的记忆、没有顺序的堵点。
Transformer 为这三个优势付的代价:
- 序列长度的平方级内存 / 算力。n 个 token 之间的成对 attention 是 n² 个 entry。n = 1024 还行;n = 1,000,000 就炸了。 2020 年之后一半的 ML 系统研究都是「高效 attention」(FlashAttention、稀疏 attention、 线性 attention、滑动窗口、ring attention)在试图打破这个平方上限。
- 参数量更大。Transformer 每层都需要独立的 Q、K、V 投影, 再加 FFN 子层。一般规模的 Transformer 参数量就远超 LSTM 当年的量级。 一部分是好事(更大容量),一部分是浪费。
- 顺序必须显式注入。attention 本身顺序无关,所以 Transformer 需要 位置编码 —— 文本 primer §2 里粗讲过,下一篇 primer 细讲。
对我们关心的那些序列(文本、代码、音频帧、kilotoken 量级的任何 tokenized 数据), 优势远远超过代价。于是有了过去 7 年。
简单说一下 RNN 卷土重来的地方:超长序列(比如 DNA、几小时的音频) attention 的 n² 代价变得不可承受。Mamba(2023)和其它状态空间模型、以及 RWKV(2023), 用现代训练技巧复活了 RNN 风格的循环 —— 在极端长度上反过来打败 Transformer。 不过对 2026 年 LLM 那种典型的 4k–128k 上下文窗口,Transformer 三个优势仍然是主线故事。
这就是历史背景。下一篇 primer 把 Transformer 本身一块一块拆开 —— block 一个个、子层一个个 —— 你会看到它怎么用同一套架构同时交付这三个性质。