跳到主要内容

让网络记住上一步 — 状态能保住多远,以及数值冲上天怎么压

这一章讲三件事: 怎么让网络带上一条随时间刷新的状态;这条状态实际能保住多远; 以及梯度往另一个方向失控时长什么样、怎么压。

它在全书链条里的位置: 第 08、09 章处理的是「有形状」的输入。 这一章处理「有先后」的输入——而先后这件事,前面所有结构都表达不了。

需要的基础: 第 07 章那条「梯度逐层相乘」的因果。这一章把「层」换成「时刻」。

1. 先看现象:「猫追狗」和「狗追猫」用的是同一批字

把一句话打散成一袋词交给前馈网络,这两句话的输入完全一样。 而它们的意思完全相反。

顺序这件事,前面所有结构都表达不了。 卷积能表达「相邻」,但它的视野上限由核的大小定死; 而「这个词指的是三十个字之前那个东西」这种关系,卷积够不着。

书里给的办法是让网络带一条随时间刷新的状态: 在每个时刻保留前一时刻的活性值,再结合当前输入更新自身, 从而形成一个带反馈环路的网络1

这条状态,书里叫隐状态。 更新规则一句话:

h₀ = 0 起点是全零
h₁ = f(h₀, x₁) 读第 1 个输入,刷新一次
h₂ = f(h₁, x₂) 读第 2 个输入,再刷新一次

h_T = f(h_{T-1}, x_T) 读完最后一个

⚠ 每一步用的都是同一个 f —— 这就是「循环」这两个字的全部含义

这一族「每一步都调用同一个 f」的网络就叫循环神经网络,英文缩写就叫 RNN; 书里把那个 f 叫作「循环神经网络单元」,它每一步同时接收当前输入和前一时刻的隐状态,输出新的隐状态2

求梯度怎么办? 书里说:既可以用「随时间反向传播」手工推导, 也可以让自动求导框架沿计算图自动完成3—— 因为把时间展开之后,它就是一个很深的前馈网络,只是每一层共用同一组参数。

「展开之后就是很深的网络」这句话是这一章两个故障的共同病根。 书里也直说了:由于权重在时间维度上反复相乘,会出现梯度爆炸或梯度消失; 前者让损失发散,后者让远端梯度几乎归零,使得网络只能学到「近距离」的依赖关系; 这种现象被称为长程依赖问题4

全章主走查:数字求和任务,一条长度 10 的序列
═══════════════════════════════════════════════════════════════════
输入 [6, 7, 0, 0, 1, 0, 0, 0, 0, 0] 目标:6 + 7 = 13
───────────────────────────────────────────────────────────────────
§3 ① 查表 10 个数字 → 10 个 32 维向量
§3 ② 逐步刷新 10 个时刻,每步刷新一次 32 维隐状态
§3 ③ 取末时刻 只保留 h₁₀,当作整条序列的摘要
§3 ④ 打分 32 维 → 19 个类别得分(和最小 0、最大 18)
§4 ⑤ 换长度重跑 长度 10 到 35,看成绩怎么掉
§6 ⑥ 另一头的故障:梯度冲上天,再坍塌为零
§7 ⑦ 按模截断,阈值 5.0
═══════════════════════════════════════════════════════════════════

2. 任务长什么样:只有前两位有用,其余全是零

书里设计这个任务的目的很明确:量出这条状态能保住多远。

规则5:

  • 输入是一串数字,前两个数字是 0 到 9 的随机数;
  • 其余位置以 0 为主,只零星填充一个随机数字;
  • 模型要预测的是前两个数字的和。
书里给的示例(长度 10)
───────────────────────────────────
1 2 0 0 0 0 0 0 0 0 → 3
2 4 0 0 3 0 0 0 0 0 → 6 ← 中间那个 3 是干扰
3 2 0 0 0 0 1 0 0 0 → 5
4 1 0 0 0 0 0 0 4 0 → 5
───────────────────────────────────
答案的范围:0 + 0 = 0 到 9 + 9 = 18,一共 19 种

这个任务的巧妙之处在于:决定答案的信息全部在开头,而模型必须一路读到最后才输出。 一串按先后排好的东西,就叫序列;这里的序列就是那串数字。序列越长,状态就得把这份信息扛得越远。

