上一章的死因是:每读一个词就把整个记忆重写一遍,写 100 次之后原来的信息就没了。 LSTM 的解法非常直接——加一条不打岔的传送带,再用几个"阀门"控制什么时候往上传、什么时候清空。
回看 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 是一条笔直的斜线。对数坐标(每格差 10 倍)上这么直,说明每往回一步都在乘同一个小于 1 的数;
曲线从左往右在往上爬,所以反着读(梯度真正的传播方向)就是一路往下掉,几十步就滑到底。
② LSTM 那条几乎不下降,因为它的梯度除了每一步乘遗忘门 f,还会从 h 那条路拐回来一份。把遗忘门偏置拉到 −1
(f ≈ 0.27)再看:LSTM 也会掉下去——门控不是白给的,它得被学成接近 1 才有用。
LSTM 有两条线:细胞状态 c(长期记忆,走传送带)和 隐藏状态 h(短期输出,对外汇报)。 c 和 h 都是一串数(后面的演示把它设成 4 维,所以有 4 个「记忆槽」)。 每个门本身就是一个小网络:[ht−1, xt] 乘一组权重、过 sigmoid;那组权重和其他参数一起被训练出来。 三个门各自决定一件事:
| 门 | 公式 | 它决定什么 | 直觉 |
|---|---|---|---|
| 遗忘门 f | σ(Wf·[ht−1, xt]) | 旧记忆保留多少 | 开度 0 = 全忘;1 = 全留。 遇到句号、段落切换时它会被学成接近 0 |
| 输入门 i | σ(Wi·[ht−1, xt]) | 新信息写进去多少 | "这个新词重要吗?值得记下来吗?" |
| 候选记忆 g | tanh(Wg·[ht−1, xt]) | 新信息的内容是什么 | 用 tanh 这个 |
| 输出门 o | σ(Wo·[ht−1, xt]) | 把多少记忆报出去 | 记忆里可能存着"主语是复数"这类此刻不需要说的信息 |
鼠标停在公式里的 f、i、o 上——第 3 节「门控沙盘」里对应的那个滑块会亮。
⊙ 是"逐元素相乘"。注意 ct 的更新里有加法——这就是那条传送带。
这两行公式不算复杂,但"数据从哪流到哪"很容易看晕。下面把它画成一张结构图: 绿色粗线是 c 的传送带,四个方块是四个门,每个方块里的数字都是这个 1 维 LSTM 单元在当前这一步的取值。 按 ①→⑥ 逐个部件高亮,亮起来的就是这一步真正在发生的数据流。
互动 · 一个 LSTM 单元的逐步拆解(渐进高亮)
上面三个滑块改的是输入(当前词 x(t)、上一步的 h 和 c), 四个门里所有的数会跟着重算——门不是被拖出来的,是被算出来的。
上一节那四条式子,其实只做两件事:先把记忆改一遍,再把记忆读出来。 而“改”的全部内容就是下面这一条——它只有乘法和加法,没有任何一步会把数值重新算一遍。 (这一节是另一组演示数据——权重和输入都是手工设定的,不是训练出来的。)
动画 · 真的算一遍:这一步的记忆被改成了多少
示意图 · 传送带上到底在发生什么
图上三个站台就是三个时间步。每一步只对记忆做两件事:打个折、加一笔—— 这和 RNN「把整个记忆重写一遍」是两回事。
把记忆 c 想成一个余额:
· f = 旧余额保留几成(不是清零,是打折)
· i ⊙ g = 这一笔要存进多少(g 可正可负,所以能存也能取)
所以 ct = f·ct−1 + 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。
全程没有任何一步“重新计算”过记忆——它只是被乘了一下、又加了一下。
下面是一个真实的 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 步之后的数,不是同一行。)
2014 年提出的 GRU 做了一次精简:把细胞状态和隐藏状态合并,三个门砍成两个。
| 对比项 | LSTM | GRU |
|---|---|---|
| 状态 | 两条:c(长期)+ h(短期) | 一条:h 合并了两者 |
| 门数量 | 3 个(遗忘 / 输入 / 输出) | 2 个(重置 reset / 更新 update) |
| 4 组权重矩阵 | 3 组,少 25% | |
| 传送带 | 有一条独立的 c 通道,梯度路径最干净 | 没有独立通道,但用 1−z 的耦合设计部分补偿 |
| 效果 | 长序列、需要精确记忆时略好 | 大多数任务上和 LSTM 打平,每步计算也少一些 |
| 实际怎么选 | 差别很小,别纠结。标准建议:先试 GRU(每步计算少一些,但一样要一步一步走), 如果长距离依赖明显不够,再换 LSTM。 | |
互动 · GRU 的一个时间步:更新门 z 怎么把两个门合起来
LSTM 把职责分得很细:三个门各管一件事,另有一条独立的传送带。GRU 把其中两件事合并成两个门、一条状态; 计算少一些,但一样要按时间顺序一步一步算。
| 变体 | 改了什么 | 为什么 |
|---|---|---|
| Peephole LSTM | 让门能"看到"细胞状态本身 | 有些任务上更准,但实际收益不稳定 |
| Coupled forget-input | f = 1 − i(两个门绑在一起) | 少一组参数,效果几乎不变。GRU 的更新门用的是同一种耦合思路(Cho 2014) |
| BiLSTM | 正反两个 LSTM,输出拼接 | 每个词同时看到左右两边。但只能当编码器:解码时后文还没生成出来,看不到 |
| ConvLSTM | 把矩阵乘法换成卷积 | 处理视频、降水预报这类"有空间结构的序列"。参数量反而约 9 倍:3×3 核的每个输出通道都要看全部输入通道 |
| mLSTM / xLSTM | 2024 年的现代版本:改成分块并行(长记忆切成块、块内并行算)+ 指数门控(门能开得更大) | 让 LSTM 也能像 Transformer 一样并行训练,重新回到研究视野 |
一张表 · 这几个变体到底改了什么(h = d = 512,一层)
2024 年 Sepp Hochreiter(LSTM 的发明者本人)团队提出了 xLSTM, 把 LSTM 的两个关键弱点(不能并行、记忆容量固定)都改掉了,在语言建模上能和 Transformer 掰手腕。 表里五行,前面四行都在微调参数,只有它动的是这两件事——门控这个思想本身并没过时, 过时的只是 1997 年的实现方式。
| 问题 | LSTM 治好了吗 | 说明 | 什么时候真的会痛 → 换什么 |
|---|---|---|---|
| 大幅缓解 | 加法路径让梯度不被反复缩放。但路径长度仍是 O(n),超长序列还是会忘 | 要跨几百步记住一个词 → 还得靠注意力(下一章起) | |
| 没治 | 仍然需要梯度裁剪 | 层数一深、序列一长,阈值没调好训练就崩 → 梯度裁剪(《初始化》那章) | |
| 不能并行 | 完全没治 | ht 依赖 ht−1,串行链一点没变。这是它被淘汰的真正原因 | 序列长、模型大、要压训练时间 → 注意力 / Transformer |
| 记忆容量固定 | 完全没治 | 不管句子多长,还是只有固定大小的 c 和 h | 要记住的东西比 c 还宽 → 外挂记忆 / 检索,或注意力 |
| 训练慢 | 更慢了 | 参数量是 RNN 的 4 倍,而且还是串行 | 同样的预算想训更大模型 → 先换 GRU(少一组权重),或换能并行的结构 |
互动 · 同一个隐藏维度,四个结构各要多少参数
互动 · 为什么它输给了 Transformer:T 步就得等 T 次
LSTM 的参数量是同样隐藏维度的简单 RNN 的 4 倍(4 组门权重)。 在 2015 年这不是问题,但在今天动辄几百亿参数的语境下, 用 4 倍的参数去换一个仍然不能并行的结构,性价比就说不通了。 这才是 Transformer 胜出的经济账。
上一章说 RNN 对世界做了一个假设(“规则不随位置改变”)。 这一章把假设换了一种装法——而且换装的这一步,是整个序列部分最关键的一步。
回到第 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 = 给记忆加一条不打岔的传送带 + 三个阀门。
关键是那个加法 ct = f·ct−1 + i·g——它让梯度原封不动穿过很多步。
GRU 是精简版:两个门、一条状态,效果接近,参数少一组。
但它只治好了
上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。