阶段 3 · 处理序列

LSTM:给记忆
装上阀门和传送带

上一章的死因是:每读一个词就把整个记忆重写一遍,写 100 次之后原来的信息就没了。 LSTM 的解法非常直接——加一条不打岔的传送带,再用几个"阀门"控制什么时候往上传、什么时候清空。

1

RNN 的记忆为什么保不住

回看 RNN 的公式:ht = tanh(Wxxt + Whht−1 + b)。

问题出在整个记忆每一步都被完全覆盖。第 1 个词的信息想活到第 100 步, 就得在 100 次 tanh 里都不被冲淡——而 tanh 的斜率除了 0 点都小于 1,一层层乘下来,越乘越小。

🎯 类比

把 RNN 想象成一个每收到一封新邮件,就把整个收件箱内容重写一遍的秘书。 第一封邮件的重要信息,在第一百次重写时早就面目全非了。
LSTM 换了做法:收件箱本身不动(细胞状态),秘书只决定「要不要删掉某一封」「要不要加一封新的」。 改动是加法而不是重写。

这就是《归一化层》那章里残差连接用的同一个想法: 给梯度修一条不经过任何非线性变换的直路。

💡 记住这个对比

RNN 每读一步就把整个状态重算一遍;LSTM 的 c 只做两件事:打一个折、加一笔。 f、i、g 是下一节的三个门,这里先记住形状。后者的关键是那个 +: 梯度可以通过加法原封不动地流过很多步—— 这条路径就是 LSTM 治好了梯度消失的原因。

下面让一个 4 维的普通 RNN 和一个 4 维的 LSTM 读同一段序列—— 只在第 1 步喂一个输入,其余时间步都喂 0——再在最后一步取损失 L = ½‖hn‖²,然后用真正的 BPTT 把梯度一路反传回第 1 步,看还剩多少。

互动 · 梯度传得回多远

序列长度
—
RNN 传回第 1 步的梯度
—
LSTM(c 通路)
—
RNN ÷ LSTM
—

⚠️ 读这张图的两个要点

① RNN 是一条笔直的斜线。对数坐标(每格差 10 倍)上这么直,说明每往回一步都在乘同一个小于 1 的数; 曲线从左往右在往上爬,所以反着读(梯度真正的传播方向)就是一路往下掉,几十步就滑到底。
② LSTM 那条几乎不下降,因为它的梯度除了每一步乘遗忘门 f,还会从 h 那条路拐回来一份。把遗忘门偏置拉到 −1 (f ≈ 0.27)再看:LSTM 也会掉下去——门控不是白给的,它得被学成接近 1 才有用。

2

三个门,各管一件事

LSTM 有两条线:细胞状态 c(长期记忆,走传送带)和 隐藏状态 h(短期输出,对外汇报)。 c 和 h 都是一串数(后面的演示把它设成 4 维,所以有 4 个「记忆槽」)。 每个门本身就是一个小网络:[ht−1, xt] 乘一组权重、过 sigmoid;那组权重和其他参数一起被训练出来。 三个门各自决定一件事:

门公式它决定什么直觉
遗忘门 fσ(Wf·[ht−1, xt]) 旧记忆保留多少 开度 0 = 全忘;1 = 全留。
遇到句号、段落切换时它会被学成接近 0
输入门 iσ(Wi·[ht−1, xt]) 新信息写进去多少 "这个新词重要吗?值得记下来吗?"
候选记忆 gtanh(Wg·[ht−1, xt]) 新信息的内容是什么 用 tanh 这个激活函数而不是 sigmoid,因为内容要有正有负
输出门 oσ(Wo·[ht−1, xt]) 把多少记忆报出去 记忆里可能存着"主语是复数"这类此刻不需要说的信息

鼠标停在公式里的 f、i、o 上——第 3 节「门控沙盘」里对应的那个滑块会亮。

