跳到主要内容

直连边 — 加深反而更差,一条边把它救回来

这一章讲三件事: 那条让信号越过中间几层的边长什么样;十八层是怎么拼出来的; 以及书里那组「有没有这条边」的对照实验,到底干不干净。

它在全书链条里的位置: 第 07 章量出了「层一多信号就传不回去」, 换激活函数只是把病压住。这一章给出真正的解法。

需要的基础: 第 08 章的卷积与七层形状;第 07 章那四组梯度。

1. 先看现象:把网络加深,成绩掉了

第 08 章那个七层的老网络,在手写数字子集上得到 0.8800。

现在换一个十八层的。 直觉上层数翻了一倍多,成绩应该更好。 实际是 0.8100——掉了 7 个百分点。

书里对这个结果的评语很直接:仅仅把网络加深却不加残差连接,反倒不如更浅的 LeNet-5, 与「深度 = 性能」的直觉相反,根源在于缺少残差连接带来的优化困难1

把那条边打开,同一个网络回到 0.8750。

全章主走查:同一份手写数字子集,同一个十八层网络,只改一个布尔量
═══════════════════════════════════════════════════════════════════
数据 训练 1 000 / 验证 200 / 测试 200(和第 08 章同一份)
训练 每步喂 64 条,训 5 轮
───────────────────────────────────────────────────────────────────
§5 ① 把那条边关掉 → 测试 0.8100 / 损失 0.6627
§5 ② 把那条边打开 → 测试 0.8750 / 损失 0.3543
§5 ③ 作为对照,第 08 章那个七层的是 0.8800 / 0.4177
§6 ④ 核一遍这组对照干不干净:两边的学习率并不一样
═══════════════════════════════════════════════════════════════════

三组成绩都是书里的实际打印输出。⚠ 第 ④ 步是这一章最要紧的一节:
书里正文写「其他超参数保持一致」,而代码里关边那版每步挪 0.005、开边那版挪 0.01。

2. 残差单元:主干照旧,旁边加一条直通的边

结论先行:改动小得出奇——把这一段的输入直接加到它的输出上。

书里的说法是:残差网络的核心改造是给非线性层加一条直连边—— 梯度可以越过中间几层直接回传,因此即便层数堆得很深,浅层参数也仍能得到有效更新, 缓解了梯度消失2

普通的一段 加了直连边的一段
──────────────── ─────────────────────────────
x x ─────────────┐
↓ ↓ │ ← 这条就是直连边
两层卷积 两层卷积 │
↓ ↓ │
f(x) f(x) ──── + ────┘

f(x) + x

这个带边的最小积木叫残差单元,整张网络就是它的串联2

为什么这条边有用? 从反向的角度看最清楚: 加法的反向是「原样分给两边」(第 02 章第 3 节那条规则)。 所以上游传来的梯度,一份进主干、一份原封不动地直接到达输入端

梯度回传时
─────────────────────────────────────────────
主干那一路: 被两层卷积各乘一次 → 可能被压小
直连那一路: 原样通过 → 一点没少
─────────────────────────────────────────────
两路相加 ⇒ 即使主干那一路被压没了,输入端至少还收得到直连那一份

这就是第 07 章那个问题的解法: 那里的病根是「每过一层就乘一次小于 1 的数」, 而这条边给梯度开了一条不用乘的通道。

书里画的典型结构里,主干是「若干通道一致的卷积层」3ResNet 家族在不同深度下用两种残差单元4:

深度主干长什么样为什么
浅的(18、34 层)两层 3×3 等宽卷积结构最简单
深的(50 层以上)1×1 压通道 → 3×3 → 1×1 恢复通道「中间细、两头粗」,把参数量控制在合理范围

第二种那个「中间细」的形状,行话叫瓶颈结构—— 先把通道数压下来再做贵的那一步 3×3 卷积,算完再恢复。

3. 形状对不上怎么办,以及主干里那两件事

上一节那个加法有个前提:两边形状必须一样。

书里点破了这个矛盾:公式成立的前提是主干的输入输出形状一致, 但残差单元在工程中经常承担「改通道、下采样」的任务,主干侧已经把形状变了5

