RepVGG: Making VGG-style ConvNets Great Again(CVPR 2021)

论文地址:https://arxiv.org/abs/2101.03697

  1. Contributions
    在GPU上,3x3卷积的计算密度(理论运算量除以所用时间)可达1x1和5x5卷积的四倍。在训练时,使用一个类似ResNet-style的多分支模型,而推理时转化成VGG-style的单路模型。利用了多分支模型训练时的优势(性能高)和单路模型推理时的好处(速度快、省内存)。
  2. Architecture
    [图片]

仅使用3 × 3 conv和ReLU,通过结构重参数化技术,将训练时多分支转换为推理时单一路径
re-parameterization
[图片]

RepVGG在训练时每一层都有三个分支,分别是identify,1x1,3x3
1x1卷积层:用0填充为3x3卷积
identify:构建了一个做了恒等映射3x3的卷积层
用卷积可加性原理,将三个分支的卷积层和bias对应相加组成最终一个conv3x3的形式即可
[图片]

对于图像分类,使用global average pooling,然后使用全连接层作为head。对于其他任务,使用特定的head
(1)stage1分辨率很高,这很耗时,因此只使用一层
(2)最后的stage应该有更多的通道,所以只使用一层来保存参数
(3) 大多数层放在倒数第二阶段(ImageNet上的输出分辨率为14×14),参考的resnet

总结:
(1)推理时的等价性不代表训练时的等价性,直接训练一个 3x3 卷积最终也会得到同样形状的参数,虽然形状相同,但性能不同
(2)添加一些能在推理阶段去掉的参数,也许会work
(3)1x1卷积的表征能力弱于3x3卷积,因为前者可以看作一个有很多参数为0的3x3卷积,但是1x1 + 3x3的性能却好于3x3 + 3x3,一个强结构加一个弱结构大概率好于两个强结构相加
3. Experiments
ImageNet-1K
[图片]

使用简单的数据增强(随机裁剪和左右翻转)
使用Winograd算法加速3×3 conv(仅当步幅为1时)
Cityscapes
[图片]

参考官方在ResNet-50/101的最后两个阶段使用Dilated Convolution,RepVGG的最后两个阶段使用Dilated Convolution
但由于dilated conv效率低下,RepVGG-fast仅仅在stage4的最后4层和stage5的唯一一层使用Dilated Convolution
由实验结果得出,使用更多的dilated conv并不能提高性能

