RepVGG学习笔记

📣 论文下载地址:https://arxiv.org/abs/2101.03697
📣 官方源码(Pytorch实现):https://github.com/DingXiaoH/RepVGG

0 前言

🐾 R e p V G G RepVGG RepVGG最大的创新之处是:结构重参数化。结构重参数化是一个新的概念,在训练和推理阶段采用不同的策略。比如:在训练阶段,使用一个类似 R e s N e t ResNet ResNet的多分支模型,而在推理时转化成 V G G VGG VGG的单路模型。

1 结构重参数化

🐾 结构重参数化就是在训练阶段采用多分支模型,而在推理测试阶段使用单分支模型,可以理解为如下图所示,训练时模型的基本组件为 A A A,而在测试阶段模型的基本组件为 B B B
在这里插入图片描述
🐾 那么关键的问题来了,训练的多分支模型是如何转化到测试的单分支模型的呢?👍 👍 👍 全文参考 强力推荐:结构重参数化主要分为两步,第一步主要是将 C o n v 2 D Conv2D Conv2D算子和 B N BN BN算子融合以及将只有 B N BN BN的分支转换成一个 C o n v 2 D 3 × 3 Conv2D 3\times3 Conv2D3×3分支;第二步将多个分支上的 C o n v 2 D 3 × 3 Conv2D 3\times3 Conv2D3×3卷积分支融合成单路结构的一个 3 × 3 3\times3 3×3卷积。

1.1 结构重参数化第一步(将 C o n v 2 D Conv2D Conv2D算子和 B N BN BN算子融合以及将只有 B N BN BN的分支转换成一个 C o n v 2 D Conv2D Conv2D算子)

  • Conv2D + BN ——> Conv2D
    BN ——> Conv2D
    卷积计算方式: y i = W i ∗ x i + b y_i=W_i*x_i + b yi=Wixi+b,在模型训练阶段,若忽略偏置的影响,则只有公式: y i = W i ∗ x i y_i=W_i*x_i yi=Wixi。标准化 B N BN BN的计算公式为: y i = x i − u i ( δ i ) 2 + ϵ × γ i + β i y_i = \frac{x_i - u_i}{\sqrt{(\delta_i)^2+\epsilon}}\times \gamma_i+\beta_i yi=(δi)2+ϵ xiui×γi+βi,将 B N BN BN层的计算公式按类似卷积计算公式展开就为: y i = γ i δ i 2 + ϵ x i + ( β i − u i ⋅ γ i δ i 2 + ϵ ) y_i= \frac{\gamma_i}{\sqrt{\delta_i^2+\epsilon}}x_i+(\beta_i - \frac{u_i \cdot \gamma_i}{\sqrt{\delta_i^2+\epsilon}}) yi=δi2+ϵ γixi+(βiδi2+ϵ uiγi),在所有的模型中,都是卷积层后接标准化(归一化)层,那么就有卷积层的输出是 B N BN BN层的输入,也就是说在忽略卷积偏置的条件下, y i = W i ∗ x i y_i=W_i*x_i yi=Wixi就是 B N BN BN计算公式中 x i x_i xi的输入。
    上步骤说到卷积在忽略偏置的条件下,计算公式为: y i = W i ∗ x i y_i=W_i*x_i yi=Wixi,就输入特征图和卷积核来说,卷积计算的理论过程是对应位置相乘后相加,卷积核移位,再同理计算下一个值。如下图所示:
    在这里插入图片描述

