引言
卷积网络是计算机视觉的骨架。从 LeNet 到 ConvNeXt,二十年演进的主线是「用更少的参数与计算,换更高的精度」。工程上选骨干不是挑最新最深的,而是要在精度、延迟、显存、部署友好度之间取舍。要做出正确的取舍,必须能看懂参数量、FLOPs、感受野这几个基本量。
本文先讲清卷积的计算细节,再按时间线拆解经典骨干的设计动机,最后用两段代码把「加载骨干看规模」与「手算输出尺寸」落地。
目录
- 卷积基础
- 骨干演进史
- 归一化与激活
- 代码:加载骨干与统计参数量
- 代码:手算输出尺寸与感受野
- 骨干对比表
- 骨干选型
- 注意力模块
- 骨干在检测与分割中的用法
- 特征可视化与调试
- 权衡取舍
- 常见坑清单
- 小结
1. 卷积基础
1.1 卷积与感受野
卷积核在输入上滑动,逐位置做加权求和。一个 3×3 卷积核只看 3×3 的邻域,但堆叠多层后,深层神经元的「感受野」会扩大:两层 3×3 卷积的感受野等于一层 5×5,三层等于 7×7。用小核堆叠替代大核,参数更少、非线性更强,这是 VGG 的核心洞见。
1.2 输出尺寸与参数量计算
输出尺寸公式(向下取整):
H_out = floor((H_in + 2 * padding - kernel) / stride) + 1
参数量(不含偏置):
params = kernel_h * kernel_w * C_in * C_out
一个 3×3、输入 256 通道、输出 256 通道的卷积,参数量是 3×3×256×256 ≈ 590K。可见通道数是参数量的平方级放大因素,这也是深度可分离卷积能大幅压缩参数的原因。
1.3 池化与步长
池化(最大池化、平均池化)降低空间分辨率、扩大感受野、提供一定平移不变性。现代骨干越来越多用步长卷积替代池化,因为步长卷积可学习。全局平均池化(GAP)把特征图压成向量,替代全连接层,大幅减少参数量,ResNet 之后成为标配。
2. 骨干演进史
2.1 LeNet 与 AlexNet
- LeNet-5(1998):2 个卷积加 3 个全连接,约 6 万参数,用于手写数字识别。
- AlexNet(2012):8 层,约 6100 万参数,引入 ReLU、Dropout、数据增强,在 ImageNet 上把 Top-5 错误率从 26% 降到 15%,点燃深度学习。
2.2 VGG
VGG16/19(2014)用统一的 3×3 卷积堆到 16 到 19 层,结构规整、特征通用,至今仍常作特征提取器与风格迁移骨干。代价是参数量巨大(VGG16 约 1.38 亿参数,其中全连接层占大头),显存与计算都不友好。
2.3 GoogLeNet 与 Inception
GoogLeNet(2014)提出 Inception 模块,在同一层并行使用 1×1、3×3、5×5 卷积与池化再拼接,多尺度提取特征。1×1 卷积用于降维,控制计算量。整个网络仅约 700 万参数,却拿下当年冠军,证明了「更深更宽不一定要更多参数」。
2.4 ResNet 与残差
ResNet(2015)用残差连接解决深层网络的退化问题:让网络学习残差 F(x) = H(x) − x,输出 F(x) + x。恒等映射让梯度可以直达浅层,使 152 层甚至上千层成为可能。ResNet-50 约 2560 万参数,ImageNet Top-1 约 76.1%,是工业界最通用的骨干之一。
2.5 DenseNet
DenseNet 把每一层与之前所有层相连(密集连接),特征复用强、参数效率高,但显存占用大,因为要保存所有中间特征。工程上不如 ResNet 常用。
2.6 MobileNet 与深度可分离卷积
MobileNetV1 用深度可分离卷积,把标准卷积拆成「逐通道卷积 + 1×1 逐点卷积」,计算量降到约 1/8 到 1/9。MobileNetV2 引入倒残差与线性瓶颈,MobileNetV3 加入 SE 注意力与 h-swish 激活。MobileNetV3-Small 仅约 254 万参数,ImageNet Top-1 约 67.7%,是端侧首选。
2.7 EfficientNet 与复合缩放
EfficientNet(2019)提出复合缩放:同时按固定比例放大深度、宽度、分辨率,而不是单独放大某一个。EfficientNet-B0 约 530 万参数,Top-1 约 77.1%;B7 约 6600 万参数,Top-1 约 84.3%。它用神经架构搜索找到基线,再统一缩放。
2.8 ConvNeXt
ConvNeXt(2022)把 ViT 的设计经验(大核 7×7、LayerNorm、GELU、更少的激活与归一化)搬回纯卷积网络,证明 CNN 在同等训练配方下可与 Swin Transformer 打平。ConvNeXt-B 约 8900 万参数,ImageNet Top-1 约 83.8%。
3. 归一化与激活
| 归一化 | 统计维度 | 特点 |
|---|---|---|
| BatchNorm | 跨 batch 与空间 | CNN 标配,依赖 batch size |
| LayerNorm | 跨通道与空间 | Transformer 标配,与 batch 无关 |
| GroupNorm | 分组通道 | 小 batch 场景稳定 |
| InstanceNorm | 单样本跨空间 | 风格迁移常用 |
激活函数上,ReLU 简单高效但有神经元死亡问题;GELU、SiLU(Swish)更平滑,在深层网络与 Transformer 中表现更好。MobileNetV3 用 h-swish 近似 Swish,兼顾精度与端侧速度。
| 激活函数 | 公式要点 | 适用场景 |
|---|---|---|
| ReLU | max(0, x) | 通用 CNN,最快 |
| LeakyReLU | 负区间给一个小斜率 | 缓解神经元死亡 |
| GELU | 高斯误差线性单元 | Transformer、深层网络 |
| SiLU | x 乘 sigmoid | EfficientNet、YOLO |
| h-swish | 分段近似 SiLU | 端侧,省计算 |
选择原则:追求速度用 ReLU;深层或 Transformer 用 GELU;端侧用 h-swish;训练不稳定时尝试 LeakyReLU。
4. 代码:加载骨干与统计参数量
下面用 torchvision 加载两个代表骨干,打印参数量与结构,帮助直观感受规模差异。
import torch
from torchvision.models import (
resnet50, ResNet50_Weights,
mobilenet_v3_small, MobileNet_V3_Small_Weights,
convnext_tiny, ConvNeXt_Tiny_Weights,
)
def summarize(name, model):
total = sum(p.numel() for p in model.parameters())
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"{name:<22s} total={total/1e6:6.2f}M trainable={trainable/1e6:6.2f}M")
summarize("ResNet-50", resnet50(weights=ResNet50_Weights.IMAGENET1K_V2))
summarize("MobileNetV3-Small", mobilenet_v3_small(weights=MobileNet_V3_Small_Weights.IMAGENET1K_V1))
summarize("ConvNeXt-Tiny", convnext_tiny(weights=ConvNeXt_Tiny_Weights.IMAGENET1K_V1))
# 替换分类头为 10 类,演示迁移学习的接口
model = resnet50(weights=ResNet50_Weights.IMAGENET1K_V2)
model.fc = torch.nn.Linear(model.fc.in_features, 10)
print("new head:", model.fc)
输出大致是 ResNet-50 约 25.6M、MobileNetV3-Small 约 2.5M、ConvNeXt-Tiny 约 28.6M。参数量直接决定了模型体积与显存占用,是端侧部署的第一道门槛。
5. 代码:手算输出尺寸与感受野
理解公式的最好方式是手写一遍。下面的工具函数计算每层输出尺寸,并累乘得到感受野。
def conv_out(h, kernel, stride=1, padding=0):
return (h + 2 * padding - kernel) // stride + 1
# 模拟 VGG 的一段:224 输入,两个 3x3 卷积加最大池化
h = 224
h = conv_out(h, 3, stride=1, padding=1) # 224
h = conv_out(h, 3, stride=1, padding=1) # 224
h = conv_out(h, 2, stride=2, padding=0) # 112
print("after block:", h)
# 感受野计算:jump 表示输出上一个像素对应输入的步长
def receptive_field(layers):
rf, jump = 1, 1
for k, s in layers:
rf = rf + (k - 1) * jump
jump = jump * s
return rf
# 三个 3x3 stride 1 卷积的感受野
print("rf 3x(3x3):", receptive_field([(3, 1), (3, 1), (3, 1)])) # 7
三个 3×3 卷积的感受野是 7×7,与一个 7×7 卷积相同,但参数是 3×3×3 等于 27 个权重位置,远少于 7×7 等于 49,这正是小核堆叠的优势。
6. 骨干对比表
| 骨干 | 参数量 | ImageNet Top-1 | 输入尺寸 | 特点 |
|---|---|---|---|---|
| VGG16 | 138M | 71.5% | 224 | 结构规整,参数巨大 |
| ResNet-50 | 25.6M | 76.1% | 224 | 通用骨干,残差连接 |
| ResNet-152 | 60M | 78.3% | 224 | 更深更强,延迟高 |
| MobileNetV3-Small | 2.5M | 67.7% | 224 | 端侧首选,深度可分离 |
| EfficientNet-B0 | 5.3M | 77.1% | 224 | 复合缩放,精度效率平衡 |
| EfficientNet-B7 | 66M | 84.3% | 600 | 高精度,输入分辨率大 |
| ConvNeXt-Tiny | 28.6M | 82.1% | 224 | 现代化 CNN |
| ConvNeXt-Base | 89M | 83.8% | 224 | 与 Swin 打平 |
指标来自 ImageNet-1K 验证集,实际值随训练配方略有浮动,此处用于量级比较。
7. 骨干选型
选骨干可以按场景快速决策:
- 云端高精度分类:EfficientNet-B4/B7、ConvNeXt-Base、ResNet-152。
- 通用检测分割骨干:ResNet-50/101 + FPN,生态最成熟,预训练权重最全。
- 端侧实时:MobileNetV3、EfficientNet-B0、YOLO-n 系列自带骨干。
- 需要 Transformer 特性:Swin-T/B,但显存与部署复杂度更高。
- 医学与遥感:优先用域内预训练或自监督权重,ImageNet 权重迁移收益有限。
- 部署到特定芯片:优先选算子支持完整的骨干,避免自定义算子回退 CPU。
- 多模态与开放词汇:直接复用 CLIP 的 ViT 骨干,省去自训。
一个实用建议:检测与分割任务里,骨干的重要性不如数据与训练配方,先用 ResNet-50 跑通,再考虑换更强骨干。
8. 注意力模块
注意力让网络学会「看哪里」。三种轻量模块在骨干里被广泛插入:
- SE(Squeeze-and-Excitation):通道注意力。全局平均池化压缩空间,再用两层全连接加 sigmoid 生成每个通道的权重。参数量极小,MobileNetV3 与 EfficientNet 都用了它。
- CBAM:在通道注意力后接空间注意力,两个维度都加权,效果更好但稍慢。
- ECA:用一维卷积替代 SE 的全连接,进一步压缩参数,适合端侧。
import torch
import torch.nn as nn
class SEBlock(nn.Module):
def __init__(self, channels, reduction=16):
super().__init__()
self.pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channels, channels // reduction, bias=False),
nn.ReLU(inplace=True),
nn.Linear(channels // reduction, channels, bias=False),
nn.Sigmoid(),
)
def forward(self, x):
b, c, _, _ = x.shape
w = self.pool(x).view(b, c) # squeeze
w = self.fc(w).view(b, c, 1, 1) # excitation
return x * w # 通道加权
se = SEBlock(256)
print(se(torch.randn(2, 256, 14, 14)).shape) # (2, 256, 14, 14)
注意 reduction 越大参数越少但表达力越弱,常用 16;通道数很小时(如小于 8)reduction 要相应调小,避免 channels // reduction 变成 0。
9. 骨干在检测与分割中的用法
分类骨干只输出一个向量,检测与分割则需要多尺度特征图:
- 骨干按阶段输出 C3、C4、C5(相对输入的 1/8、1/16、1/32 分辨率)。
- FPN(特征金字塔)自顶向下融合深层语义与浅层细节,是检测分割的标准组件。
- 分割常用空洞卷积(dilated convolution)在不降分辨率的前提下扩大感受野,DeepLabv3+ 的 ASPP 即基于此。
- 检测头接在 FPN 的每个尺度上,分别负责不同大小的目标。
| 组件 | 作用 | 典型配置 |
|---|---|---|
| C3/C4/C5 | 多尺度骨干特征 | 1/8、1/16、1/32 |
| FPN | 自顶向下融合 | 256 通道,P3 到 P7 |
| ASPP | 多空洞率并行 | 6、12、18 |
| GAP | 分类头 | 全局平均池化 |
工程上,骨干只占检测模型的一部分,换骨干带来的收益常常不如调 FPN 与检测头,因此先用 ResNet-50 + FPN 把管线跑通。
10. 特征可视化与调试
用 hook 提取中间特征,能快速确认骨干是否正常工作。
import torch
from torchvision.models import resnet18
model = resnet18(weights="DEFAULT").eval()
feats = {}
def hook(name):
def fn(module, inp, out):
feats[name] = out.detach()
return fn
model.layer1.register_forward_hook(hook("layer1"))
model.layer3.register_forward_hook(hook("layer3"))
with torch.inference_mode():
model(torch.randn(1, 3, 224, 224))
for k, v in feats.items():
print(k, tuple(v.shape))
# layer1 (1, 64, 56, 56) layer3 (1, 256, 14, 14)
确认每层输出形状与预期一致,是排查「尺寸算错」「通道数错位」最快的手段。若要做可解释性,可在此基础上实现 Grad-CAM,把梯度回传到目标层生成热力图。
11. 权衡取舍
- 精度与延迟:EfficientNet-B7 比 B0 精度高 7 个点,但计算量高一个数量级。
- 参数量与显存:VGG 参数量是 ResNet-50 的五倍,但精度更低,现代几乎不再用。
- 归一化选择:BatchNorm 依赖大 batch,小 batch 或分布式训练时改用 GroupNorm 或 SyncBN。
- 预训练来源:ImageNet 预训练是最通用的起点,但域差异大时(医学、遥感)用域内预训练或自监督权重更好。
12. 常见坑清单
- 预训练归一化不一致:用 ImageNet 权重却用自定义均值方差,精度悄悄下降。
- BatchNorm 在小 batch 下崩:batch 小于 8 时统计量不稳,考虑冻结 BN 或换 GroupNorm。
- 冻结骨干时忘设 eval:BN 仍更新统计量,破坏预训练特征。
- 混淆 stride 与 pooling:两者都降分辨率,但可学习性不同,混用易算错尺寸。
- 通道数翻倍:把中间层通道随意改大,参数量平方增长导致 OOM。
- 输入分辨率不匹配:EfficientNet 训练分辨率与推理不一致,精度掉点。
- 盲目追深:ResNet-152 相比 50 的提升远小于延迟增加,性价比低。
- 忽略 FLOPs:只看参数量,忽略了高分辨率输入带来的计算爆炸。
- 通道剪枝后不重训:直接剪枝不微调,精度断崖。
- 混淆 Top-1 与业务指标:ImageNet 的 Top-1 高不等于业务指标好。
- 忽略输入分辨率:EfficientNet 对分辨率敏感,训练与推理不一致会掉点。
13. 小结
卷积骨干的演进主线是「用更少的参数与计算换更高的精度」:VGG 证明深度有用,ResNet 用残差让深度可训练,MobileNet 用深度可分离卷积压缩计算,EfficientNet 用复合缩放平衡三者,ConvNeXt 把 Transformer 经验带回 CNN。工程上,通用任务用 ResNet-50 起步,端侧用 MobileNetV3 或 EfficientNet-B0,高精度云端用 EfficientNet-B7 或 ConvNeXt。理解参数量、FLOPs、感受野三个量,是做出正确取舍的前提。
延伸阅读
- 视觉 Transformer 与多模态模型 — CNN 与 Transformer 的对比
- 目标检测与 YOLO 系列工程落地 — 骨干在检测中的角色
- 模型压缩与端侧部署 — 骨干规模与部署的取舍
- 深度学习与 CNN 进阶 — 卷积原理的深入讲解
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。