yolov6的骨干网络组件18:RepVGG,多分支进行训练,单分支进行推理(我有了SCI的主意)

RepVGG重参数化卷积 (Re-parameterization VGG)

!abstract 论文信息

  • 标题: RepVGG: Making VGG-style ConvNets Great Again
  • 作者: Xiaohan Ding, Xiangyu Zhang, Ningning Ma, Jungong Han, Guiguang Ding, Jian Sun
  • 年份: 2021
  • 会议: CVPR 2021
  • 论文链接: https://arxiv.org/abs/2101.03697
  • 核心贡献: 训练时多分支,推理时单分支,兼顾精度和速度

一、核心思想

复制代码
RepVGG的核心: 训练时使用多分支提升精度,推理时融合为单分支提升速度

训练阶段 vs 推理阶段:
(如下图)

直觉理解:
- 多分支训练: 类似集成学习,提升模型表达能力
- 重参数化: 将多分支数学等价融合为单分支
- 最佳实践: 训练时复杂,推理时简单

二、模块结构

复制代码
RepVGG块结构:

训练时 (多分支):
输入特征图 (C×H×W)
    │
    ├──────────────────┐
    │                  │
    ▼                  ▼
┌─────────┐    ┌─────────────┐
│ 3×3 Conv│    │ 1×1 Conv    │
│ (主分支) │    │ (辅助分支)   │
└─────────┘    └─────────────┘
    │                  │
    ▼                  ▼
    │                  │
    ├──────────────────┤
    │                  │
    │     ┌──────┐     │
    │     │Identity│    │
    │     │(恒等) │    │
    │     └──────┘     │
    │         │        │
    ▼         ▼        ▼
┌─────────────────────────────────┐
│          Add (相加)              │
└─────────────────────────────────┘
    │
    ▼
输出特征图 (C×H×W)

推理时 (单分支):
输入特征图 (C×H×W)
    │
    ▼
┌─────────────────────────────────┐
│  3×3 Conv (融合后的卷积)         │
│  权重 = 3×3权重 + 1×1权重填充   │
│  偏置 = 3×3偏置 + 1×1偏置 + BN偏置│
└─────────────────────────────────┘
    │
    ▼
输出特征图 (C×H×W)

三、数学公式

复制代码
RepVGG的数学表达:

1. 训练时前向传播:
   y = BN₃ₓ₃(Conv₃ₓ₃(x)) + BN₁ₓ₁(Conv₁ₓ₁(x)) + BN₀(x)
   
   其中:
   - BN₃ₓ₃: 3×3卷积后的BatchNorm
   - BN₁ₓ₁: 1×1卷积后的BatchNorm
   - BN₀: 恒等映射的BatchNorm

2. BatchNorm融合:
   对于BN层: y = γ × (x - μ) / √(σ² + ε) + β
   
   融合到卷积权重:
   W'ᵢ = γᵢ / √(σ²ᵢ + ε) × Wᵢ
   b'ᵢ = γᵢ / √(σ²ᵢ + ε) × (bᵢ - μᵢ) + βᵢ

3. 1×1卷积填充为3×3:
   W₃ₓ₃₁ₓ₁[i,j] = W₁ₓ₁ if (i,j)=(1,1) else 0
   
   即将1×1卷积核零填充到3×3大小

4. 恒等映射转换:
   W_identity = 单位矩阵 (对角线为1)
   转换为1×1卷积后再填充为3×3