这里说的记忆:那条状态还留着多少开头的信息——不是人脑那种记忆。 书里的说法是:序列越长还能保持高准确率,记忆能力就越强6

数据是怎么造的? 前两位一共 10 × 10 = 100 种组合,可以一次穷举; 再随机挑一个填 0 的位置换成随机数字,增加多样性; 每个基础组合生成 k 条,训练集 k = 3、验证与测试集 k = 5 (后两者取更多样本,可降低成绩估计的抖动、让不同长度间的对比更稳定)7

所以长度 5 那一套的规模是:训练 300、验证 500、测试 5008注意训练集只有 300 条——这个数很小,后面读成绩时要记着它。

3. 主走查:一条长度 10 的序列从头走到尾

模型分三段9:

① 嵌入层 把每个数字查表换成一个向量
② 循环层 沿时间步逐步刷新隐状态,取末时刻作为整条序列的表示
③ 输出层 把这个表示映射到 19 个类别得分

先说第 ① 段,因为这里有一个新零件。

数字 6 本身只是一个数。 直接把它喂进去,模型能用的信息只有「6 比 5 大」这一条。 书里的做法是把单个数字提升为一个可学习的向量,让每一维都能编码这个数字的某种属性, 从而比标量输入承载更多信息,给后续模型留出拟合空间10

这个「查表换向量」的动作叫嵌入,那张表叫嵌入矩阵。 它的形状是 10 × 32:10 行对应 10 个数字,每行是那个数字的 32 维向量10表里的值不是人定的,是训练出来的。

主走查第 ① 步
────────────────────────────────────────────────
输入序列 [6, 7, 0, 0, 1, 0, 0, 0, 0, 0] 形状 [10]
查表 取嵌入矩阵的第 6、7、0、0、1、0… 行
输出 形状 [10, 32] —— 10 个时刻,每个时刻一个 32 维向量
────────────────────────────────────────────────
按批算的话形状是 [批大小, 10, 32]

第 ② 段,循环层。 每一步做的事是: 把当前输入和上一时刻的隐状态各做一次线性映射、相加,再过一道非线性11:

主走查第 ② 步(逐时刻)
──────────────────────────────────────────────────────────
t=1 h₁ = tanh(x₁·W + h₀·U + b) h₀ 是全零,x₁ 是数字 6 的向量
t=2 h₂ = tanh(x₂·W + h₁·U + b) 开始把 7 也揉进来
t=3 h₃ = tanh(x₃·W + h₂·U + b) x₃ 是数字 0 的向量

t=10 h₁₀ = tanh(x₁₀·W + h₉·U + b)
──────────────────────────────────────────────────────────
每一步的 W、U、b 都是同一组 —— 10 个时刻共用
每一步输出的形状都是 [批大小, 32]

这里有一个不起眼、但很能说明问题的实现细节: 书里把这一层的权重乘了 0.1 做小尺度初始化,代码注释写明理由—— 否则标准正态初始化会让那道非线性一开始就饱和、网络难以训练12

这正是第 07 章那件事的另一个版本: 起点定得不好,曲线一开始就停在压平区, 梯度还没开始传就已经没了。第 13 章会把这件事系统地讲一遍。

第 ③ 段:只取最后一个时刻的隐状态。 书里的说法是: 为简化任务,这里只取最后一个时刻的隐状态作为输出, 相当于把整段序列「摘要」成一个固定长度的向量13

主走查第 ③④ 步
────────────────────────────────────────────
取 h₁₀ 形状 [批大小, 32]
过线性层 [批大小, 32] × [32, 19] → [批大小, 19]
────────────────────────────────────────────
19 个得分,对应答案 0 到 18;取最大的那个作为预测

这里再一次用到第 07 章那条规矩:输出层不叠挤压,直接吐未归一化分数14

训练设定: 每个数字的向量取 32 维、隐状态也取 32 维、每步喂 8 条、 用自适应步长的优化器(每步挪 0.001)、训 500 轮15

4. 成绩随长度下滑,但不是单调下滑

书里在长度 10、15、20、25、30、35 六套数据上各训一遍。