补救办法很统一:在直连边上插一个 1×1 卷积,让它跟着主干一起变形5:

  • 通道数不一致 —— 让这个 1×1 卷积把 D 个通道映射到 P 个通道;
  • 空间尺寸不一致 —— 让它带上恰当的步长,完成同步下采样。

1×1 卷积是什么? 它的核只覆盖一个位置,所以完全不看邻居; 它做的事只有一件:把这个位置上的 D 个通道值,线性组合成 P 个新值。 换句话说,它是「只在通道方向上」的一次全连接。

主干里还有一件事,书里用笔记框交代了:残差网络往往很深、直接优化容易出现梯度问题, 因此残差单元中通常额外加入批量规范化层来稳定训练; 并明写细节参见第 7.5.1 节6——也就是这份拆解的第 13 章第 6 到第 8 节。

这里只需要记住它做的那件事:把这一层的输出重新拉回到一个稳定的刻度上。 第 13 章会把这句话变成两组具体的数。

它还带来一个连带的代码细节,书里也用提醒框标了: 主干卷积层的偏置全部设成关闭,因为紧跟着的那个规范化层会重新调整每个通道的均值, 再叠一个显式的偏置不仅多余,还会带来额外计算开销7

「不仅多余」这半句值得停一下: 规范化会先减去均值, 而偏置正是加在均值上的那个数——加完再减掉,等于白加。

4. 十八层怎么拼:六个模块

书里把 ResNet18 拆成六个模块8:

模块一 步长 2 的 7×7 卷积(输出 64 通道)+ 规范化 + 激活
+ 步长 2 的 3×3 最大汇聚 ← 快速把图缩小
模块二 2 个残差单元,输入输出都是 64 通道,尺寸不变
模块三 2 个残差单元,通道 64 → 128,尺寸减半
模块四 2 个残差单元,通道 128 → 256,尺寸减半
模块五 2 个残差单元,通道 256 → 512,尺寸减半
模块六 全局平均汇聚压成 1×1,再接一个全连接层输出类别得分
───────────────────────────────────────────────────────────
数一数带参数的层:1(模块一)+ 2×2×4(模块二到五)+ 1(全连接)= 18

这个「18」怎么数,第 08 章第 6 节说过:只数带可学习参数的层,不含汇聚层9

模块三到五里,每个模块的第一个残差单元负责「维度过渡」 ——把通道数提上去、空间尺寸减半;其余残差单元保持形状不变,专注于在新维度上做特征精修10模块二是个例外:它的两个残差单元始终保持 64 通道、尺寸不变10

注意通道与尺寸的走向: 64 → 128 → 256 → 512,而尺寸一路减半。 这和第 08 章那个七层的走向完全一样——缩空间、涨通道是卷积网络的通用形状。

模块六那个「全局平均汇聚」值得单独说: 它把每个通道的整张特征图压成一个数。 512 个通道压完就是 512 个数,不管前面的空间尺寸是多少这一步是取代「展平 + 大全连接层」的标准做法,能省下大量参数。

5. 主走查:0.8100 对 0.8750

书里的对照实验通过一个布尔量控制:关掉直连边跑一次,打开再跑一次11

第一次,关掉。 每步喂 64 条、训 5 轮12:

[Test] accuracy/loss: 0.8100 / 0.6627

书里对训练曲线的描述是:损失下降明显放缓,部分轮次甚至出现波动, 反映出深层网络在缺少残差连接时的优化困难13

第二次,打开14:

[Test] accuracy/loss: 0.8750 / 0.3543

书里对这一次的描述是:损失下降明显更顺畅,准确率也快速上升15; 结论是「和没有使用残差连接的相比,添加了残差连接后,模型效果有了一定的提升」16

三个数摆在一起:

七层的老网络(第 08 章) 0.8800 / 0.4177
十八层,关掉直连边 0.8100 / 0.6627 ← 比七层还差 7 个百分点
十八层,打开直连边 0.8750 / 0.3543 ← 追回来了,但仍略低于七层

