AI 工程基础体系 · 第 59/100 篇。内容覆盖机器学习、深度学习与生成式 AI;模型、数据、评测、权限和成本会作为同一生产系统处理。
卷积神经网络:卷积、感受野、池化、残差与视觉任务
卷积神经网络(Convolutional Neural Network,CNN)是一类利用局部连接、参数共享和层级特征变换处理网格数据的神经网络。图像是二维网格,视频是带时间维度的三维网格,语音频谱也可以表示为二维网格,因此 CNN 不只是“图像分类模型”,而是一种利用空间或局部结构的建模方法。
一张 RGB 图像通常表示为张量:
其中 是输入通道数,RGB 图像通常为 3; 和 分别是高度与宽度。批量输入再增加一个批次维度:
这里 是批量大小。PyTorch 的二维卷积通常使用这种 NCHW 排列。
一、卷积究竟计算了什么
1. 从局部加权和开始
设输入是一个单通道二维矩阵:
使用一个 卷积核:
步幅(stride)为 1,不补零(padding),则输出空间尺寸为:
本例中 、、、、,所以输出为 。
左上角输出为:
向右移动一个像素后:
继续计算得到:
卷积核每次只观察输入的一个局部窗口,并对窗口中的元素做加权求和。训练过程中,网络会学习这些权重,使某些卷积核响应边缘、纹理、方向、颜色变化或更高层的形状。
2. CNN 中的“卷积”通常是互相关
数学上的离散卷积通常要求在计算前翻转卷积核:
而深度学习框架中的 Conv2d 通常计算的是互相关(cross-correlation):
也就是说,权重没有预先翻转。由于卷积核参数是通过训练学习的,这种差异通常不影响模型表达能力,但在手算、复现传统信号处理公式或验证实现时必须区分。
PyTorch 的典型调用如下:
import torch
from torch import nn
x = torch.tensor(
[[[
[1., 2., 3., 0.],
[4., 5., 6., 1.],
[7., 8., 9., 2.],
[0., 1., 2., 3.],
]]]
) # shape: [N=1, C=1, H=4, W=4]
conv = nn.Conv2d(
in_channels=1,
out_channels=1,
kernel_size=2,
stride=1,
padding=0,
bias=False,
)
with torch.no_grad():
conv.weight.copy_(torch.tensor(
[[[
[1., 0.],
[0., -1.],
]]]
))
y = conv(x)
print(y.shape)
print(y)
输出形状应为 torch.Size([1, 1, 3, 3]),数值与上面的手算结果一致。这里设置 bias=False 是为了让输出只包含卷积加权和;默认情况下,Conv2d 还会为每个输出通道增加一个偏置。
3. 多通道卷积不是每个通道独立卷积
如果输入有 个通道,一个输出通道对应一组权重:
其输出为:
其中 和 由步幅、填充和膨胀率决定, 是第 个输出通道的偏置。
如果输入有 3 个通道、输出有 64 个通道、卷积核为 ,不计偏置时参数量为:
加上 64 个偏置后为 1792 个参数。输出通道不是“对每个输入通道各做一次卷积后直接保留”,而是把所有输入通道的信息融合成一个输出特征图。
因此,Conv2d(3, 64, 3) 的权重形状是:
[64, 3, 3, 3]
第一维对应输出通道,第二维对应输入通道,后两维对应空间卷积核。
4. 步幅、填充和膨胀率分别改变什么
**步幅(stride)**表示卷积窗口每次移动多少个像素。步幅为 2 时,窗口会跳过部分位置,通常使空间尺寸约减半,同时增加单层的下采样效果。
**填充(padding)**是在输入边界外补值,最常见的是补零。对于奇数大小的卷积核,kernel_size=3, padding=1, stride=1 通常保持高度和宽度不变:
但“保持尺寸”只在特定参数组合下成立,不是卷积层的普遍性质。
**膨胀率(dilation)**在卷积核元素之间插入间隔。例如, 卷积在膨胀率 时,有效核尺寸为:
它可以扩大感受野而不直接增加卷积核参数数量,但会减少局部位置的采样密度,过大的膨胀率可能产生栅格化采样问题。
5. 参数共享带来的平移等变性
如果输入整体平移,理想的卷积响应也会相应平移,这称为平移等变性(translation equivariance)。卷积核在所有空间位置复用同一组权重,因此模型不需要为“左上角的边缘”和“右下角的边缘”分别学习两套参数。
等变性不等于平移不变性。分类任务往往希望图像小幅移动后类别不变,这是平移不变性;卷积本身主要提供响应随位置移动的等变性,而池化、全局平均池化、数据增强和后续聚合操作才可能增强不变性。
边界填充会破坏严格的平移等变性:靠近边缘的位置会看到人为补入的零,而内部位置看到的全部是真实像素。若任务高度依赖边缘信息,填充策略可能影响结果。
6. 1×1 卷积并不等于“没有作用”
卷积不聚合相邻空间位置,但会在每个像素位置上混合通道:
因此它等价于对每个空间位置独立应用一个共享的全连接层。它常用于:
- 改变通道数;
- 降低后续 卷积的计算量;
- 在不同通道之间重新组合特征;
- 构造瓶颈结构;
- 在分组卷积或深度可分离卷积中承担通道混合职责。
如果输入为 ,使用 卷积得到 个通道,参数量为 ,与空间尺寸无关。
二、感受野:一个特征到底看到了多大区域
1. 感受野的定义
某个中间特征位置在输入图像上能够影响的区域,称为该位置的感受野(receptive field)。感受野描述的是依赖关系,不等于卷积核大小,也不等于模型一定能有效理解的上下文范围。
单个 、步幅为 1 的卷积,理论感受野是 。连续两个这样的卷积,第二层的一个位置依赖第一层的 区域,而第一层每个位置又依赖输入的 区域,因此输入上的理论感受野扩大为 ,不是 。
2. 感受野递推公式
定义:
- :第 层一个位置对应输入上的感受野边长;
- :第 层相邻两个位置在输入上的间隔,也称 jump 或有效步距;
- :第 层卷积核或池化窗口大小;
- :第 层步幅;
- :第 层膨胀率。
有效核尺寸为:
递推关系为:
初始状态通常取:
这里的直觉是:当前层的一个输出位置覆盖了 个上一层位置,而上一层相邻位置在原图中相隔 个像素。
3. 完整算例:卷积和池化如何扩大感受野
考虑以下层序列:
- 卷积,stride=1;
- 最大池化,stride=2;
- 卷积,stride=1;
- 最大池化,stride=2。
从 开始:
| 层 | ||||
|---|---|---|---|---|
| 初始 | - | - | 1 | 1 |
| Conv1 | 3 | 1 | ||
| Pool1 | 2 | 2 | ||
| Conv2 | 3 | 1 | ||
| Pool2 | 2 | 2 |
最终一个位置的理论感受野为 ,相邻输出位置在原图上的间距为 4 个像素。
这个结果说明池化的作用不只是“减少尺寸”。由于池化步幅扩大了 ,后续卷积核覆盖的输入范围也会更快增长。
4. 理论感受野、有效感受野和边界条件
理论感受野是结构上可能产生影响的最大区域,但实际梯度贡献通常集中在其中更小的区域,这称为有效感受野。不同位置的权重、激活函数、归一化和训练状态会使真实影响范围与理论范围不同。
填充还会影响感受野是否完全落在真实图像内。一个靠近边缘的输出位置虽然理论上有 感受野,但其中一部分可能对应补零区域。对于小图像,连续使用大卷积核或多次池化会使边缘信息迅速减少。
感受野过小会导致模型难以识别需要全局上下文的目标。例如,仅凭局部纹理可能无法判断一个物体属于哪个类别。感受野过大也不一定更好:过早下采样可能丢失细小目标,扩大上下文的同时降低定位精度。
三、池化:下采样与局部聚合
1. 最大池化和平均池化
给定局部窗口 ,最大池化定义为:
平均池化定义为:
最大池化保留局部最强响应,适合表示“某种模式是否出现”;平均池化保留局部平均强度,更像平滑或局部统计。
例如:
使用 、stride=2 的最大池化:
平均池化则为:
池化通常不包含可学习参数,但它改变特征图尺寸和信息分布,因此仍然会显著影响模型行为。
2. 池化带来的收益和代价
下采样减少 后,后续层的激活和计算量下降。例如,高宽都减半时,空间位置数量约变为原来的四分之一。这可以降低显存与推理成本,并扩大后续层相对于原图的感受野。
代价是空间精度下降。一个 池化无法知道最大值在窗口内的精确亚像素位置;多个下采样层叠加后,小目标可能只剩几个像素,甚至完全消失。
池化还可能引入混叠(aliasing)。当高频细节在下采样前没有被充分处理时,不同纹理可能映射成相同的低分辨率模式。对于分类任务,这种损失有时可以接受;对于分割、关键点和检测任务,通常需要跳跃连接、特征金字塔、较温和的下采样或专门的抗混叠设计来弥补。
3. 池化不是获得不变性的充分条件
常见误解是“最大池化让模型对平移完全不敏感”。实际情况更复杂:
- 小幅平移可能改变池化窗口中的最大值;
- 窗口边界固定,目标跨过边界时响应可能突变;
- 多次池化会降低位置精度,但不保证语义不变;
- 对旋转、尺度、形变的鲁棒性不能由普通池化自动保证。
因此,池化提供的是局部聚合和下采样机制,而不是普遍的几何不变性证明。
四、残差连接:让深层网络更容易优化
1. 从直接学习映射到学习残差
设某个网络块输入为 ,希望学习的目标映射为 。普通堆叠层直接学习:
残差块改为学习:
并输出:
这里 通常由两个或三个卷积层、归一化层和激活函数组成。残差连接不是简单地“增加一条捷径”,而是改变了参数化方式:如果最优映射接近恒等映射,网络只需使 接近零,而不必让一串非线性层精确复制 。
2. 梯度为什么更容易传播
设损失为 ,残差块输出:
对输入求导:
其中 是恒等映射的雅可比矩阵。即使 的梯度较小,梯度中仍然存在直接的 路径。
对比普通串联结构:
其梯度为:
多个层串联时,梯度是许多雅可比矩阵的乘积,可能逐层变小或变大。残差连接不能从数学上保证永不梯度消失,但提供了更稳定的优化路径。
3. 尺寸不一致时不能直接相加
只有当 与 形状一致时,才能直接执行:
y = x + residual
如果通道数或空间尺寸发生变化,需要投影分支:
常见实现是 卷积配合合适的 stride:
import torch
from torch import nn
class ResidualBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1):
super().__init__()
self.main = nn.Sequential(
nn.Conv2d(in_channels, out_channels, 3,
stride=stride, padding=1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels, 3,
stride=1, padding=1, bias=False),
nn.BatchNorm2d(out_channels),
)
if in_channels != out_channels or stride != 1:
self.skip = nn.Sequential(
nn.Conv2d(in_channels, out_channels, 1,
stride=stride, bias=False),
nn.BatchNorm2d(out_channels),
)
else:
self.skip = nn.Identity()
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
return self.relu(self.main(x) + self.skip(x))
block = ResidualBlock(32, 64, stride=2)
x = torch.randn(4, 32, 56, 56)
y = block(x)
print(y.shape) # torch.Size([4, 64, 28, 28])
这里主分支通过 stride=2 卷积把空间尺寸减半并把通道从 32 变为 64;跳跃分支通过 stride=2 卷积完成同样的形状变换,因此两条路径可以相加。
4. 激活位置影响块的行为
一种常见的残差块是:
另一类设计会把归一化和激活放到卷积之前,称为 pre-activation 风格。二者都属于残差思想,但训练行为、初始化敏感性和深层优化特征可能不同。不能只看到“有 skip connection”就认为所有残差网络结构等价。
残差连接还用于跨尺度特征融合、编码器—解码器跳跃连接和特征金字塔。它们的共同点是保留或复用早期特征,但加法残差与通道拼接并不相同:加法要求形状一致并融合为同一表示,拼接会增加通道数,后续层需要再学习如何组合。
五、从 CNN 组件到视觉任务
1. 图像分类
分类模型输入整张图像,输出类别概率:
其中 是分类头输出的 logits。多分类交叉熵为:
典型 CNN 分类器可以由卷积主干、全局平均池化和线性分类头构成:
import torch
from torch import nn
class SmallCNN(nn.Module):
def __init__(self, num_classes=10):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
nn.MaxPool2d(2), # H,W -> H/2,W/2
ResidualBlock(32, 64, stride=2),
ResidualBlock(64, 128, stride=2),
)
self.head = nn.Sequential(
nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten(),
nn.Linear(128, num_classes),
)
def forward(self, x):
return self.head(self.features(x))
model = SmallCNN(num_classes=10)
x = torch.randn(8, 3, 128, 128)
logits = model(x)
labels = torch.randint(0, 10, (8,))
loss = nn.CrossEntropyLoss()(logits, labels)
print(logits.shape) # [8, 10]
print(loss.item())
AdaptiveAvgPool2d((1, 1)) 将每个通道的空间特征聚合成一个数,因此分类头不依赖固定的输入高宽。它减少了全连接层参数,并使模型更关注“某类特征是否在图像中出现”,但也会牺牲精确位置。
训练步骤的核心状态变化是:
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3)
model.train()
optimizer.zero_grad(set_to_none=True)
logits = model(x)
loss = nn.CrossEntropyLoss()(logits, labels)
loss.backward()
optimizer.step()
zero_grad 清理上一轮累积的梯度;backward 根据当前损失计算参数梯度;step 使用优化器更新参数。验证时必须切换到 eval(),并使用 torch.no_grad(),否则 BatchNorm 和 Dropout 等模块可能处于训练行为,评测结果会失真。
model.eval()
with torch.no_grad():
val_logits = model(x)
pred = val_logits.argmax(dim=1)
2. 目标检测
检测同时回答两个问题:图像中有什么,以及它在哪里。典型输出包含类别、置信度和边界框:
CNN 在检测器中通常承担主干特征提取、区域特征编码或多尺度特征构建。检测不能只看分类准确率,因为一个模型可能类别判断正确,却把框定位在错误位置。
常见定位指标使用交并比(IoU):
当预测框与真实框重叠程度超过指定阈值时,才可能被计为正确检测。实际评测通常还要综合不同置信度阈值下的精确率—召回率曲线,并报告 mAP 等指标。
检测任务中,过度池化的风险更明显:小目标经过多次 stride=2 后可能只剩一个或零个有效特征位置。增加高分辨率分支、使用特征金字塔或减少早期下采样,都是针对这一因果问题的结构选择,而不是无条件提高网络深度。
3. 语义分割和实例分割
语义分割为每个像素预测类别:
编码器通过卷积和下采样扩大上下文并提取语义,解码器再逐步恢复空间分辨率。编码器—解码器之间的跳跃连接把早期的边缘和位置信息传给高分辨率解码层,以弥补下采样损失。
像素级交叉熵可写为:
当前景极小或类别严重不均衡时,仅优化像素交叉熵可能使模型偏向背景;此时可以结合 Dice 损失、类别权重或针对任务设计的采样策略,但评价指标也必须与业务目标一致。
实例分割比语义分割多区分“同类的不同实例”。因此模型不仅需要像素类别,还需要实例级表示、检测框、掩码或其他实例分离机制。
4. 图像生成与生成式 AI
CNN 也广泛存在于生成式模型中。扩散模型的 U-Net 通常包含卷积编码器、逐级下采样、残差块、上采样和跨层跳跃连接;某些阶段还会加入注意力,以建模远距离关系。卷积擅长局部纹理和多尺度空间结构,注意力则能补充更大范围的依赖。
生成任务与分类任务的输出和评测不同。分类模型输出有限类别 logits,生成模型通常预测噪声、速度或其他连续目标,最终还要经过多步采样得到图像。不能因为生成模型使用 U-Net 或残差块,就把它当作普通图像分类 CNN;损失、采样过程、质量评估和成本结构都不同。
六、一个 CNN 为什么会失败
1. 训练准确率高、验证准确率低
这通常说明模型记住了训练数据中的细节,而不是学到可泛化的视觉规律。图像任务中尤其要检查:
- 同一主体或同一视频帧是否同时出现在训练集和验证集;
- 数据增强是否只应用在训练集;
- 预处理的均值、方差是否来自允许使用的训练数据;
- 标签生成规则是否在不同数据分片中一致;
- 是否存在文件名、拍摄设备或背景等捷径特征。
如果相邻视频帧随机划分到训练集和验证集,验证准确率可能虚高;按主体、场景或时间切分通常更接近真实部署分布。
2. 训练损失不下降
应先区分实现错误与优化困难。一个最小诊断流程是:
- 用极小数据集,例如 8~32 个样本;
- 关闭复杂增强;
- 反复训练同一批数据;
- 检查损失是否接近零、预测是否过拟合;
- 再逐项恢复归一化、增强、混合精度和分布式训练。
如果极小数据都无法过拟合,应检查标签范围、输出类别数、输入通道顺序、损失函数输入是否为 logits、学习率、梯度是否为 NaN 以及模型参数是否真的加入优化器。
CrossEntropyLoss 需要未归一化的 logits 和整数类别标签:
logits = model(images) # 不要先 softmax
loss = nn.CrossEntropyLoss()(logits, labels.long())
若先对 logits 调用 softmax 再传给 CrossEntropyLoss,虽然有时仍能运行,但数值和梯度行为不符合该损失的标准用法。多标签任务也不能使用普通单标签交叉熵,而应根据标签形式考虑 BCEWithLogitsLoss 等目标。
3. 训练正常但部署精度下降
这通常不是“卷积失效”,而是训练—部署链路发生了变化。需要核对:
- 图像颜色通道是否从 RGB 变成 BGR;
- resize、crop、padding 的规则是否一致;
- 训练时归一化与推理时归一化是否一致;
- 输入分辨率变化是否破坏小目标;
- 模型是否忘记
eval(); - 量化、剪枝或半精度转换是否改变了数值范围;
- 部署硬件是否使用了不同的算子实现。
对边界敏感的分割和检测模型,resize 的插值方式也很重要。图像可以使用双线性等连续插值,但离散标签通常应使用最近邻插值,否则类别编号会被混合成无效标签。
七、评测、数据权限与生产成本
视觉模型的“效果”必须绑定到数据切分、标签协议和业务代价。分类可以报告 accuracy、precision、recall、F1 和混淆矩阵;类别不平衡时,单独报告 accuracy 往往会掩盖少数类失败。检测需要同时衡量分类和定位;分割需要关注像素级 IoU、Dice、边界质量以及小目标表现。
图像数据还涉及肖像、版权、地理位置、医疗信息和内部场景等权限问题。能够访问文件不等于可以用于训练或评测。数据管线应记录来源、授权范围、保留期限、脱敏处理和用途限制;如果验证集包含受限数据,应限制日志、可视化样本和调试导出权限,避免通过模型开发流程扩大数据暴露面。
成本来自多个阶段:
卷积的计算量大致与下式成正比:
提高输入分辨率会同时增加空间位置数和显存;提高通道数会增加参数、激活和计算;重复保存中间特征用于调试会增加存储与权限风险。模型压缩、降低输入分辨率、使用 瓶颈或深度可分离卷积都可能降低成本,但必须在目标类别、边界质量和延迟约束下重新评测。
八、深度可分离卷积:降低计算的结构取舍
普通卷积同时进行空间聚合和通道混合。深度可分离卷积将其拆为两步:
- 深度卷积(depthwise convolution):每个输入通道独立使用一个 核;
- 逐点卷积(pointwise convolution):使用 卷积混合通道。
普通卷积的参数量为:
深度可分离卷积的参数量为:
当 、通道数较大时,后者通常显著更小。但它限制了空间卷积和通道融合的耦合方式,实际精度、硬件速度和内存访问效率不能只由参数量推断。某些硬件对普通卷积有高度优化,理论 FLOPs 更低的结构不一定具有更低的端到端延迟。
九、理解 CNN 的一条主线
CNN 的计算可以按以下因果链理解:
- 卷积在局部窗口内共享权重,提取局部模式;
- 多通道卷积融合颜色或前层特征;
- 多层堆叠扩大理论感受野,形成从边缘到纹理再到形状的层级表示;
- 池化或 stride 卷积降低空间分辨率,减少计算并增加上下文覆盖范围;
- 残差连接为信息和梯度提供直接路径,使更深的网络更容易优化;
- 分类、检测、分割和生成任务根据输出结构重新组织这些特征;
- 数据切分、权限、评测协议和部署成本决定模型是否真的能作为生产系统使用。
因此,“卷积核越大越好”“池化越多越强”“残差连接能解决所有梯度问题”都不是成立的普遍结论。卷积核大小决定局部采样范围,层数和步幅共同决定感受野;池化在降低成本的同时损失位置精度;残差连接改善优化条件,但不能替代合理的数据、损失、评测和部署设计。理解这些组件之间的因果关系,才能根据视觉任务的定位精度、上下文需求、数据规模和成本约束选择 CNN 结构。
系列导航与关联阅读
- 系列入口:AI 工程完整学习路线:从机器学习与 Transformer 到 RAG、Agent 和生产治理
- 上一篇:自动微分与反向传播:计算图、梯度累积、截断和数值检查
- 下一篇:序列模型:RNN、LSTM、GRU、Teacher Forcing 与长依赖
官方资料
本文依据研究论文、标准组织与主流框架官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。

评论
0 条讨论