⊙ 是"逐元素相乘"。注意 ct 的更新里有加法——这就是那条传送带。

这两行公式不算复杂,但"数据从哪流到哪"很容易看晕。下面把它画成一张结构图: 绿色粗线是 c 的传送带,四个方块是四个门,每个方块里的数字都是这个 1 维 LSTM 单元在当前这一步的取值。 按 ①→⑥ 逐个部件高亮,亮起来的就是这一步真正在发生的数据流。

互动 · 一个 LSTM 单元的逐步拆解(渐进高亮)

上面三个滑块改的是输入(当前词 x(t)、上一步的 h 和 c), 四个门里所有的数会跟着重算——门不是被拖出来的,是被算出来的。

M

数学 · 一条传送带,四个阀门

上一节那四条式子,其实只做两件事:先把记忆改一遍,再把记忆读出来。 而“改”的全部内容就是下面这一条——它只有乘法和加法,没有任何一步会把数值重新算一遍。 (这一节是另一组演示数据——权重和输入都是手工设定的,不是训练出来的。)

动画 · 真的算一遍:这一步的记忆被改成了多少

示意图 · 传送带上到底在发生什么

记忆 c:只做「乘一个数、加一个数」,从头到尾不重新计算 × f + i⊙g × f + i⊙g × f + i⊙g h = o ⊙ tanh(c)(只是报告,不动带子) 读下去 →

图上三个站台就是三个时间步。每一步只对记忆做两件事:打个折、加一笔—— 这和 RNN「把整个记忆重写一遍」是两回事。

🎯 用「银行账户」理解这一条式子

把记忆 c 想成一个余额:
· f = 旧余额保留几成(不是清零,是打折)
· i ⊙ g = 这一笔要存进多少(g 可正可负,所以能存也能取)
所以 ct = f·ct−1 + i·g 就是「打折后的旧余额,加上这一笔新账」。
转账是加法,不是把账本撕了重写——梯度就是从这条加法里原样传回去的。

那为什么不干脆 c ← c + i·g,永远不忘?

1997 年最早的 LSTM 就是这样——没有遗忘门。结果记忆只增不减,装满之后旧东西清不掉,一换任务就卡住; 2000 年 Gers 等人才补上 f,才有了今天这一版(第 8 节那张论文卡就是它)。

上面那一行是活的:拖本节那根时间滑块,它会跟着变。

🎬 自己验一遍

把时间滑块拖到第 7 个词,盯着读数里 f 和 c 的那几个数:
· f 在 0.5~0.8 的步,旧值能留下一半以上;
· f 掉到 0.36 那一步(第 7 个词、第 2 个记忆槽),旧值 −0.29 先打折成 f×(−0.29) = −0.10, 再加上这一步新写入的 +0.19,得到新的 c = 0.09。
全程没有任何一步“重新计算”过记忆——它只是被乘了一下、又加了一下。

3

亲手玩一遍门控

下面是一个真实的 LSTM 单元在跑。这是一组手工设定的演示数据(每个词的写入强度 g 是给定的,不是训练出来的), 拖动三个门的开度,看细胞状态怎么被保留、覆盖、输出。 序列是:"我 昨天 在 公园 看见 一只 猫。它 很 可爱"—— "它"指的是"猫",中间隔着一个"。",也就是 2 个时间步。

互动 · 门控沙盘

👆 试试这几个操作

① 把遗忘门拉到 1、输入门拉到 0:一个字都不写进去,记忆永远是空的(曲线贴在 0 上)。
② 把遗忘门拉到 0:每读一个词就清空记忆,退化成"只看当前词"。
③ 把两者都调到 0.5 附近:可以看到记忆衰减得很明显。(把 f 拉到 0.9 以上,同样的信息能撑得久得多。)

上面的沙盘是"给定一组门,看结果"。真实的 LSTM 不是这样工作的:它随着每个词一步步改主意。 这是另一组演示数据,也全是手工设定的——序列是: "张三 出生 在 北京。李四 出生 在 上海。他 说 的 是 上海 话"。 每个词编成一个 6 维向量,跑一遍 4 维的 LSTM 单元—— 横轴是 token,四条彩线是四个记忆槽(槽0 存人名、槽1 存第二个人名、槽2 存地点。 这条分工也是手工安排的,目的是让你看清门在做什么), 上面三条是三个门在同一时刻的开度。拖滑块或者点播放,看记忆怎么被写、被清、被报出来。

互动 · 门控沿 token 逐个展开

互动 · 记忆到底记得多久:f 决定半衰期

💡 在这张图上要看到的三件事

① 门是一个向量,不是一个开关。读到「。」时,遗忘门在【槽2 地点】掉到 0.02, 在另外三个槽还是 0.75——「北京」被清掉了,人名留着。第 2 节表里那一行 f,其实是 4 个数。
② 读到「他」,遗忘门一起冲到 0.98。因为接下来还要用这份记忆,它必须撑住; 同时输出门从 0.23 开到 0.94,把记忆报出来。
③ 记忆一直在按 0.75 衰减,所以"最近写进去的那一笔"数值最大—— 读到「他」这一刻(第 11 个词),槽1(李四)0.26 大于 槽0(张三)0.07, 模型就是这样判断「他」指谁的,这里没有写任何一条"指代规则"。 (拖到第 16 个词「话」时表里是 0.02 和 0.06;那是又走了 5 步之后的数,不是同一行。)

4

GRU:把三个门砍成两个

2014 年提出的 GRU 做了一次精简:把细胞状态和隐藏状态合并,三个门砍成两个。

对比项LSTMGRU
状态两条:c(长期)+ h(短期)一条:h 合并了两者
门数量3 个(遗忘 / 输入 / 输出)2 个(重置 reset / 更新 update)
参数量4 组权重矩阵3 组,少 25%
传送带有一条独立的 c 通道,梯度路径最干净 没有独立通道,但用 1−z 的耦合设计部分补偿
效果长序列、需要精确记忆时略好大多数任务上和 LSTM 打平,每步计算也少一些
实际怎么选 差别很小,别纠结。标准建议:先试 GRU(每步计算少一些,但一样要一步一步走), 如果长距离依赖明显不够,再换 LSTM。

互动 · GRU 的一个时间步:更新门 z 怎么把两个门合起来

LSTM 把职责分得很细:三个门各管一件事,另有一条独立的传送带。GRU 把其中两件事合并成两个门、一条状态; 计算少一些,但一样要按时间顺序一步一步算。

5

同一条传送带,还能怎么改

变体改了什么为什么
Peephole LSTM让门能"看到"细胞状态本身 有些任务上更准,但实际收益不稳定
Coupled forget-inputf = 1 − i(两个门绑在一起) 少一组参数,效果几乎不变。GRU 的更新门用的是同一种耦合思路(Cho 2014)
BiLSTM正反两个 LSTM,输出拼接 每个词同时看到左右两边。但只能当编码器:解码时后文还没生成出来,看不到
ConvLSTM把矩阵乘法换成卷积 处理视频、降水预报这类"有空间结构的序列"。参数量反而约 9 倍:3×3 核的每个输出通道都要看全部输入通道
mLSTM / xLSTM2024 年的现代版本:改成分块并行(长记忆切成块、块内并行算)+ 指数门控(门能开得更大) 让 LSTM 也能像 Transformer 一样并行训练,重新回到研究视野

一张表 · 这几个变体到底改了什么(h = d = 512,一层)

2024 年 Sepp Hochreiter(LSTM 的发明者本人)团队提出了 xLSTM, 把 LSTM 的两个关键弱点(不能并行、记忆容量固定)都改掉了,在语言建模上能和 Transformer 掰手腕。 表里五行,前面四行都在微调参数,只有它动的是这两件事——门控这个思想本身并没过时, 过时的只是 1997 年的实现方式。