注意最后一行:打开之后是 0.8750,仍然略低于七层那个 0.8800。 所以这组数支持的结论是「这条边能把加深的损失追回来」, 而不是「加深比不加深更好」。 在这份只有 1 000 条训练样本、只训 5 轮的数据上, 十八层本来就没有发挥空间。

6. 这组对照干不干净:两边的学习率并不一样

这一节是全章最要紧的一节,而它讲的是一处正文与代码打架。

书里在打开直连边那一次的开头写道:「现在打开残差连接,其他超参数保持一致,重新训练」14

但代码里不是这样:

关掉直连边打开直连边
数据训练 1 000 / 验证 200 / 测试 200
每步喂多少6464
轮数55
损失函数交叉熵交叉熵
每步挪多远0.005120.0117

两边的学习率差了一倍。

这意味着什么? 意味着 0.8100 → 0.8750 这个提升里, 有多少来自那条边、有多少来自「步子大了一倍」,这组实验答不出来。 尤其是在只训 5 轮的设定下——步子大一倍,5 轮走的总距离就大一倍, 而这恰恰是「训练轮数不足」时最容易见效的那个旋钮。

判断(我们的,不是书里的):这条结论仍然站得住,但支撑它的证据不是那 6.5 个百分点。 真正的证据是两条损失曲线的形态:关掉那一次「下降明显放缓、部分轮次出现波动」13, 打开那一次「下降明显更顺畅」15形态上的差别不是靠调学习率能造出来的 ——学习率大一倍会让曲线更陡,但不会把「波动」变成「顺畅」。 再加上第 2 节那条从加法反向推出来的机制,以及这条边在整个行业里被验证过的程度, 「残差有用」这个结论并不依赖这组实验。

如果错,会错在: 如果在 5 轮这么短的训练里,学习率翻倍本身就足以让曲线从「波动」变成「顺畅」, 那么形态这条证据也一并作废。判据很好验: 把关掉直连边那一版的学习率也改成 0.01, 重跑一次,看曲线形态变不变。书里没有做这一步。

这一节的方法论价值大于它的结论: 读到任何一组「只改了一个东西」的对照实验,都该去核一遍代码里是不是真的只改了一个。 正文里的「其他保持一致」是一句声明,不是一份证据。

7. 案例:换到彩色图上,0.7094

这一节把同一个十八层网络搬到一个更难的数据集上。

数据: 6 万张 32×32 的彩色图,分 10 个类别、每类 6 000 张18; 按 40 000 / 10 000 / 10 000 切成训练、验证、测试19

模型直接用框架自带的那一版,实例化时传两个参数: 一个表示从随机起点开始训(而不是拿别人训好的参数当起点), 另一个把类别数设成 1020

书里在这里主动交代了一处妥协: 那一版默认面向 224×224、1 000 类的大数据集, 对于 32×32 这种输入,这种简化做法只能算粗略沿用; 如需更贴合的小尺寸变体,可参考配套代码仓库里的实现20

训练设定和前面几节明显不同,而且书里给了理由: 这个网络层数较多、参数量也大,纯梯度下降很难收敛到合理的损失水平; 所以改用带自适应步长的那一种优化器,并叠加一定的权重衰减来抑制过拟合,训 30 轮21

「权重衰减」是第 14 章的内容,这里只需要知道它是压过拟合的一种手段。

结果22:

[Test] accuracy/loss: 0.7094 / 0.9058

再做一次单条预测:真实类别 ship,预测 ship23

0.7094 怎么读? 十类任务随机猜是 0.1,所以 0.71 是真的学到了东西; 但这个数据集上,同样的网络训练充分能到 0.93 以上。 差距的来源仍然是训练预算:30 轮、没有数据增强、也没有让每步挪多远随训练进程变化 (这种「随训练进程改步子」的做法叫学习率调度,第 12 章第 9 节讲)。 书里也把这件事留成了动手练习,建议组合第 7 章的优化技巧把精度推高24

8. 作者的判断与证据