先看损失曲线,书里的解读分两半16:

  • 在较短的序列上,训练损失能随轮次下降到接近 0, 但验证损失整体反而越走越高—— 网络把信息集中拟合到序列尾部,而把开头那对决定答案的关键数字「遗忘」了;

  • 序列越长,连训练损失都越来越难稳定降下去, 说明这种结构难以建模这种长程依赖。

书里在这里做了一件值得称赞的事:为抑制单次训练的随机波动, 展示的损失与准确率曲线均为 5 个随机种子下独立训练后取平均的结果16这是全书里少数几处做了多次重复的实验。 对照第 05 章那个 15 条测试样本的 0.9333, 这里的做法要稳健得多。

再看成绩随长度的变化。书里的原话是:总体上序列越长、准确率越低, 个别长度仍有起伏,如长度 30 处略有回升17

「个别长度仍有起伏」这半句必须照抄,不能写成「一路掉」。 长度 30 处的回升是书里点名的例外——把它抹掉,读者复现时看到回升会以为自己跑错了。

5. 那两个数是从图上读的,不是打印出来的

这一节要交代一件出处上的事,因为它影响你怎么用这两个数。

书里正文没有打印任何一个具体的准确率数值。 第 6.1.4 节的代码里有打印语句,但正文里没有贴出输出18; 成绩随长度的变化只画在了一张折线图上17

从那张图的纵轴刻度读:长度 10 时约 0.35,长度 35 时掉到约 0.15。 这两个数是我们从图上读的,书正文没有给出逐点数值。

怎么读这两个数? 19 个类别随机猜是 1/19 ≈ 0.053。 所以 0.35 是随机的六倍多,0.15 是三倍——两个都远超随机,但都远谈不上「会做这道题」。

为什么连长度 10 都只有 0.35? 三个原因叠在一起: 训练集只有 300 条、这个任务本身要求精确记住两个数字并求和、 而这一版循环单元的状态每走一步就被压一次——下一章会把这条压缩的路径改掉。

6. 另一头的故障:梯度冲上天,再坍塌为零

上一节讲的是梯度太小。这一节讲太大。

书里对两者的难度给了一个很好的对比:梯度爆炸用权重衰减或梯度截断就能压住, 相对「好治」;梯度消失则牵涉到模型结构本身,通常要换上带门控(在信息流上加开关、 决定放多少过去)的循环单元才能根治19

怎么把爆炸复现出来? 书里选长度 20 的数据集, 并同时动了三个旋钮来放大这个现象20:

旋钮调成什么为什么这么调
每步喂多少64(原来是 8)一批里的样本多,梯度加起来就大
每步挪多远0.2(原来是 0.001)步子大,参数一下就冲远
损失怎么汇总求和而不是取平均少了除以批大小那一步,梯度直接大 64 倍

第三个旋钮最关键,也最容易被读快。 第 03 章说过「损失的最后一步是取平均」, 这里把取平均换成了求和——批里有 64 条,梯度就整整大了 64 倍。

「一批喂多少」和「每步挪多远」这两个旋钮一起放大会发生什么, 第 12 章第 2 节有一组专门的实验。

结果书里描述得很生动21:

阶段一 梯度迅速攀升,连带参数取值越冲越大
阶段二 参数一大,那道非线性的输入就落进两端的压平区
阶段三 压平区的导数几乎归零 → 梯度随之坍塌为零
────────────────────────────────────────────────
最终 模型再难继续学习

注意这条链子的形状:爆炸的终点不是「一直很大」,而是「先很大,然后变成零」。 这是这一节最反直觉、也最值得记住的一点—— 你在训练打印出来的那串记录里看到损失变成一个巨大的数、然后梯度归零,病根多半在这里。

书里还专门为这个实验另写了一个轻量训练循环。 理由是: 观察梯度爆炸的关键是在反向之后、参数更新之前抓取梯度的范数(一组数的整体长度: 每个数平方、加起来、再开方), 而第三版 Runner 内部已经把反向与更新打成一体,难以在中间插桩22

梯度的范数越大,说明这一步要挪的幅度越大。

7. 按模截断:超过阈值就等比例缩回去

结论先行:梯度太大就把它按比例缩小,方向不变、长度受限。

