引言

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
训练轮数90300
优化器SGDAdamW
学习率0.1 (step)5e-4 (cosine)
权重衰减1e-40.05
增强简单RandAugment + CutMix
标签平滑✅ (0.1)
EMA

效果:此步骤将 ResNet-50 从 79.8% 提升到 80.5%。

1.2 层次化结构

参考 Swin-T 的宏观设计:

阶段Swin-TResNet-50ConvNeXt-T
Stage 1C=96, L=2C=256, L=3C=96, L=3
Stage 2C=192, L=2C=512, L=4C=192, L=3
Stage 3C=384, L=6C=1024, L=6C=384, L=9
Stage 4C=768, L=2C=2048, L=3C=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 SizeTop-1 Acc相对延迟
3×380.6%1.00×
5×581.2%1.05×
7×781.8%1.10×
11×1181.9%1.25×
13×1381.7%1.38×

7×7 在精度和效率之间取得最佳平衡。

效果:最终达到 81.9%。


5. 微观设计:LayerNorm 与激活函数

5.1 归一化层

组件ResNetConvNeXt
归一化BatchNormLayerNorm
位置卷积后每个分支开始处

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 激活函数

组件ResNetConvNeXt
激活函数ReLUGELU
使用位置每个卷积后仅 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()  # ResNet

5.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 1Stage 2Stage 3Stage 4参数量
ConvNeXt-TC=96, L=3C=192, L=3C=384, L=9C=768, L=328M
ConvNeXt-SC=96, L=3C=192, L=3C=384, L=27C=768, L=350M
ConvNeXt-BC=128, L=3C=256, L=3C=512, L=27C=1024, L=389M
ConvNeXt-LC=128, L=3C=256, L=3C=512, L=27C=1024, L=3198M

完整代码

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-1Top-5参数量FLOPs
Swin-T81.3%95.5%28M4.5B
ConvNeXt-T82.1%95.9%28M4.5B
Swin-S83.1%96.3%50M8.7B
ConvNeXt-S83.1%96.4%50M8.7B
Swin-B83.5%96.5%88M15.4B
ConvNeXt-B83.8%96.6%89M15.4B

结论:ConvNeXt 与 Swin Transformer 在 ImageNet 上性能相当。

下游任务对比

COCO 目标检测

BackboneBox AP参数量
Swin-T46.0%48M
ConvNeXt-T46.2%48M
Swin-B49.5%107M
ConvNeXt-B49.6%107M

ADE20K 语义分割

BackbonemIoU参数量
Swin-S47.6%69M
ConvNeXt-S48.1%69M

架构对比

特性ConvNeXtSwin Transformer
核心操作大核深度可分离卷积移位窗口注意力
位置编码无(使用 LayerNorm)相对位置偏置
归一化LayerNormLayerNorm
激活函数GELUGELU
多尺度Patch MergingPatch Merging
计算方式卷积(高效)注意力(灵活)

优势对比

优势ConvNeXtSwin Transformer
实现简洁性✅ 更接近经典 CNN需实现窗口注意力
硬件友好性✅ 卷积操作成熟中等(窗口操作需优化)
全局建模需大核/深层✅ 原生全局(通过多层)
灵活适配中等✅ 更灵活(可调整窗口大小)

ConvNeXt-V2

ConvNeXt-V22 在 V1 基础上进行了进一步改进:

主要创新

  1. 全局响应归一化(GRN):替代 LayerNorm,解决特征崩溃问题
  2. 改进的训练配方:更强的数据增强和正则化
  3. 更大的模型容量: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-B85.1%
ConvNeXt-V2-L86.5%
ConvNeXt-V2-G87.8%

设计原则总结

ConvNeXt 的设计过程揭示了现代 CNN 架构的关键原则:

1. 向 Transformer 学习

Transformer 技术ConvNeXt 的对应
LayerNormLayerNorm
GELUGELU
大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

  1. Liu, Z., et al. (2022). A ConvNet for the 2020s. CVPR 2022. https://arxiv.org/abs/2201.03545

  2. Woo, S., et al. (2023). ConvNeXt V2: Co-designing and Scaling ConvNets with Masked Autoencoders. arXiv:2301.00808