据上图所示,得到 y 1 y_1 y1位置的计算值为: w 1 ∙ x 1 + w 2 ∙ x 2 + w 3 ∙ x 3 + w 4 ∙ x 5 + w 5 ∙ x 6 + w 6 ∙ x 7 + w 7 ∙ x 9 + w 8 ∙ x 10 + w 9 ∙ x 11 w_1∙x_1+w_2∙x_2+w_3∙x_3+w_4∙x_5+w_5∙x_6+w_6∙x_7+w_7∙x_9+w_8∙x_{10}+w_9∙x_{11} w1x1+w2x2+w3x3+w4x5+w5x6+w6x7+w7x9+w8x10+w9x11,以该点位置为例,该值输入到 B N BN BN层中,计算公式为: ( w 1 ∙ x 1 + w 2 ∙ x 2 + w 3 ∙ x 3 + w 4 ∙ x 5 + w 5 ∙ x 6 + w 6 ∙ x 7 + w 7 ∙ x 9 + w 8 ∙ x 10 + w 9 ∙ x 11 ) ⋅ γ 1 δ 1 2 + ϵ + ( β 1 − u 1 ⋅ γ 1 δ 1 2 + ϵ ) (w_1∙x_1+w_2∙x_2+w_3∙x_3+w_4∙x_5+w_5∙x_6+w_6∙x_7+w_7∙x_9+w_8∙x_{10}+w_9∙x_{11}) \cdot \frac{\gamma_1}{\sqrt{\delta_1^2+\epsilon}}+(\beta_1 - \frac{u_1 \cdot \gamma_1}{\sqrt{\delta_1^2+\epsilon}}) (w1x1+w2x2+w3x3+w4x5+w5x6+w6x7+w7x9+w8x10+w9x11)δ12+ϵ γ1+(β1δ12+ϵ u1γ1)。到这里 C o n v 2 D Conv2D Conv2D就和 B N BN BN层融合在一个计算公式内了。通过公式也可以发现,该公式就是加了偏置的卷积计算公式,所以就可以融合为一个卷积分支了。(理解到这里就算差不多了,但最关键的还是代码了,接着看~~)

🐾 根据学习发现,原论文中的 c o n v 2 d + B N conv2d + BN conv2d+BN或者 B N BN BN,都是转换为 3 × 3 3\times3 3×3卷积,然后再将转化的多个 3 × 3 3\times3 3×3卷积进行融合,为一个 3 × 3 3\times3 3×3卷积,才算结束。那么就有这几种转化需要理解:

  • 1 × 1 卷积 1\times1 卷积 1×1卷积 ——> 3 × 3 卷积 3\times3 卷积 3×3卷积
    原始 1 × 1 1\times1 1×1卷积,只需要将卷积核设置 p a d = 1 pad=1 pad=1的0填充,就可以将 1 × 1 1\times1 1×1卷积转换为 3 × 3 3\times3 3×3卷积。
    如下图所示:
    在这里插入图片描述
  • B N 标准化 BN标准化 BN标准化 ——> 3 × 3 卷积 3\times3 卷积 3×3卷积
    只有 B N BN BN的模型结构,需要构建一个卷积层,然后再根据 C o n v 2 D + B N Conv2D + BN Conv2D+BN转换公式进行转换,得到 3 × 3 3\times3 3×3卷积层。

1.2 结构重参数化第二步(多分支的 3 × 3 3\times3 3×3卷积融合成一个 3 × 3 3\times3 3×3卷积)

在这里插入图片描述

根据图例,融合后的3分支卷积的计算过程可以理解为: y 1 = w 1 ∗ x 1 + b 1 、 y 2 = w 2 ∗ x 1 + b 2 、 y 3 = w 3 ∗ x 1 + b 3 y_1=w_1*x_1+b_1、y_2=w_2*x_1+b_2、y_3=w_3*x_1+b_3 y1=w1x1+b1y2=w2x1+b2y3=w3x1+b3默认输入都是 x 1 x_1 x1,多分支再进行融合可计算为: y i = ( w 1 + w 2 + w 3 ) ∗ x 1 + ( b 1 + b 2 + b 3 ) y_i=(w_1+w_2+w_3)*x_1+(b_1+b_2+b_3) yi=(w1+w2+w3)x1+(b1+b2+b3)
至此,整个的融合过程就结束了。

2 代码分析

