网络参数重组论文二(ACNet,RepMLP)

本系列文章介绍一种最近比较火的设计网络的思想,即利用网络的重参数化(意义在于训练时间模型有一组参数,而推理时间模型有另一组参数),把多层合成一层,进行网络加速。

本文中介绍的文章分别构造了名为ACNet,RepMLP,他们的作用都是创建一个可替代卷积层的模块,在训练过程中使用这些复杂的模块,在测试过程中将模块参数重组为简单的卷积。

1. Re-parameterizing Convolutions into Fully-connected Layers for Image Recognition

一个能够较好完成图像识别的网络需要具有如下三个特征:

  1. 局部先验:卷积神经网络之所以能够成功识别图像,是因为图像的局部性(即一个像素与其相邻像素的关系比遥远像素的关系更大),因为卷积层只处理一个局部的邻域。

  2. 全局识别:传统的ConvNets通过由深层叠成的conv层形成的大接受域来模拟远距离的依赖。但是,重复的局部操作计算效率低,可能导致优化困难。

  3. 位置先验:例如,人脸识别时,人脸照片很可能是居中对齐的,所以眼睛出现在顶部,鼻子出现在中间。由于转换层在不同位置之间共享参数,因此不能有效地利用这种位置先验的能力。

RepMLP提出,直接使用FC作为特征映射之间的转换来代替conv。通过扁平化一个特征地图,可以获得位置感知(因为它的参数与位置相关)和全局容量(因为每个输出点与每个输入点相关)。

但是,由于空间信息丢失,FC没有局部优先级。RepMLP提出用结构重参数化技术将局部先验融入到FC中。即,训练时的RepMLP有FC层、conv层和BN层,但可以等效地参数转换为一个只有三个FC层的推理时间块。

在这里插入图片描述
如图所示,根据之前描述的三个特征,RepMLP在训练阶段的网络结构对应地包括三个部分:

1.1 全局感知器

