YOLOv5算法改进(20)— 引入 RepVGG 重参数化模块

2023-09-21 12:09:50

前言:Hello大家好,我是小哥谈。RepVGG 重参数化模块是一种用于深度卷积神经网络的模块化设计方法,旨在通过将卷积层和全连接层统一为卷积层来简化网络结构并提高计算效率。该方法通过重参数化,将常规的卷积层分解为一个轻量级的卷积层和一个恒等映射层,从而达到降低计算复杂度的目的。本节课就简单介绍一下如何在YOLOv5中引入RepVGG 重参数化模块!🌈 

 前期回顾:

          YOLOv5算法改进(1)— 如何去改进YOLOv5算法

          YOLOv5算法改进(2)— 添加SE注意力机制

          YOLOv5算法改进(3)— 添加CBAM注意力机制

          YOLOv5算法改进(4)— 添加CA注意力机制

          YOLOv5算法改进(5)— 添加ECA注意力机制

          YOLOv5算法改进(6)— 添加SOCA注意力机制

          YOLOv5算法改进(7)— 添加SimAM注意力机制

          YOLOv5算法改进(8)— 替换主干网络之MobileNetV3

          YOLOv5算法改进(9)— 替换主干网络之ShuffleNetV2

          YOLOv5算法改进(10)— 替换主干网络之GhostNet

          YOLOv5算法改进(11)— 替换主干网络之EfficientNetv2

          YOLOv5算法改进(12)— 替换主干网络之Swin Transformer

          YOLOv5算法改进(13)— 替换主干网络之PP-LCNet

          YOLOv5算法改进(14)— 更换Neck之BiFPN

          YOLOv5算法改进(15)— 更换Neck之AFPN

          YOLOv5算法改进(16)— 增加小目标检测层

          YOLOv5算法改进(17)— 更换损失函数(EIoU、AlphaIoU、SIoU和WIoU)

          YOLOv5算法改进(18)— 更换激活函数(SiLU、ReLU、ELU、Hardswish、Mish、Softplus等)

          YOLOv5算法改进(19)— 更换NMS(DIoU-NMS、CIoU-NMS、EIoU-NMS、GIoU-NMS 、SIoU-NMS和Soft-NMS)

          目录

🚀1.RepVGG 重参数化模块介绍

🚀2.RepVGG 模型架构及优点

💥💥2.1 RepVGG 模型架构

💥💥2.2 RepVGG 优点

💥💥2.3 实验对比

🚀3.YOLOv5结合RepVGG 重参数化模块

💥💥步骤1:在common.py中添加RepVGG 重参数化模块

💥💥步骤2:修改yolo.py文件

💥💥步骤3:添加RepVGGBlock

💥💥步骤4:创建自定义的yaml文件

💥💥步骤5:验证是否加入成功

🚀1.RepVGG 重参数化模块介绍

RepVGG 重参数化模块是一种用于深度卷积神经网络的模块化设计方法,旨在通过将卷积层和全连接层统一为卷积层来简化网络结构并提高计算效率该方法通过重参数化,将常规的卷积层分解为一个轻量级的卷积层和一个恒等映射层,从而达到降低计算复杂度的目的

在RepVGG中,每个重参数化模块由两个部分组成:卷积层恒等映射层卷积层用于学习输入特征的高级表示而恒等映射层则保留了输入特征的原始信息通过将这两个部分连接在一起,可以实现卷积层和全连接层的统一,并且只需要进行一次卷积操作即可得到输出。

重参数化模块的主要优势是减少了网络中的参数数量和计算复杂度,从而提高了模型的训练速度和推断速度,并且不会显著影响模型的性能。此外,由于模块化设计的特性,RepVGG还具有较好的可解释性和可扩展性。

论文题目:《RepVGG: Making VGG-style ConvNets Great Again》

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

代码实现:  mirrors / DingXiaoH / RepVGG · GitCode


🚀2.RepVGG 模型架构及优点

💥💥2.1 RepVGG 模型架构