6

治好了什么,没治好什么

问题LSTM 治好了吗说明什么时候真的会痛 → 换什么
梯度消失大幅缓解 加法路径让梯度不被反复缩放。但路径长度仍是 O(n),超长序列还是会忘 要跨几百步记住一个词 → 还得靠注意力(下一章起)
梯度爆炸没治 仍然需要梯度裁剪 层数一深、序列一长,阈值没调好训练就崩 → 梯度裁剪(《初始化》那章)
不能并行完全没治 ht 依赖 ht−1,串行链一点没变。这是它被淘汰的真正原因 序列长、模型大、要压训练时间 → 注意力 / Transformer
记忆容量固定完全没治 不管句子多长,还是只有固定大小的 c 和 h 要记住的东西比 c 还宽 → 外挂记忆 / 检索,或注意力
训练慢更慢了 参数量是 RNN 的 4 倍,而且还是串行 同样的预算想训更大模型 → 先换 GRU(少一组权重),或换能并行的结构

互动 · 同一个隐藏维度,四个结构各要多少参数

LSTM ÷ 简单 RNN
—
GNMT 编码+解码 16 层(h=1024)
—

互动 · 为什么它输给了 Transformer:T 步就得等 T 次

⚠️ 一个容易忽略的成本

LSTM 的参数量是同样隐藏维度的简单 RNN 的 4 倍(4 组门权重)。 在 2015 年这不是问题,但在今天动辄几百亿参数的语境下, 用 4 倍的参数去换一个仍然不能并行的结构,性价比就说不通了。 这才是 Transformer 胜出的经济账。

7

小结

上一章说 RNN 对世界做了一个假设(“规则不随位置改变”)。 这一章把假设换了一种装法——而且换装的这一步,是整个序列部分最关键的一步。

它对应哪条线 ② 没有免费午餐 → 必须有归纳偏置—— 三个门就是对世界的一个断言,而 LSTM 的全部精妙在于:这个断言不是写死的,是可以学的。
一句话 这一章断言:“记忆该保留多久,可以从当前读到的东西里读出来”: 读到句号就清空,读到人名就记住。所以门不是常数,而是 [ht−1, xt] 的函数—— “什么该忘”从结构上写死(RNN:每步全部重写),变成了一个 0~1 之间可滑动、可学的参数。
它牺牲了什么 牺牲了成本:4 组门权重是同样隐藏维度的 RNN 的 4 倍, 而串行链一点没变。更讽刺的是,多出来的那部分参数量并没有买到对应的效果—— GRU 用两个门就打得平,参数还少一组(第 4 节那张表)。 这也是“规模会赢”在这一章的影子:再精巧的结构,也算不过算力账。
🎬 自己验一遍

回到第 3 节那个互动(标题是「门控沙盘」)。 把「遗忘门 f」滑块拉到 1,「输入门 i」拉到 0——曲线贴着一根 0 线不动: 一个字都没写进去,记忆只能是空的。

然后把「输入门」拉回 0.6,「遗忘门」停在 0.6~0.8 之间:记忆开始缓慢衰减,但“猫”留下的那一笔 能一直活到“它”出现。(真实的遗忘门通常比这大——这也是半衰期长、能撑过一句话的原因。)

那个瞬间你看到的就是「一个可学出来的归纳偏置」: 如果“该忘多少”是个常数(比如死写 0.5), 模型就只能看着信息均匀流失;而因为它是 [ht−1, xt] 的函数, 它才有机会学到"读到句号时把它关小一点"。 你如果没想指着那条曲线说这句话,就把遗忘门再拉一遍。

它在暗线里站在哪

下面就是这一章的答案——你读后面的章时会发现,每章都有这么一块。

