论文原文:RepVGG: Making VGG-style ConvNets Great Again
这篇文章最大的亮点就是 其训练的模型结构和推理的模型结构不同,推理结构仅使用 3x3 卷积和 ReLU 激活函数的堆叠得到,也正对应文章标题所述的 VGG-style 。
这里涉及到一个概念 —— “ 结构重参数化 ” ,推理模型结构也正是通过对训练模型的结构重参数化得到的。
正如上图所示,(B)是 RepVGG 训练时所采用的的模型结构,而(C)是推理时所采用的结构。
为什么要这么做?
从 VGG-style 的模型来说,其具有五大优势:
- 3x3 卷积速度非常快。现有的加速库都对 3x3 卷积有所优化,诸如 NVIDIA 的 cuDNN、Intel 的 MKL。例:在 GPU 上,3x3 卷积的计算密度(理论运算量除以所用时间)可达 1x1 和 5x5卷积 的四倍。
- 单路架构速度非常快。减少分支可以加快模型推理速度(这点可以看 ShuffleNet V2 )。
- 单路架构省内存。例如,ResNet 的 shortcut 虽然不占计算量,却增加了一倍的显存占用(见下图,这点也可以看 ShuffleNet V2 )。
- 单路架构灵活性更好,容易改变各层的宽度。
- RepVGG 主体部分只有一种算子:3x3 卷积接 ReLU 。
如果想要模型速度比较快,十分建议阅读 ShuffleNet V2 的文章,里面的 4 大设计准则可以帮助模型提速(想必 RepVGG 也是借鉴了其中非常多的网络结构设计思想精华)。
上图是 ResNet 的 shortcut 结构显存占用情况,这里忽略了模型参数等,仅包含了特征图显存占用。
既然说 RepVGG 最大的亮点是利用结构重参数化,将训练模型重构成为 VGG-style 的 plain 的结构,实现了 SOTA ,那么这个过程是怎么实现的呢?请看下图:
其实,简要地说,重构主要分成三步:
- Step1. 融合卷积层( identity )和 BN 层,再对融合后的 1x1 卷积和 identity 进行补零(类似 padding 操作)扩展成 3x3 大小的卷积核;
- Step2. 利用卷积的可加性,将 3 个 3x3 卷积合并为一个;
- Step3. 完成结构重参数化,得到 plain 形式的推理模型。
其中, identity 可以通过构造一个单位矩阵(在通道方向),假设输入和输出的特征图大小和宽度( channels = 2)相同时,第一个卷积核的第一个通道数值为 1、第二个通道数值为 0,而第二个卷积核的第一个通道数值为 0 ,第二个通道数值为 1,利用这样的 1x1 卷积就可以得到 identity 的一样效果。再将 1x1 卷积的周围元素填充为 0 ,直至变成 3x3 卷积大小,便可实现 identity 到 3x3 卷积核的过程。
为更直观地看出其中的变化,我从一作丁霄汉的另一篇文章 ACNet 中截取了一张图以更好地诠释这一过程:
虽然 ACNet 中使用的 3x3 卷积、1x3 卷积和 3x1 卷积,但是这和 RepVGG 的 3x3卷积、1x1 卷积和 identity 并无本质区别,都可以通过类似的操作进行填充、融合最终得到一个 3x3 卷积。
此外,作者还对 RepVGG 的训练模型分支的有效性进行了消融实验,结果如下图:
实验证明,两个分支都能够有效地提高模型精度,因此在训练模型中保留两个分支。
网络结构:
RepVGG 分为 A 和 B 两个版本,RepVGG-A 的 5 个 stage 分别有 [1, 2, 4, 14, 1] 层, RepVGG-B 的 5 个 stage 分别有 [1, 4, 6, 16, 1] 层,宽度是 [64, 128, 256, 512] 的若干倍,这里设置了两个超参数 a 和 b 来对宽度进行缩放。其中每个 stage 的第一层是 stride=2 的降采样、B 版本为 heavyweight 的 RepVGG 。
文章所定义的 7 个模型:
模型性能表现
仅使用简单的数据增强,在 ImageNet 上训练 120 个 epochs 的结果:
使用标签平滑、mixup 等增强技术在 ImageNet 上训练 200 个 epochs 的结果: