单位:FAIR (DenseNet共同一作,曾获CVPR2017 best paper),UC伯克利
ArXiv:https://arxiv.org/abs/2201.03545
Github:https://github.com/facebookresearch/ConvNeXt
导读:提到“年代”一词,不免让人提前设想当时有如何的大事件或大人物。正当其时的“2020s”年代,从Transformer开始,引爆了一股“咆哮”的热潮,各种框架层出不穷,借用凯明一句话“without bells and whistles”,沉淀下来的实用性如何?本文作者长篇分析设计CNN架构的若干技巧,对照Swin Transformer的设计理念,渐进式“现代化”改造ResNet,取得了良好的效果,对深度网络的设计具有较大的参考价值。 ConvNeXt在与Transformer的较量中,给CNN掰回一局。
计算机视觉迎来了一个“咆哮”的2020s年代,它的起点从引入视觉Transformer开始,即ViT,它能快速超过CNN并取得SOTA的识别性能。而原始的ViT架构,在通用的计算机视觉任务上如目标检测和语义分割等面临不少困难。直到具有分层架构的Transformer如Swin Transformer,通过重新引入ConvNet一些设计,它作为一种通用的视觉backbone并在多个视觉任务上取得优良的表现,才让Transformer变得实用。但是,这种混合架构的有效性,在很大程度归功于Transformer的内在优势,而不是卷积固有的归纳偏差(inductive biases)。这篇文章重新审视设计的空间,并测试一个纯CNN网络能取得什么样的上限。作者通过逐渐将一个标准的ResNet模型,不断往ViT的设计中改造,并发现了几个关键的成分导致了性能的差异。作者构造的纯ConvNet系列模型,能够取得超过Swin Transformer的性能,在ImageNet-1K上取得了超过87.8%的Top1精度。
如图,作者分别对比了CNN和Transformer中最具代表性的模型,CNN模型如ResNet、ConvNeXt,Transformer模型如DeiT、Swin Transformer,ConvNeXt在精度和运算量上能取得最好的平衡,在扩展性上也能与Swin Transformer相当但设计更加简单。圆圈的直径越大,运算量越大。
回顾 2010 年代,这十年以深度学习的巨大进步和影响为标志。主要驱动力是神经网络的复兴,特别是卷积神经网络 (ConvNets)。十年来,视觉识别领域成功地从工程特征转向设计(ConvNet)架构。尽管反向传播训练的卷积网络的发明可以追溯到 1980 年代,直到 2012 年底,我们才看到视觉特征学习的真正潜力。 AlexNet [40] 的引入沉淀了“ImageNet 时刻”[59],开创了计算机视觉的新时代。此后,该领域迅速发展。 VGGNet [64]、Inceptions [68]、ResNe(X)t [28, 87]、DenseNet [36]、MobileNet [34]、EfficientNet [71] 和 RegNet [54] 等代表性 ConvNet 专注于不同方面,如准确性、效率和可扩展性,并推广了许多有用的设计原则。
ConvNets在计算机视觉中的统治地位并非巧合:在许多应用场景中,“滑窗”设计是视觉处理所固有的,尤其是在处理高分辨率图像时。ConvNets有几个内置的归纳偏置,使得它们能够适合各种计算机视觉任务处理。其中最重要的一个是平移不变性,这对于目标检测任务是一种理想的属性。ConNets本质上也是高效的,因为当以滑窗方式处理时,计算是共享的。几十年来,在有限种类的识别任务如数字、人脸、人体中,默认使用ConvNets。进入2010s年代后,基于区域的检测器进一步将ConvNets提升到成为视觉识别系统中基本构造单元的地位。
大约在同一时间,自然语言处理NLP的神经网络设计之旅(the odyssey of network design,奥德赛,漫长而惊险的旅行)走上了一条截然不同的道路,因为Transformers去掉了循环神经网络RNN成为主导的主干网络。尽管语言与视觉领域之间感兴趣点存在差异,但随着视觉转换器(ViT)的引入彻底改变了网络架构设计的格局,这两个领域流惊奇地在2020s汇合了。除了最初的“patchify”层将图像分割成一系列补丁之外,ViT没有引入图像特定的归纳偏置,并只在原始的NLP transformer上做最小的改动。ViT的一个主要关注点是可扩展性的行为上:借助更大的模型和数据集,Transformers可以取得显著超过标准ResNet的性能。这些在图像分类任务上取得的结果是令人鼓舞的,但计算机视觉不仅限于图像分类。如之前所述,过去十年很多计算机视觉任务的解决方法很大程度上取决于滑窗,全卷积的范式。在没有ConvNets归纳偏置的情况下,普通的ViT模型在被用作通用视觉主干时面临许多挑战。最大的挑战是ViT的全局注意力设计,它相对输入尺度大小具有二次方复杂度。这对于ImageNet分类可能是接受的,但难以处理高分辨率的输入。
分层Transformer采用混合方法去弥补这一差距。例如,“滑窗”策略(如局部窗口内的注意力)被重新引入到Transformers中,使得他们的行为与ConvNets相似。Swin Transformer是朝着这个方向发展的里程碑式的工作,它首次证明了Transformer可以用作通用的视觉主干,并在图像分类之外的一系列计算机视觉任务上实现了SOTA的性能。Swin Transformer的成功和迅速采用也解释了一件事:卷积的本质并没有变得无关紧要;反而,它仍然很受欢迎,并从未褪色。
从这个角度来看,Transformers在计算机视觉方面的许多进步都旨在恢复卷积。然而,这些尝试有代价的:滑窗自注意力的朴素实现可能很昂贵;使用循环移位等先进方法,可以优化速度但在系统设计上更加复杂。另一方面,具有讽刺意味的是,ConvNets已经满足了许多所需要的属性,尽管是以一种直接、简洁的方法。ConvNets失去动力的唯一原因是分层的Transformers在许多计算机视觉任务中超过了它们,而性能差异通常是归因于Transformer卓越的可扩展性,其中关键组件是多头注意力。
与过去十年中逐渐改进的ConvNet不同,ViT的采用是箭步改变。在最近的文献中,在比较两者时通常采用系统级比较,如Swin Transformer与ResNet。ConvNets与分层ViT同时具有相似性与差异性:它们都具备了相似的归纳偏置,但在训练过程中和宏观/微观层面的架构设计上存在显著差异。在这篇文中,作者研究了ConvNets和Transformer之间的架构区别,并在比较网络性能时尝试定位混在因子。作者的研究旨在弥合ConvNet在ViT出现前后的差距,并测试纯ConvNet所能达到的极限。
为此,作者从标准的ResNet开始,以一种渐进式的改进过程。作者逐渐将架构“现代化”,朝着分层Transformer如SwinT去构建。该探索由一个关键的问题引导:Transformer中的设计决策如何影响ConvNet的性能?作者发现了几个关键组件导致了该性能的差异。在COCO目标检测与分割,ADE20K语义分割等任务上评估,令人惊讶的是,完全由标准ConvNet模块构建的ConvNeXt,具有相当竞争力。作者希望这些新的观察与讨论能够挑战一些共同的观念,并鼓励人们重新思考CNN在计算机视觉中的重要性。
如图,作者将标准的ResNet朝着Swin Transformer现代化迈进,而不引入任何基于注意力的模块。前景条是ResNet50、Swin-Tiny在FLOPs上的精度。ResNet200、Swin-Base方法的结果用灰色条显示。阴影线表示未采用修改。很多Transformer架构能够合并到ConvNet中,它们会带来不断提升的性能。最后,ConvNeXt模型,可以超过Swin Transformer。题外话,虽然看过论文不少,但没有像作者如此总结与归纳,体现差距的地方在于:从归纳中发现影响因子,并实验论证。
本章节中,作者提供了ResNet到类似于Transformer的ConvNet的轨迹。作者根据FLOP考虑两种模型大小,一种是ResNet50、Swin-Tiny体,其FLOPs约为4.5G;另一种是ResNet200、Swin-Base体,其FLOPs为15.0G。
在高层次上,作者探索旨在调查和遵循Swin Transformer的不同设计级别,同时保持作为标准ConvNet的简洁性。路线图如上图所示,作者开始的起点是ResNet50模型。作者首先使用训练Transformer的类似训练技术对其进行训练,并与原始的ResNet50相对获得了很大改进的结果。这将是作者的基线工作。然后,作者设计了一系列设计决策,总结为:
上图展示了作者“现代化”网络的每一步的实现过程与步骤。由于网络复杂性与性能密切相关,因此在探索过程中对FLOPs进行了粗略控制,尽管在中间步骤中,FLOPs可能高于或低于参考模型。所有模型都在ImageNet-1K上进行训练与测试。
除了网络架构的设计,训练过程也会影响最终性能。ViT不仅带来了一组新的模块和架构设计决策,而且还为视觉引入了不同的训练技巧,如AdamW优化器。这主要与优化策略相关的超参设置有关。因此,我们探索的第一步就是使用视觉Transformer训练过程来训练基线模型,如ResNet50/200。最近的研究表明,一组现代训练技术可以显著提高ResNet50的性能。在作者的研究中,作者使用了接近Deit和Swin Transformer相近的训练方法。训练ResNet从原始的90个epoch到300个epoch,使用AdamW优化器、数据增强技术如Mixup、Cutmix、RandAugment、Random Erasing等,随机深度、标签平滑等正则化方法。就其本身而言,这种提升的训练方案将ResNet50性能从76.1%提高到78.8%,这意味着传统ConvNet和Transformer之间的很大一部分性能差异可能来自训练技巧。 作者在整个“现代化”过程中使用这种固定超参的训练方法。ResNet50报告的精度是通过三种不同的随机种子进行训练获得的平均值。
作者先从Swin Transformer的宏观设计开始分析。Swin Transformer遵循ConvNet使用多阶段设计,其中每个阶段具有不同的分辨率。有两个有趣的设计因素考虑:阶段计算比例,和“主干细胞”结构。
(1) 改变阶段计算比例。
ResNet跨阶段计算分布的原始设计在很大程度上经验性的。res4阶段旨在与目标检测等下游任务兼容,其中检测头网络在 14 × 14 14 \times 14 14×14特征图上运行。另一方面,Swin Transformer遵循相同的设计原则,但阶段计算比例略有不同,为 1 : 1 : 3 : 1 1:1:3:1 1:1:3:1。对于较大的Swin Transformer,比例是 1 : 1 : 9 : 1 1:1:9:1 1:1:9:1。按照设计,作者将每个阶段的块数从ResNet50中的 3 : 4 : 6 : 3 3:4:6:3 3:4:6:3调整为 3 : 3 : 9 : 3 3:3:9:3 3:3:9:3,这也使FLOPs与Swin-Tiny对齐。这将模型准确率从78.8%提升到79.4%。值得注意的是,有一些研究充分讨论计算分布,似乎存在更优化的设计。作者将使用这个比例运算。
(2) 将主干Patchify。
通常,主干细胞设计关注的是在网络开始时如何处理输入图像。由于自然图像固有的冗余性,一个常见的干细胞在标准的ConvNet和ViT中积极地将输入图像下采样到合适的特征图大小。标准ResNet模型中的干细胞包含有一个 7 × 7 7 \times 7 7×7步长为2的卷积层,然后跟着一个最大池化层,这导致了输入图像的4倍下采样。在ViT中,干细胞采用了更激进的“patchify”策略,相当于对应较大的内核大小(核大小为14或16)和非重叠卷积。Swin Transformer采用类似的patchify层,但使用了更小的 4 × 4 4\times 4 4×4补丁大小来适应架构的多阶段设计。作者将ResNet风格的干细胞替换为使用 4 × 4 4\times 4 4×4、步长为4的卷积层实现补丁化层。准确率从79.4提升到79.5,表明ResNet中的干细胞可以使用更简单的patchify层来替代,产生相似的性能。
作者将在网络中采用补丁化干细胞( 4 × 4 4\times 4 4×4、步长为4的卷积层)。
本部分作者尝试采用ResNeXt的思想,它比原始的ResNet取得了更好的FLOPs/Accuracy均衡。核心组件是分组卷积,其中卷积滤波器被分为不同的组。在较高的层面上,ResNeXt的指导原则是“使用更多的组,扩大宽度”。更精确地说,ResNeXt对BottleNeck中的 3 × 3\times 3×卷积采用分组卷积。这显著降低FLOPs,因此扩展了网络宽度以补偿容量损失。
在作者的案例中,使用深度卷积DW-CNN,这是分组卷积的一种特殊情况,每一组只有一个通道。DW-CNN已经被MobileNet和Xception推广。作者注意到,DW-CNN类似于self-attention中的加权求和操作,它在每个通道的基础上操作,即仅在空间维度上融合信息。DW-CNN和Point-wise CNN的组合导致了空间与通道混合的分离,这是ViT共享的属性,其中每个操作要么混合空间维度或通道维度的信息,而不能同时混合两者。DW-CNN的使用有效地降低了FLOPs,预期地一样,和准确度。按照ResNeXt提到的策略,作者将网络宽度增加到Swin-Tiny相同的通道数(从64到96),FLOPs增加到5.3G,识别率也达到了了80.5%。
每个Transformer块的重要设计是它创造了一个倒置的BottleNeck,即MLP块的隐藏维度是输入维度的四倍。有趣的是,Transformer这种设计与ConvNets中使用的扩展比为4的倒置BottleNeck设计有关联。这个想法被MobileNet-v2推广,随后在几个先进的ConvNets网络中获得关注。
模块修改和对应的规格。(a) ResNeXt的一个块;(b) 作者创建的反转维度后的BottleNeck块;(c)将DW-CNN层上移后的模块。
作者探索了倒置BottleNeck的设计。比较有意思的是,网络的FLOPs减少到4.6G,但精度从80.5提升到80.6,在ResNet200和Swin-Base中,这一步带来了更大的收益,从81.9增加到82.6,运算量反而减低。
在这部分,作者关注大卷积核的行为。ViT最显著的方面之一是它们的非局部注意力,它使每一层都具有全局感受野。虽然过去ConvNets也使用大卷积核,但黄金标准(VGG普及)是堆叠小卷积核( 3 × 3 3\times 3 3×3)的卷积层,在现代GPU上具有高效的硬件实现。尽管Swin Transformer将局部窗口重新引入自注意力模块中,但窗口大小至少为 7 × 7 7 \times 7 7×7,明显大于 3 × 3 3\times 3 3×3的ResNe(X)t等。
(1) 上移DW-CNN层
为了探索大内核,一个先决条件是向上移动DW-CNN的位置,上图c。这在ViT设计中也很明显:MSA模块放置在MLP层前。这是一个自然的设计选择:即复杂而低效率的模块(如MSA、大内核)具有更少的通道,而高效率的 1 × 1 1\times 1 1×1将完成繁重的工作。这个中间步骤将FLOPs降低到4.1G,性能也暂时下降到79.9%。
(2) 扩大卷积核尺寸
通过所有这些准备,采用更大内核的好处是明显的。作者尝试了几个内核大小,包括 3 , 5 , 7 , 9 , 11 {3,5,7,9,11} 3,5,7,9,11。网络的性能从79.9(3×3)增加到80.6(7×7),而网络的FLOPs大致保持不变。此外,作者观察到大核的好处在(7×7)出达到饱和。因此,默认采用核大小为7的卷积层。
至此,作者完成了对宏观网络架构的检查。有趣的是,Transformer中的很大一部分设计选择可能会映射到ConvNet的实例化中。
本节作者在微观尺度上研究其他几个架构差异:这里大部分探索都是在层级完成的,重点关注激活函数和归一化层的选择。
(1) 使用GELU替代ReLU
NLP和视觉架构之间的一个差异是使用哪些激活函数的细节。随着时间的推移有不少激活函数被开发出来,但是ReLUctant即整流线性单元由于其简单性和效率,仍然在ConvNets中广泛使用。ReLU也被用在原始Transformer论文中。高斯误差线性单元GELU,可以看作是ReLU的一个平滑变体,被用于最先进的Transformer中,包括谷歌的BERT,OpenAI的GPT2,和大多数最近的ViT中。作者发现ReLU可以用GELU代替,尽管精度保持不变,都为80.6%。
(2) 更少的激活函数
Transformer和ResNet块之间的一个小区别是Transformer的激活层更少。考虑一个带有QKV的线性嵌入层、投影层和MLP块中的两个线性层的Transformer块,在MLP块中只有一个激活函数。相比之下,通常的做法是在每个卷积层(包括1×1)后附件一个激活函数。这里,作者研究了当坚持相同的策略时,性能如何变化。如图,作者从残差块中消除了所有的GELU层,除了两个 1 × 1 1\times 1 1×1层之间的一个,复制了Transformer块的样式。这个过程将结果提高了0.7%到81.3%,与Swin-Tiny性能相当。因此,作者在每个模块中使用单个GELU激活。
(3) 更少的归一化层
Transformer块通常具有更少的归一化层。这里,作者删除了两个BN层,在conv 1 × 1 1\times 1 1×1层之前只留有一个BN层。这进一步将性能提升到81.4%,超过Swin-Tiny的效果。注意,作者设计的模块的归一化层比Transformer还要少,因为根据经验作者发现在模块的开头添加一个额外的BN层并不能提高性能。
(4) 用LN替代BN
BatchNorm是ConvNets的重要组成部分,因为它提高了收敛性并减少过拟合。但是,BN可能会对模型的性能产生一些不利的影响。尽管有很多尝试去开发可替代的归一化技术,但BN在多数视觉任务中仍然是首先。另一方面,Transformer中更简单的层归一化即Layer Norm,在不同的应用场景中实现良好的性能。
在原始的ResNet中直接用LN替换BN,将导致性能欠佳。随着网络架构和训练技术的修改,作者重新审视了使用LN替代BN的影响。观察到,作者的ConvNet模型使用LN训练没有任何困难;实际上,性能稍好一点,达到了81.5%。因此,作者在每个残差块中采用LN作为归一化。
(5) 单独的下采样层
ResNet中下采样是在每一层中通过残差块来实现的,即使用 3 × 3 3\times 3 3×3、步长为2的卷积(在shortcut中是 1 × 1 1\times 1 1×1、步长为2的卷积)。在Swin Transformer中,一个单独的下采样层被添加到各个阶段之间。作者探索了一个类似的策略,采用使用 2 × 2 2\times 2 2×2、步长为2的卷积进行空间下采样。这种修改,出乎意料地导致了不同的训练。进一步调查表明,在空间分辨率发生变化的地方,添加归一化层有利于稳定训练。其中包括Swin Transformer中也使用的几个LN层:每个下采样层前有一个,主干细胞之后有一个,和全局池化层之后的一个。作者将性能提升到82.0%,明显超过了Swin-Tiny的81.3%。
至此,作者已经完成了第一次“演练”,并发现了 ConvNeXt,一个纯 ConvNet,它在这个计算机制中的 ImageNet-1K 分类性能优于 Swin Transformer。值得注意的是,到目前为止讨论的所有设计选择都改编自ViT。此外,即使在 ConvNet 文献中,这些设计也并不新颖——在过去十年中,它们都是单独研究的,但不是集体研究的。作者提出的 ConvNeXt 模型具有与 Swin Transformer 大致相同的 FLOP、#params.、吞吐量和内存使用,但不需要专门的模块,例如移位窗口注意力或相对位置偏差。
这些发现令人鼓舞,但尚未完全令人信服——迄今为止,作者的探索仅限于小规模,但ViT的可扩展性才是真正让它们与众不同的地方。此外,ConvNet 能否在对象检测和语义分割等下游任务上与 Swin Transformers 竞争的问题是计算机视觉从业者关注的核心问题。在下一节中,作者将在数据和模型大小方面扩展 ConvNeXt 模型,并在一组不同的视觉识别任务上评估它们。
在ImageNet-1K上,与各大深度网络的性能对比。不难发现,ConvNeXt-Tiny相比较于同等运算量的Swin-Tiny,取得了超过0.8%到82.1%的Top1 acc。在更大输入图像分辨率即384下,更大容量的ConvNext-Base也比Swin-Base取得更好的性能。
ConvNeXt 基本构造块
class Block(nn.Module):
r""" ConvNeXt Block. There are two equivalent implementations:
(1) DwConv -> LayerNorm (channels_first) -> 1x1 Conv -> GELU -> 1x1 Conv; all in (N, C, H, W)
(2) DwConv -> Permute to (N, H, W, C); LayerNorm (channels_last) -> Linear -> GELU -> Linear; Permute back
We use (2) as we find it slightly faster in PyTorch
Args:
dim (int): Number of input channels.
drop_path (float): Stochastic depth rate. Default: 0.0
layer_scale_init_value (float): Init value for Layer Scale. Default: 1e-6.
"""
def __init__(self, dim, drop_path=0., layer_scale_init_value=1e-6):
super().__init__()
self.dwconv = nn.Conv2d(dim, dim, kernel_size=7, padding=3, groups=dim) # depthwise conv
self.norm = LayerNorm(dim, eps=1e-6)
self.pwconv1 = nn.Linear(dim, 4 * dim) # pointwise/1x1 convs, implemented with linear layers
self.act = nn.GELU()
self.pwconv2 = nn.Linear(4 * dim, dim)
self.gamma = nn.Parameter(layer_scale_init_value * torch.ones((dim)),
requires_grad=True) if layer_scale_init_value > 0 else None
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) # (N, C, H, W) -> (N, H, W, C)
x = self.norm(x)
x = self.pwconv1(x)
x = self.act(x)
x = self.pwconv2(x)
if self.gamma is not None:
x = self.gamma * x
x = x.permute(0, 3, 1, 2) # (N, H, W, C) -> (N, C, H, W)
x = input + self.drop_path(x)
return
ConvNeXt整体结构:
class ConvNeXt(nn.Module):
r""" ConvNeXt
A PyTorch impl of : `A ConvNet for the 2020s` -
https://arxiv.org/pdf/2201.03545.pdf
Args:
in_chans (int): Number of input image channels. Default: 3
num_classes (int): Number of classes for classification head. Default: 1000
depths (tuple(int)): Number of blocks at each stage. Default: [3, 3, 9, 3]
dims (int): Feature dimension at each stage. Default: [96, 192, 384, 768]
drop_path_rate (float): Stochastic depth rate. Default: 0.
layer_scale_init_value (float): Init value for Layer Scale. Default: 1e-6.
head_init_scale (float): Init scaling value for classifier weights and biases. Default: 1.
"""
def __init__(self, in_chans=3, num_classes=1000,
depths=[3, 3, 9, 3], dims=[96, 192, 384, 768], drop_path_rate=0.,
layer_scale_init_value=1e-6, head_init_scale=1.,
):
super().__init__()
self.downsample_layers = nn.ModuleList() # stem and 3 intermediate downsampling conv layers
stem = nn.Sequential(
nn.Conv2d(in_chans, dims[0], kernel_size=4, stride=4),
LayerNorm(dims[0], eps=1e-6, data_format="channels_first")
)
self.downsample_layers.append(stem)
for i in range(3):
downsample_layer = nn.Sequential(
LayerNorm(dims[i], eps=1e-6, data_format="channels_first"),
nn.Conv2d(dims[i], dims[i+1], kernel_size=2, stride=2),
)
self.downsample_layers.append(downsample_layer)
self.stages = nn.ModuleList() # 4 feature resolution stages, each consisting of multiple residual blocks
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(
*[Block(dim=dims[i], drop_path=dp_rates[cur + j],
layer_scale_init_value=layer_scale_init_value) for j in range(depths[i])]
)
self.stages.append(stage)
cur += depths[i]
self.norm = nn.LayerNorm(dims[-1], eps=1e-6) # final norm layer
self.head = nn.Linear(dims[-1], num_classes)
self.apply(self._init_weights)
self.head.weight.data.mul_(head_init_scale)
self.head.bias.data.mul_(head_init_scale)
def _init_weights(self, m):
if isinstance(m, (nn.Conv2d, nn.Linear)):
trunc_normal_(m.weight, std=.02)
nn.init.constant_(m.bias, 0)
def forward_features(self, x):
for i in range(4):
x = self.downsample_layers[i](x)
x = self.stages[i](x)
return self.norm(x.mean([-2, -1])) # global average pooling, (N, C, H, W) -> (N, C)
def forward(self, x):
x = self.forward_features(x)
x = self.head(x)
return x
结论:纯CNN架构,也能取得超过同等规模的Swin Transformer的识别性能。Transformer出色的可扩展性,在该ConvNext上也能体现出来;此外,在下游任务如目标检测、语义分割等,ConvNeXt也能取得出色的性能。这一局与Transformer较量,ConvNeXt为CNN扳回一局。