🐾 下面就根据大佬的博文的代码来详细剖析下, C o n v 2 D + B N Conv2D + BN Conv2D+BN 转换为 3 × 3 3\times3 3×3卷积。

from collections import OrderedDict
import numpy as np
import torch
import torch.nn as nn

def main():
    torch.random.manual_seed(0)
    f1 = torch.randn(1, 2, 3, 3)
    module = 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)
    ))

    module.eval()

    with torch.no_grad():
        output1 = module(f1)
        print(output1)

    # fuse conv + bn
    kernel = module.conv.weight            # Conv2D 卷积核
    running_mean = module.bn.running_mean  # BN 均值
    running_var = module.bn.running_var    # BN 方差
    gamma = module.bn.weight               # BN  gamma 可学习参数
    beta = module.bn.bias                  # BN  beta  可学习参数
    eps = module.bn.eps                    # BN  eps   可学习参数
    std = (running_var + eps).sqrt()       # BN  根号下方差加eps(很小的数)
    t = (gamma / std).reshape(-1, 1, 1, 1)  # [ch] -> [ch, 1, 1, 1]
    kernel = kernel * t
    bias = beta - running_mean * gamma / std
    fused_conv = nn.Conv2d(in_channels=2, out_channels=2, kernel_size=3, stride=1, padding=1, bias=True)
    fused_conv.load_state_dict(OrderedDict(weight=kernel, bias=bias))

    with torch.no_grad():
        output2 = fused_conv(f1)
        print(output2)

    np.testing.assert_allclose(output1.numpy(), output2.numpy(), rtol=1e-03, atol=1e-05)
    print("convert module has been tested, and the result looks good!")


if __name__ == '__main__':
    main()

上述代码中可以看到是定义了一个 n n . S e q u e n t i a l nn.Sequential nn.Sequential序列容器,里面包含 3 × 3 3\times3 3×3卷积和 B N BN BN归一化,此时卷积是忽略偏置系数的,而这个卷积可以理解为是定义在模型训练阶段的,然后就是通过公式: ( x 1 1 ⋅ k 1 1 + x 2 2 ⋅ k 1 1 + x 3 3 ⋅ k 3 3 + x 4 4 ⋅ k 4 4 ) ⋅ γ 1 δ 1 2 + ϵ + ( β 1 − u 1 ⋅ γ 1 δ 1 2 + ϵ ) (x_1^1 \cdot k_1^1+x_2^2 \cdot k_1^1+x_3^3 \cdot k_3^3+x_4^4 \cdot k_4^4) \cdot \frac{\gamma_1}{\sqrt{\delta_1^2+\epsilon}}+(\beta_1 - \frac{u_1 \cdot \gamma_1}{\sqrt{\delta_1^2+\epsilon}}) (x11k11+x22k11+x33k33+x44k44)δ12+ϵ γ1+(β1δ12+ϵ u1γ1) 来进行二者的融合。根据代码再结合此公式就很好理解了,融合后的 3 × 3 3\times3 3×3卷积的偏置系数为: β 1 − u 1 ⋅ γ 1 δ 1 2 + ϵ \beta_1 - \frac{u_1 \cdot \gamma_1}{\sqrt{\delta_1^2+\epsilon}} β1δ12+ϵ u1γ1,需要注意的是融合后的卷积是有偏置系数的,而且是在模型推理阶段。然后就是卷积核的参数,其计算的公式为:待融合的卷积权重参数 × γ 1 δ 1 2 + ϵ \times \frac{\gamma_1}{\sqrt{\delta_1^2+\epsilon}} ×δ12+ϵ γ1,可以发现融合过程是严格按着上述公式计算得来的。新的卷积就融合了 C o n v 2 d D + B N Conv2dD + BN Conv2dD+BN的所有参数信息。(值得注意的就是:融合前的卷积是不带有偏置的,而融合后的卷积要设置偏置为 T r u e True True

📝 📝 📝

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

勇敢牛牛@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值