跳到主要内容

卷积:把「位置无关」写进结构里

这一章讲三件事: 卷积这个操作到底是什么(它能手算);为什么它天生就带第 07 章缺的那两个性质;以及怎么把「卷积 + 池化(把一小块区域压成一个数,降低分辨率)」堆成网络,赢下鸟与飞机。 位置:全书视觉线的枢纽;第 13 章的 3D 版、第 15 章的分割,全是这一章的直系后代。

1. 把上一章的两个要求翻译成数学

上一章结尾,我们对理想结构提了两个要求1:

  1. 局部性:判断一个像素属于什么图案,只需要看它附近的像素;远处是树是云不重要。
  2. 平移不变性:同一个图案,出现在图的左上角和右下角,该被同样认出来。

先试试在旧框架里硬凑:全连接就是「每个输出 = 全部输入的加权和」。要局部性,就把远处像素的权重强制设零;要平移不变,就让「位置 (x,y) 看 (x+1,y)」的权重和「位置 (x′,y′) 看 (x′+1,y′)」的权重永远相等——训练时要同步更新这些绑在一起的权重1。理论上可行,工程上是噩梦:稀疏、捆绑、自定义更新规则。

卷积就是把这两件事一次性做对的现成操作。 而且它还附带第三个礼物:参数量和图像尺寸脱钩——参数只取决于那个小权重块的大小1

2. 卷积是什么:手算一遍就会了

卷积核(kernel)就是一小块权重,比如 3×3。卷积(convolution)就是:把这块小权重扣在图的每个 3×3 邻域上,对应位置相乘再求和,得到一个输出像素;滑到下一个位置,再算一个2

用书里那个极简例子手算。输入是 4×4 的单通道图,中间有个十字;卷积核是:

kernel = [[0, 1, 0],
[1, 1, 1],
[0, 1, 0]] ← 一个「十字检测器」

输入(局部) 核扣在左上角 3×3 上:
0 1 0 0 0×0 + 1×1 + 0×0
1 1 1 0 1×0 + 1×1 + 1×1
0 1 0 0 0×0 + 1×1 + 0×0
0 0 0 0 = 5 → 输出左上角是 5

图说:这个 5 的含义是「这个位置和十字图案的吻合度」。
核滑完整个图,就得到一张「哪里像十字」的热度图。

三个性质立刻从这副样子里长出来2:

  • 局部性:输出一个像素只看输入的 3×3 邻域;
  • 平移不变:同一组权重滑遍全图——「十字」在哪个位置都能点亮;
  • 参数少:9 个数管整张图,不管图是 32×32 还是 4000×3000。

一个精确的对照值得记住:卷积等价于一个「绝大多数权重为零、其余权重彼此捆绑相等」的全连接层2。所以它不是新物种,是全连接加了两条正确的约束。约束不是限制表达能力,是把「不用学的东西」从参数里删掉了。

一个术语提醒:严格数学意义的卷积会把核翻转;PyTorch 的不翻——较真地说它算「互相关」,但全行业都这么叫2

3. 两个手工核:模糊与找边

网络里的核是随机初始化、被反向传播学出来的。但先把权重手工设成已知值,能立刻看清卷积在干什么3:

手工核效果为什么
全部 1/9图像变模糊每个输出像素 = 邻域 9 个的平均,相邻输出变得相似
[[-1,0,1],[-1,0,1],[-1,0,1]]竖直边缘变亮输出 = 右三列之和 − 左三列之和;左右亮度突变处数值大,均匀处为零

第二个核手算一个:对像素 (2,2),输出 = i₁₃−i₁₁ + i₂₃−i₂₁ + i₃₃−i₃₁——右边三格减左边三格3

历史上,计算机视觉(让机器处理和理解图像的这个领域)专家的手艺就是设计这样的滤波器组合;深度学习的回答是:别设计了,让核从数据里学——卷积网络训练的目标就是「学出一组组核,让它们把图变成对任务有用的特征图」3

