参考:RepVGG网络简介
一、RepVGG概览
RepVGG通过"训练时复杂、推理时简单"的 trick,让神经网络在保持高速推理的同时拥有更高精度,更适合实际应用中的硬件加速和部署。
- 设计思路:
- 传统VGG网络结构简单,就是一堆3×3卷积叠起来,好处是推理速度快,但精度不如复杂的ResNet这类带"分支"的网络(比如ResNet的残差连接)。
- 而RepVGG想了个办法:训练的时候用复杂的多分支结构(类似ResNet,有主路、1×1卷积分支、恒等映射分支),这样容易训练出高精度;
y = Conv3x3(x) + Conv1x1(x) + Identity(x)
但推理的时候,把这些分支通过数学技巧合并成单一的3×3卷积,变回简单的VGG式结构,保证速度。
- 关键技术:
- 这个"数学技巧"叫结构重参数化:把训练时多个分支的参数(比如3×3卷积、1×1卷积、恒等分支的参数)通过代数运算,合并成一个3×3卷积的参数。就像把不同的零件拆了重组,变成一个更简单的零件,但功能不变。
- 效果如何:
- 在ImageNet图像分类任务上,RepVGG的精度超过了80%,这是简单结构的网络首次达到这个水平。
- 速度上,在GPU上比ResNet-50快83%,比ResNet-101快101%,而且参数更少,性价比很高。
训练时,在卷积核的结构上增加了分支结构,引入了类似 ResNet 的残差结构和 1×1 的卷积分支,具体如下:
- 主分支:保留 3×3 卷积作为主分支,用于提取特征,是主要的特征提取路径。例如在对图像进行分类任务时,3×3 卷积可以捕捉图像中不同尺度的局部特征。
- 1×1 卷积分支:增加 1×1 的卷积作为一个分支。1×1 卷积可以在不改变特征图尺寸的情况下,调整通道数,实现跨通道的信息交互和整合 。比如,当需要对特征进行降维或升维操作时,1×1 卷积能高效地完成任务,在减少计算量的同时,帮助网络更好地学习特征表示。
- 恒等映射(Identity)分支:即残差结构中的恒等映射分支。当输入和输出的特征图维度相匹配时,直接将输入添加到输出中,这有助于解决梯度消失问题,使得网络能够更好地训练,学习到更复杂的特征。在深层网络中,随着层数增加,梯度在反向传播过程中容易逐渐消失,而恒等映射分支可以让梯度更顺畅地传播,让网络训练更加稳定。


在RepVGG中,训练时每个3×3卷积层都有平行的1×1卷积分支和恒等映射分支,进行等价转换为3×3卷积核,推理时还是简单的VGG的卷积块堆叠。但3乘3的卷积核不是直接将训练时的3×3 的卷积核保留下来这么简单。3×3 卷积核是转换后的重要组成部分,但还需要对其他分支的参数进行处理,并与原 3×3 卷积核参数合并。
通过结构重参数化技术,将这些分支的参数(包括 3×3 卷积、1×1 卷积、恒等映射分支的参数以及各分支后的 BN 层参数)进行等价转换,合并为推理时单一的 3×3 卷积核参数。
- 需先把1×1卷积只需在原来权重周围补一圈零,转换为3×3卷积形式
- 恒等映射则转换为以单位矩阵为卷积核的特殊1×1卷积,再进一步转换为3×3卷积形式。
- 同时,每个分支后的BN层在推理时也可转换为带偏置的卷积层相关参数。
- 最后,将这三个分支转换后的卷积核和偏置分别相加,得到最终推理时单一的3×3卷积核参数。
结构重参数化(structural re-parameterization technique)
第一步主要是将Conv2d算子和BN算子融合以及将只有BN的这三个分支,分别转换成一个Conv2d矩阵,
第二步将每个分支上的3x3卷积层融合成一个卷积层,合并的过程其实也很简单,直接将这三个卷积层的参数相加即可。
具体融合过程如图

二、实验
卷积层和批归一化层BN的融合实验
import torch
import torch.nn as nn
from collections import OrderedDict
import numpy as np
def create_model():
"""创建包含卷积层和BN层的模型"""
model = nn.Sequential(OrderedDict([
('conv', nn.Conv2d(
in_channels=2,
out_channels=2,
kernel_size=3,
stride=1,
padding=1,
bias=False
)),
('bn', nn.BatchNorm2d(num_features=2))
]))
return model
def print_model_parameters(model, prefix="Original"):
"""打印模型参数信息"""
print(f"\n{
prefix} Model Parameters:")
for name, param in model.named_parameters():
if param.requires_grad:
print(f" {
name}: shape={
param.shape}")
def fuse_conv_bn(conv, bn):
"""融合卷积层和BN层"""
# 获取卷积核和BN参数
kernel = conv.weight
running_mean = bn.running_mean
running_var = bn.running_var
gamma = bn.weight
beta = bn.bias
eps = bn.eps
# 计算融合参数
std = (running_var + eps).sqrt()
t = (gamma / std).reshape(-1, 1, 1, 1)
fused_kernel = kernel * t
fused_bias = beta - running_mean * gamma / std
# 创建融合后的卷积层
fused_conv = nn.Conv2d(
in_channels=conv.in_channels,
out_channels=conv.out_channels,
kernel_size=conv.kernel_size,
stride=conv.stride,
padding=conv.padding,
bias=True
)
# 加载融合参数
fused_conv.load_state_dict(OrderedDict([
('weight', fused_kernel),
('bias', fused_bias)
]))
return fused_conv
def main():
# 设置随机种子以确保结果可复现
torch.manual_seed(0)
np.random.seed(0)
# 创建输入张量
input_tensor = torch.randn(1, 2, 3, 3)
print(f"Input shape: {
input_tensor.shape}")
# 创建并初始化模型
model = create_model()
model.eval() # 设置为评估模式
# 打印模型结构
print("\nModel Architecture:")
print(model)
print_model_parameters(model)
# 原始模型前向传播
with torch.no_grad():
output_before_fusion = model(input_tensor)
print(f"\nOutput shape before fusion: {
output_before_fusion.shape}")
print(f"Sample output values before fusion:\n{
output_before_fusion[0, 0, :2

最低0.47元/天 解锁文章
2754

被折叠的 条评论
为什么被折叠?