书里给了证据的:

  • 加深不加边反而更差——0.8100 对第 08 章的 0.8800,两个都是实际打印1;
  • 加上边能追回来——0.8750,实际打印14;
  • 两次训练曲线的形态差别——书里分别描述了「放缓、波动」与「更顺畅」1315

属于作者主动交代的边界:

  • 框架自带那一版对 32×32 只能算粗略沿用20;
  • 用自适应优化器是因为纯梯度下降在这个规模上很难收敛21

书里说了、但代码不支持的:

  • 「其他超参数保持一致」——见第 6 节,两边学习率是 0.005 对 0.011217这是我们在全书里发现的唯一一处正文与代码直接打架的地方, 所以这份拆解把它单列成一节。

9. 边界与局限

这一章没覆盖的:

没讲什么依据 / 为什么值得知道
更深的那些版本(34/50/101/152 层)只在图里画了瓶颈结构,没有实现25
拿别人训好的参数当起点实际项目里几乎必做,书里只在实例化参数里带过一句
数据增强训练图像模型的标配,全书没有
直连边为什么真正有效的理论解释书里只给了「梯度可以越过中间几层」这一句
更现代的直连边摆法第 17 章第 2 节会讲到规范化摆在边的前面还是后面

出门会撞见的名字:

  • 残差连接 / 跳跃连接(residual connection / skip connection) —— 同一条边的两种叫法;

  • nn.BatchNorm2d —— 第 3 节那个把输出拉回刻度的层,第 13 章展开;

  • 瓶颈结构(bottleneck) —— 第 2 节那个「中间细、两头粗」的主干;

  • 全局平均汇聚(global average pooling) —— 第 4 节模块六那一步;

  • weights=None —— 第 7 节那个「从随机起点开始训」的开关; 换成传入一份现成的参数,就是拿别人在大规模数据上先训好的模型当起点, 这种事先在大数据上训一遍的做法就叫预训练

补充(不在书里,来自通用知识): 这条直连边最早出现在 2015 年的残差网络论文里, 而今天它已经出现在几乎所有深层结构中——包括第 16 章那个编码器组块。 第 16 章第 6 节你会看到它以另一个名字出现:加与规范。

10. 可带走的

  1. 加深本身不保证更好:同一份数据,十八层关掉直连边(0.8100)比七层(0.8800)还差;
  2. 直连边就是把这一段的输入直接加到它的输出上,改动小得出奇;
  3. 它有效的原因来自加法的反向:梯度原样分给两边,直连那一路一点没少;
  4. 形状对不上时,在直连边上插一个 1×1 卷积跟着主干一起变形;
  5. 1×1 卷积不看邻居,它只是「只在通道方向上」的一次全连接;
  6. 主干卷积的偏置要关掉,因为紧跟的规范化层会把均值重新调掉,加了等于白加;
  7. 十八层 = 1 + 16 + 1,只数带参数的层;通道 64 → 512、尺寸一路减半;
  8. 打开直连边后回到 0.8750,追回了加深的损失,但仍略低于七层那个 0.8800;
  9. 这组对照不干净:两边的学习率是 0.005 对 0.01,而正文写的是「其他超参数保持一致」;
  10. 读任何「只改了一个东西」的对照,都要去核一遍代码里是不是真的只改了一个。

11. 原文地图

