第 19 章:卷积网络:计算机如何看见图像
本章问题:CNN 为什么特别适合图像?
全连接层能算任意函数,但用在图像上参数会爆炸,而且完全忽略像素之间的空间邻近关系。这一章引入卷积,把"邻近像素有关联"这个先验直接内置进层结构。
19.1 全连接看图像的问题
第 11 章讲了手工视觉特征的辉煌——SIFT、HOG——和它们的局限。第 17 章讲了全连接层:每个输入连接每个输出,一个巨大的矩阵乘法。
如果把全连接层直接用到图像上呢?
一张 200×200 的 RGB 图片有 200×200×3 = 120,000 个像素。如果第一个隐藏层有 64 个神经元,那第一个权重矩阵就是 120,000 × 64 = 7,680,000 个参数——仅仅一层就超过 7 百万个参数。
这不仅是浪费,更深层的问题在于——它完全忽略了图像的空间结构。
在全连接层眼里,输入只是一个被打平的一维向量。(0,0) 位置的像素和 (199,199) 位置的像素同等"近"——它们都只是向量里不同的下标。但实际上一张图片是有邻域结构的:猫的眼睛在鼻子上方,鼻子在嘴巴上方。相邻的像素之间有信息——远处的像素之间基本没有直接结构联系。
全连接层不利用任何关于"邻近"的知识。它得花费大量数据去"学习"这个空间的天然属性。而卷积层把这个属性内置进了层的结构本身——不需要额外学习。
19.2 卷积在做什么:滑动窗口
卷积的直觉来自人眼看世界的方式:我们不在同一时刻看整张图片的所有区域。我们的注意力在一个小窗口内来回移动,把局部信息逐步聚集成整体理解。
卷积操作就是把这个过程做成一个数学运算。
你有一个输入图像(比如 5×5 像素)。你有一个小矩阵叫卷积核(也叫滤波器,比如 3×3)。你把卷积核叠在图像左上角,做逐元素乘积再求和,得到一个数。然后你把卷积核往右滑一格,再算一次。再滑,再算……一直到覆盖整张图片。你得到的输出叫特征图——一张比原图稍小的、每个位置上的值代表这个位置"有多符合卷积核寻找的模式"。
用数字举个例子。3×3 的卷积核:
这是一个锐化滤波器。中间是正中心像素,周围是负邻域——这算出的是"这个像素比邻居亮多少"。滑过猫的毛色边界时,输出会在边缘处出现极大的正值(暗的背景紧邻亮的皮毛),把边缘突显出来。
不同的卷积核检测不同的模式。一个核可能对水平线有反应,另一个对竖线。一个核对红色到绿色的过渡有反应,另一个对亮度变化。每个卷积层都不是只有一个核——它有一组核。每个核在整张图上扫一遍,产生一张特征图。一组核产生一组特征图——它们堆叠起来成为多通道输出,作为下一层的输入。
19.3 为什么卷积很"省"
和全连接层相比,卷积层有两个关键属性让它的参数数量剧减。
局部连接。 卷积核只看到当前位置周围的一个小窗口(比如 3×3 或 5×5),而不是整个输入的所有位置。一个输出值只依赖于局部的小块输入——这直接利用了"图像中的信息是空间邻域相关的"这个事实。
权重共享。 同一个卷积核在整张图片上滑动——左上角用的是同一组权重,右下角用的也是同一组。这意味着:如果你的核学会了在图像的某处检测竖直边缘,它自动会在任何位置检测竖直边缘。
在全连接层中,一个 200×200 的图像打平后连接到一个 64 神经元的隐藏层就是 120,000×64 个独立的一维向量权重。在卷积层中,64 个 3×3 核——每个核有 3×3×(输入通道数)个参数,可能总共只有几百到几千个权重。在遍历这些通道的整个滑动过程中,这些权重被共享。节省了两到三个数量级的参数。
19.4 池化:让网络对微小偏移不敏感
卷积层输出的特征图尺寸和原图差不多大(稍小一些)。在网络的浅层阶段,逐像素位置的信息还在——边缘在第 13 行第 27 列。但你不需要保持这么高的位置精度一直到网络顶层——对于"这是一只猫",猫在图片的偏上方还是正中央并不重要。
池化层做的是降采样——削减特征图的分辨率,同时保留主要信息。
最常见的是最大池化:用 2×2 的窗口在特征图上滑动,每个窗口只保留四个值中的最大值(代表"最明显的存在"),丢掉剩下三个。步长设为 2——完全不重叠。一张 24×24 的特征图经过 2×2 最大池化变成 12×12。
池化的效果:
- 计算量降低——特征图面积减半又减半,后续层的参数也跟着减。
- 平移不变性——哪怕猫在图片中稍微向左或向右偏移了几个像素,池化层选取的最大值仍然代表"这里有边/有纹理",这个统计信息在局部窗口下保持稳定。
- 增大感受野——池化后每个特征图像素对应的原始图像区域增大了一倍(stride 2 的累积效应意味着下一层每个神经元能"看到"上一层两个步长范围的输入区),深度加深时,顶层神经元能"看到"原图的更大范围区域。
一个典型的 CNN 结构模式是:
浅层卷积抓到边缘和纹理,池化降低分辨率;深层卷积抓到物体部件和语义特征,池化继续降低分辨率;最后展平(把二维特征图拉伸成一维),输入全连接层做最终分类。
19.5 通道:网络的第三个维度
到此为止,输入图像有三个通道(RGB)。当一个 3×3 的卷积核作用在一个三通道图像上时,这个核本身也有三个通道——它在图像的每一个通道上做 3×3 乘积,然后三个通道的结果相加。所以一个"3×3 的卷积核"在 RGB 图像上实际是 3×3×3 = 27 个权重。
卷积层的输出通道数(特征图的层数)等于这一层有多少个独立的卷积核。每个核自己独立地在所有输入通道上做卷积,产生一张输出特征图。所以卷积层的参数数是:K × C_in × kH × kW(K 个独立核 × 每个核的输入通道连接 × 核高度 × 核宽度)。
更深的层的输入通道数可能是 64、128、256(被前一层的一堆核各自产生的特征图堆叠出来的)。每个特征图变成了单个"通道"的信息——早期这些通道分别可能对应不同方向的边缘和颜色块,但到了网络的后期层,每一个通道都不再对应一个人眼能解释的"特征",而是对更高阶统计量的一个高维投影。
19.6 最小代码:10 层 CNN 训练 MNIST
下面的代码用 PyTorch 训练一个小型 CNN 识别手写数字 MNIST(28×28 灰度图,10 个数字类别)。约 40 行。
三个 epoch 后测试准确率可达 ~98%。核心结构就在 CNN 的参数里——两个卷积层、两次池化、最后全连接分类。整个流程和前两章的"训练循环"完全一样,唯一变化是网络层采用了更有结构的卷积设计。
19.7 本章小实验:手算一个卷积
取一张 5×5 的"假图片"(全 0,中间几个位置是 1)。取一个 3×3 的核 [[1,0,-1],[1,0,-1],[1,0,-1]](它是竖边检测器——左边全 +1,右边全 -1)。
用手一步一步把核在图片上滑动,计算输出。
算到中间你会发现:每当核窗从亮区域扫过到暗区域——也就是竖列跨越一个亮到暗的过渡区——卷积的输出就会出现极大的正或负(白色区域(1)乘以核正侧(+1)给正贡献,黑色区域(0)不贡献)。
这个核在整张图上能找到"亮暗竖直边界"的位置。CNN 中的卷积核和这个完全一样,只不过它们检测的模式不是人手设计的——它们是从数据中学来的。
19.8 本章地图
19.9 本章结语:看图不是看像素——是看模式
全连接层在所有像素之间建立全量连接——这个自由度过大。CNN 把"看世界的方式"设为一组可滑动的局部模式检测器——这是一种更接近生物视觉系统的归纳偏置(inductive bias)。这个偏置在 2012 到 2020 年代初驱动了整个视觉 AI 的爆发。
但当任务从"看图"变到"读句子"时,局部滑动窗口不再管用——文本的语义和远距离词序关联更紧密。卷积的局部特性在序列处理上开始失效。
这就引入了序列模型——RNN、LSTM,以及它们没能完全解决的问题。
下一章,循环网络:机器如何记住一句话。
Discussion
留言区 · GitHub-powered comments via Giscus