RepMLP: Re-parameterizing Convolutions into Fully-connected Layers for Image Recognition
Abstract
我们提出了RepMLP,一个多层感知器风格的图像识别神经网络构建块,它由一系列全连接(FC)层组成。与卷积层相比,FC层更高效,更善于构建长期依赖关系和位置模式,但在捕获局部结构方面较差,因此通常不太适合用于图像识别。我们提出了一种结构重参数化(structural re-parameterization)技术,将局部先验加入到FC中,使其具有强大的图像识别能力。具体来说,我们在训练期间在RepMLP中构建卷积层,并将它们合并到FC中进行推理。在CIFAR上,一个简单的纯MLP模型显示了非常接近CNN的性能。通过在传统CNN中插入RepMLP,我们将ResNets在ImageNet上的准确率提高了1.8%,在人脸识别上提高了2.9%,在Cityscapes上提高了2.3%,且有着更低的FLOPs。我们有趣的发现强调,将FC的全局表征能力和位置感知与卷积的局部先验相结合,可以以更快的速度提高神经网络在具有平移不变性(例如,语义分割)和有着对齐图像和位置模式(例如,人脸识别)的任务的性能。代码可见https://github.com/DingXiaoH/RepMLP。
1. Introduction
图像的局部性(即相对于远距离的像素,一个像素与其临近像素相关性更强)造就了卷积神经网络(Convolutional Neural Network, ConvNet)在图像识别中的成功。在本文中,我们将这种归纳偏差称为局部先验(local prior)。
除此之外,我们还希望能够捕获长期依赖关系,这在本文中称为全局能力(global capacity)。传统的卷积神经网络通过深层的卷积神经层[25]形成的大的接收域来构建远程依赖。然而,重复的局部操作在计算上是低效的,可能会导致优化困难。之前的一些研究通过基于自注意力的模块增强了global capacity[25,11,23],但没有local prior。例如,ViT[11]是一个没有卷积的pure-Transformer模型,它将图像以序列的形式输入到Transformers中。由于缺少local prior这一重要的归纳偏差,ViT需要大量的训练数据(JFT-300M的3 × 108幅图像)来收敛。
另一方面,一些图像具有固有的位置先验,由于不同位置之间的参数是共享的,因此不能被一个卷积层有效利用。例如,当某人试图通过面部识别解锁手机时,脸部照片很可能位于中间,眼睛在上面,鼻子在中间。我们把利用位置先验的能力称为位置感知(positional perception)。
本文重新讨论了全连接(FC)层,为传统的ConvNet提供global capacity和positional perception。在某些情况下,我们直接使用FC代替卷积层来作为特征映射之间的转换。通过扁平化特征map,将其输入FC,然后重新调整大小,我们就可以享受positional perception(因为它的参数与位置相关)和global capacity(因为每个输出点与每个输入点相关)。无论从实际速度还是理论FLOPs来看,这种运算都是高效的,如表4所示。对于主要关注点是准确度和吞吐量而不是参数数量的应用场景,人们可能更喜欢基于FC的模型而不是传统的ConvNets。例如,GPU推断通常有几十GBs的内存,因此与计算和内部特征maps所消耗的内存相比,参数所占用的内存很小。
但是,由于空间信息丢失,FC没有local prior。在本文中,我们提出了一种structural re-parameterization技术,将local prior加入到FC中。具体而言,我们在训练过程中构造平行于FC的conv和batch normalization (BN)[15]层,然后将训练后的参数合并到FC中,以减少参数的数量和推理延迟。在此基础上,我们提出了一种重参数化的多层感知器(RepMLP)。如图1所示,训练时间RepMLP有FC层、conv层和BN层,但可以等效转换为只有三个FC层的推理时的块。structural re-parameterization的意义在于训练时间模型有一组参数,推理时模型有另一组参数,我们将训练时模型的参数转化为推理时模型的参数。注意,我们不会在每次推理之前推导参数。相反,我们一次性地转换它,然后就可以丢弃训练时的模型。
与conv相比,RepMLP在相同数量的参数下运行得更快,并且具有global capacity和positional perception。与自注意力模块相比[25,11],它更简单且可以利用图像的局部性。实验结果表明(表4,5,6),RepMLP在各种视觉任务,包括1)一般分类任务(如ImageNet[8])、2)带有位置先验的任务(如人脸识别)和3)平移不变任务(如语义分割)中均优于传统的卷积神经网络。
我们的贡献总结如下。
- 我们提出利用FC的global capacity和positional perception,并配备local prior进行图像识别。
- 我们提出了一个简单的、平台无关的、可微的算法来将并行的conv和BN合并到FC中,而无需任何推理时间开销。
- 我们提出了RepMLP,一个高效的构建模块,并展示了它在多视觉任务上的有效性。
2.Related Work
2.1. Designs for Global Capacity
non-local网络[25]通过自注意力机制对远距离依赖进行建模。对于每个查询位置,non-local模块首先计算查询位置与所有位置的成对关系,形成注意力map,然后将所有位置的特征与注意力map中定义的权重进行加权和。然后将聚合特征添加到每个查询位置的特征中。
GCNet[1]创建了一个基于查询无关公式的简化网络,在保持non-local网络的准确性的同时,减少了计算量。GC块的输入经过全局注意池化、特征转换(1 × 1 conv)和特征聚合。
与这些工作相比,RepMLP更简单,因为它不使用自注意力,并且只包含三个FC层。如表4所示,与比Non-local模块和GC块相比,RepMLP提高了更多的ResNet-50性能。
2.2. Structural Re-parameterization
在本文中,structural re-parameterization是指构造平行于FC的conv层和BN层进行训练,然后将参数合并到FC中进行推理。下面的两项工作也可以归类为structural re-parameterization。
非对称卷积块(Asymmetric Convolution Block,ACB)[9]是常规卷积层的替代,它使用水平(如1 × 3)和垂直(3 × 1)卷积来加强正方形(3 × 3)卷积的“skeleton”。在几个卷积网络基准测试中报告了合理的性能改进。
RepVGG[10]是一个类似于vgg的架构,因为它的主体只使用3 × 3的conv和ReLU进行推理。该推理时间体系结构由具有恒等和1 × 1分支的训练时间体系结构转化而来。
RepMLP与ACB的关系更密切,因为它们都是神经网络构建块,但我们的贡献不是让卷积更强,而是让MLP在图像识别方面更强大,作为常规conv的替代。此外,RepMLP内部的训练时的卷积可能会由ACB、RepVGG块、或者其他形式的卷积来做进一步的改进。
3. RepMLP
训练时的RepMLP由三个部分组成,分别是Global Perceptron, Partition Perceptron and Local Perceptron(图1)。在本节中,我们介绍我们的公式,描述每个组件,并展示如何将训练时的RepMLP转换为三个FC层进行推理,其中的关键是一个简单的,平台无关且可微的方法,用于合并一个conv到一个FC。
3.1. Formulation
在本文中,特征map被表示为一个张量,其中N是batch size,C是channels的数量,H和W是高和宽。我们使用F和W分别表示卷积和FC的核。为了简化和更易于复现,我们使用如PyTorch[20]的相同的数据格式,使用伪代码表示转换过程。比如,经过K x K卷积的数据流表示为:
其中是输出特征map,
是输出channels的数量,p是用来填补的像素数量,
是卷积核(我们假设卷积是密集的,即groups的数量为1)。从现在开始,为了简化我们假设
(步长为1,
)
对于FC,P和Q为输入和输出维度,和
分别是输入和输出,核为
且矩阵乘法(MMUL)表示为:
我们现在关注以为输入、以
为输出的FC。假设FC没有改变分辨率,即
。我们使用RS(“reshape”的简称)作为只改变张量形状规格,而不改变数据在内存中的顺序的函数,这是免费的。输入首先被flatten为长度为CHW的N个向量,
,乘以核
,然后输出
,再将其大小转变为
。为了可读性,在没有歧义的情况下,省略RS:
这样的FC不能利用图像的局部性,因为它根据每个输入点计算每个输出点,而不知道位置信息。
3.2. Components of RepMLP
我们不以上面的方式去使用FC,因为其不仅缺少了local prior,还有大量的参数()。常见设置中,ImageNet中是
,该单个FC就有10G个参数,这是不能接受的。为了减少参数,我们提出了Global Perceptron和Partition Perceptron去分开构建内和外分区依赖(inter- and intra-partition dependencies)。
Global Perceptron 将特征map分割,这样不同的分区就能共享参数。比如,大小的输入可以分成
,我们将每个7x7的块称为分区(partition)。我们为该分隔使用一种高效的实现方法,即内存重安排的简单操作。让h和w表示每个分区所期望的高和宽(假设H和W能分别被h和w整除,否则就简单地对输入进行填补(pad)),输入
首先被重置大小为
。注意该操作是免费的,因为内存没有移动数据。然后重新安排轴的顺序为
,这里高效地移动了内存中的数据。在PyTorch中仅需要调用一个函数(permute)。然后
张量被重置大小为
(在图1中被标志为partition map),这个过程也是免费的。在这里,参数数量从
减少到了
但是,分割打破了同一channel的不同分区之间的相关性。换句话说,模型将单独查看分区,完全不知道它们是并排放置的。为了将相关性添加到每个分区上,Global Perceptron 1)使用平均池化为每个分区获取一个像素,2)将其输入BN和一个两层MLP,然后3)重置大小并将其添加到partition map上。该附加操作能够通过自动传播高效实现(即隐式地将复制到
),这样每个像素就会和别的分区相关了。然后partition map将被输入Partition Perceptron 和Local Perceptron。注意如果
,我们直接将特征map输入Perceptron 和Local Perceptron,而不进行分割,因此这里就不需要Global Perceptron了。
Partition Perceptron 有一个FC和一个BN层,以partition map作为输入。输出以和上面相反的顺序进行reshaped, re-arranged和reshaped来变为
。我们受groupwise conv [5, 26]启发,进一步减少FC3的参数。g表示groups的数量,表示groupwise conv为:
相似地,groupwise FC的核是,其中有g倍的参数。虽然groupwise FC不直接被想PyTorch的计算框架支持,但其可以使用groupwise 1x1卷积来等价替换。该实现由3步组成:1)重置
为空间大小为1x1的“特征map”; 2)实现g个组的1x1卷积; 3)重置输出“特征map”为
。我们表示groupwise矩阵乘法(gMMUL)为:
Local Perceptron 将partition map输入到几个卷积层中。受[9,10]启发,这里每个卷积层后跟着一个BN层。图1展示了一个且
的例子。理论上,对核大小的唯一限制是
(因为使用大于分辨率的核是没有意义的),但是在ConvNet的常见实现中我们仅设置奇数核大小。为了简化,我们标注时使用了K x K,实际上非方形的卷积也是可以的(如1x3,3x1)。卷积的padding用于保持分辨率(即
),且组的数量g应该和Partition Perceptron一样。所有卷积分支和Partition Perceptron的输出将加在一起作为最后的输出。
3.3. A Simple, Platform-agnostic, Differentiable Algorithm for Merging Conv into FC
在将一个RepMLP转换成一个三层FC层前,我们首先展示如何将一个conv合并到FC。FC核为、卷积核为
且padding为p,我们期望构建
为:
我们注意到对于与有着相同大小的任意核
,MMUL的可加性保证了:
所以只要我们能构造和有一样大小的
,且满足下面的式子,这样就可以将
合并到
:
很明显,肯定存在,因为一个卷积可以看作是一个在空间位置中共享参数的稀疏FC,这也是其平移不变性的恶原理,但是怎么使用给定的
和p去构建它就没有那么显而易见了。因为当前计算平台使用不同的卷积算法(im2col-[2], Winograd- [17], FFT-[18], MEC-[4], and sliding-window-based)且数据内存分配和padding实现都有所不同,在某个特定平台构建矩阵的方法可能在另一个平台不适用。在本文中,我们提出一个简单且平台无关的解决方案。
如上所述,对于任意、conv核
和padding p,存在一个FC核
为:
使用等式2的公式,我们有:
插入一个单位矩阵并使用结合律:
我们注意到因为是由
构建的,
是与F在特征映射
上的卷积,该特征映射
由
重置大小而来。带有显式的RS,式子为:
将等式10与等式13和14相比,得到:
代码实现:
def _convert_conv_to_fc(self, conv_kernel, conv_bias): #等式(15) #等式(12) I = torch.eye(self.C * self.h * self.w // self.fc3_groups).repeat(1, self.fc3_groups).reshape(self.C * self.h * self.w // self.fc3_groups, self.C, self.h, self.w).to(conv_kernel.device) fc_k = F.conv2d(I, conv_kernel, padding=conv_kernel.size(2)//2, groups=self.fc3_groups) fc_k = fc_k.reshape(self.O * self.h * self.w // self.fc3_groups, self.C * self.h * self.w).t() fc_bias = conv_bias.repeat_interleave(self.h * self.w) return fc_k, fc_bias