暗线这一章的回答
A 信息流动 数据形状:每步把 [batch, h] 和 [batch, d] 拼成 [batch, h+d], 乘一个 [h+d, 4h] 的大矩阵,切成四份分别过 σ / σ / tanh / σ, 再按 c ← f⊙c + i⊙g、h ← o⊙tanh(c) 算出两个新状态。
关键是 c 和 h 形状一样、路径不同:c 从头到尾不经过 tanh, 只参与加法和逐元素乘法。这就是那条“传送带”在代码里的真实形状
B 什么被牺牲了 牺牲了参数量(4 组门权重,是同尺寸 RNN 的 4 倍) 和实现简单度;而困扰 RNN 的两个真正的大问题—— 不能并行、记忆容量固定——一个都没解决。
换来的是一样很硬的东西:梯度路径上只剩一个乘法因子。 衰减从“必然指数下降”变成“可控”—— 它不需要记住得更久,只需要让不想让它忘的地方不用乘。
另一个意外的好处:推理状态仍然是 O(1),不需要 KV cache
C 参数账本 单层公式:4 × (h×(h+d)) + 4h。
以 h=512、d=512 为例:4×512×1024 + 2048 ≈ 210 万参数—— 恰好是单层简单 RNN(52.5 万)的 4 倍; 一层 Transformer 的注意力在同样维度下约 105 万, 也就是说LSTM 比注意力层还贵一倍。
真实规模的例子:2016 年 Google 的 GNMT 机器翻译系统用了 编码器、解码器各 8 层 LSTM,每层 1024 维隐藏(光这 16 层 LSTM 的权重就约 1.34 亿个,不含词表那部分), 训练在 96 块 K80 GPU 上跑了大约 6 天。 这就是“算力账决定架构”的具体含义
D 它假设了什么 两条:
① “记忆的价值会随时间衰减,而衰减多少可以从当前输入里读出来”。 这是一个关于语言的断言——它假设句号、段落、话题切换是可检测的信号, 读到它们就该清空。
② “保留量和写入量是两个应当分开学的东西”——所以有 f 和 i 两个门。 但 Coupled forget-input(f = 1−i)说明这条假设并不必要, 效果几乎不变还少一组参数——这正是 GRU 砍掉一个门的依据
E 违背了哪个直觉 “忘得少就是记得好”——不,遗忘门干的主要是“保护”。 训练完之后它学到的值通常偏大(不少槽接近 1),也就是几乎不忘; 它的作用不是删除旧信息,而是防止旧信息被新信息覆盖。
第二个:“记得更久需要更大的容量”——不是。 c 的大小和 RNN 的 h 一模一样,更长的记忆靠的是一个接近 1 的乘积,而不是更大的盒子。
第三个:“1997 年的结构早该退场”——2024 年的 xLSTM 用同一套门控思想回到了前沿。
🎯 前后钩子

它接住了上一章的什么:RNN(《RNN 循环神经网络》) 的病是“每个时间步把记忆整个重写一遍”,所以梯度必然指数衰减。 这一章把“重写”换成了“打折 + 追加”。

它给下一章留了什么:LSTM 解决了“怎么记住”,却没法解决“怎么不用一个字一个字地读”。 而只要还在逐字递推,就永远不可能并行。 要打断这条串行链,就得允许解码时回头看整个原文—— 这就是《Seq2Seq 与注意力》要回答的问题。

一句话带走 LSTM

LSTM = 给记忆加一条不打岔的传送带 + 三个阀门。 关键是那个加法 ct = f·ct−1 + i·g——它让梯度原封不动穿过很多步。
GRU 是精简版:两个门、一条状态,效果接近,参数少一组。
但它只治好了梯度消失,没治好"不能并行"和"记忆有瓶颈"—— 瓶颈归更后面的《注意力机制》,并行归《Transformer 自注意力》。

8

拓展阅读

上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。

📄 这一章的说法从哪来

💻 工业界怎么写

∑ 更严格的形式