【论文解读】A ConvNet for the 2020s

探索是由一个关键问题引导的:Transformers中的设计决策如何影响ConvNets的性能?

1.训练决策

训练从ResNets最初的90个时期扩展到300个时期。我们使用AdamW优化器[46]、数据增强技术,如Mixup[90]、Cutmix[89]、RandAugment[14]、随机擦除[91],以及正则化方案,包括随机深度[36]和标签平滑[69]。

2.宏观设计

有两个有趣的设计考虑因素:阶段计算比例和“主干”结构。

2.1阶段计算比例

但阶段计算比例略有不同,为1:1:3:1。对于较大的SwinTransformer,比例为1:1:9:1。根据设计,我们将每个阶段的块数从ResNet-50中的(3,4,6,3)调整为(3,3,9,3),这也将FLOP与Swin-T对齐。这将模型的准确率从78.8%提高到79.4%

class ConvNeXt(nn.Module):
    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.,
                 ):

研究人员已经彻底研究了计算的分布[53,54],很可能存在更优化的设计。

2.2将主干结构patch化

主干结构:将输入下采样至合适的feature map的过程

在Transformer中,主干结构使用了一种更激进的“patchify”策略,这与大的内核大小相对应(例如内核大小=14或16)和非重叠卷积。

Swin Transformer使用了类似的“patchify”层,但patch大小较小,为4,以适应架构的多阶段设计。我们用一个使用4*4,步幅为4的卷积层。

        stem = nn.Sequential(
            nn.Conv2d(in_chans, dims[0], kernel_size=4, stride=4),
            LayerNorm(dims[0], eps=1e-6, data_format="channels_first")
        )

2.3. ResNeXt-ify

ResNeXt的指导原则是“使用更多的组,扩大宽度”。

使用深度卷积,这是分组卷积的一种特殊情况,其中组的数量等于通道的数量

原因:深度卷积类似于自注意中的加权和运算,它在每个通道的基础上进行运算,即仅在空间维度上混合信息。

self.dwconv = nn.Conv2d(dim, dim, kernel_size=7, padding=3, groups=dim)
 # depthwise conv

2.4. Inverted Bottleneck

反向瓶颈:MLP块的隐藏维度比输入维度宽四倍

class Block(nn.Module):
    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()

2.5 大卷积核

最显著的方面之一是它们的非局部自我注意,这使每一层都有一个全局的感受野。

增大卷积核:

采用更大的内核大小卷积的好处是显著的。较大内核大小的好处在7*7时达到饱和点

self.dwconv = nn.Conv2d(dim, dim, kernel_size=7, padding=3, groups=dim) # depthwise conv

2.微观设计

重点关注激活函数和规范化层的具体选择。

2.1更换激活函数

高斯误差线性单元,或GELU[32],可以被认为是ReLU的一种更平滑的变体

2.2使用更少的激活函数

Transformer和ResNet块之间的一个小区别是Transformer具有较少的激活功能。

2.3 使用更少的BN层

只保留了一个BN层在1*1卷积之前

2.4 将BN替换成LN

在原始ResNet中直接用LN代替BN将导致次优性能[83]。随着网络架构和训练技术的所有修改,在使用LN训练时没有任何困难;事实上,性能稍好。

2.5分离出来的下采样层

在ResNet中,空间下采样是通过每个阶段开始时的残差块来实现的,使用3*3个conv,步幅2(和1*1在shortcut处具有步幅2的conv)。在Swin Transformers中,在级之间添加了一个单独的下采样层

我们使用2*2具有用于空间下采样的步长为2的conv层。但是这个改变导致了训练的不收敛,因此又引入了归一化层帮助稳定训练

        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)

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

猜你喜欢

转载自blog.csdn.net/weixin_50862344/article/details/129783744