网络重参数化可以认为是模型压缩领域的一种新技术,它的主要思想是把一个K x K的卷积层等价转换为若干种特定网络层的并联或串联,转换后的网络更复杂、参数更多,因此转换的网络在训练阶段有希望达到更好的效果,在推理阶段,又可以把大网络的参数等价转换到原来的K x K卷积层中,使得网络兼具了训练阶段大网络的更好的学习能力和推理阶段小网络的更高的计算速度。

RepVGG沿用ResNet的残差边,只不过RepVGG在每一层都使用了残差连接,3x3卷积接上一个identity和1x1的卷积。如下图所示,图中B代表在训练时所使用的模型结构,图中C代表推理时所用的模型结构,在训练模型当中,在每一分支输出前加一个BN层,如何将B->C就是重参数化的重要体现。

💥💥2.2 RepVGG 优点

🍀(1)Fast,使用重参数化后,在推理上速度会有非常大的提升,并且有助于模型部署提高实用性。

🍀(2)节省内存,采用多分支模型,在每次计算都需要多份内存分别保存每条分支的结果,所以导致内存消耗大。

🍀(3)修改模型会更加方便,使用多分支存在一个问题,就是每条分支的输入通道和输出通道要保持一致,那么对模型的修改产生不便,如果是单路径就不存在这个问题。

💥💥2.3 实验对比

在论文中,作者提出7个模型和主流模型在Imagenet上进行对比,对比试验结果如下:

从实验可以看出,RepVGG的推理速度是比较快的,虽然参数比ResNet大,但是使用重参数化替换成推理模型,说明这个工作是非常有效果的。

总之,RepVGG当中的结构重参数化这个方法值得我们去学习,对模型的部署非常友好,在未来的发展中,重参数化会越来越受欢迎(YOLOv6使用了这个方式)🐳


🚀3.YOLOv5结合RepVGG 重参数化模块

💥💥步骤1:在common.py中添加RepVGG 重参数化模块

将下面RepVGG 重参数化模块的代码复制粘贴到common.py文件的末尾。

# build repvgg block
# -----------------------------
def conv_bn(in_channels, out_channels, kernel_size, stride, padding, groups=1):
    result = nn.Sequential()
    result.add_module('conv', nn.Conv2d(in_channels=in_channels, out_channels=out_channels,
                                        kernel_size=kernel_size, stride=stride, padding=padding, groups=groups,
                                        bias=False))
    result.add_module('bn', nn.BatchNorm2d(num_features=out_channels))

    return result


class SEBlock(nn.Module):

    def __init__(self, input_channels, internal_neurons):
        super(SEBlock, self).__init__()
        self.down = nn.Conv2d(in_channels=input_channels, out_channels=internal_neurons, kernel_size=1, stride=1,
                              bias=True)
        self.up = nn.Conv2d(in_channels=internal_neurons, out_channels=input_channels, kernel_size=1, stride=1,
                            bias=True)
        self.input_channels = input_channels

    def forward(self, inputs):
        x = F.avg_pool2d(inputs, kernel_size=inputs.size(3))
        x = self.down(x)
        x = F.relu(x)
        x = self.up(x)
        x = torch.sigmoid(x)
        x = x.view(-1, self.input_channels, 1, 1)
        return inputs * x


