引言
ConvNeXt1 是 FAIR(Meta AI)在 2022 年提出的现代 CNN 架构,其核心思想是:将 Vision Transformer 的现代技术「逆向工程」到 ResNet 中,从而实现经典 CNN 的全面现代化。
ConvNeXt 在 ImageNet 分类、COCO 检测、ADE20K 分割等多个任务上取得了与 Swin Transformer 相当甚至更优的性能,证明了在视觉领域精心设计的纯卷积网络依然具有强大的竞争力。
设计动机:为什么需要现代 CNN?
CNN 的历史地位
卷积神经网络自 2012 年 AlexNet 诞生以来,一直是计算机视觉的主导架构:
AlexNet (2012) → VGG (2014) → GoogLeNet (2015) → ResNet (2016) → ...
ResNet 通过残差连接解决了深层网络的训练难题,成为视觉领域的基础 Backbone。
Transformer 的冲击
2020-2021 年,Vision Transformer(ViT)和 Swin Transformer 的出现给 CNN 带来了巨大冲击:
| 特性 | CNN (ResNet) | Transformer (ViT/Swin) |
|---|---|---|
| ImageNet Top-1 | ~80.0% | 83.1% (Swin-B) |
| 归纳偏置 | 强 | 弱 |
| 数据效率 | 高 | 低(需大规模预训练) |
| 架构设计 | 传统 | 现代(LayerNorm、GELU 等) |
ConvNeXt 的核心洞察
ConvNeXt 的作者提出了一个关键问题:如果我们用现代技术全面升级 ResNet,能达到 Transformer 的性能吗?
答案是肯定的。ConvNeXt 通过系统性的「现代化」改造,将 ResNet-50 的性能从 79.8% 提升到 82.8%,超过 Swin-T 的 81.3%。
现代化改造路径
ConvNeXt 的设计遵循了「由粗到细」的原则,逐层改进:
1. Macro Design:宏观架构
1.1 训练策略同步
首先,将训练设置与 Transformer 对齐:
| 设置项 | ResNet 原始 | ConvNeXt |
|---|---|---|
| 训练轮数 | 90 | 300 |
| 优化器 | SGD | AdamW |
| 学习率 | 0.1 (step) | 5e-4 (cosine) |
| 权重衰减 | 1e-4 | 0.05 |
| 增强 | 简单 | RandAugment + CutMix |
| 标签平滑 | ❌ | ✅ (0.1) |
| EMA | ❌ | ✅ |
效果:此步骤将 ResNet-50 从 79.8% 提升到 80.5%。
1.2 层次化结构
参考 Swin-T 的宏观设计:
| 阶段 | Swin-T | ResNet-50 | ConvNeXt-T |
|---|---|---|---|
| Stage 1 | C=96, L=2 | C=256, L=3 | C=96, L=3 |
| Stage 2 | C=192, L=2 | C=512, L=4 | C=192, L=3 |
| Stage 3 | C=384, L=6 | C=1024, L=6 | C=384, L=9 |
| Stage 4 | C=768, L=2 | C=2048, L=3 | C=768, L=3 |
ConvNeXt-T 调整了各阶段的通道数和层数比例。
效果:此步骤使性能达到 81.1%。
2. ResNeXt-ify:Grouped Convolution
2.1 深度可分离卷积的启发
借鉴 MobileNet 的思想,使用深度可分离卷积替代标准卷积:
标准卷积:
Input (Cin) ──→ Output (Cout)
│
▼
Cin × Cout × K × K 个参数
深度可分离卷积:
Input (Cin) ──→ Depthwise (Cin) ──→ Pointwise (Cout)
│
└── 每个通道独立卷积
2.2 实现
将 ResNet 的 Bottleneck 中的 卷积替换为深度可分离卷积:
# 标准 Bottleneck
class Bottleneck(nn.Module):
def __init__(self, in_planes, planes, stride=1):
super().__init__()
self.conv1 = nn.Conv2d(in_planes, planes, 1)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, 3, stride, 1)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = nn.Conv2d(planes, planes * 4, 1)
self.bn3 = nn.BatchNorm2d(planes * 4)
# ConvNeXt Block: 深度可分离卷积
class ConvNeXtBlock(nn.Module):
def __init__(self, dim, drop_path=0.):
super().__init__()
# Depthwise Conv (替换 3x3 卷积)
self.dwconv = nn.Conv2d(dim, dim, 7, padding=3, groups=dim)
self.norm = nn.LayerNorm(dim, eps=1e-6)
self.pwconv1 = nn.Linear(dim, 4 * dim) # 扩展
self.act = nn.GELU()
self.pwconv2 = nn.Linear(4 * dim, dim) # 收缩
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
def forward(self, x):
input = x
x = self.dwconv(x) # 深度可分离卷积
x = x.permute(0, 2, 3, 1) # [B, C, H, W] → [B, H, W, C]
x = self.norm(x)
x = self.pwconv1(x)
x = self.act(x)
x = self.pwconv2(x)
x = x.permute(0, 3, 1, 2) # [B, H, W, C] → [B, C, H, W]
return input + self.drop_path(x)注意:ConvNeXt 使用的是大 kernel 深度可分离卷积(7×7),而非 MobileNet 的小 kernel(3×3)。
效果:此步骤使性能达到 81.8%。
3. Inverted Bottleneck:倒置残差块
3.1 MobileNetV2 的启发
MobileNetV2 提出的倒置残差块将「两头细、中间粗」变为「两头粗、中间细」:
标准残差块: 倒置残差块:
┌───┐ ┌───┐
│ 1 │ │ 4 │ (扩展)
│ ↓ │ │ ↓ │
│ 4 │ │ 1 │ (压缩)
│ ↓ │ │ ↓ │
│ 1 │ │ 1 │ (输入)
└───┘ └───┘
ResNet Bottleneck: ConvNeXt Block:
┌───┐ ┌───┐
│ 1 │ │ 1 │ (输入)
│ ↓ │ │ ↓ │
│ 4 │ │ 4 │ (扩展)
│ ↓ │ │ ↓ │
│ 1 │ │ 1 │ (输出)
└───┘ └───┘
3.2 实现
在 ConvNeXt Block 中,深度卷积位于两个 Pointwise 卷积之间:
# ResNet: 1x1 → 3x3 → 1x1 (先压缩后扩展)
# ConvNeXt: 1x1 → 7x7 → 1x1 (先扩展后压缩)
class ConvNeXtBlock(nn.Module):
def __init__(self, dim):
super().__init__()
# 隐层的扩展层
self.pwconv1 = nn.Linear(dim, 4 * dim)
self.act = nn.GELU()
# 大核深度卷积 (处于中间位置)
self.dwconv = nn.Conv2d(4 * dim, 4 * dim, 7, padding=3, groups=4 * dim)
# 输出投影
self.norm = nn.LayerNorm(4 * dim, eps=1e-6)
self.pwconv2 = nn.Linear(4 * dim, dim)效果:此步骤带来微小提升,达到 81.9%。
4. 大 Kernel 卷积
4.1 视觉 Transformer 的特性
Vision Transformer 的 MSA 操作本质上具有全局感受野。ConvNeXt 通过增大卷积核来部分模仿这一特性:
| 卷积核大小 | 理论感受野 | 参数效率 |
|---|---|---|
| 3×3 | 局部 | 高 |
| 5×5 | 稍大 | 中 |
| 7×7 | 接近窗口大小 | 较低 |
| 11×11 | 更大 | 低 |
4.2 实验结果
作者测试了不同 kernel size 的效果:
| Kernel Size | Top-1 Acc | 相对延迟 |
|---|---|---|
| 3×3 | 80.6% | 1.00× |
| 5×5 | 81.2% | 1.05× |
| 7×7 | 81.8% | 1.10× |
| 11×11 | 81.9% | 1.25× |
| 13×13 | 81.7% | 1.38× |
7×7 在精度和效率之间取得最佳平衡。
效果:最终达到 81.9%。
5. 微观设计:LayerNorm 与激活函数
5.1 归一化层
| 组件 | ResNet | ConvNeXt |
|---|---|---|
| 归一化 | BatchNorm | LayerNorm |
| 位置 | 卷积后 | 每个分支开始处 |
LayerNorm 对每个样本独立归一化,更稳定且不受 batch size 影响:
# BatchNorm: 在 batch 维度上归一化
# LayerNorm: 在通道/空间维度上归一化
# ConvNeXt 的 LayerNorm
x = x.permute(0, 2, 3, 1) # [B, C, H, W] → [B, H, W, C]
x = nn.LayerNorm(x, eps=1e-6)
x = x.permute(0, 3, 1, 2) # [B, H, W, C] → [B, C, H, W]5.2 激活函数
| 组件 | ResNet | ConvNeXt |
|---|---|---|
| 激活函数 | ReLU | GELU |
| 使用位置 | 每个卷积后 | 仅 FFN 中 |
GELU (Gaussian Error Linear Unit) 相比 ReLU 更平滑:
# GELU vs ReLU
# ReLU: max(0, x)
# GELU: x * sigmoid(1.702 * x) (近似)
nn.GELU() # ConvNeXt
nn.ReLU() # ResNet5.3 激活函数位置
ResNet 在卷积之间使用激活函数(pre-activation 风格较少),而 Transformer 在 FFN 中使用两处激活。ConvNeXt 采用类似 Transformer 的策略:
ResNet: Conv → BN → ReLU → Conv → BN → ReLU → Conv → BN → Add
ConvNeXt: LayerNorm → Linear → GELU → DepthConv → LayerNorm → Linear → Add
ConvNeXt 完整架构
架构配置
| 配置 | Stage 1 | Stage 2 | Stage 3 | Stage 4 | 参数量 |
|---|---|---|---|---|---|
| ConvNeXt-T | C=96, L=3 | C=192, L=3 | C=384, L=9 | C=768, L=3 | 28M |
| ConvNeXt-S | C=96, L=3 | C=192, L=3 | C=384, L=27 | C=768, L=3 | 50M |
| ConvNeXt-B | C=128, L=3 | C=256, L=3 | C=512, L=27 | C=1024, L=3 | 89M |
| ConvNeXt-L | C=128, L=3 | C=256, L=3 | C=512, L=27 | C=1024, L=3 | 198M |
完整代码
import torch
import torch.nn as nn
from torch.nn import DropPath
class ConvNeXtBlock(nn.Module):
"""ConvNeXt 基本块"""
def __init__(self, dim, drop_path=0.):
super().__init__()
# 大核深度卷积
self.dwconv = nn.Conv2d(dim, dim, kernel_size=7, padding=3, groups=dim)
self.norm = nn.LayerNorm(dim, eps=1e-6)
# Pointwise 前馈
self.pwconv1 = nn.Linear(dim, 4 * dim)
self.act = nn.GELU()
self.pwconv2 = nn.Linear(4 * dim, dim)
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
def forward(self, x):
input = x
x = self.dwconv(x)
x = x.permute(0, 2, 3, 1) # [B, C, H, W] → [B, H, W, C]
x = self.norm(x)
x = self.pwconv1(x)
x = self.act(x)
x = self.pwconv2(x)
x = x.permute(0, 3, 1, 2) # [B, H, W, C] → [B, C, H, W]
return input + self.drop_path(x)
class ConvNeXt(nn.Module):
"""ConvNeXt 完整架构"""
def __init__(self, in_chans=3, num_classes=1000,
depths=[3, 3, 9, 3], dims=[96, 192, 384, 768],
drop_path_rate=0.):
super().__init__()
# Stem: 4x4 卷积,步长 4
self.stem = nn.Sequential(
nn.Conv2d(in_chans, dims[0], kernel_size=4, stride=4),
nn.LayerNorm([dims[0], 56, 56], eps=1e-6)
)
# 阶段
self.stages = nn.ModuleList()
dp_rates = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))]
cur = 0
for i in range(4):
stage = nn.Sequential(
*[ConvNeXtBlock(dim=dims[i], drop_path=dp_rates[cur + j])
for j in range(depths[i])]
)
self.stages.append(stage)
cur += depths[i]
# Patch Merging (除最后一个阶段)
if i < 3:
downsample = nn.Sequential(
nn.LayerNorm([dims[i], ], eps=1e-6),
nn.Conv2d(dims[i], dims[i+1], kernel_size=2, stride=2)
)
self.stages.append(downsample)
# Head
self.norm = nn.LayerNorm(dims[-1], eps=1e-6)
self.head = nn.Linear(dims[-1], num_classes)
self.apply(self._init_weights)
def _init_weights(self, m):
if isinstance(m, (nn.Conv2d, nn.Linear)):
nn.init.trunc_normal_(m.weight, std=0.02)
if m.bias is not None:
nn.init.zeros_(m.bias)
elif isinstance(m, nn.LayerNorm):
nn.init.ones_(m.weight)
nn.init.zeros_(m.bias)
def forward(self, x):
x = self.stem(x)
for stage in self.stages:
x = stage(x)
x = x.mean([-2, -1]) # 全局平均池化
x = self.norm(x)
return self.head(x)与 Swin Transformer 的对比
性能对比(ImageNet 分类)
| 模型 | Top-1 | Top-5 | 参数量 | FLOPs |
|---|---|---|---|---|
| Swin-T | 81.3% | 95.5% | 28M | 4.5B |
| ConvNeXt-T | 82.1% | 95.9% | 28M | 4.5B |
| Swin-S | 83.1% | 96.3% | 50M | 8.7B |
| ConvNeXt-S | 83.1% | 96.4% | 50M | 8.7B |
| Swin-B | 83.5% | 96.5% | 88M | 15.4B |
| ConvNeXt-B | 83.8% | 96.6% | 89M | 15.4B |
结论:ConvNeXt 与 Swin Transformer 在 ImageNet 上性能相当。
下游任务对比
COCO 目标检测
| Backbone | Box AP | 参数量 |
|---|---|---|
| Swin-T | 46.0% | 48M |
| ConvNeXt-T | 46.2% | 48M |
| Swin-B | 49.5% | 107M |
| ConvNeXt-B | 49.6% | 107M |
ADE20K 语义分割
| Backbone | mIoU | 参数量 |
|---|---|---|
| Swin-S | 47.6% | 69M |
| ConvNeXt-S | 48.1% | 69M |
架构对比
| 特性 | ConvNeXt | Swin Transformer |
|---|---|---|
| 核心操作 | 大核深度可分离卷积 | 移位窗口注意力 |
| 位置编码 | 无(使用 LayerNorm) | 相对位置偏置 |
| 归一化 | LayerNorm | LayerNorm |
| 激活函数 | GELU | GELU |
| 多尺度 | Patch Merging | Patch Merging |
| 计算方式 | 卷积(高效) | 注意力(灵活) |
优势对比
| 优势 | ConvNeXt | Swin Transformer |
|---|---|---|
| 实现简洁性 | ✅ 更接近经典 CNN | 需实现窗口注意力 |
| 硬件友好性 | ✅ 卷积操作成熟 | 中等(窗口操作需优化) |
| 全局建模 | 需大核/深层 | ✅ 原生全局(通过多层) |
| 灵活适配 | 中等 | ✅ 更灵活(可调整窗口大小) |
ConvNeXt-V2
ConvNeXt-V22 在 V1 基础上进行了进一步改进:
主要创新
- 全局响应归一化(GRN):替代 LayerNorm,解决特征崩溃问题
- 改进的训练配方:更强的数据增强和正则化
- 更大的模型容量:ConvNeXt-V2-FC (FC = Fully Convolutions)
GRN 层
class GRN(nn.Module):
"""全局响应归一化"""
def __init__(self, dim):
super().__init__()
self.gamma = nn.Parameter(torch.zeros(1, 1, 1, dim))
self.beta = nn.Parameter(torch.zeros(1, 1, 1, dim))
def forward(self, x):
# x: [B, H, W, C]
# L2 归一化
Gx = torch.norm(x, p=2, dim=(1, 2), keepdim=True)
Nx = Gx / (Gx.mean(dim=-1, keepdim=True) + 1e-6)
return self.gamma * (x * Nx) + self.beta + x性能提升
| 模型 | ImageNet Top-1 |
|---|---|
| ConvNeXt-B (V1) | 83.8% |
| ConvNeXt-V2-B | 85.1% |
| ConvNeXt-V2-L | 86.5% |
| ConvNeXt-V2-G | 87.8% |
设计原则总结
ConvNeXt 的设计过程揭示了现代 CNN 架构的关键原则:
1. 向 Transformer 学习
| Transformer 技术 | ConvNeXt 的对应 |
|---|---|
| LayerNorm | LayerNorm |
| GELU | GELU |
| 大kernel / 全局注意力 | 7×7 深度可分离卷积 |
| 倒置瓶颈 | 倒置残差结构 |
| 训练策略 | AdamW + 增强 |
2. 保持 CNN 的优势
| CNN 优势 | ConvNeXt 的保留 |
|---|---|
| 局部连接 | 深度可分离卷积 |
| 平移不变性 | 卷积的固有特性 |
| 硬件友好 | 纯卷积操作 |
| 参数效率 | 通道分组 |
3. 实验驱动
ConvNeXt 的每一步改进都基于严格的消融实验:
ResNet-50 Baseline: 79.8%
↓
+ 训练配方: 80.5% (+0.7)
↓
+ Macro 设计: 81.1% (+0.6)
↓
+ ResNeXt 化: 81.8% (+0.7)
↓
+ 倒置瓶颈: 81.9% (+0.1)
↓
+ 大 Kernel: 82.1% (+0.2)
↓
+ LayerNorm + GELU: 82.6% (+0.5)
↓
ConvNeXt-T: 82.1% (最终)
适用场景与建议
选择 ConvNeXt 的场景
| 场景 | 推荐理由 |
|---|---|
| 生产部署 | 卷积操作成熟,推理效率高 |
| 资源受限环境 | 参数效率与 FLOPs 效率兼优 |
| 追求简洁 | 实现接近经典 CNN,易于维护 |
| 竞赛刷榜 | 与 Swin 相当的精度 |
选择 Swin Transformer 的场景
| 场景 | 推荐理由 |
|---|---|
| 超高分辨率输入 | 窗口注意力复杂度更低 |
| 需要更灵活的结构 | 窗口大小可调 |
| 多模态融合 | 自注意力的跨模态对齐更自然 |
| 理论分析 | 注意力机制更易解释 |
参考
Footnotes
-
Liu, Z., et al. (2022). A ConvNet for the 2020s. CVPR 2022. https://arxiv.org/abs/2201.03545 ↩
-
Woo, S., et al. (2023). ConvNeXt V2: Co-designing and Scaling ConvNets with Masked Autoencoders. arXiv:2301.00808 ↩