由于分割打破了同一通道中不同分区之间的关联。(即,模型将单独查看分区,完全不知道它们是并排放置的)。为了在每个分区上添加相关,全局感知器:

  1. 对特征图 H × W H\times W H×W分割为大小为 h × w h\times w h×w的分区,将参数量从 C O H 2 W 2 COH^2W^2 COH2W2减少为 C O h 2 w 2 COh^2w^2 COh2w2
  2. 使用平均池为每个分区获取一个像素, 通过BN和一个两层MLP为该像素提供数据,得到 ( N H W h w , C , 1 , 1 ) (\frac{NHW}{hw},C,1,1) (hwNHW,C,1,1)
  3. 将其添加到分区映射上。即,隐式地将 ( N H W h w , C , 1 , 1 ) (\frac{NHW}{hw},C,1,1) (hwNHW,C,1,1))复制到 ( N H W h w , C , h , w ) (\frac{NHW}{hw},C,h,w) (hwNHW,C,h,w),以便每个像素都与其他分区相关。
  4. 将分区图送入分区感知机和局部感知机。
    在这里插入图片描述
    注:当 H = h , W = w H=h, W=w H=h,W=w时,我们直接将输入特征映射输入到分区感知机和局部感知机中,而不进行分割,因此不会有全局感知器。
    def __init__(self, ...):
        if self.need_global_perceptron:
            internal_neurons = int(self.C * self.h_parts * self.w_parts // fc1_fc2_reduction)
            self.fc1_fc2 = nn.Sequential()
            self.fc1_fc2.add_module('fc1', nn.Linear(self.C * self.h_parts * self.w_parts, internal_neurons))
            self.fc1_fc2.add_module('relu', nn.ReLU())
            self.fc1_fc2.add_module('fc2', nn.Linear(internal_neurons, self.C * self.h_parts * self.w_parts))
            if deploy:
                self.avg = nn.AvgPool2d(kernel_size=(self.h, self.w))
            else:
                self.avg = nn.Sequential()
                self.avg.add_module('avg', nn.AvgPool2d(kernel_size=(self.h, self.w)))
                self.avg.add_module('bn', nn.BatchNorm2d(num_features=self.C))

    def forward(self, inputs):
        if self.need_global_perceptron:
        	# avg, bn
            v = self.avg(inputs)
            # 
            v = v.reshape(-1, self.C * self.h_parts * self.w_parts)
            # fc1, relu, fc2
            v = self.fc1_fc2(v)
            v = v.reshape(-1, self.C, self.h_parts, 1, self.w_parts, 1)
            inputs = inputs.reshape(-1, self.C, self.h_parts, self.h, self.w_parts, self.w)
            inputs = inputs + v
        else:
            inputs = inputs.reshape(-1, self.C, self.h_parts, self.h, self.w_parts, self.w)

1.2 分区感知机

为了对远距离依赖进行建模,分区感知机包含有一个FC层和一个BN层,这两个层取分区映射,将输出 ( N H W h w , O , h , w ) (\frac{NHW}{hw},O,h,w) (hwNHW,O,h,w)按之前的逆顺序重排为为 ( N , O , H , W ) (N, O, H, W) (N,O,H,W),并通过群卷积减少参数数目。

在这里插入图片描述

    def __init__(self, ...):
        self.fc3 = nn.Conv2d(self.C * self.h * self.w, self.O * self.h * self.w, 1, 1, 0, bias=deploy, groups=fc3_groups)
        nn.init.orthogonal_(self.fc3.weight)
        self.fc3_bn = nn.Identity() if deploy else nn.BatchNorm1d(self.O * self.h * self.w)

    def forward(self, inputs):
    	#   Feed partition map into Partition Perceptron
        fc3_inputs = partitions.reshape(-1, self.C * self.h * self.w, 1, 1)
        fc3_out = self.fc3(fc3_inputs)
        fc3_out = fc3_out.reshape(-1, self.O * self.h * self.w)
        fc3_out = self.fc3_bn(fc3_out)
        fc3_out = fc3_out.reshape(-1, self.h_parts, self.w_parts, self.O, self.h, self.w)

1.3 局部感知机

如下图所示,局部感知机通过几个转换层捕获局部模式。

在这里插入图片描述

    def __init__(self, ...):
        self.reparam_conv_k = reparam_conv_k
        if not deploy and reparam_conv_k is not None:
            for k in reparam_conv_k:
                conv_branch = nn.Sequential()
                conv_branch.add_module('conv', nn.Conv2d(in_channels=self.C, out_channels=self.O, kernel_size=k, padding=k // 2, bias=False, groups=fc3_groups))
                conv_branch.add_module('bn', nn.BatchNorm2d(self.O))
                self.__setattr__('repconv{}'.format(k), conv_branch)
                
    def forward(self, inputs):
        #   Feed partition map into Local Perceptron
        if self.reparam_conv_k is not None and not self.deploy:
            conv_inputs = partitions.reshape(-1, self.C, self.h, self.w)
            conv_out = 0
            for k in self.reparam_conv_k:
                conv_branch = self.__getattr__('repconv{}'.format(k))
                conv_out += conv_branch(conv_inputs)
            conv_out = conv_out.reshape(-1, self.h_parts, self.w_parts, self.O, self.h, self.w)
            fc3_out += conv_out

        fc3_out = fc3_out.permute(0, 3, 1, 4, 2, 5)  # N, O, h_parts, out_h, w_parts, out_w
        out = fc3_out.reshape(*self.target_shape)

1.4 RepMLP-ResNet

如图所示为MLP model和Wide ConvNet的对比。

在这里插入图片描述

2. ACNet: Strengthening the Kernel Skeletons for Powerful CNN via Asymmetric Convolution Blocks

ACNet中在训练阶段使用非对称卷积块(Asymmetric Convolution Block, ACB)代替标准的平方核卷积层来构建一个非对称卷积网络(ACNet),并在测试阶段将ACNet等价地转换为原来的架构,因此不需要额外的计算。

如图所示,在训练阶段,将原本单独的3x3conv替换为ACB,ACB分别包含3×3conv、1×3conv和3×1conv,并对它们的输出进行求和。在测试阶段,我们将模型转换回与原模型相同的结构,将每个ACB中的非对称核添加到骨架上,即正方形核的交叉部分。
在这里插入图片描述
具体地来说,参数的重组分成两步:

    def get_equivalent_kernel_bias(self):
    	# 1.对每个分支,等价地将BN的参数融合为带偏置的卷积中
        hor_k, hor_b = self._fuse_bn_tensor(self.hor_conv, self.hor_bn)
        ver_k, ver_b = self._fuse_bn_tensor(self.ver_conv, self.ver_bn)
        square_k, square_b = self._fuse_bn_tensor(self.square_conv, self.square_bn)
        # 2.将融合核和偏置项相加得到单层
        self._add_to_square_kernel(square_k, hor_k)
        self._add_to_square_kernel(square_k, ver_k)
        return square_k, hor_b + ver_b + square_b
  1. 对每个分支,等价地将BN的参数融合为带偏置的卷积中
    def _fuse_bn_tensor(self, conv, bn):
        std = (bn.running_var + bn.eps).sqrt()
        t = (bn.weight / std).reshape(-1, 1, 1, 1)
        return conv.weight * t, bn.bias - bn.running_mean * bn.weight / std
  1. 将融合核和偏置项相加得到单层
    def _add_to_square_kernel(self, square_kernel, asym_kernel):
        asym_h = asym_kernel.size(2)
        asym_w = asym_kernel.size(3)
        square_h = square_kernel.size(2)
        square_w = square_kernel.size(3)
        square_kernel[:, :, square_h // 2 - asym_h // 2: square_h // 2 - asym_h // 2 + asym_h,
                square_w // 2 - asym_w // 2: square_w // 2 - asym_w // 2 + asym_w] += asym_kernel

在这里插入图片描述

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值