主题原书章原文位置
直连边与残差单元第5章 卷积神经网络text/06-ch05.txt:859(搜「核心改造是给非线性层加一条直连边」)
两种残差单元第5章 卷积神经网络text/06-ch05.txt:885(搜「在不同深度下使用了两种残差单元」)
形状对不上与 1×1第5章 卷积神经网络text/06-ch05.txt:921(搜「成立的前提是」) · text/06-ch05.txt:975(搜「都设为False」)
规范化层的位置第5章 卷积神经网络text/06-ch05.txt:948(搜「残差网络往往很深」)
六个模块第5章 卷积神经网络text/06-ch05.txt:1053(搜「拆成 6 个模块」) · text/06-ch05.txt:1072(搜「维度过渡」)
关掉直连边的一次第5章 卷积神经网络text/06-ch05.txt:1136(搜「lr=0.005」) · text/06-ch05.txt:1177(搜「0.8100」) · text/06-ch05.txt:1179(搜「反倒不如更浅的」)
打开直连边的一次第5章 卷积神经网络text/06-ch05.txt:1186(搜「其他超参数保持一致」) · text/06-ch05.txt:1198(搜「lr=0.01」) · text/06-ch05.txt:1235(搜「0.8750」)
彩色图数据集第5章 卷积神经网络text/06-ch05.txt:1260(搜「共有 60 000 张彩色图像」) · text/06-ch05.txt:1285(搜「把原始训练集再拆分」)
框架自带那一版第5章 卷积神经网络text/06-ch05.txt:1373(搜「只能算粗略沿用」)
训练设定与成绩第5章 卷积神经网络text/06-ch05.txt:1388(搜「很难收敛到合理的损」) · text/06-ch05.txt:1452(搜「0.7094」)