4. 边角、padding 与「看得更远」

两个工程细节,一次说清。

边界。 3×3 核扣在角上时,左上没邻居。默认做法是不算——输出每边小一圈(32→30)。要保住尺寸,就在图外圈一圈假像素(padding=1,补零),输出回到 32×324

这里先记一笔账,第 09 章会来兑现。那一章会出现一种连法,叫残差连接:把一块网络的输入原封不动加到它的输出上,给信号留一条不经过这块计算的近路。既然要把前后两个张量逐位置相加,两者的尺寸就必须一模一样——32×32 加 30×30 是加不起来的。于是「padding 保住尺寸」从一个可选的细节,变成了那种结构的硬性前提。

看得更远。 3×3 的核只能看 3 个像素宽的东西;鸟有翅膀,跨好几像素,怎么办?用大核?32×32 的核就退化回全连接了。正解是池化 + 堆叠5:

  • 池化(pooling):把图缩小。最常用的是最大池化(max pooling):每 2×2 取最大值,图减半。「最大」的用意:卷积+激活的输出在「检测到特征」的位置数值高,取最大保证强响应活下来5
  • 堆叠:第一层卷积看 3×3;池化减半后,第二层卷积的 3×3 作用在半尺寸图上,折回原图就是 6×6——层数越深,每个输出像素「看得到」的输入区域越大。这个区域叫感受野(receptive field)。

书里算了一笔精确的账:conv(3×3) → maxpool(2×2) → conv(3×3),第二个卷积输出的一个像素,受输入 8×8 区域影响5。小核 + 池化 + 堆叠 = 用小参数看大图案,这是卷积网络的底层循环。

5. 主走查:鸟图穿过整个网络

现在把零件装成整网,跟同一只鸟图走完一遍(第 07 章那只)6:

输入 [1, 3, 32, 32] ← 1 张图、3 通道、32×32
Conv2d(3→16, 3×3, padding=1) → [1, 16, 32, 32] 16 组「特征热度图」
Tanh → 形状不变 过敏感区弯折
MaxPool2d(2) → [1, 16, 16, 16] 缩一半
Conv2d(16→8, 3×3, padding=1) → [1, 8, 16, 16] 在「特征」上再找特征
Tanh → MaxPool2d(2) → [1, 8, 8, 8]
view(拉直) → [1, 512]
Linear(512→32) → Tanh → Linear(32→2) → [1, 2] 两个 logits

图说:前半段把「图」变成「特征」,后半段把「特征」变成「答案」。
通道数一路减(16→8)、图尺寸一路缩(32→16→8),是分类网的典型收束。

先数参数:[432, 16, 1152, 8, 16384, 32, 64, 2],合计 180906。对照第 07 章:全连接 374 万。砍掉 99% 的参数,来自两处:卷积层参数只跟核大小和通道数走(432=16×3×3×3+16);第一层 Linear 的输入从 3072 缩到了 512。

训练照旧(第 05 章那台机器,CrossEntropyLoss、SGD、100 轮、批量 64),结果7:

训练集准确率 95%
验证集准确率 90% ← 全连接网是 81%;错误率 (100−90)/(100−81) ≈ 几乎减半

书里把这一步的意义写成了一句加粗的话:模型结构比参数数量重要7。374 万参数输给 1.8 万参数,不是参数输了,是「结构里没有位置」输给了「结构里写死了局部性和平移不变」。

6. 两件后勤事:存模型、上 GPU

网络类定型了,两件天天要用的后勤8:

  • 存模型:torch.save(model.state_dict(), path) 只存参数(一本「名字→张量」的字典),不存结构;读回来要先造一个同样的 Net 实例再 load_state_dict参数和结构分家存,意味着改类定义后旧存档就废了。
  • 上 GPU:model.to(device) 把模型所有参数搬进显存(注意:Module.to 是原地改,Tensor.to 是返回新张量——这对不对称是个常见坑);数据在训练循环里逐批 .to(device)先搬模型、后建优化器,否则优化器拿着的是 CPU 上的旧参数对象8

