跳到主要内容

网络设计空间:宽度、深度、正则化、跳连与初始化

这一章讲三件事: 会写自己的 nn.Module 子类(框架的最后一块拼图);加宽、加深各买到什么、各踩什么坑;防过拟合的三件机制各异的工具。 全章有一条主走查(第 3 节起):第 08 章那只鸟图走到网络中段时的 8 个中间数,本章每件工具都在这同一组数上真动一次手。 位置:第 08 章给了「能跑」,这一章给「会改」。读完你就能打开任何一篇论文的 PyTorch 实现,不被吓住——这是书里给本章定的目标1

1. 先补齐框架的最后一块:自定义 Module

第 08 章的网络有个小事故:nn.Sequential 串起来的模型跑到 Linear 前直接报错——mat1 and mat2 shapes cannot be multiplied (64x8 and 512x32)2。原因:池化出来的 8×8×8 要先 view 拉直成 512 才能进线性层,而 Sequential 是「一个挨一个过」,中间没法插一句 reshape。

解法是把网络写成 nn.Module 的子类:__init__ 里造好所有带参数的子模块(赋值成 self.xxx,PyTorch 自动注册),forward 里用普通 Python 写数据怎么流3:

class Net(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(16, 8, kernel_size=3, padding=1)
self.fc1 = nn.Linear(8 * 8 * 8, 32)
self.fc2 = nn.Linear(32, 2)

def forward(self, x):
out = F.max_pool2d(torch.tanh(self.conv1(x)), 2)
out = F.max_pool2d(torch.tanh(self.conv2(out)), 2)
out = out.view(-1, 8 * 8 * 8) ← 那句插不进去的 reshape,在这里随便写
out = torch.tanh(self.fc1(out))
return self.fc2(out)

注意上面 F.max_pool2d 这种写法:functional API。池化和激活没有参数,不必注册成子模块,直接当函数调;有参数的(Conv、Linear)留在 __init__ 里注册,不然优化器找不到它们的参数4。一条分界线:有内部状态(参数)的用模块,无状态(自己不存参数)的用函数。 至此,任何论文里的结构你都能写:Sequential 管直行,子类管任意流。

2. 容量维一:宽度

宽度 = 每层的神经元数(卷积里是通道数)。加宽只要改一个构造参数:第一层卷积从 16 通道改成 32,参数从 18090 涨到 383865

这两个数不必背,但值得拆开看一眼——多出来的两万个参数,九成都长在同一个地方:

16 通道版 32 通道版
conv1 权重 16×3×3×3 = 432 32×3×3×3 = 864
conv1 偏置 16 32
conv2 权重 8×16×3×3 = 1152 16×32×3×3 = 4608
conv2 偏置 8 16
fc1 权重 32×512 = 16384 32×1024 = 32768 ← 涨了 16384,占全部增量的 80%
fc1 偏置 32 32
fc2 66 66
─────────────────────────────────────────────────────────
合计 18090 38386 (+20296)

图说:通道翻倍,卷积层的参数只翻两到四倍(几百到几千);
真正吃掉参数的是「拉直之后进第一个全连接层」那一步 ——
通道多一倍,拉直后的长度就多一倍,而它要乘的那张表有 32 行。
这也是第 08 章那句「结构比参数量重要」的另一面:参数容易长在没用的地方。

宽度买到的是容量(capacity:模型能表达多复杂的函数);代价是第 05 章那条规律:容量超出数据所需,多出来的参数就用来背题。书里给的操作口径值得背:先把模型加大到能拟合训练集,再缩小到不再过拟合5

3. 主走查:一组 8 个数,挨个穿过本章的每件工具

本章后面要讲五件东西(weight decay、dropout、batch norm、跳连、初始化)。五件平铺着讲一遍,就成了一份目录——读完记得住名字,说不出它们各自把什么改成了什么。所以先钉一个具体的东西下来,让每件工具都在它身上真动一次手。

钉的还是上一节那个网,输入还是第 08 章那只鸟图。鸟图过完 conv1 → 池化 → conv2 之后,张量是 [1, 8, 16, 16]:8 个通道,每通道 16×16。挑其中一个位置(比如第 3 行第 5 列),它在 8 个通道上的那 8 个数,就是本章的走查对象:

a = [ 2.4, -0.7, 5.1, 0.3, -1.8, 3.6, 0.9, -0.2 ]
通道1 通道2 通道3 通道4 通道5 通道6 通道7 通道8

图说:这 8 个数是网络中段的中间结果 —— 不是像素,也不是答案,
是「在这个位置上,8 个卷积核各自觉得有多像自己要找的图案」。
数值大 = 像,接近 0 = 不像,负 = 反着像。

这 8 个数是为演示编的,不是书里的实测值——书里没有印中间层的数值。后面每件工具,要么改这 8 个数、要么改产生这 8 个数的权重;改成什么,当场写出来。

4. 防过拟合三件套:正则化

正则化(regularization)是所有「防止模型把训练集背下来」技术的总称。书里讲三件,机制完全不同6:

工具机制一句话手感
weight decay(权重衰减)在损失里加一项「所有权重的平方和」(L2 惩罚);求导后等效于每步把每个权重按自身大小缩小一点权重不敢长歪,函数更平滑
dropout(随机丢弃)训练时每次迭代随机把一部分神经元输出置零(卷积里常整通道置零)每次迭代都是一张略不同的网,神经元没法互相串通着背题
batch norm(批归一化)对每个小批量,把每层的中间值按本批的均值方差(这批数波动有多大)归一化输入稳在激活函数的敏感区,梯度不被压死;顺带有一点正则效果

走查第一步:weight decay 把每个权重按自身大小往回剃

它不动那 8 个数,动的是产生这 8 个数的权重。书里用的强度是 l2_lambda = 0.001,训练设置沿用第 08 章:SGD、学习率 0.01、批量 64、100 个 epoch6

L2 项对某个权重 w 的梯度是 2λw,所以每一步除了照常按数据更新之外,还额外做一次:

w ← w − 学习率 × 2λw = w × (1 − 2 × 0.01 × 0.001) = w × 0.99998

拿 conv2 里一个 w = 0.50 的权重看:
一步之后 0.49999 ← 肉眼看不出任何变化
但一个 epoch 有约 156 个批次(10000 张训练图 ÷ 批量 64),
100 个 epoch 就是 15600 步:0.50 × 0.99998^15600 ≈ 0.50 × 0.73 = 0.366

图说:单看一步毫无威力,是「每一步都剃一点、剃一万五千次」堆出来的效果 ——
weight decay 这个名字(权重衰减)就是这么来的。
关键在「按自身大小剃」:同一批里 w=5.0 的那个每步被剃掉 0.0001,
是 w=0.5 那个(0.00001)的十倍。它专治「某个权重长得特别大」,
不是把所有权重一刀切地压小。

(0.99998、15600、0.73 这三个数是我们代入算的;λ=0.001、学习率 0.01、批量 64、100 个 epoch 都是书里的设置。)

书里给的直觉是花园:你不想园子里某一株长得太高、把别的都遮住;weight decay 就是那把剪子,λ 越大剪子越大6

走查第二步:dropout 把 8 个数随机抹掉几个,剩下的补回来

轮到 dropout,它直接动那 8 个数。书里用的是 nn.Dropout2d(p=0.4)——按整个通道置零,对我们这个位置来说就是:8 个数各有 40% 的概率变成 06

这一次迭代,随机抽中了第 2、5、7 个:

原 [ 2.4, -0.7, 5.1, 0.3, -1.8, 3.6, 0.9, -0.2 ]
置零 [ 2.4, 0 , 5.1, 0.3, 0 , 3.6, 0 , -0.2 ]
再放大 [ 4.00, 0 , 8.50, 0.50, 0 , 6.00, 0 , -0.33 ]
↑ 活下来的每个都除以 (1−p)=0.6,也就是乘 1.667

下一次迭代重新抽,可能抽中第 1、4、8 —— 每一步面对的都是一张略不同的网。

为什么活下来的要放大 1/(1−p)?因为下一层收到的总量不能因为丢了四成就跟着掉四成。平均而言,丢掉 40% 再把剩下的乘 1.667,期望值正好回到原值(0.6 × 1.667 = 1.0)——所以推理时把 dropout 整个关掉、别的什么都不改,下一层收到的量级也是对的。

但注意这只是「平均而言」:上面这一次,8 个数原来加起来是 9.6,抹完放大之后是 18.67。单次抽样能偏得很远,回到原值的是期望(把这件事重复无数次之后的平均结果),不是每一次。 这个抖动本身就是它的药效——神经元没法指望某个固定的伙伴,只好各自都学点有用的。

走查第三步:batch norm 按「这一批」的统计量把每个特征拉平

前两件在单个样本上就说得清,batch norm 不行——它必须看一整批。这一步书里正好给了一张带数的小表(3 个样本、4 个特征),我们照抄它走一遍6:

样本S1 样本S2 样本S3 这批的均值 这批的标准差
特征 F1 1 2 3 2.00 0.82
特征 F2 3 4 3 3.33 0.47
特征 F3 2 7 8 5.66 2.62
特征 F4 5 2 9 5.33 2.86

归一化 =(自己 − 本行均值)÷ 本行标准差:

F1 → [ -1.22, 0.00, 1.22 ]
F2 → [ -0.70, 1.43, -0.70 ]
F3 → [ -1.40, 0.51, 0.89 ]
F4 → [ -0.12, -1.16, 1.28 ]

图说:横着看是一批里的三个样本,竖着算的是每个特征各自的均值和波动。
走完这一步,每一行都变成「平均 0、波动 1」——原来 F3 跨了 2 到 8、
F1 只跨 1 到 3,归一化之后两行摆在同一个量程上。

(表里的原始值和均值/标准差是书里那张示意图上的原数;归一化后那四行是我们按公式算的。)

拉平之后好在哪?第 06 章讲过,tanh 这类激活函数在输入的绝对值一大就躺平、梯度归零。F3 里那个 8 送进 tanh 基本就是死的;归一化成 0.89 之后,它落回了函数还有坡度的那一段。这就是为什么 batch norm 更像「收敛助手」,正则只是副作用。

副作用是这么来的:同一个样本,和不同的同批伙伴一起进来,算出的均值和标准差就不同,它被拉成的数也就不同——等于每个 epoch 都看到这个样本的一个略微不同的版本,和第 14 章要讲的数据增广是一个道理6

两个实战要点

weight decay 别手写:SGD 优化器自带 weight_decay 参数,和在损失里加 L2 项数学上等价,用它避免自己写错6dropout 和 batch norm 在训练和推理时行为不同:dropout 推理时必须关闭,batch norm 推理时要换用训练期攒下的全局统计。这就是 model.train() / model.eval() 这对开关管的事——train() 打开随机丢弃、让归一化用当前这一批的统计量;eval() 关掉丢弃、让归一化改用训练期攒下的那份。忘了切 eval(),程序不会报任何错,只会安静地给出偏掉的结果:同一张图连问两次,答案还不一样。第 13 章那个真实项目的验证循环里,model.eval() 就是雷打不动的第一行6

书里拿同一个鸟/飞机问题把三件都单独试了一遍,结论微妙:weight decay 和 dropout 的「训练-验证差距」明显收窄(真·正则化),而 batch norm 让训练准确率逼近 100%(它更像收敛助手,正则只是副作用)7

5. 容量维二:深度,和它的 2015 年

深度 = 层数。深买到的是层级抽象:浅层认边缘,中层认部件,深层认整体——「先看人的轮廓,再在轮廓里找脸,再在脸里找嘴」8。对程序员还有个更亲的读法:加深 = 模型能对输入执行更长的操作序列。

但 2015 年底之前,超过 20 层的网络基本训不动。病根是梯度消失(vanishing gradients):反向传播是从损失往回的一长串乘法,链太长,乘到早期层时梯度已经小得没了——早期层的参数收不到更新信号,等于没在训练9

破局的是 2015 年 12 月何恺明等人的 ResNet(残差网络),技巧简单到可以一行写完——跳连(skip connection):把某块的输入直接加到块的输出上9这就是第 08 章末尾预告过的那个「残差连接」:两个名字一件事,书里两个都用,本章之后统一叫跳连。

普通: out = F.max_pool2d(torch.relu(self.conv3(out)), 2)
跳连: out1 = out ← 存一份
out = F.max_pool2d(torch.relu(self.conv3(out)) + out1, 2)
↑ 加上块的输入

走查第四步:跳连把块的输入原样加回块的输出

同一组 8 个数,再走一次。设 conv3 这一块的输入是 out1、输出是 out(过完 ReLU,负的都被压成了 0):

out1(块的输入) [ 0.5, -0.1, 1.2, 0.0, -0.4, 0.8, 0.2, -0.1 ]
out (块的输出) [ 2.4, 0.0, 5.1, 0.3, 0.0, 3.6, 0.9, 0.0 ]
out + out1 [ 2.9, -0.1, 6.3, 0.3, -0.4, 4.4, 1.1, -0.1 ]

图说:盯住第 2、5、8 个。块的输出已经被 ReLU 压成 0 了 ——
这一块对它们「无话可说」;但加上 out1 之后,−0.1、−0.4、−0.1 又回来了。
信息没有被这一块吃掉,它只是从旁边绕了过去。

(out1 那 8 个数同样是为演示编的;out 是本章那组走查数过 ReLU 的结果。)

为什么这就能治梯度消失?反向那一侧更要紧。这一块的输出对输入求导,是 f′(x) + 1——那个 +1 是加法白送的,梯度沿这条加法近路不用穿过那一长串卷积就能流回早期参数9

没有跳连:假设每一块的导数是 0.9,一百块串起来
0.9^100 ≈ 0.000027 ← 传到最前面已经没了

有跳连: 每一块的导数变成 f′ + 1;哪怕 f′ 小到 0.01,
这条链上永远保留着一条「全乘 1」的通路,乘一百次还是 1

图说:梯度消失的机制就是「一长串小于 1 的数连乘」。
跳连不是把每个数改大,是在这串乘法里插进一条全是 1 的路。

书里还补了两句史实:跳连不是 ResNet 发明的(Highway networks、U-Net 更早),但 ResNet 用它第一次让一百多层变得可训,顺带刷新了整个视觉领域的评测榜;后来的 DenseNet 把跳连用到更激进——每层都和后面好几层直连9

深度在 PyTorch 里的写法也顺手:把「conv+batchnorm+ReLU+跳连」封成一个 ResBlock,然后 nn.Sequential(*(100 * [ResBlock(n_chans)]))——一百层的网络,是 for 循环拼出来的10

6. 一个少有人提的坑:初始化

书里少见地吐了个槽:PyTorch 的默认权重初始化不理想,而且因为历史原因一直没改(书里给了 GitHub issue 链接)11

走查最后一步:同一组数,在 100 层里被初始化放大或压死

为什么初始化重要,拿走查那 8 个数里最大的那个 5.1 看最省事——假设每一层大致把输入按同一个倍数放缩:

每层放大 1.5 倍(权重偏大): 5.1 × 1.5^10 = 5.1 × 57.7 = 294 ← 才 10 层
5.1 × 1.5^100 ≈ 天文数字 → 溢出成 inf

每层缩小 0.7 倍(权重偏小): 5.1 × 0.7^10 = 5.1 × 0.028 = 0.14
5.1 × 0.7^100 ≈ 1.6e-16 → 在浮点数里等于 0

图说:1.5 和 0.7 离 1 都不算远,可它们各自被连乘一百次之后,一个炸了一个没了。
好的初始化要做的就一件事:让这个倍数尽量停在 1 附近。

(1.5、0.7 和那 100 层是我们为演示取的;5.1 来自本章走查那组数。)

前向如此,反向的梯度也一样——第 5 节那条 0.9^100 ≈ 0.000027 说的是同一件事。好的初始化让「每一层的输出方差」大致稳定。书里的深网实验用了 Kaiming 初始化(按 ResNet 论文的口径给卷积核一个合适方差),并把 batch norm 的输出方差先压到 0.5——没有这两个动作,100 层的实验根本收敛不了11。这门手艺的源头文献:2010 年 Glorot & Bengio(Xavier 初始化),2015 年 He 等(Kaiming 初始化)11

7. 收尾:Fred the cat,以及真正的边界

Jane 的鸟/飞机分类器到此能用了。但书里结尾特意讲了它解决不了的两件事12:

第一件,它只会「整图分类」——大图里框出鸟在哪,它做不到(那是检测/分割,第 15 章)。

第二件更要命:猫 Fred 走到镜头前时,模型不会说「这不是鸟也不是飞机」——它会高高兴兴地给出「鸟,99%」。对远离训练分布的输入照样自信作答,这叫过泛化(overgeneralization)。书里的判词很重:这是把模型带进生产环境时最主要的问题之一,「而真实世界里,你没法信任输入的情况占大多数」12

判断(我们的,不是书里的): 「过泛化」这个词是作者的用法,不是行业标准术语(业界更常说「分布外输入」,out-of-distribution)。机制是真的、重要的;词在别人的文档里撞见时别认不出。 如果错,会错在: 如果读者去查「overgeneralization」的文献,会发现它常指别的(语言习得中的过度规则化);本书语境下就按「对分布外输入自信乱答」记。

8. 作者的判断与证据

说法性质
跳连改善收敛、残差网的损失面更平滑「被观察到」的实验事实,书里以观察报告的形式给出,非定理9
100 层 ResBlock 网络可拼可训有据(代码给出),但作者坦白:更慢、更娇气,他们没训到收敛10
三件套在鸟/飞机上的对比图有据但作者自我警告:实验设置简单,换随机种子差异可能大于方法间差异——别过度解读那几条曲线7
「PyTorch 默认初始化不理想」作者(即 PyTorch 核心开发者)的内部人判断,附 issue 链接;属于该信的坦白11
「本章结构到成书时已不算最新」作者主动声明:「架构演化极快,最新最好的架构是另一本书的事」1

9. 边界与局限

  • 本章所有变体都只在 32×32 小图的二分类上试过;「哪个技巧有用」的结论是定性的,别拿那张对比图当选型表用
  • dropout 的最佳位置、weight decay 的大小、批归一化要不要配 dropout——书里明说这些依赖具体项目,没给通解。
  • 深度那一节是「知道怎么做」的演示,不是「该在 CIFAR 上做」的建议:作者明说 32×32 小图不该追深度10

10. 可带走的

  1. 会写 nn.Module 子类 = 会写任何结构;Sequential 管直行,子类管任意流;有参数的注册成模块,无参数的走 functional API。
  2. 容量 = 宽度 × 深度两个维度;口径:加大到能拟合,再缩到不再过拟合。
  3. 正则化三件套机制不同:weight decay 每步把权重乘 0.99998(按自身大小剃,一万五千步剃到 73%)、dropout 每步随机把四成输出置零、剩下的乘 1.667 补回来、batch norm 按这一批的均值和标准差把每个特征拉成「平均 0、波动 1」;batch norm 更像收敛助手。
  4. train/eval 两个模式不是仪式:dropout 和 batch norm 在两边行为不同,忘了 eval 是无声的事故。
  5. 梯度消失 = 长链乘法把早期层的信号乘没了(0.9 连乘 100 次是 0.000027);跳连让每块的导数变成 f′+1,链上永远留着一条全乘 1 的路,100+ 层因此可训。
  6. 深网靠 for 循环拼:定义一次 ResBlock,乘 100。
  7. 初始化不是细节:每层放大 1.5 倍、连乘 100 层就溢出,缩小 0.7 倍、连乘 100 层就归零;PyTorch 默认初始化不理想,Kaiming/Xavier 是按论文给的方差初始化。
  8. 模型对没见过的输入会自信乱答(过泛化)——上线前这比准确率更要紧。

11. 原文地图

主题原书章原文位置
Sequential 报错与子类解法ch8text/16-ch08-8-using-convolutions-to-generalize.txt:664(搜「The model has zero chance of」) · :717(搜「class Net(nn.Module)」)
functional API 对照表ch8text/16-ch08-8-using-convolutions-to-generalize.txt:826(搜「functional API」) · :844(搜「Comparing module-based and functional」)
宽度与参数 38386ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1240(搜「Width」) · text/16-ch08-8-using-convolutions-to-generalize.txt:1295(搜「38386」)
「加大到拟合再缩小」口径ch5text/13-ch05-5-the-mechanics-of-learning.txt:1283(搜「increase the size until it fits」)
正则化三件套ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1319(搜「L2 regularization」) · :1377(搜「Dropout」) · :1428(搜「Batch Normalization」)
weight decay 的由来与强度ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1328(搜「referred to as weight decay」) · :1333(搜「proportional to its current value」) · :1340(搜「pruning shears」) · :1356(搜「l2_lambda = 0.001」)
dropout 的置零比例ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1401(搜「Dropout2d(p=0.4)」)
batch norm 的 3×4 示意数据ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1455(搜「BatchNorm for three samples」)
训练设置(SGD/lr/批量)ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1160(搜「batch_size=64」) · :1168(搜「lr=1e-2」)
weight decay 与优化器参数ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1371(搜「weight_decay parameter」)
train/eval 与 silent failurech8text/16-ch08-8-using-convolutions-to-generalize.txt:1419(搜「bypassed」) · :1427(搜「silent failure」)
三变体对比的定性结论ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1753(搜「Comparing the designs」)
深度的意义与梯度消失ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1516(搜「identify the person」) · :1619(搜「vanishing gradients」)
ResNet 跳连ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1537(搜「residual networks」) · :1593(搜「skip connection a la ResNet」) · :1621(搜「direct path」)
ResBlock×100ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1646(搜「ResBlock」) · :1700(搜「NetResDeep」)
初始化吐槽与文献ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1729(搜「Initialization」) · :1732(搜「not ideal」)
Fred the cat 与过泛化ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1814(搜「Fred the cat」) · :1818(搜「overgeneralization」)

Footnotes

  1. 出处:「8 Using convolutions to generalize」第 1236 段(text/16-ch08-8-using-convolutions-to-generalize.txt:1236,搜「read the latest research paper」)与第 1794 段(:1794,搜「already outdated」)。本章目标:让你能读最新论文并翻译成 PyTorch,或读别人写好的实现「不被咖啡呛到」;同时声明架构迭代极快。 2

  2. 出处:「8 Using convolutions to generalize」第 664 段(text/16-ch08-8-using-convolutions-to-generalize.txt:664,搜「The model has zero chance of」)与第 671 段(:671,搜「mat1 and mat2」)。Sequential 里缺了把 8×8×8 拉直成 512 的 reshape,直接报形状错误。

  3. 出处:「8 Using convolutions to generalize」第 717 段(text/16-ch08-8-using-convolutions-to-generalize.txt:717,搜「class Net(nn.Module)」)与第 792 段(:792,搜「automatically registers」)。赋值为属性的子模块被自动注册;parameters() 递归收集。

  4. 出处:「8 Using convolutions to generalize」第 826 段(text/16-ch08-8-using-convolutions-to-generalize.txt:826,搜「functional API」)。functional = 「无内部状态」;表 8.1 对照:模块版参数存为属性、自动注册,函数版参数当参数传、手动管理。

  5. 出处:「8 Using convolutions to generalize」第 1240 段(text/16-ch08-8-using-convolutions-to-generalize.txt:1240,搜「Width」)、「5 The mechanics of learning」第 1283 段(text/13-ch05-5-the-mechanics-of-learning.txt:1283,搜「increase the size until it fits」)与第 1295 段(text/16-ch08-8-using-convolutions-to-generalize.txt:1295,搜「38386」)。宽度参数化 n_chans1;两步口径原文:「先把尺寸加到有拟合能力,再缩到过拟合消失」。 2

  6. 出处:「8 Using convolutions to generalize」第 1319 段(text/16-ch08-8-using-convolutions-to-generalize.txt:1319,搜「L2 regularization」)、第 1328 段(:1328,搜「referred to as weight decay」)与第 1333 段(:1333,搜「proportional to its current value」)、第 1340 段(:1340,搜「pruning shears」)、第 1356 段(:1356,搜「l2_lambda = 0.001」)、第 1371 段(:1371,搜「weight_decay parameter」)、第 1377 段(:1377,搜「Dropout」)、第 1401 段(:1401,搜「Dropout2d(p=0.4)」)、第 1428 段(:1428,搜「Batch Normalization」)与第 1455 段(:1455,搜「BatchNorm for three samples」)。L2 项对参数 w_i 的负梯度是 −2λw_i,等效于按当前值成比例缩权(weight decay 之名由此而来);书里的花园/剪子比喻在 1340 段;演示强度 l2_lambda=0.001;SGD 的 weight_decay 参数与手写 L2 项完全等价;dropout 论文是 2014 年 Srivastava 等(Hinton 组),书里的实现是 nn.Dropout2d(p=0.4) 按整通道置零;batch norm 是 2015 年 Ioffe & Szegedy,书里那张示意图给了 3 样本 × 4 特征的原始值与逐特征的均值/标准差(F1 1/2/3 → 2、.82;F2 3/4/3 → 3.33、.47;F3 2/7/8 → 5.66、2.62;F4 5/2/9 → 5.33、2.86),归一化之后的数书里没印,是我们按公式算的。训练设置(SGD、lr=1e-2、batch_size=64)见第 1160 与 1168 段。 2 3 4 5 6 7 8

  7. 出处:「8 Using convolutions to generalize」第 1419 段(text/16-ch08-8-using-convolutions-to-generalize.txt:1419,搜「bypassed」)、第 1427 段(:1427,搜「silent failure」)与第 1790 段(:1790,搜「narrower gap」)。eval 模式会关掉 dropout、冻结 batch norm 的运行统计;「不以 eval 模式推理是一个会损害性能的 silent failure」;三变体对比:weight decay/dropout 的训练-验证差距更窄,batch norm 让训练准确率逼近 100%。 2

  8. 出处:「8 Using convolutions to generalize」第 1516 段(text/16-ch08-8-using-convolutions-to-generalize.txt:1516,搜「identify the person」)与第 1520 段(:1520,搜「sequence of operations」)。深度=层级信息处理;对开发者,深网像「找轮廓→找头→找嘴」的操作序列。

  9. 出处:「8 Using convolutions to generalize」第 1619 段(text/16-ch08-8-using-convolutions-to-generalize.txt:1619,搜「vanishing gradients」)、第 1537 段(:1537,搜「residual networks」)与第 1621 段(:1621,搜「direct path」)。2015 年 12 月前 20+ 层训不动;ResNet(arXiv:1512.03385)用跳连打开到 100+ 层;「跳连为深层参数到损失之间造了一条直达路径」;Highway/U-Net 更早用过跳连。 2 3 4 5

  10. 出处:「8 Using convolutions to generalize」第 1646 段(text/16-ch08-8-using-convolutions-to-generalize.txt:1646,搜「ResBlock」)与第 1700 段(:1700,搜「NetResDeep」)。ResBlock=conv+batchnorm+ReLU+跳连;NetResDeep 用 nn.Sequential 装 100 个块;作者坦白该网「更慢、收敛更脆弱」,没训到收敛。 2 3

  11. 出处:「8 Using convolutions to generalize」第 1729 段(text/16-ch08-8-using-convolutions-to-generalize.txt:1729,搜「Initialization」)与第 1732 段(:1732,搜「not ideal」)。原文:「出于历史原因,PyTorch 的默认权重初始化不理想」,附 GitHub issue #18182;文献:Glorot & Bengio 2010(Xavier)、He et al. 2015(Kaiming)、Zhang et al. 2019(不要 batch norm 的初始化)。 2 3 4

  12. 出处:「8 Using convolutions to generalize」第 1812 段(text/16-ch08-8-using-convolutions-to-generalize.txt:1812,搜「bounding boxes」)与第 1814 段(:1814,搜「Fred the cat」)。两个遗留问题:不会画框;猫进镜头会「高兴地以 0.99 概率输出飞机或鸟」——对远离训练分布的输入过度自信,叫过泛化,「真实世界的大多数情况都无法信任输入」。 2