Footnotes

  1. 出处:「第5章 卷积神经网络」第 1179 段(text/06-ch05.txt:1179,搜「反倒不如更浅的」)。原话:可以看到,仅仅把网络加深却不加残差连接,反倒不如更浅的 LeNet-5,与「深度 = 性能」的直觉相反,根源在于缺少残差连接带来的优化困难。 2

  2. 出处:「第5章 卷积神经网络」第 859 段(text/06-ch05.txt:859,搜「核心改造是给非线性层加一条直连边」)。原话:梯度可以越过中间几层直接回传,因此即便层数堆得很深,浅层参数也仍能得到有效更新,缓解了梯度消失;这种带直连边的最小积木叫残差单元,整张残差网络就是它的串联。 2

  3. 出处:「第5章 卷积神经网络」第 865 段(text/06-ch05.txt:865,搜「典型的残差单元结构」)。原文:由若干通道一致的卷积层串成主干,再叠加一条跨越主干的直连边。

  4. 出处:「第5章 卷积神经网络」第 885 段(text/06-ch05.txt:885,搜「在不同深度下使用了两种残差单元」)。原文:浅层版本采用两层 3 × 3 等宽卷积加直连边;深层版本则采用「中间细、两头粗」的瓶颈结构——先用 1 × 1 卷积压通道,再做 3 × 3 卷积,最后用 1 × 1 卷积恢复通道,从而控制参数量在合理范围。

  5. 出处:「第5章 卷积神经网络」第 921 段(text/06-ch05.txt:921,搜「成立的前提是」)。原文:残差单元在工程中经常承担「改通道、下采样」的任务,主干侧已经把形状变了,直连边也必须做对应变形;补救方法很统一——在直连边上插一个 1 × 1 卷积,让它跟着主干一起变形。 2

  6. 出处:「第5章 卷积神经网络」第 948 段(text/06-ch05.txt:948,搜「残差网络往往很深」)。这是书里的一条笔记,原文明写「批量规范化的细节参见第 7.5.1 节」。

  7. 出处:「第5章 卷积神经网络」第 975 段(text/06-ch05.txt:975,搜「都设为False」)。原话:卷积层的 bias 都设为 False,因为紧跟着的 BatchNorm 层会重新调整每个通道的均值,再叠加显式偏置不仅多余,还会带来额外计算开销。

  8. 出处:「第5章 卷积神经网络」第 1053 段(text/06-ch05.txt:1053,搜「拆成 6 个模块」)。原文逐条列出六个模块各自的组成。

  9. 出处:「第5章 卷积神经网络」第 543 段(text/06-ch05.txt:543,搜「许多卷积网络以」)。原话:ResNet18 里的 18 指带可学习参数的层(卷积 + 全连接),不计入无参数的汇聚层。

  10. 出处:「第5章 卷积神经网络」第 1072 段(text/06-ch05.txt:1072,搜「维度过渡」)。原文:模块三到模块五的模块内第一个残差单元负责把通道数提升、空间尺寸减半;其余残差单元保持输入输出形状一致;模块二是个例外,它的两个残差单元始终保持 64 通道、空间尺寸不变。 2

  11. 出处:「第5章 卷积神经网络」第 1115 段(text/06-ch05.txt:1115,搜「为验证残差连接对深层网络训练的促进作用」)。原文:先关掉残差连接跑一次手写体识别,再打开残差连接重跑一次,比较两次的训练曲线和最终准确率。

  12. 出处:「第5章 卷积神经网络」第 1136 段(text/06-ch05.txt:1136,搜「lr=0.005」)。这一行是关掉直连边那一次的优化器定义,学习率为 0.005;批大小 64 与 5 个回合见第 1128 段与第 1140 段(text/06-ch05.txt:1140,搜「num_epochs=5」)。 2 3

  13. 出处:「第5章 卷积神经网络」第 1144 段(text/06-ch05.txt:1144,搜「损失下降明显放缓」)。原文接着说:部分轮次甚至出现波动,反映出深层网络在缺少残差连接时的优化困难。 2 3

  14. 出处:「第5章 卷积神经网络」第 1186 段(text/06-ch05.txt:1186,搜「其他超参数保持一致」)。原话:现在打开残差连接,其他超参数保持一致,重新训练。测试结果见第 1235 段(text/06-ch05.txt:1235,搜「0.8750」)。 2 3

  15. 出处:「第5章 卷积神经网络」第 1206 段(text/06-ch05.txt:1206,搜「明显更顺畅」)。原文:损失下降明显更顺畅,准确率也快速上升。 2 3

  16. 出处:「第5章 卷积神经网络」第 1237 段(text/06-ch05.txt:1237,搜「模型效果有了一定的」)。

  17. 出处:「第5章 卷积神经网络」第 1198 段(text/06-ch05.txt:1198,搜「lr=0.01」)。这一行是打开直连边那一次的优化器定义,学习率为 0.01。与第 1136 段那一行对照,两次实验的学习率相差一倍。 2

  18. 出处:「第5章 卷积神经网络」第 1260 段(text/06-ch05.txt:1260,搜「共有 60 000 张彩色图像」)。原文:分为 10 个类别,每类 6 000 张,分辨率统一为 32 × 32。

  19. 出处:「第5章 卷积神经网络」第 1285 段(text/06-ch05.txt:1285,搜「把原始训练集再拆分」)。原文:前 4 个批文件合并为训练集(40 000 条),第 5 个批文件作为验证集(10 000 条),原始测试批保持为测试集(10 000 条)。

  20. 出处:「第5章 卷积神经网络」第 1373 段(text/06-ch05.txt:1373,搜「只能算粗略沿用」)。原文:该模型默认面向 ImageNet(224 × 224、1 000 类),因此实例化时要传入 weights=None(从随机初始化开始训练)和 num_classes=10;对于 32 × 32 输入这种简化做法只能算粗略沿用,如需更贴合的小尺寸变体,可参考配套仓库中的实现。 2 3

  21. 出处:「第5章 卷积神经网络」第 1388 段(text/06-ch05.txt:1388,搜「很难收敛到合理的损」)。原文:这里 ResNet18 层数较多、参数量也大,纯 SGD 很难收敛到合理的损失水平;改用带自适应学习率的 Adam,并叠加一定的权重衰减抑制过拟合,共训练 30 个回合。 2

  22. 出处:「第5章 卷积神经网络」第 1452 段(text/06-ch05.txt:1452,搜「0.7094」)。实际输出为 [Test] accuracy/loss: 0.7094/0.9058

  23. 出处:「第5章 卷积神经网络」第 1472 段(text/06-ch05.txt:1472,搜「The true category is ship」)。

  24. 出处:「第5章 卷积神经网络」第 1438 段(text/06-ch05.txt:1438,搜「换成 SGD 时容易过拟合」)。这是动手练习 5.8,建议组合第 7 章的优化技巧(如带预热的余弦学习率、动量与 Nesterov)把验证集精度推高。

  25. 出处:「第5章 卷积神经网络」第 1029 段(text/06-ch05.txt:1029,搜「仿照上面的写法实现瓶颈型残差单元」)。这是动手练习 5.6,书里只画了瓶颈结构的示意图,没有给出实现。