书里的说法是:梯度截断是缓解梯度爆炸最常用的一招—— 一旦梯度的模超过预设阈值,就把它压回安全范围; 书里管它叫启发式:没有理论保证、但实践中管用的那类招数。 常见做法有两种,按值截断(对每个分量硬性裁剪)和按模截断(对整体范数做缩放), 本实验采用后者23

按模截断的规则
────────────────────────────────────────────
设梯度的整体长度是 ‖g‖,阈值是 b

‖g‖ ≤ b → 原样保留
‖g‖ > b → 等比例缩小到 b:g ← (b / ‖g‖) · g
────────────────────────────────────────────
关键:方向不变,只限长度

「方向不变」这四个字是它比按值截断好的原因: 按值截断会把某些分量削平、某些不削,结果是方向被改了; 按模截断整体缩放,梯度指的还是原来那个方向。

书里取阈值 5.0,重跑同一组会爆炸的设定24。 结果:即便在原本会触发爆炸的学习率与批大小下,整条曲线也始终被压在阈值附近上下波动, 没有再出现失控式攀升25

补充(不在书里,依据我们的 frontier 书架): 框架里这一步的实现有一处很值得看的工程细节。 它不是写成「如果缩放系数小于 1 就缩放」,而是先算出系数、 再无条件地取它和 1.0 中的较小值,然后一律相乘。 源码注释给的理由是:这样可以避开一次 if 判断, 而那个判断会要求处理器和显卡之间做一次同步等待26

这条细节值得记住的原因不是它本身,而是它揭示的那类权衡: 在显卡上,「多做一次无用的乘法」常常比「做一次判断」更便宜 ——因为判断需要把数从显卡搬回处理器,而那一步会让整条流水线停下来等。

书里另外提了一句工程约定:第三版 Runner 提供了一个截断参数, 会在反向之后、更新之前执行这一步27第 11 章那个影评实验就用了它。

8. 作者的判断与证据

书里给了证据的:

  • 成绩随长度下滑——一张折线图,并明确点出长度 30 处的回升17;
  • 梯度先攀升后坍塌——一张对数坐标的曲线图,并给了三阶段的解释21;
  • 按模截断能压住——同一组设定下的第二张曲线图,始终压在阈值附近25;
  • 五个随机种子取平均——书里主动说明了这一点16

书里给了做法、但没有打印数值的:

  • 各长度的准确率——只有图,正文没有贴出任何一个数18所以第 5 节那两个数标注了来源是「从图上读的」。

判断(我们的,不是书里的):这一章的实验设计比它的结论更值得学。 「只有前两位有用、其余是干扰、答案必须等到最后才输出」—— 这个任务把「记忆长度」这个模糊的说法,变成了一个可以扫参数的横轴。 同理,第 6 节那三个旋钮一起调,是为了把一个偶发故障变成必然可复现的现象。 如果错,会错在: 三个旋钮一起调,意味着这组实验没法回答「哪个旋钮的贡献最大」; 它是「复现故障」的实验,不是「归因」的实验。判据是:书里有没有单独调过其中一个。

9. 边界与局限

这一章没覆盖的:

没讲什么依据 / 为什么值得知道
门控循环单元书里把它整个留成了动手练习28
双向第 11 章才出现
每个时刻都要输出的那类任务这一章只做「整条序列一个答案」
给隐状态之间也加直连边书里留成了动手练习,并给了主教材的公式编号29
循环单元怎么写得更快书里承认这一版逐时刻循环效率不高,同样留成了练习30

出门会撞见的名字:

  • 隐状态(hidden state) —— 第 1 节那条随时间刷新的东西; 注意别和第 06 章的「隐藏层」混,那是空间上的层,这是时间上的状态;

  • 随时间反向传播(BPTT) —— 把时间展开之后再做反向传播,第 1 节提到过3;

  • 梯度爆炸 / 梯度截断(gradient explosion / clipping) —— 第 6、7 节那两件事;

  • torch.nn.utils.clip_grad_norm_ —— 按模截断在框架里的名字27;

  • nn.RNN —— 框架内置的这一版循环层;书里把「手写版与它对齐」留成了练习, 并提醒两处差异:权重矩阵互为转置,而且内置那一版带两份偏置、有效偏置是二者之和31;

  • 长程依赖(long-term dependencies) —— 第 1 节那个统称。