class RepVGGBlock(nn.Module):

    def __init__(self, in_channels, out_channels, kernel_size=3,
                 stride=1, padding=1, dilation=1, groups=1, padding_mode='zeros', deploy=False, use_se=False):
        super(RepVGGBlock, self).__init__()
        self.deploy = deploy
        self.groups = groups
        self.in_channels = in_channels

        padding_11 = padding - kernel_size // 2

        self.nonlinearity = nn.SiLU()

        # self.nonlinearity = nn.ReLU()

        if use_se:
            self.se = SEBlock(out_channels, internal_neurons=out_channels // 16)
        else:
            self.se = nn.Identity()

        if deploy:
            self.rbr_reparam = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size,
                                         stride=stride,
                                         padding=padding, dilation=dilation, groups=groups, bias=True,
                                         padding_mode=padding_mode)

        else:
            self.rbr_identity = nn.BatchNorm2d(
                num_features=in_channels) if out_channels == in_channels and stride == 1 else None
            self.rbr_dense = conv_bn(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size,
                                     stride=stride, padding=padding, groups=groups)
            self.rbr_1x1 = conv_bn(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=stride,
                                   padding=padding_11, groups=groups)
            # print('RepVGG Block, identity = ', self.rbr_identity)

    def get_equivalent_kernel_bias(self):
        kernel3x3, bias3x3 = self._fuse_bn_tensor(self.rbr_dense)
        kernel1x1, bias1x1 = self._fuse_bn_tensor(self.rbr_1x1)
        kernelid, biasid = self._fuse_bn_tensor(self.rbr_identity)
        return kernel3x3 + self._pad_1x1_to_3x3_tensor(kernel1x1) + kernelid, bias3x3 + bias1x1 + biasid

    def _pad_1x1_to_3x3_tensor(self, kernel1x1):
        if kernel1x1 is None:
            return 0
        else:
            return torch.nn.functional.pad(kernel1x1, [1, 1, 1, 1])

    def _fuse_bn_tensor(self, branch):
        if branch is None:
            return 0, 0
        if isinstance(branch, nn.Sequential):
            kernel = branch.conv.weight
            running_mean = branch.bn.running_mean
            running_var = branch.bn.running_var
            gamma = branch.bn.weight
            beta = branch.bn.bias
            eps = branch.bn.eps
        else:
            assert isinstance(branch, nn.BatchNorm2d)
            if not hasattr(self, 'id_tensor'):
                input_dim = self.in_channels // self.groups
                kernel_value = np.zeros((self.in_channels, input_dim, 3, 3), dtype=np.float32)
                for i in range(self.in_channels):
                    kernel_value[i, i % input_dim, 1, 1] = 1
                self.id_tensor = torch.from_numpy(kernel_value).to(branch.weight.device)
            kernel = self.id_tensor
            running_mean = branch.running_mean
            running_var = branch.running_var
            gamma = branch.weight
            beta = branch.bias
            eps = branch.eps
        std = (running_var + eps).sqrt()
        t = (gamma / std).reshape(-1, 1, 1, 1)
        return kernel * t, beta - running_mean * gamma / std

    def forward(self, inputs):
        if hasattr(self, 'rbr_reparam'):
            return self.nonlinearity(self.se(self.rbr_reparam(inputs)))

        if self.rbr_identity is None:
            id_out = 0
        else:
            id_out = self.rbr_identity(inputs)

        return self.nonlinearity(self.se(self.rbr_dense(inputs) + self.rbr_1x1(inputs) + id_out))

    def fusevggforward(self, x):
        return self.nonlinearity(self.rbr_dense(x))
# repvgg block end
# -----------------------------

此时出现报错,具体如下图所示:

解决方法:

需要导入包

import torch.nn.functional as F

导入后,则报错消失。

💥💥步骤2:修改yolo.py文件

修改yolo.py文件,在yolo.py文件中找到 def fuse(self): ,用如下代码替换:

# --------------------------repvgg refuse---------------------------------
    def fuse(self):  # fuse model Conv2d() + BatchNorm2d() layers
        print('Fusing layers... ')
        for m in self.model.modules():
            if type(m) is RepVGGBlock:
                if hasattr(m, 'rbr_1x1'):
                    kernel, bias = m.get_equivalent_kernel_bias()
                    rbr_reparam = nn.Conv2d(in_channels=m.rbr_dense.conv.in_channels,
                                                out_channels=m.rbr_dense.conv.out_channels,
                                                kernel_size=m.rbr_dense.conv.kernel_size,
                                                stride=m.rbr_dense.conv.stride,
                                                padding=m.rbr_dense.conv.padding, dilation=m.rbr_dense.conv.dilation,
                                                groups=m.rbr_dense.conv.groups, bias=True)
                    rbr_reparam.weight.data = kernel
                    rbr_reparam.bias.data = bias
                    for para in self.parameters():
                        para.detach_()
                    m.rbr_dense = rbr_reparam
                    m.__delattr__('rbr_1x1')
                    if hasattr(m, 'rbr_identity'):
                        m.__delattr__('rbr_identity')
                    if hasattr(m, 'id_tensor'):
                        m.__delattr__('id_tensor')
                    m.deploy = True
                    delattr(m, 'se')
                    m.forward = m.fusevggforward  # update forward
            if isinstance(m, (Conv, DWConv)) and hasattr(m, 'bn'):
                m.conv = fuse_conv_and_bn(m.conv, m.bn)  # update conv
                delattr(m, 'bn')  # remove batchnorm
                m.forward = m.forward_fuse  # update forward
        self.info()
        return self
    # --------------------------end repvgg & shuffle refuse--------------------------------

💥💥步骤3:添加RepVGGBlock

yolo.py文件中,找到parse_model函数,将RepVGGBlock添加到如下位置:

💥💥步骤4:创建自定义的yaml文件

models文件夹中复制yolov5s.yaml,粘贴并重命名为yolov5s_RepVGG.yaml

yaml文件完整代码如下:

# create by pogg
# parameters
nc: 80  # number of classes
depth_multiple: 1  # model depth multiple
width_multiple: 1  # layer channel multiple

# anchors
anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32

# YOLOv5-repvgg backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Focus, [32, 3]],  # 0-P1/2
   [-1, 1, RepVGGBlock, [64, 3, 2]], # 1-P2/4
   [-1, 1, C3, [64]],
   [-1, 1, RepVGGBlock, [128, 3, 2]], # 3-P3/8
   [-1, 3, C3, [128]],
   [-1, 1, RepVGGBlock, [256, 3, 2]], # 5-P4/16
   [-1, 3, C3, [256]],
   [-1, 1, RepVGGBlock, [512, 3, 2]], # 7-P4/16
   [-1, 1, SPP, [512, [5, 9, 13]]],
   [-1, 1, C3, [512, False]],  # 9
  ]