5. 最终融合:
   W_fused = W'₃ₓ₃ + Pad(W'₁ₓ₁) + Pad(W'_identity)
   b_fused = b'₃ₓ₃ + b'₁ₓ₁ + b'_identity

6. 推理时:
   y = Conv_fused(x)  // 单次3×3卷积

四、代码实现

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F

class RepVGGBlock(nn.Module):
    """RepVGG块: 训练时多分支,推理时单分支"""
    def __init__(self, c1, c2, stride=1, deploy=False, use_se=False):
        """
        Args:
            c1: 输入通道数
            c2: 输出通道数
            stride: 步长
            deploy: 是否为部署模式
            use_se: 是否使用SE注意力
        """
        super().__init__()
        self.deploy = deploy
        self.c1 = c1
        self.c2 = c2
        self.stride = stride
        
        # 训练时的多分支
        if deploy:
            # 部署模式: 单个3×3卷积
            self.fused_conv = nn.Conv2d(c1, c2, 3, stride, 1, bias=True)
        else:
            # 训练模式: 三个分支
            self.conv3x3 = nn.Conv2d(c1, c2, 3, stride, 1, bias=False)
            self.bn3x3 = nn.BatchNorm2d(c2)
            
            self.conv1x1 = nn.Conv2d(c1, c2, 1, stride, 0, bias=False)
            self.bn1x1 = nn.BatchNorm2d(c2)
            
            # 恒等分支 (仅当c1==c2且stride==1时)
            if c1 == c2 and stride == 1:
                self.bn_identity = nn.BatchNorm2d(c2)
            else:
                self.bn_identity = None
        
        # 激活函数
        self.act = nn.ReLU()
    
    def forward(self, x):
        if self.deploy:
            # 部署模式: 直接使用融合后的卷积
            return self.act(self.fused_conv(x))
        
        # 训练模式: 多分支
        y3x3 = self.bn3x3(self.conv3x3(x))
        y1x1 = self.bn1x1(self.conv1x1(x))
        
        if self.bn_identity is not None:
            y_identity = self.bn_identity(x)
            out = y3x3 + y1x1 + y_identity
        else:
            out = y3x3 + y1x1
        
        return self.act(out)
    
    def _fuse_bn(self, conv, bn):
        """融合卷积和BN层"""
        gamma = bn.weight
        beta = bn.bias
        mean = bn.running_mean
        var = bn.running_var
        eps = bn.eps
        
        # 计算融合权重
        std = torch.sqrt(var + eps)
        W_fused = conv.weight * (gamma / std).view(-1, 1, 1, 1)
        
        # 计算融合偏置
        if conv.bias is not None:
            b_fused = (conv.bias - mean) * (gamma / std) + beta
        else:
            b_fused = (-mean) * (gamma / std) + beta
        
        return W_fused, b_fused
    
    def _pad_1x1_to_3x3(self, W1x1):
        """将1×1卷积核填充到3×3"""
        if W1x1 is None:
            return 0
        return F.pad(W1x1, [1, 1, 1, 1])
    
    def _identity_to_3x3(self, identity_weight, c):
        """将恒等映射转换为3×3卷积核"""
        if identity_weight is None:
            return 0
        # 创建单位卷积核
        identity_conv = torch.zeros(c, c, 3, 3)
        for i in range(c):
            identity_conv[i, i, 1, 1] = 1.0
        return identity_conv * identity_weight.view(-1, 1, 1, 1)
    
    def switch_to_deploy(self):
        """将训练模式转换为部署模式"""
        if self.deploy:
            return
        
        # 融合3×3分支
        W3x3, b3x3 = self._fuse_bn(self.conv3x3, self.bn3x3)
        
        # 融合1×1分支
        W1x1, b1x1 = self._fuse_bn(self.conv1x1, self.bn1x1)
        W1x1 = self._pad_1x1_to_3x3(W1x1)
        
        # 融合恒等分支
        if self.bn_identity is not None:
            W_identity = self._identity_to_3x3(self.bn_identity.weight, self.c2)
            b_identity = self.bn_identity.bias
        else:
            W_identity = 0
            b_identity = 0
        
        # 最终融合
        W_fused = W3x3 + W1x1 + W_identity
        b_fused = b3x3 + b1x1 + b_identity
        
        # 创建融合后的卷积层
        self.fused_conv = nn.Conv2d(self.c1, self.c2, 3, self.stride, 1, bias=True)
        self.fused_conv.weight.data = W_fused
        self.fused_conv.bias.data = b_fused
        
        # 删除训练时的分支
        del self.conv3x3
        del self.bn3x3
        del self.conv1x1
        del self.bn1x1
        if self.bn_identity is not None:
            del self.bn_identity
        
        self.deploy = True

# 使用示例
if __name__ == "__main__":
    # 训练模式
    block_train = RepVGGBlock(c1=64, c2=64, deploy=False)
    x = torch.randn(1, 64, 32, 32)
    
    print("训练模式:")
    out_train = block_train(x)
    print(f"输入形状: {x.shape}")
    print(f"输出形状: {out_train.shape}")
    
    # 转换为部署模式
    block_train.switch_to_deploy()
    
    print("\n部署模式:")
    out_deploy = block_train(x)
    print(f"输出形状: {out_deploy.shape}")
    
    # 验证输出一致性
    print(f"输出差异: {torch.abs(out_train - out_deploy).max().item():.6f}")

五、在YOLO中的应用

复制代码
RepVGG在YOLO中的应用:

1. YOLOv6:
   - 使用RepVGG块作为基本构建单元
   - 训练时多分支,推理时单分支
   - 推理速度提升约30%

2. YOLOv7:
   - 部分层使用RepVGG块
   - 结合E-ELAN结构

3. YOLOv9:
   - GELAN中可选RepBlock
   - 针对部署优化

RepVGG在YOLOv6中的配置:
┌─────────────────────────────────────┐
│  backbone:                          │
│    - [-1, 1, RepVGG, [64]]         │
│    - [-1, 1, RepVGG, [128]]        │
│    - [-1, 1, RepVGG, [256]]        │
│    ...                              │
└─────────────────────────────────────┘

性能对比:
┌──────────────┬──────────┬──────────┬──────────┐
│ 模型         │ 训练模式 │ 部署模式 │ 速度提升 │
├──────────────┼──────────┼──────────┼──────────┤
│ YOLOv6-n     │ 12.8ms   │ 9.8ms   │ +23%     │
│ YOLOv6-s     │ 15.2ms   │ 11.6ms  │ +24%     │
│ YOLOv6-m     │ 19.8ms   │ 15.2ms  │ +23%     │
│ YOLOv6-l     │ 25.6ms   │ 19.8ms  │ +23%     │
└──────────────┴──────────┴──────────┴──────────┘

六、优缺点

优点

复制代码
✅ 推理速度快: 单分支结构,硬件友好
✅ 精度高: 多分支训练提升表达能力
✅ 内存效率: 推理时内存占用少
✅ 部署友好: 简单的3×3卷积结构
✅ 灵活性: 可与其他技术结合使用

缺点

复制代码
❌ 训练复杂: 需要维护多个分支
❌ 转换开销: 需要额外的融合步骤
❌ 结构限制: 主要适用于3×3卷积
❌ 部署代码: 需要专门的部署代码

参考

相关推荐
大鹏的NLP博客1 小时前
大模型 Tokenizer:从字符到 Byte,再到大词表
深度学习·机器学习·大模型·分词
__Wedream__1 小时前
ICPR 2022 多教师知识蒸馏技术应用于图像超分辨率重建——MTKDSR
图像处理·人工智能·深度学习·超分辨率重建·图像复原和增强
湘美书院--湘美谈教育2 小时前
湘美书院谈探索式教育,中医药AI研究络病理论
大数据·人工智能·深度学习·学习·生活
kaixin_啊啊2 小时前
头歌——人工智能(深度学习初体验)
人工智能·深度学习
硅谷秋水3 小时前
WAM-Nav:用于统一视觉导航的非对称潜世界动作建模
深度学习·机器学习·计算机视觉·语言模型·机器人
三言老师3 小时前
ss高效查看网络连接端口实操
linux·运维·服务器·网络
卡梅德生物科技小能手3 小时前
卡梅德生物科普 Zika Virus(寨卡病毒)|病毒结构与科研实验体系解析
经验分享·深度学习·生活
三言老师3 小时前
traceroute路由追踪实操
linux·运维·服务器·网络
荒--4 小时前
kali 操作基础(二)
linux·网络