10. 可带走的

  1. 前馈网络看不见顺序,所以要给网络加一条随时间刷新的状态;
  2. 每一步用的是同一组参数——这就是「循环」两个字的全部含义;
  3. 展开之后它就是一个很深的网络,所以第 07 章那两类故障在这里都会出现;
  4. 数字先要查表换成向量,那张表是训练出来的,不是人定的;
  5. 只取最后一个时刻的隐状态,相当于把整段序列摘要成一个固定长度的向量;
  6. 成绩随长度总体下滑,但不是单调的——书里点名长度 30 处略有回升;
  7. 那两个约 0.35 和约 0.15 是从图上读的,书正文没打印任何具体数值;
  8. 爆炸的终点不是「一直很大」,是「先很大、再坍塌为零」;
  9. 复现爆炸靠三个旋钮一起调:批大到 64、步子大到 0.2、损失求和不取平均;
  10. 按模截断只限长度、不改方向,这是它比按值截断更可取的原因;
  11. 框架里那一步用取最小值而不是判断,为的是避开显卡与处理器之间的一次同步等待。

11. 原文地图

主题原书章原文位置
隐状态与循环单元第6章 循环神经网络text/07-ch06.txt:3(搜「一类带有短期记忆的神经网络」) · text/07-ch06.txt:39(搜「循环神经网络擅长处理长度可变的序列」)
长程依赖问题第6章 循环神经网络text/07-ch06.txt:50(搜「图灵完备」)
任务规则与示例第6章 循环神经网络text/07-ch06.txt:64(搜「对长时间间隔」) · text/07-ch06.txt:90(搜「前两位数字一共有」)
数据规模第6章 循环神经网络text/07-ch06.txt:197(搜「训练集数量」)
三段结构与嵌入第6章 循环神经网络text/07-ch06.txt:240(搜「整体由三段串联」) · text/07-ch06.txt:246(搜「提升为可学习的嵌入」)
循环层与小尺度初始化第6章 循环神经网络text/07-ch06.txt:317(搜「经嵌入层查表后变为」) · text/07-ch06.txt:340(搜「否则标准正态初始化会让」)
只取末时刻第6章 循环神经网络text/07-ch06.txt:368(搜「为简化任务」)
损失曲线与多种子平均第6章 循环神经网络text/07-ch06.txt:497(搜「六个数据集上的损失曲线」)
成绩随长度变化第6章 循环神经网络text/07-ch06.txt:542(搜「个别长度仍有起伏」)
梯度爆炸的复现第6章 循环神经网络text/07-ch06.txt:610(搜「为放大梯度爆炸现象」) · text/07-ch06.txt:657(搜「梯度迅速攀升」)
按模截断第6章 循环神经网络text/07-ch06.txt:678(搜「缓解梯度爆炸常用的」) · text/07-ch06.txt:713(搜「压在阈值附近上下波动」)