# YOLOv5 head
head:
  [[-1, 1, Conv, [128, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, C3, [128, False]],  # 13

   [-1, 1, Conv, [128, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 4], 1, Concat, [1]],  # cat backbone P3
   [-1, 3, C3, [128, False]],  # 17 (P3/8-small)

   [-1, 1, Conv, [128, 3, 2]],
   [[-1, 14], 1, Concat, [1]],  # cat head P4
   [-1, 3, C3, [128, False]],  # 20 (P4/16-medium)

   [-1, 1, Conv, [128, 3, 2]],
   [[-1, 10], 1, Concat, [1]],  # cat head P5
   [-1, 3, C3, [128, False]],  # 23 (P5/32-large)

   [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]

💥💥步骤5:验证是否加入成功

yolo.py文件里,配置我们刚才自定义的yolov5s_RepVGG.yaml

然后运行yolo.py,得到结果。

这样就算添加成功了!~🎉🎉🎉  


更多推荐

微信小程序商城怎么弄

微信小程序商城怎么弄?这是一个常见的问题,对于那些想要在微信上创建一个自己的商城的人来说。下面为您介绍一些基本的步骤和注意事项,帮助您轻松地创建一个微信小程序商城。首先,要创建一个微信小程序商城,您需要注册一个微信小程序账号并开通商户号。具体步骤如下:打开微信公众平台,点击右上角的“立即注册”,选择小程序账号,并按照提

【lesson8】gdb的介绍及使用

文章目录gdb的介绍什么是gdb?背景认识gdb的使用gcc/g++程序文件名-o将来生成的可执行程序名-ggdb调试命令gdb可执行程序名quitlistl0Enterr(run)b(breakpoint)+n(行号)infobd(delete)+断点编号n(next)p(printf)+变量名s(step)btfi

Python中的文件I/O操作:常见问题与解决方案

目录常见问题代码示例文件路径问题:文件权限问题:文件编码问题:文件读写模式问题:文件未关闭问题:大文件处理问题:文件读写过程中的异常处理问题:文件内容格式问题:跨平台换行符问题:二进制文件处理问题:总结在Python编程中,文件I/O操作是常见的任务之一。无论是读取文件内容、写入新数据还是追加信息,文件I/O操作都是不

客观题:Android基础【基础题】

一.单选题(共6题,60分)1.(单选题,10分)Android四层架构中,应用框架层使用的是什么语法?A.JavaB.AndroidC.CD.C++正确答案:AAndroid四层架构通常指的是Android应用的四个关键组件,包括:应用层(ApplicationLayer):这是最顶层的层次,包括用户界面(UI)和应

GaussDB技术解读系列:数据库迁移创新实践

近日,以“数智赋能共筑未来”为主题的第14届中国数据库技术大会(DTCC2023)在北京举行,在GaussDB“五高两易”核心技术,给世界一个更优选择的专场,华为云数据库生态工具研发总监窦德明分享了GaussDB数据库的迁移创新实践。本篇将分享GaussDB数据库迁移的创新实践。易迁移能力是企业数据库替换选型的关键考量

华硕电脑怎么录屏?分享实用录制经验!

“华硕电脑怎么录屏呀,刚买的笔记本电脑,是华硕的,自我感觉挺好用的,但是不知道怎么录屏,最近刚好要录一个教程,怎么都找不到在哪里录制,有人能教教我吗?”随着电脑技术的不断发展,人们越来越依赖电脑进行工作和娱乐。录屏功能作为电脑的一项重要功能,已经逐渐成为人们日常生活中的必备工具。华硕作为知名的电脑品牌,为用户提供了便捷

C语言——自定义类型结构体_学习笔记

结构体的基本概念结构体是一种用户自定义的数据类型,可以包含多个不同类型的变量。通过使用结构体,我们可以将相关联的数据组织在一起,便于管理和使用。结构体的声明正常的结构体声明在C语言中,结构体(struct)指的是一种数据结构,是C语言中聚合数据类型的一类。结构体可以包含多个不同类型的数据成员,例如:int、float、

linux的应用线程同步与驱动同步机制

同步机制在Linux应用程序和内核中的驱动程序中,有一些常见的同步机制用于实现线程或进程之间的同步和数据访问保护。下面是它们的一些主要机制:Linux应用程序中的同步机制:互斥锁(Mutex):用于保护共享资源,确保只有一个线程可以访问该资源。应用程序可以使用pthread_mutex_t类型的互斥锁,使用pthrea

爬虫入门基础:深入解析HTTP协议的工作过程

目录一、HTTP协议简介二、HTTP协议的工作过程三、请求方法与常见用途四、请求头与常见字段五、状态码与常见含义六、进阶话题和注意事项总结在如今这个数字化时代,互联网已经成为我们获取信息、交流和娱乐的主要渠道。而在互联网中,HTTP协议则扮演着至关重要的角色。HTTP,全称HypertextTransferProtoc

SpringMVC之JSR303和拦截器

目录一.JSR303二.JSR常用的注解三.JSR快速入门四.拦截器⭐⭐⭐拦截器和过滤器有什么不一样,或者它们的区别是什么??五.拦截器快速入门--登录的案例一.JSR303JSR303是Java规范的一部分,全称为BeanValidation框架。它定义了一组基于标注的验证注解,用于验证Java对象的属性值的合法性。

【Java 基础篇】ThreadPoolExecutor 详解

多线程编程是现代应用程序开发中的一个重要主题。为了更有效地管理和利用多线程资源,Java提供了丰富的线程池支持。ThreadPoolExecutor类是Java中用于创建和管理线程池的核心类之一,本文将详细介绍ThreadPoolExecutor的使用方法和原理。线程池的基本概念在深入探讨ThreadPoolExecu

热文推荐