A ConvNet for the 2020s
博主看了论文和一些大佬总结的博客文档,可以总结的是FAIR团队真是太有钱了,真是做了大量的实验才能总结出这一个调参后的模型,把ResNet从76.1一步步干到82.0,让CNN框架又看到了一些希望啊!
76.1 -----> 82.0

可以看到真的是堆叠了大量tricks,炼丹人真的不容易哦
1. 训练策略优化
这一部分从76.1提升到78.8,DL发展了这么久,各种训练策略都在各种花式更新中,2021年timm和torchvision团队均有工作讲述如何通过优化训练策略来使resnet50性能提升到80以上。

2. 宏观设计
2.1 改变stage compute ratio
改变layer0到layer3的block数量比例,由标准的(3,4,6,3)改为Swin-T使用的(3,3,9,3),即1:1:3:1也相应的改进为1:1:9:1,这部分从78.8提升79.4
2.2 使用patchify的stem
从VIT开始,为了将图片转化为token,图片都会先被分割成一个个patch,而在传统ResNet中stem层是使用一个stride=2的7x7卷积加最大池化层。
本文仿照Swin-T的做法,用stride=4的4x4卷积来进行stem,使得滑动窗口不再相交,每次只处理一个patch的信息,同时将BN对标成LB,这一步与Swin-T中保持一致。(79.4 ----> 79.5)
# 标准ResNet
stem = nn.Sequential(
nn.Conv2d(in_chans, dims[0], kernel_size=7, stride=2),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
# ConvNeXt
stem = nn.Sequential(
nn.Conv2d(in_chans, dims[0], kernel_size=4, stride=4),
LayerNorm(dims[0], eps=1e-6, data_format="channels_first")
)
3.ResNext化
由于ResNext在FLOPS/ACCURACY的trade-off比Resnet更优秀,主要是因为使用分组卷积。ResNeXt的指导准则是“分更多的组,拓宽width”,因此本文直接使用了depthwise conv,即分组数等于输入通道数。这个技术在之前主要是应用在MobileNet这种轻量级网络中,用于降低计算量。但在这里,作者发现dw conv由于每个卷积核单独处理一个通道,这种形式跟self-attention机制很相似,都是在单个通道内做空间信息的混合加权。(79.5----->80.5)
将bottleneck中3 * 3卷积替换成dw conv,再把宽度从64提升到96。
4. 反瓶颈结构
标准的Resnet中会使用瓶颈结构先降维卷积再升维,来减少计算量,而反瓶颈结构是先升维再降维,认为这样能让信息在不同维度特征空间之间转换时避免压缩维度带来的信息损失,后来在Transformer的MLP中也使用了类似的结构,中间层全连接层维度数是两端的4倍。(80.5------>80.6)

5. 大卷积核
由于Swin-T中使用了7x7卷积核,这一步主要是为了对齐比较。又因为inverted bottleneck放大了中间卷积层的缘故,直接替换会导致参数量增大,因而作者把dw conv的位置进行了调整,放到了反瓶颈的开头。最终结果相近,说明在7x7在相同参数量下效果是一致的。(80.6---->80.6)
6.微观设计
6.1 用GELU替换ReLU(80.6-80.6)
主要是为了对齐比较,并没有带来提升。
6.2 减少激活层数量(80.6-81.3)
由于Transformer中只使用了一个激活层,因此在设计上进行了效仿,结果发现只在block中的两个1x1卷积之间使用一层激活层,其他地方不适用,反而带来了0.7个点的提升。这说明太频繁地做非线性投影对于网络特征的信息传递实际上是有害的。
6.3 减少归一化层数量(81.3-81.4)
基于跟减少激活层相同的逻辑,由于Transformer中LN层很少,本文也只保留了1x1卷积之前的一层LN,而两个1x1卷积层之间甚至没有使用归一化层,只做了非线性投影。
6.4 用LN替换BN(81.4-81.5)
由于Transformer中使用了LN,且一些研究发现BN会对网络性能带来一些负面影响,本文将所有的BN替换为LN。
6.5 单独的下采样层(81.5-82.0)
标准ResNet的下采样层通常是stride=2的3x3卷积,对于有残差结构的block则在短路连接中使用stride=2的1x1卷积,这使得CNN的下采样层基本与其他层保持了相似的计算策略。而Swin-T中的下采样层是单独的,因此本文用stride=2的2x2卷积进行模拟。又因为这样会使训练不稳定,因此每个下采样层后面增加了LN来稳定训练。
self.downsample_layers = nn.ModuleList()
# stem也可以看成下采样层,一起存到downsample_layers中,推理时通过index进行访问
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)
# 由于网络结构是downsample-stage-downsample-stage的形式,所以stem和后面的下采样层中的LN是不会连在一起的
对以上信息进行整合后的得到单个block的设计及代码

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)
# 在1x1之前使用唯一一次LN做归一化
self.norm = LayerNorm(dim, eps=1e-6)
# 全连接层跟1x1conv等价,但pytorch计算上fc略快
self.pwconv1 = nn.Linear(dim, 4 * dim)
# 整个block只使用唯一一次激活层
self.act = nn.GELU()
# 反瓶颈结构,中间层升维了4倍
self.pwconv2 = nn.Linear(4 * dim, dim)
# gamma的作用是用于做layer scale训练策略
self.gamma = nn.Parameter(layer_scale_init_value * torch.ones((dim)),
requires_grad=True) if layer_scale_init_value > 0 else None
# drop_path是用于stoch. depth训练策略
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
def forward(self, x):
input = x
x = self.dwconv(x)
# 由于用FC来做1x1conv,所以需要调换通道顺序
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 x
通过代码可以注意到,以上Block中两层1x1卷积是用全连接层来实现的,按照作者的说法,这样会比使用卷积层略快。
671

被折叠的 条评论
为什么被折叠?