Footnotes

  1. 出处:「第6章 循环神经网络」第 3 段(text/07-ch06.txt:3,搜「一类带有短期记忆的神经网络」)。原文:它在每个时刻都会保留前一时刻的活性值(即状态),再结合当前输入更新自身状态,从而形成一个带反馈环路的网络。

  2. 出处:「第6章 循环神经网络」第 39 段(text/07-ch06.txt:39,搜「循环神经网络擅长处理长度可变的序列」)。原文:网络中带有自反馈的神经元,使其能够维护一个跨时刻的状态;RNN 沿时间从左到右扫描,每一步都调用同一个组合函数,这一函数被称为循环神经网络单元。

  3. 出处:「第6章 循环神经网络」第 48 段(text/07-ch06.txt:48,搜「随时间反向传播」)。原文:求梯度时既可以使用随时间反向传播手工推导,也可以让自动微分框架沿计算图自动完成。 2

  4. 出处:「第6章 循环神经网络」第 50 段(text/07-ch06.txt:50,搜「图灵完备」)。原文:理论上完全连接的 RNN 是图灵完备的,足以逼近所有可计算函数;但实际训练中,由于权重在时间维度上反复相乘,会出现梯度爆炸或梯度消失——前者让损失发散,后者让远端梯度消失到几乎为零,使得 RNN 只能学到「近距离」的依赖关系;这种现象被称为长程依赖问题。

  5. 出处:「第6章 循环神经网络」第 64 段(text/07-ch06.txt:64,搜「对长时间间隔」)。任务规则原文:输入是一串数字,前两个数字为 0∼9,其余数字以 0 为主、零星填充随机数字;模型需要预测前两个数字的和。示例见第 69–79 段的图 6.2。

  6. 出处:「第6章 循环神经网络」第 82 段(text/07-ch06.txt:82,搜「序列越长还能保持高准确率」)。原文:记忆能力就越强;下面通过让 SRN 在多种长度的数据集上分别训练、评估,量化它的记忆距离。

  7. 出处:「第6章 循环神经网络」第 90 段(text/07-ch06.txt:90,搜「前两位数字一共有」)。原文:通过参数 k 控制每个基础组合生成的样本数,k = 3 用于训练集,k = 5 用于验证集和测试集;括号里给的理由是验证/测试集取较多样本可降低准确率估计的方差、让不同长度间的对比更稳定。

  8. 出处:「第6章 循环神经网络」第 197 段(text/07-ch06.txt:197,搜「训练集数量」)。这是书里加载长度 5 那一套数据后的实际打印输出:训练集 300、验证集 500、测试集 500。

  9. 出处:「第6章 循环神经网络」第 240 段(text/07-ch06.txt:240,搜「整体由三段串联」)。三段依次是嵌入层、SRN 层、输出层。

  10. 出处:「第6章 循环神经网络」第 246 段(text/07-ch06.txt:246,搜「提升为可学习的嵌入」)。原文:让每一维都能编码该数字的某种属性,从而比标量输入承载更多信息,给后续模型留出拟合空间;具体做法是维护一张嵌入矩阵,第 i 行就是数字 i 的 M 维嵌入。 2

  11. 出处:「第6章 循环神经网络」第 317 段(text/07-ch06.txt:317,搜「经嵌入层查表后变为」)。原文:在每个时间步,SRN 把当前输入与上一时刻隐状态分别做线性映射后相加,再过一层非线性激活,得到当前隐状态。

  12. 出处:「第6章 循环神经网络」第 340 段(text/07-ch06.txt:340,搜「否则标准正态初始化会让」)。这是书里代码里的注释,原文:权重用小尺度初始化(×0.1),否则标准正态初始化会让 tanh 一开始就饱和、SRN 难以训练。

  13. 出处:「第6章 循环神经网络」第 368 段(text/07-ch06.txt:368,搜「为简化任务」)。原话:这里只取最后一个时刻的隐状态作为输出,相当于把整段序列「摘要」成一个固定长度的向量。

  14. 出处:「第6章 循环神经网络」第 395 段(text/07-ch06.txt:395,搜「工程上分类网络只输出未归一化分数」)。原文:PyTorch 的 CrossEntropyLoss 内部已包含 log-Softmax,直接接收未归一化分数更省也更稳。

  15. 出处:「第6章 循环神经网络」第 440 段(text/07-ch06.txt:440,搜「借助RunnerV3进行训练」)。超参数见同段之后的代码:嵌入维度 32、隐状态维度 32、类别数 19、批大小 8、Adam 学习率 0.001、500 个回合。

  16. 出处:「第6章 循环神经网络」第 497 段(text/07-ch06.txt:497,搜「六个数据集上的损失曲线」)。原文接着说:SRN 把信息集中拟合到序列尾部,而把开头那对决定标签的关键数字「遗忘」了;而序列越长,连训练损失都越来越难稳定降下去。同一段末尾写明:为抑制单次训练的随机波动,本节展示的损失与准确率曲线均为 5 个随机种子下独立训练后取平均的结果。 2 3

  17. 出处:「第6章 循环神经网络」第 542 段(text/07-ch06.txt:542,搜「个别长度仍有起伏」)。原话:总体上序列越长、准确率越低(个别长度仍有起伏,如长度 30 处略有回升),印证 SRN 保持远距离依赖的能力随距离衰减。 2 3

  18. 出处:「第6章 循环神经网络」第 532 段(text/07-ch06.txt:532,搜「载入训练阶段保留的最优模型」)。这一小节的代码里有打印语句,但正文没有贴出任何一次的实际输出;成绩只呈现在第 541 段那张折线图上。 2

  19. 出处:「第6章 循环神经网络」第 565 段(text/07-ch06.txt:565,搜「两者的诊治难度差很多」)。原话:梯度爆炸用权重衰减或梯度截断就能压住,相对「好治」;梯度消失则牵涉到模型结构本身,通常要换上 LSTM 这类带门控的循环单元才能根治。

  20. 出处:「第6章 循环神经网络」第 610 段(text/07-ch06.txt:610,搜「为放大梯度爆炸现象」)。原文:本实验把批大小调到 64、学习率调到 0.2,并把交叉熵损失的 reduction 设为 sum(对小批量内损失求和而非取平均),让单步反向产生更大的梯度。

  21. 出处:「第6章 循环神经网络」第 657 段(text/07-ch06.txt:657,搜「梯度迅速攀升」)。原话:放大学习率和批大小后梯度迅速攀升,连带参数取值越冲越大;当激活值落入 Tanh 饱和区,导数几乎归零,梯度也随之坍塌为零,模型再难继续学习。 2

  22. 出处:「第6章 循环神经网络」第 575 段(text/07-ch06.txt:575,搜「观察梯度爆炸的关键」)。原文:可是 RunnerV3.fit 内部已经把 backward 与 step 打成一体,难以在中间插桩;我们干脆另写一个轻量训练循环,把记录点显式暴露在能控制的位置。

  23. 出处:「第6章 循环神经网络」第 678 段(text/07-ch06.txt:678,搜「缓解梯度爆炸常用的」)。原文:常见做法有两种——按值截断(对每个分量做硬性裁剪)和按模截断(对整体范数做缩放),本实验采用后者。

  24. 出处:「第6章 循环神经网络」第 710 段(text/07-ch06.txt:710,搜「clip_norm=5.0」)。这是书里把截断阈值传入训练循环的那一行。

  25. 出处:「第6章 循环神经网络」第 713 段(text/07-ch06.txt:713,搜「压在阈值附近上下波动」)。原话:即便在原本会触发爆炸的学习率与批大小下,整条曲线也始终被压在阈值附近上下波动,没有再出现失控式攀升——按模截断确实把梯度爆炸控制在了可训练区间。 2

  26. 补充(不在书里,依据我们的 frontier 书架):框架的按模截断实现不写成条件判断,而是先算缩放系数、再取它与 1.0 的较小值后无条件相乘。依据: shelf=ai-frontier-reference/pytorch@src:torch/nn/utils/clip_grad.py:164 @c187ef3271d5555d2d79a7c599263be58e036d62 事实=源码里先写 clip_coef = max_norm / (total_norm + 1e-6),紧接的注释写明这样做是为了避开一个 if clip_coef < 1: 判断,而该判断在梯度不在处理器内存里时会要求处理器与设备之间做一次同步;随后用 torch.clamp(clip_coef, max=1.0) 取较小值。

  27. 出处:「第6章 循环神经网络」第 692 段(text/07-ch06.txt:692,搜「在反向传播之后」)。原文:可以在反向传播之后、优化器更新之前调用 torch.nn.utils.clip_grad_norm_ 进行按模截断;配套的 RunnerV3 提供了 grad_clip_norm 参数,会在这两步之间执行这一操作。 2

  28. 出处:「第6章 循环神经网络」第 1004 段(text/07-ch06.txt:1004,搜「仿照 LSTM 算子的实现写一个」)。这是动手练习 6.7,书里没有实现门控循环单元。

  29. 出处:「第6章 循环神经网络」第 561 段(text/07-ch06.txt:561,搜「相当于一种迷你」)。这是动手练习 6.3,原文让读者参考主教材的公式给循环单元加上隐状态之间的残差连接。

  30. 出处:「第6章 循环神经网络」第 874 段(text/07-ch06.txt:874,搜「运行效率不高」)。这是动手练习 6.5,原文承认逐时刻循环的实现效率不高,请读者思考更高效的写法。

  31. 出处:「第6章 循环神经网络」第 374 段(text/07-ch06.txt:374,搜「重写本节这个从零实现的」)。这是动手练习 6.1,原文提醒两处差异:内置那一版的两个权重矩阵与手写的互为转置,而且它带两份偏置、有效偏置为二者之和。