• 【图像分类】2022-RepLKNet CVPR 31x31卷积了解一下


    图像分类】2022-RepLKNet CVPR

    论文题目:Scaling up your kernels to 31x31: Revisiting large kernel design in cnns

    论文链接:https://arxiv.org/abs/2203.06717

    论文代码:https://github.com/MegEngine/RepLKNet

    论文团队:清华大学、旷视科技

    引用:

    Ding X, Zhang X, Han J, et al. Scaling up your kernels to 31x31: Revisiting large kernel design in cnns[C]//Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition. 2022: 11963-11975.

    Ding X , Zhang X , Zhou Y , et al. Scaling Up Your Kernels to 31x31: Revisiting Large Kernel Design in CNNs[J]. arXiv e-prints, 2022.

    1. 简介

    1.1 简介

    CVPR22最新论文,RepVGG作者提出RepLKNet:采用31×31大kernel的CNN网络,性能超过Swin,作者在论文中提出了大kernel size卷积的4个设计准则,并设计了31x32的纯CNN结构,在图像分类和下游检测分割上超过Swin!

    1.2 摘要

    在图像分类、特征学习等前置任务(pretext task)以及目标检测、语义分割等下游任务(downstream task)上,卷积网络的性能不断被ViTs(vision transformer)网络超越。人们普遍认为ViTs的性能主要得益于MHSA(multi-head self-attention)机制,并为此进行了很多研究,从不同的角度对比MHSA与卷积之间的优劣。

    解释VisTs与CNNs的性能差异不是这篇论文的目的,相对于研究MHSA和卷积的差异,论文则关注于ViTs与CNNs在构建长距离位置关系(long-range spatial connections)的范式上的差异

    在ViTs中,MHSA通常使用较大的感受域( ≥ 7 × 7 \ge 7\times 7 7×7),每个输出都能包含较大范围的信息。

    而在CNNs中,目前的做法都是通过堆叠较小( 3 × 3 3\times 3 3×3)的卷积来增大感受域,每个输出所包含信息的范围较小。

    image-20220806141405009

    基于上面发现的感受域差异,论文尝试通过引入少量大核卷积层来弥补ViTs和CNNs之间的性能差异。借此提出了RepLKNet网络,通过重参数化的大卷积来建立空间关系。RepLKNet网络基于Swin Transformer主干进行改造,将MHSA替换为大的深度卷积,性能比ViTs网络更好。另外,论文通过图1的可视化发现,引入大卷积核相对于堆叠小卷积能显著提升有效感受域(ERFs),甚至可以跟ViTs一样能够关注形状特征

    1.3 创新点

    • 我们提出了五条准则,例如应用重新参数化的大深度卷积,以设计高效、高性能的大内核CNN。根据指导原则,我们提出了RepLKNet,这是一种纯CNN架构

    • 我们的研究进一步揭示,与小核CNN相比,大核CNN具有更大的有效感受野和更高的形状偏差,而不是纹理偏差。

    2. 网络

    2.1 网络简介

    image-20220806143049009

    RepLKNet的结构如图4所示,各模块细节如下:

    • Stem:由于RepLKNet的主要应用是下游任务,所以需要在网络前期捕捉更多的细节。在开始的stride=2 3x3卷积下采样之后接一个3x3深度卷积来提取低维特征,之后接一个1x1卷积3x3深度卷积用于下采样

    • Stages 1-4:每个stage包含多个RepLK Block,block里面包含了准则1建议的深度卷积和准则2建议的短路连接。

      根据准则3,每个深度卷积并行一个5x5深度卷积用于结构重参数。除了感受域和空间特征提取能力,模型的特征表达能力还和特征的维度有关。为了增加非线性和通道间的信息交流,在深度卷积前用1x1卷积增加特征维度。

      参考transformers和MLPs网络使用的Feed-Forward Network(FFN),论文提出CNN风格的ConvFFN,包含短路连接、两个1x1卷积核GELU。在应用时,ConvFFN的中间特征一般为输入的4倍。参照ViT和Swin,将ConvFFN放置在每个RepLK Block后面。

    • Transition Blocks:放在stage之间,先用1x1卷积扩大特征维度,再通过两个3x3深度卷积来进行2倍下采样。

    总的来说,每个stage有3个超参数:ReLK Block数 B B B,维度 C C C,以及卷积核大小 K K K所以一个RepLKNet的结构可表达为。 [ B 1 , B 2 , B 3 , B 4 ] , [ C 1 , C 2 , C 3 , C 4 ] , [ K 1 , K 2 , K 3 , K 4 ] [B_1,B_2,B_3,B_4],[C_1,C_2,C_3,C_4],[K_1,K_2,K_3,K_4] [B1,B2,B3,B4],[C1,C2,C3,C4],[K1,K2,K3,K4]

    2.2 五条准则

    1) 准则一:大深度卷积在实践中是有效的

    large depth-wise convolutions can be efficient in practice

    人们一般认为大卷积的计算成本很高,参数量和计算量与卷积核大小成二次方关系,而DW深度卷积恰好可以弥补这一缺点。将各stage的卷积核从 [ 3 , 3 , 3 , 3 ] [3,3,3,3] [3,3,3,3]提升到 [ 31 , 29 , 17 , 13 ] [31,29,17,13] [31,29,17,13]深度卷积,仅带来了18.6%的计算量增加和10.4%的参数量增加。

    人们可能会担心,DW卷积在GPU等现代并行计算设备上可能非常低效。传统的DW 3×3内核也是如此,因为DW操作引入了较低的计算与内存访问成本之比,这对现代计算架构不友好。然而,我们发现,当内核大小变大时,计算密度会增加:例如,在DW 11×11内核中,每次我们从特征映射加载一个值时,它最多可以进行121次乘法,而在3×3内核中,这个数字只有9次。因此,根据屋顶线模型,当内核大小变大时,实际延迟的增加幅度不应超过触发器的增加幅度。

    image-20220806142158180

    2) 残差连接对于大核卷积非常重要

    为了证明这一点,我们使用MobileNet V2[77]进行基准测试,因为它大量使用DW层,并且有两个已发布的变体。对于大型内核对应层,我们只需将所有DW 3×3层替换为13×13。所有模型都在ImageNet上进行了100个历元的相同训练配置。表2显示,大内核将具有快捷方式的MobileNet V2的精度提高了0.77%。然而,如果没有捷径,大核将精度降低到仅53.98%。

    image-20220806142329671

    3) 重参数化小核有助于训练

    image-20220806142709195

    4) 大卷积核能够更好的用于下游任务中

    large convolutions boost downstream tasks much more than ImageNet classification

    如前面表3所示,相对于分类任务,卷积核增大为分割任务带来的收益更多。而表5的结果也有类似的现象,大卷积核在ADE20K数据集上的提升更为明显。这表明,即使预训练模型有相似的ImageNet性能,但其在下游任务中的性能可能差距较大。

    image-20220806142730623

    论文认为导致这一现象的原因主要有两点:

    • 大卷积核能显著增加有效感受域(ERF),可包含更多的上下文信息,这对下游任务十分关键。

    • 大卷积能引导网络倾向于学习更多形状特征。图像分类仅需要上下文或形状信息,而目标识别则非常需要形状信息。所以,倾向于更多形状特征的模型显然更适合下游任务。

      ViTs之所以在下游任务中表现强劲,也是得益于其强大的形状特征提取能力。相反,ImageNet预训练的传统卷积网络则倾向于上下文信息。

    5) 大内核(例如13×13)即使在小特征映射(例如7×7)上也很有用。

    为了验证大卷积小特征图上的有效性,将MobileNetV2最后的stage(特征图大小为7x7)的深度卷积分别扩大至7x7和13x13进行对比,实验结构附带了准则3建议的结构重参数。结果如表4所示,尽管特征图已经很小了,增大卷积核依然可以带来性能提升。

    image-20220806142950660

    2.3 总结

    Large-Kernel CNNs have Larger ERF than Deep Small-Kernel Models

    一般来说,堆叠的小卷积最终也能达到跟单个大卷积一样的感受域大小,但为什么传统网络的性能要低于大卷积核网络呢?论文认为,尽管能达到同样大小的感受域,单层大卷积核要比多层小卷积更有效,主要有两点:

    • 根据有效感受域特性,其大小与 O ( K L ) \mathcal{O}(K\sqrt{L}) O(KL )成比例关系。可以看到,有效感受域与卷积核大小成线性关系,而与深度成次线性关系。
    • 深度的增加会带来训练问题。尽管ResNet似乎已经解决了这个问题,但近期有研究表明,ResNet的有效感受域并没有随着深度增加而显著增加。

    所以大卷积核的设计仅需要更少的层就可以达到预定的有效感受域,同时避免了深度增加带来的优化问题。

    论文也对ResNet和RepLKNet的有效感受域进行可视化和统计,发现RepLkNet整体有效感受域要大于ResNet。

    Large-kernel Models are More Similar to Human in Shape Bias

    image-20220806144913159

    有研究发现ViT更接近人体视觉,基于目标的形状进行预测,而CNN则更多地依赖局部上下文。论文借用https://github.com/bethgelab/model-vs-human的工具来计算模型的形状特征的偏向性,得到图5的结果,结果越低越好。从结果来看,大卷积核的RepLKNet更注重形状特征,当卷积核减少时,RepLKNet-3则变为更注重上下文特征。

    image-20220806145232131

    空洞卷积是一个常用的扩大卷积范围的方法,所以论文对空洞深度卷积和普通深度卷积进行了对比。如表11所示,尽管最大感受域可能一样,但空洞深度卷积的表达能力要弱很多,准确率下降非常明显。这也是符合预期的,虽然空洞卷积的感受域较大,但其计算用的特征非常少。

    3. 代码

    原版代码不能运行。因为有重参数化

    import torch
    import torch.nn as nn
    from timm.models.layers import DropPath
    
    """
    代码来自https://github.com/NNDEV1/RepLKNet
    没有重参数
    """
    
    
    class DepthWiseConv2d(nn.Module):
    
        def __init__(self, c_in, c_out, kernels_per_layer, kernel_size, stride,padding=0):
            super(DepthWiseConv2d, self).__init__()
    
            self.conv_dw = nn.Conv2d(c_in, c_in * kernels_per_layer, kernel_size=kernel_size, padding=1, groups=c_in,
                                     bias=False,)
            self.conv_pw = nn.Conv2d(c_in * kernels_per_layer, c_out, kernel_size=1, stride=stride, bias=False)
    
        def forward(self, x):
            return self.conv_pw(self.conv_dw(x))
    
    
    class Stem(nn.Module):
    
        def __init__(self, c_in, c_out):
            super(Stem, self).__init__()
    
            self.conv1 = nn.Conv2d(c_in, c_out, kernel_size=3, stride=2, bias=False)
            self.conv_dw1 = DepthWiseConv2d(c_out, c_out, kernels_per_layer=8, stride=1, kernel_size=3)
            self.conv2 = nn.Conv2d(c_out, c_out, kernel_size=1, padding=0, stride=1, bias=False)
            self.conv_dw2 = DepthWiseConv2d(c_out, c_out, kernels_per_layer=8, stride=2, kernel_size=3)
    
        def forward(self, x):
            x = self.conv1(x)
            x = self.conv_dw1(x)
            x = self.conv2(x)
            x = self.conv_dw2(x)
    
            return x
    
    
    class Transition(nn.Module):
    
        def __init__(self, c_in, c_out, kernels_per_layer, kernel_size):
            super(Transition, self).__init__()
    
            self.conv1 = nn.Conv2d(c_in, c_in, kernel_size=1, stride=1, padding="same", bias=False)
            self.dw_conv = DepthWiseConv2d(c_in, c_out, kernels_per_layer, kernel_size, stride=2)
    
        def forward(self, x):
            x = self.conv1(x)
            x = self.dw_conv(x)
    
            return x
    
    
    class RepLKBlock(nn.Module):
    
        def __init__(self, kernel_size, c_in, c_out, prob):
            super(RepLKBlock, self).__init__()
    
            # Only works for kernel sizes up to 9x9 5 1 7 2 9 3 11 4 13 5 15 6 17 7 19 8 21 9 23 10 25 11 27 12 29 13 31 14
            if kernel_size <= 9:
                padding = kernel_size // 3
            elif kernel_size % 2 == 1:
                padding = kernel_size // 2 - 1
            # print(kernel_size)
            self.bn = nn.BatchNorm2d(c_in)
            self.conv1 = nn.Conv2d(c_in, c_out, kernel_size=1, stride=1, bias=False)
            self.conv_dw = DepthWiseConv2d(c_out, c_out, kernel_size=kernel_size, stride=1, kernels_per_layer=8)
            self.conv2 = nn.Conv2d(c_out, c_out, kernel_size=1, stride=1, padding=padding, bias=False)
            self.drop_path = DropPath(prob)
    
        def forward(self, x):
            add = x
            x = self.bn(x)
            x = self.conv1(x)
            x = self.conv_dw(x)
            x = self.conv2(x)
            return self.drop_path(x) + add
    
    
    class ConvFFN(nn.Module):
    
        def __init__(self, c_in, c_out, prob):
            super(ConvFFN, self).__init__()
    
            self.bn = nn.BatchNorm2d(c_in)
            self.conv1 = nn.Conv2d(c_in, c_out, kernel_size=3, padding="same", stride=1, bias=False)
            self.gelu = nn.GELU()
            self.conv2 = nn.Conv2d(c_out, c_out, kernel_size=3, padding="same", stride=1, bias=False)
            self.drop_path = DropPath(prob)
    
        def forward(self, x):
            add = x
            x = self.bn(x)
            x = self.conv1(x)
            x = self.gelu(x)
            x = self.conv2(x)
            return self.drop_path(x) + add
    
    
    # Stem
    # Stage 1: (RepLKBlock, ConvFFN, ..., RepLKBlock, ConvFFN)
    # Transition 1
    # Stage 2: (RepLKBlock, ConvFFN, ..., RepLKBlock, ConvFFN)
    # Transition 2
    # Stage 3: (RepLKBlock, ConvFFN, ..., RepLKBlock, ConvFFN)
    # Transition 3
    # Stage 4: (RepLKBlock, ConvFFN, ..., RepLKBlock, ConvFFN)
    # Transition 4
    
    class RepLKNet(nn.Module):
    
        def __init__(self,
                     num_classes=1000,
                     layers=[3, 3, 3, 3],
                     drop_path_rate=0.3,
                     kernel_sizes=[31, 29, 15, 7],
                     c_in=3,
                     channels=[32, 32, 32, 32]):
            super(RepLKNet, self).__init__()
            c_out = channels[0]
            self.stem = Stem(c_in, c_out)
    
            ################
            c_out = channels[0]
            modules1 = []
            for i in range(layers[0]):
                modules1.append(RepLKBlock(kernel_sizes[0], c_out, c_out, prob=drop_path_rate))
                modules1.append(ConvFFN(c_out, c_out, prob=drop_path_rate))
            #  stage 1
            self.stage1 = nn.Sequential(*modules1)
            self.transition1 = Transition(c_out, channels[1], kernels_per_layer=8, kernel_size=3)
    
            #####################
            c_out = channels[1]
            modules2 = []
            for i in range(layers[1]):
                modules2.append(RepLKBlock(kernel_sizes[1], c_out, c_out, prob=drop_path_rate))
                modules2.append(ConvFFN(c_out, c_out, prob=drop_path_rate))
            self.stage2 = nn.Sequential(*modules2)
            self.transition2 = Transition(c_out, channels[2], kernels_per_layer=16, kernel_size=3)
    
            #####################
            c_out = channels[2]
            modules3 = []
            for i in range(layers[2]):
                modules3.append(RepLKBlock(kernel_sizes[2], c_out, c_out, prob=drop_path_rate))
                modules3.append(ConvFFN(c_out, c_out, prob=drop_path_rate))
            self.stage3 = nn.Sequential(*modules3)
            self.transition3 = Transition(c_out, channels[3], kernels_per_layer=32, kernel_size=3)
    
            #####################
            c_out = channels[3]
            modules4 = []
            for i in range(layers[3]):
                modules4.append(RepLKBlock(kernel_sizes[3], c_out, c_out, prob=drop_path_rate))
                modules4.append(ConvFFN(c_out, c_out, prob=drop_path_rate))
            self.stage4 = nn.Sequential(*modules4)
    
            # self.transition4 = Transition(c_out, channels[3], kernels_per_layer=64, kernel_size=3)
            c_out = channels[3]
    
            self.adaptive_pool = nn.AdaptiveAvgPool2d((1, 1))
    
            self.fc = nn.Sequential(*[nn.Linear(c_out, c_out // 2),
                                      nn.ReLU(inplace=True),
                                      nn.Linear(c_out // 2, c_out // 4),
                                      nn.ReLU(inplace=True),
                                      nn.Linear(c_out // 4, num_classes)])
    
        def forward(self, x):
    
            x = self.stem(x)  # 1/4 图
          
            x = self.stage1(x)
            x = self.transition1(x)  # 1/8 图
            x = self.stage2(x) # torch.Size([1, 256, 28, 28])
            # print(x.shape)
            x = self.transition2(x)  # 1/16 图
            x = self.stage3(x) # torch.Size([1, 512, 14, 14])
            x = self.transition3(x)  # # 1/32 图
            x = self.stage4(x)  # 1/32 图
    
            x = self.adaptive_pool(x)
            x = x.view((x.size(0), -1))
            x = self.fc(x)
    
            return x
    
    def create_RepLKNet31Small(drop_path_rate=0.3, num_classes=1000, **kwargs):
        return RepLKNet(kernel_sizes=[31, 29, 15, 7],
                        layers=[2, 2, 18, 2],
                        channels=[128, 256, 512, 1024],
                        drop_path_rate=drop_path_rate,
                        num_classes=num_classes, **kwargs)
    
    def create_RepLKNet31B(drop_path_rate=0.3, num_classes=1000, **kwargs):
        return RepLKNet(kernel_sizes=[31, 29, 27, 13],
                        layers=[2, 2, 18, 2],
                        channels=[128, 256, 512, 1024],
                        drop_path_rate=drop_path_rate,
                        num_classes=num_classes, **kwargs)
    
    
    def create_RepLKNet31L(drop_path_rate=0.3, num_classes=1000, **kwargs):
        return RepLKNet(kernel_sizes=[31, 29, 27, 13],
                        layers=[2, 2, 18, 2],
                        channels=[192, 384, 768, 1536],
                        drop_path_rate=drop_path_rate,
                        num_classes=num_classes, **kwargs)
    
    
    def create_RepLKNetXL(drop_path_rate=0.3, num_classes=1000, **kwargs):
        return RepLKNet(kernel_sizes=[27, 27, 27, 13],
                        layers=[2, 2, 18, 2],
                        channels=[256, 512, 1024, 2048],
                        drop_path_rate=drop_path_rate,
                        num_classes=num_classes, **kwargs)
    
    
    if __name__ == '__main__':
        from thop import profile
    
        model = create_RepLKNet31Small(num_classes=1000)
        input = torch.randn(1, 3, 224, 224)
        #  使用B的模型时,1/16的特征图必须大于 27的kernel_size。所以 B 以上模型,必须 input = torch.randn(1, 3, 432,432)
        # model = create_RepLKNet31B(num_classes=1000)
        # input = torch.randn(1, 3, 512,512)
        y = model(input)
        print(y.shape)
        # flops, params = profile(model, inputs=(input,))
        # print("flops:{:.3f}G".format(flops / 1e9))
        # print("params:{:.3f}M".format(params / 1e6))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
  • 相关阅读:
    BLEMotion-Kit 开发环境搭建&评估板程序下载
    BC1.2 PD协议
    解决flutter不识别yaml里面配置的git项目
    nodejs+vue+elementui在线音乐分享网站管理系统
    Python计算目标检测中的IoU
    java线程中断
    SpringBoot 常用注解总结
    KT148A语音芯片组合播放之间有间隔不连贯的处理方法V1
    Java 类型信息详解和反射机制
    VMware Workstation Pro16 的下载与安装
  • 原文地址:https://blog.csdn.net/wujing1_1/article/details/126196898