卷积网络与经典骨干架构

本文梳理卷积网络的核心概念与经典骨干架构,回答卷积核与感受野怎么算、参数量与 FLOPs 如何评估、ResNet 到 ConvNeXt 该怎么选等工程问题。覆盖输出尺寸公式、深度可分离卷积、残差连接、复合缩放、归一化与激活,并给出加载骨干统计参数量与手算 FLOPs 两段可运行 PyTorch 代码,附各骨干参数量与 ImageNet 指标对比表及权衡与常见坑。

引言

卷积网络是计算机视觉的骨架。从 LeNet 到 ConvNeXt,二十年演进的主线是「用更少的参数与计算,换更高的精度」。工程上选骨干不是挑最新最深的,而是要在精度、延迟、显存、部署友好度之间取舍。要做出正确的取舍,必须能看懂参数量、FLOPs、感受野这几个基本量。

本文先讲清卷积的计算细节,再按时间线拆解经典骨干的设计动机,最后用两段代码把「加载骨干看规模」与「手算输出尺寸」落地。

目录

  1. 卷积基础
  2. 骨干演进史
  3. 归一化与激活
  4. 代码:加载骨干与统计参数量
  5. 代码:手算输出尺寸与感受野
  6. 骨干对比表
  7. 骨干选型
  8. 注意力模块
  9. 骨干在检测与分割中的用法
  10. 特征可视化与调试
  11. 权衡取舍
  12. 常见坑清单
  13. 小结

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,兼顾精度与端侧速度。

激活函数公式要点适用场景
ReLUmax(0, x)通用 CNN,最快
LeakyReLU负区间给一个小斜率缓解神经元死亡
GELU高斯误差线性单元Transformer、深层网络
SiLUx 乘 sigmoidEfficientNet、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输入尺寸特点
VGG16138M71.5%224结构规整,参数巨大
ResNet-5025.6M76.1%224通用骨干,残差连接
ResNet-15260M78.3%224更深更强,延迟高
MobileNetV3-Small2.5M67.7%224端侧首选,深度可分离
EfficientNet-B05.3M77.1%224复合缩放,精度效率平衡
EfficientNet-B766M84.3%600高精度,输入分辨率大
ConvNeXt-Tiny28.6M82.1%224现代化 CNN
ConvNeXt-Base89M83.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、感受野三个量,是做出正确取舍的前提。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「计算机视觉」更多文章

  1. 检测与分割的评估指标
  2. 多模态视觉语言模型
  3. 3D 视觉:点云与深度估计