**RepVGG: Making VGG-Style ConvNets Great Again** 是一种通过结构重参数化(Structural Re-parameterization)技术,将训练时的多分支复杂结构转换为推理时的单路VGG风格结构(仅含3×3卷积和ReLU)的卷积神经网络设计方法。以下是其核心思想与实现的分步解析: --- ### **1. 核心动机** - **VGG风格的优势**: - 结构简单(仅堆叠3×3卷积和ReLU),硬件友好(适合并行计算)。 - 但纯VGG网络在深层时易梯度消失,性能落后于ResNet等复杂结构。 - **现有结构的矛盾**: - 训练时需多分支结构(如ResNet的残差连接、Inception的多路径)提升梯度流动。 - 推理时多分支增加内存访问开销(MACs),降低实际速度。 - **RepVGG的解决方案**: - **训练时**:采用多分支结构(如ResNet的恒等映射+1×1卷积分支)。 - **推理时**:通过重参数化将多分支合并为单路3×3卷积,实现“训练复杂、推理简单”。 --- ### **2. 结构重参数化技术** #### **(1) 训练阶段的多分支设计** RepVGG的训练块(Block)包含三个并行分支: 1. **3×3卷积分支**:主路径,提取局部特征。 2. **1×1卷积分支**:调整通道数,增加非线性。 3. **恒等映射(Identity)分支**:短连接,缓解梯度消失。 **数学形式**: 输入 \( x \),输出 \( y \) 为: \[ y = \text{ReLU}(\text{BN}_3(\text{Conv}_3(x)) + \text{BN}_1(\text{Conv}_1(x)) + \text{BN}_0(x)) \] 其中 \( \text{Conv}_3 \) 是3×3卷积,\( \text{Conv}_1 \) 是1×1卷积,\( \text{BN}_i \) 是批归一化层。 #### **(2) 推理阶段的单路转换** 通过以下步骤将多分支合并为单个3×3卷积: 1. **BN融合**: - 将每个分支的卷积(Conv)和批归一化(BN)合并为带偏置的卷积: \[ \text{Conv}_{\text{fused}} = \gamma \cdot \text{Conv} / \sqrt{\sigma^2 + \epsilon} + (\beta - \gamma \cdot \mu / \sqrt{\sigma^2 + \epsilon}) \] 其中 \( \gamma, \beta \) 是BN的缩放和平移参数,\( \mu, \sigma^2 \) 是均值和方差。 2. **1×1卷积→3×3卷积**: - 用零填充将1×1卷积的权重矩阵扩展为3×3(中心为原权重,四周为零)。 3. **恒等映射→3×3卷积**: - 将恒等映射视为一个特殊的3×3卷积,其权重矩阵中心为1,其余为零。 4. **分支合并**: - 将三个分支的3×3卷积权重和偏置相加,得到最终的3×3卷积: \[ W_{\text{final}} = W_3 + W_{1\to3} + W_{\text{id}\to3}, \quad b_{\text{final}} = b_3 + b_{1\to3} + b_{\text{id}\to3} \] **效果**: - 推理时模型变为纯VGG风格(仅3×3卷积+ReLU),速度更快(实测在GPU上比ResNet50快13%)。 - 参数和计算量与训练时相同(因重参数化无额外开销)。 --- ### **3. RepVGG的核心设计** #### **(1) 阶段式架构** - 类似ResNet,分多个阶段(Stage),每个阶段内堆叠相同结构的Block。 - **下采样**:通过步长为2的3×3卷积实现,同时通道数翻倍。 #### **(2) 通道数配置** - 浅层(如Stage1)通道数较少(如64),深层(如Stage4)通道数较多(如512)。 - **优势**:平衡计算量和表达能力,避免深层参数过多。 #### **(3) 训练技巧** - **数据增强**:采用AutoAugment或RandAugment提升泛化性。 - **标签平滑**:缓解过拟合(平滑系数0.1)。 - **混合精度训练**:加速训练并减少显存占用。 --- ### **4. 性能对比** | 模型 | 参数量(M) | Top-1准确率(ImageNet) | 推理速度(img/s, V100 GPU) | |---------------|-------------|--------------------------|-----------------------------| | ResNet50 | 25.6 | 76.5% | 1236 | | RepVGG-A0 | 8.3 | 72.4% | 3448(+179%) | | RepVGG-B1 | 51.8 | 78.4% | 1472(+19%) | | RepVGG-B3 | 103.9 | 80.5% | 831 | - **优势**:在相似参数量下,RepVGG的推理速度显著优于ResNet(尤其低参数量模型)。 - **代价**:训练时需多分支结构,可能增加内存占用(但可通过梯度检查点缓解)。 --- ### **5. 代码实现(PyTorch示例)** #### **(1) 训练块定义** ```python import torch import torch.nn as nn class RepVGGBlock(nn.Module): def __init__(self, in_channels, out_channels, stride=1): super().__init__() self.stride = stride self.conv1 = nn.Conv2d(in_channels, out_channels, 1, 1, 0, bias=False) self.conv3 = nn.Conv2d(in_channels, out_channels, 3, stride, 1, bias=False) self.bn1 = nn.BatchNorm2d(out_channels) self.bn3 = nn.BatchNorm2d(out_channels) self.bn_id = nn.BatchNorm2d(in_channels) if stride == 1 else None self.relu = nn.ReLU(inplace=True) def forward(self, x): id_out = 0 if self.stride == 1: id_out = self.bn_id(x) out3 = self.bn3(self.conv3(x)) out1 = self.bn1(self.conv1(x)) return self.relu(out3 + out1 + id_out) ``` #### **(2) 重参数化转换** ```python def repvgg_convert(block): # 融合BN到Conv def fuse_bn(conv, bn): w = conv.weight std = (bn.running_var + bn.eps).sqrt() t = (bn.running_mean - bn.bias) / std w_fused = w * (bn.weight / std).reshape([-1, 1, 1, 1]) b_fused = bn.bias + (bn.weight * t) - std * (w * t).sum([1, 2, 3]) return nn.Conv2d(conv.in_channels, conv.out_channels, 3, conv.stride, 1, bias=True) # 转换3×3分支 conv3_fused = fuse_bn(block.conv3, block.bn3) # 转换1×1分支为3×3 conv1_fused = nn.Conv2d(block.conv1.in_channels, block.conv1.out_channels, 3, 1, 1, bias=True) conv1_fused.weight.data.zero_() conv1_fused.weight.data[:, :, 1, 1] = block.conv1.weight.data conv1_fused.bias.data = block.bn1.bias - block.bn1.running_mean * block.bn1.weight / ( (block.bn1.running_var + block.bn1.eps).sqrt() ) # 合并分支 final_conv = nn.Conv2d( conv3_fused.in_channels, conv3_fused.out_channels, 3, conv3_fused.stride, 1, bias=True ) final_conv.weight.data = conv3_fused.weight.data + conv1_fused.weight.data if block.stride == 1: id_conv = nn.Conv2d(block.bn_id.num_features, block.bn_id.num_features, 3, 1, 1, bias=True) id_conv.weight.data.zero_() id_conv.weight.data[:, :, 1, 1] = 1.0 final_conv.weight.data += id_conv.weight.data final_conv.bias.data = conv3_fused.bias.data + conv1_fused.bias.data + block.bn_id.bias - block.bn_id.running_mean else: final_conv.bias.data = conv3_fused.bias.data + conv1_fused.bias.data return final_conv ``` --- ### **6. 挑战与改进方向** 1. **极深网络的训练稳定性**: - 超过50层的RepVGG可能需引入更多残差连接或梯度裁剪。 2. **移动端部署优化**: - 结合通道剪枝或量化(如INT8)进一步压缩模型。 3. **自适应结构**: - 根据任务动态调整分支数量(如浅层用1×1分支,深层用恒等映射)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值