7. 作者的判断与证据

说法性质
卷积 = 稀疏 + 权重共享的全连接有据,书里拿一张 4×4 的图把两种写法逐格对齐画了出来2
conv+pool+conv 的感受野 = 8×8有据,逐层倒推(第二层 3×3 ← 池化前 6×6 ← 输入 8×8)5
18090 参数、95%/90%有据,可复现运行67
「最大池化保留强特征,代价是丢掉四分之三数据」设计动机解释;池化各家有争议(平均池化失宠、strided conv 有潜力未普及),书里照实列了几种替代方案5
「很多核训完其实没用」书里引「彩票假设」(Frankle & Carbin 2019)做的注——是研究假说,不是定论9

8. 边界与局限

  • 本章全是 2D 图像;视频、CT 走 Conv3d,音频走 Conv1d,思想不变。
  • 池化的「丢掉 3/4 数据」是真代价;现代很多架构用带步长的卷积代替池化,书里点了但没展开5
  • 90% 的验证准确率对 Jane 够用,对医学不够;第 12–15 章会看到,真正的项目瓶颈常常不在模型。
  • 卷积解决「挪位置」,不解决「没见过的东西」:第 09 章结尾 Fred 猫的故事等着。

9. 可带走的

  1. 卷积 = 小权重块滑过全图、逐位置做同一个加权求和;一次操作买到局部性、平移不变、参数与图像尺寸脱钩。
  2. 卷积核是被学的,不是被设计的;手工核(模糊、边缘)只是演示工具。
  3. padding=1 保尺寸,默认不补会每边小 kernel//2。
  4. 池化缩图、堆叠扩感受野:两层 3×3 加一个 2×2 池化,感受野就到 8×8。
  5. 分类网的典型形状:通道先升后降、分辨率一路缩、最后拉直进线性层。
  6. 18090 打赢 374 万:结构 > 参数量。 约束写进结构,等于把「不用学的东西」从参数里删掉。
  7. state_dict 只存参数不存结构;Module.to 原地、Tensor.to 返回新;先搬模型再建优化器。

10. 原文地图

主题原书章原文位置
两个要求与硬凑的麻烦ch8text/16-ch08-8-using-convolutions-to-generalize.txt:48(搜「immediate neighbors」) · :63(搜「tied weights」)
卷积定义与手算ch8text/16-ch08-8-using-convolutions-to-generalize.txt:126(搜「scalar product」) · :90(搜「cross-correlations」)
三大收益ch8text/16-ch08-8-using-convolutions-to-generalize.txt:140(搜「Local operations」) · :146(搜「size of the convolution kernel」)
等价于稀疏+共享的全连接ch8text/16-ch08-8-using-convolutions-to-generalize.txt:169(搜「sparse」) · :158(搜「THESE ARE EQUAL」)
手工核:模糊与边缘ch8text/16-ch08-8-using-convolutions-to-generalize.txt:350(搜「1.0 / 9.0」) · :393(搜「i13 - i11」)
滤波器设计是旧手艺ch8text/16-ch08-8-using-convolutions-to-generalize.txt:405(搜「combination of filters」)
paddingch8text/16-ch08-8-using-convolutions-to-generalize.txt:265(搜「Padding the boundary」) · :275(搜「ghost pix」)
池化三种方案ch8text/16-ch08-8-using-convolutions-to-generalize.txt:488(搜「Average the four pixels」)
感受野 8×8ch8text/16-ch08-8-using-convolutions-to-generalize.txt:577(搜「receptive field of 8 × 8」)
整网结构与参数 18090ch8text/16-ch08-8-using-convolutions-to-generalize.txt:201(搜「Conv2d(3, 16」) · :646(搜「18090」)
95%/90% 与「结构>参数」ch8text/16-ch08-8-using-convolutions-to-generalize.txt:1043(搜「Accuracy train: 0.95」) · :1057(搜「architecture matters more than parameter count」)
Sequential 缺 reshape 报错ch8text/16-ch08-8-using-convolutions-to-generalize.txt:671(搜「mat1 and mat2」)
存模型与 GPUch8text/16-ch08-8-using-convolutions-to-generalize.txt:1067(搜「state_dict」) · :1095(搜「modified in place」) · :1114(搜「Optimizer after moving the parameters」)
彩票假设注ch8text/16-ch08-8-using-convolutions-to-generalize.txt:188(搜「lottery ticket hypothesis」)

Footnotes

  1. 出处:「8 Using convolutions to generalize」第 48 段(text/16-ch08-8-using-convolutions-to-generalize.txt:48,搜「immediate neighbors」)与第 63 段(:63,搜「tied weights」)。在全连接里硬凑这两个性质需要「大部分权重为零 + 捆绑权重同步更新」,作者称之为不现实。 2 3

  2. 出处:「8 Using convolutions to generalize」第 126 段(text/16-ch08-8-using-convolutions-to-generalize.txt:126,搜「scalar product」)、第 90 段(:90,搜「cross-correlations」)与第 140 段(:140,搜「Local operations」)。卷积=核与每个邻域的标量积;PyTorch 的卷积严格说是互相关;三收益原文列在第 139–148 段。逐格对齐的那张图是原书的图 8.2。 2 3 4 5

  3. 出处:「8 Using convolutions to generalize」第 350 段(text/16-ch08-8-using-convolutions-to-generalize.txt:350,搜「1.0 / 9.0」)、第 393 段(:393,搜「i13 - i11」)与第 405 段(:405,搜「combination of filters」)。手工核演示;「计算机视觉专家的工作,历史上就是找出最有效的滤波器组合」。 2 3

  4. 出处:「8 Using convolutions to generalize」第 265 段(text/16-ch08-8-using-convolutions-to-generalize.txt:265,搜「Padding the boundary」)与第 320 段(:320,搜「exact same size」)。不补边输出 32→30;padding=1 补零后保持 32×32。

  5. 出处:「8 Using convolutions to generalize」第 488 段(text/16-ch08-8-using-convolutions-to-generalize.txt:488,搜「Average the four pixels」)与第 577 段(:577,搜「receptive field of 8 × 8」)。池化三方案:平均(过时)、最大(最常用、丢 3/4 数据)、带步长卷积(有潜力未普及);感受野的逐层倒推在同段。 2 3 4 5 6

  6. 出处:「8 Using convolutions to generalize」第 201 段(text/16-ch08-8-using-convolutions-to-generalize.txt:201,搜「Conv2d(3, 16」)与第 646 段(:646,搜「18090」)。参数明细 [432, 16, 1152, 8, 16384, 32, 64, 2];432 = 16 通道 × 3 输入通道 × 3×3 核 + 16 偏置。 2 3

  7. 出处:「8 Using convolutions to generalize」第 1043 段(text/16-ch08-8-using-convolutions-to-generalize.txt:1043,搜「Accuracy train: 0.95」)与第 1057 段(:1057,搜「architecture matters more than parameter count」)。原文:「参数量从 370 多万降到 18090——减少超过 99%……模型架构比参数数量更重要」。 2 3

  8. 出处:「8 Using convolutions to generalize」第 1067 段(text/16-ch08-8-using-convolutions-to-generalize.txt:1067,搜「state_dict」)、第 1095 段(:1095,搜「modified in place」)与第 1114 段(:1114,搜「Optimizer after moving the parameters」)。state_dict 只存权重;Module.to 原地修改、Tensor.to 返回新值;建议在搬运参数之后再建优化器。 2

  9. 出处:「8 Using convolutions to generalize」第 188 段(text/16-ch08-8-using-convolutions-to-generalize.txt:188,搜「lottery ticket hypothesis」)。注记原文:「这属于彩票假设的一部分——很多核最终就像没中奖的彩票」;引 Frankle & Carbin 2019(arXiv:1803.03635)。