首页 > 其他分享 >RepVGGBlock+GSConv+Bifpn+c2fca(模块融合)改进yolo5

RepVGGBlock+GSConv+Bifpn+c2fca(模块融合)改进yolo5

时间:2024-10-11 22:13:16浏览次数:1  
标签:kernel nn self rbr channels c2fca Bifpn yolo5 out

参考论文:Exploring the Close-Range Detection of UAV-Based Images on Pine Wilt Disease by an Improved Deep Learning Method

 

首先我们来介绍一下该文章使用的改进模块接着再来实现它

 

我们查看论文提出的repvggblock1和repvggblock2有什么区别:

 可以看到只是一个bn层的添加,但是在该论文的github上的开源文件里只有repvggblock1的模块结构,所以我们需要自己改动一下,把以下内容复制到models.commen(你也可以将原模块的BatchNorm2d替换为Identity)

 

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
def bn(in_channels, out_channels, kernel_size, stride, padding, groups=1):
    result = nn.Sequential()

    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 RepVGGBlock2(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(RepVGGBlock1, self).__init__()
        self.deploy = deploy
        self.groups = groups
        self.in_channels = in_channels


        assert kernel_size == 3

        assert padding == 1

        padding_11 = padding - kernel_size // 2

        self.nonlinearity = nn.ReLU()

        if use_se:
            #   Note that RepVGG-D2se uses SE before nonlinearity. But RepVGGplus models uses SE after nonlinearity.
            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_identity =nn.Identity() 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)
            # self.rbr_identity=None
            # self.rbr_bn= bn(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=stride, padding=padding, groups=groups)
            # print('RepVGG Block, identity = ', self.rbr_identity)
    def fusevggforward(self, x):
        # 将输入通过非线性操作和rbr_dense分支
        return self.nonlinearity(self.rbr_dense(x))

    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)
        # print(id_out)
        # print(id_out.shape)
        # print("input",inputs.size())
        # print("rbr_1x1",(self.rbr_1x1(inputs)).shape)
        # print("rbr_dense1",(self.rbr_dense(inputs)).shape)
        # print("self.se(self.rbr_dense(inputs) + self.rbr_1x1(inputs) + id_out)",(self.se(self.rbr_dense(inputs) + self.rbr_1x1(inputs) + id_out)).shape)
        # print("(self.nonlinearity(self.se(self.rbr_dense(inputs) + self.rbr_1x1(inputs) + id_out)))",((self.nonlinearity(self.se(self.rbr_dense(inputs) + self.rbr_1x1(inputs)  + id_out))).shape))
        return self.nonlinearity(self.se(self.rbr_dense(inputs) + self.rbr_1x1(inputs) + id_out))


    #   Optional. This may improve the accuracy and facilitates quantization in some cases.
    #   1.  Cancel the original weight decay on rbr_dense.conv.weight and rbr_1x1.conv.weight.
    #   2.  Use like this.
    #       loss = criterion(....)
    #       for every RepVGGBlock blk:
    #           loss += weight_decay_coefficient * 0.5 * blk.get_cust_L2()
    #       optimizer.zero_grad()
    #       loss.backward()
    def get_custom_L2(self):
        K3 = self.rbr_dense.conv.weight
        K1 = self.rbr_1x1.conv.weight
        t3 = (self.rbr_dense.bn.weight / ((self.rbr_dense.bn.running_var + self.rbr_dense.bn.eps).sqrt())).reshape(-1, 1, 1, 1).detach()
        t1 = (self.rbr_1x1.bn.weight / ((self.rbr_1x1.bn.running_var + self.rbr_1x1.bn.eps).sqrt())).reshape(-1, 1, 1, 1).detach()

        l2_loss_circle = (K3 ** 2).sum() - (K3[:, :, 1:2, 1:2] ** 2).sum()      # The L2 loss of the "circle" of weights in 3x3 kernel. Use regular L2 on them.
        eq_kernel = K3[:, :, 1:2, 1:2] * t3 + K1 * t1                           # The equivalent resultant central point of 3x3 kernel.
        l2_loss_eq_kernel = (eq_kernel ** 2 / (t3 ** 2 + t1 ** 2)).sum()        # Normalize for an L2 coefficient comparable to regular L2.
        return l2_loss_eq_kernel + l2_loss_circle



#   This func derives the equivalent kernel and bias in a DIFFERENTIABLE way.
#   You can get the equivalent kernel and bias at any time and do whatever you want,
    #   for example, apply some penalties or constraints during training, just like you do to the other models.
#   May be useful for quantization or pruning.
    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 switch_to_deploy(self):
        if hasattr(self, 'rbr_reparam'):
            return
        kernel, bias = self.get_equivalent_kernel_bias()
        self.rbr_reparam = nn.Conv2d(in_channels=self.rbr_dense.conv.in_channels, out_channels=self.rbr_dense.conv.out_channels,
                                     kernel_size=self.rbr_dense.conv.kernel_size, stride=self.rbr_dense.conv.stride,
                                     padding=self.rbr_dense.conv.padding, dilation=self.rbr_dense.conv.dilation, groups=self.rbr_dense.conv.groups, bias=True)
        self.rbr_reparam.weight.data = kernel
        self.rbr_reparam.bias.data = bias
        self.__delattr__('rbr_dense')
        self.__delattr__('rbr_1x1')
        if hasattr(self, 'rbr_identity'):
            self.__delattr__('rbr_identity')
        if hasattr(self, 'id_tensor'):
            self.__delattr__('id_tensor')
        self.deploy = True
View Code

 

下面是Gsconv(使用方式同repvggblock)

class GSConv(nn.Module):
    # GSConv https://github.com/AlanLi1997/slim-neck-by-gsconv
    def __init__(self, c1, c2, k=1, s=1, g=1, act=True):
        super().__init__()
        c_ = c2 // 2
        self.cv1 = Conv(c1, c_, k, s, None, g, 1, act)
        self.cv2 = Conv(c_, c_, 5, 1, None, c_, 1, act)

    def forward(self, x):
        x1 = self.cv1(x)
        x2 = torch.cat((x1, self.cv2(x1)), 1)
        # shuffle
        # y = x2.reshape(x2.shape[0], 2, x2.shape[1] // 2, x2.shape[2], x2.shape[3])
        # y = y.permute(0, 2, 1, 3, 4)
        # return y.reshape(y.shape[0], -1, y.shape[3], y.shape[4])

        b, n, h, w = x2.data.size()
        b_n = b * n // 2
        y = x2.reshape(b_n, 2, h * w)
        y = y.permute(1, 0, 2)
        y = y.reshape(2, -1, n // 2, h, w)

        return torch.cat((y[0], y[1]), 1)

 

Bifpn根据论文结构图,我们需要两种不同的bifpn(输入量的不同)(上同,但我们会看到后续操作有一点出入)

 

class BiFPN_Add2(nn.Module):
    def __init__(self, c1, c2):
        super(BiFPN_Add20, self).__init__()
        # 设置可学习参数 nn.Parameter的作用是:将一个不可训练的类型Tensor转换成可以训练的类型parameter
        # 并且会向宿主模型注册该参数 成为其一部分 即model.parameters()会包含这个parameter
        # 从而在参数优化的时候可以自动一起优化
        self.w = nn.Parameter(torch.ones(2, dtype=torch.float32), requires_grad=True)
        self.epsilon = 0.0001
        self.conv = nn.Conv2d(c1, c2, kernel_size=1, stride=1, padding=0)
        self.silu = nn.SiLU()

    def forward(self, x):
        w = self.w
        weight = w / (torch.sum(w, dim=0) + self.epsilon)
        return self.conv(self.silu(weight[0] * x[0] + weight[1] * x[1]))
# 三个分支add操作
class BiFPN_Add3(nn.Module):
    def __init__(self, c1, c2):
        super(BiFPN_Add3, self).__init__()
        self.w = nn.Parameter(torch.ones(3, dtype=torch.float32), requires_grad=True)
        self.epsilon = 0.0001
        self.conv = nn.Conv2d(c1, c2, kernel_size=1, stride=1, padding=0)
        self.silu = nn.SiLU()

    def forward(self, x):
        w = self.w
        weight = w / (torch.sum(w, dim=0) + self.epsilon)  # 将权重进行归一化
        # Fast normalized fusion
        return self.conv(self.silu(weight[0] * x[0] + weight[1] * x[1] + weight[2] * x[2]))

 

 最后是c2fca模块,根据内容及代码我们可以发现它将c2f和ca模块结合了一下(也就是我们所说的缝模块)

 

class C2f(nn.Module):
    # CSP Bottleneck with 2 convolutions
    def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super().__init__()
        self.c = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)
        self.cv2 = Conv((2 + n) * self.c, c2, 1)  # optional act=FReLU(c2)
        self.m = nn.ModuleList(Bottleneck_C2f(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))

    def forward(self, x):
        y = list(self.cv1(x).split((self.c, self.c), 1))
        y.extend(m(y[-1]) for m in self.m)
        return self.cv2(torch.cat(y, 1))
class CABottleneck(nn.Module):
    # Standard bottleneck
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5, ratio=32):  # ch_in, ch_out, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_, c2, 3, 1, g=g)
        self.add = shortcut and c1 == c2
        # self.ca=CoordAtt(c1,c2,ratio)
        self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
        self.pool_w = nn.AdaptiveAvgPool2d((1, None))
        mip = max(8, c1 // ratio)
        self.conv1 = nn.Conv2d(c1, mip, kernel_size=1, stride=1, padding=0)
        self.bn1 = nn.BatchNorm2d(mip)
        self.act = h_swish()
        self.conv_h = nn.Conv2d(mip, c2, kernel_size=1, stride=1, padding=0)
        self.conv_w = nn.Conv2d(mip, c2, kernel_size=1, stride=1, padding=0)

    def forward(self, x):
        x1 = self.cv2(self.cv1(x))
        n, c, h, w = x.size()
        # c*1*W
        x_h = self.pool_h(x1)
        # c*H*1
        # C*1*h
        x_w = self.pool_w(x1).permute(0, 1, 3, 2)
        y = torch.cat([x_h, x_w], dim=2)
        # C*1*(h+w)
        y = self.conv1(y)
        y = self.bn1(y)
        y = self.act(y)
        x_h, x_w = torch.split(y, [h, w], dim=2)
        x_w = x_w.permute(0, 1, 3, 2)
        a_h = self.conv_h(x_h).sigmoid()
        a_w = self.conv_w(x_w).sigmoid()
        out = x1 * a_w * a_h

        # out=self.ca(x1)*x1
        return x + out if self.add else out

class C2fCA(C2f):
    # C2f module with CABottleneck()
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
        super().__init__(c1, c2, n, shortcut, g, e)
        c_ = int(c2 * e)  # hidden channels
        self.m = nn.Sequential(*(CABottleneck(c_, c_,shortcut, g, e=1.0) for _ in range(n)))
View Code

 



接下来我们来到YOLO.py在大致28行处()中导入我们上面提到的模块,格式如下
from models.common import (
RepVGGBlock1,RepVGGBlock2,GSConv,C2fCA,BiFPN_Add2,BiFPN_Add3

 

在Class basemodel中添加以下函数

 def fuse(self):
        print('Fusing layers... ')
        for m in self.model.modules():
            if type(m) is RepVGGBlock2:
                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_()

                    # 替换原来的rbr_dense层为新的rbr_reparam层
                    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

                    # 删除SE模块
                    delattr(m, 'se')

                    # 更新模块的前向传播函数
                    m.forward = m.fusevggforward

            if isinstance(m, (Conv, DWConv)) and hasattr(m, 'bn'):
                # 融合Conv和BatchNorm层,更新Conv层
                m.conv = fuse_conv_and_bn(m.conv, m.bn)

                # 删除BatchNorm层
                delattr(m, 'bn')

                # 更新模块的前向传播函数
                m.forward = m.forward_fuse

        # 打印融合后的模型信息
        self.info()

        return self
        # --------------------------end repvgg & shuffle refuse--------------------------------
在def parse_model里的if m in(......)中添加我们的bifpn和c2fca并做如下添加

 

 if m in {BottleneckCSP, C3, C3TR, C3Ghost, C3x,Dense_C3,SCConv,C2fCA,C2f,  VoVGSCSP, VoVGSCSPC}:
                args.insert(2, n)  # number of repeats#将n插入到args的索引2位置
                n = 1
        elif m in [BiFPN_Add2, BiFPN_Add3]:
            c2 = max(ch[x] for x in f)
 

 最最后,我们掏出我们的yaml配置文件就可以啦

# YOLOv5 

标签:kernel,nn,self,rbr,channels,c2fca,Bifpn,yolo5,out
From: https://www.cnblogs.com/CD13R/p/18455277

相关文章

  • 爆改YOLOv8|利用BiFPN双向特征金字塔改进yolov8
    1,本文介绍BiFPN(BidirectionalFeaturePyramidNetwork)是一种增强特征金字塔网络(FPN)的方法,旨在改善多尺度特征融合。BiFPN的主要创新点包括:双向特征融合:与传统FPN仅在自下而上的方向进行特征融合不同,BiFPN引入了双向融合机制。它不仅从低层特征向高层传递信息,还从高层特征向......
  • YOLOv8改进 | Neck篇 | YOLOv8引入BiFPN双向特征金字塔网络
    1.BiFPN介绍摘要:模型效率在计算机视觉中变得越来越重要。在本文中,我们系统地研究了用于目标检测的神经网络架构设计选择,并提出了几个提高效率的关键优化。首先,我们提出了一种加权双向特征金字塔网络(BiFPN),它可以轻松快速地进行多尺度特征融合;其次,我们提出了一种复合缩放方法......
  • YOLOv8改进 | Neck | 添加双向特征金字塔BiFPN【含二次独家创新】
    ......
  • YOLOv9有效改进|加入CVPR2020的Bifpn。
    专栏介绍:YOLOv9改进系列|包含深度学习最新创新,助力高效涨点!!!一、论文摘要        Bifpn是RT-DETR中使用的特征提取模块。二、Bifpn模块详解 2.1模块简介       Bifpn: 重复加权双向特征金字塔网络 。本文用于替换YOLOv9中的FPN+PAN结构。三、 ......
  • 安装启动yolo5教程
    目录一、下载yolo5项目二、安装miniconda(建议不要安装在C盘)三、安装CUDA四、安装pytorch五、修改配置参数六、修改电脑参数七、启动项目 博主硬件:Windows10家庭中文版 一、下载yolo5项目GitHub-ultralytics/yolov5:YOLOv5......
  • yolo5纸张卡片顶点检测,实现任意倾斜角度较正
    https://blog.csdn.net/demm868/article/details/111087578向AI转型的程序员都关注了这个号????????????机器学习AI算法工程  公众号:datayx因为之前有在做一些规则卡片类的OCR识别任务,就难免会遇到这样的问题:用户上传的照片里卡片的角度是任意的,不规则的,或多或少都会存在不......
  • C#中使用yolo5进行目标检测(一)
    一:将PT模型文件转onnx:1、用到export.py安装依赖包pip3installonnxpip3installcoremltools==4.02、export.py中的配置文件路径 模型类型改好后直接点击运行,生成模型:  ---------------------------------------------------------------------------------------......
  • yolo5使用gpu时遇到的问题记录
    一、问题描述:1、训练的时候提示不支持gpu2、使用如下命令检查为Falseimporttorchtorch.cuda.is_available()二、原因:pytorch版本的问题 三、解决办法: 重新安......
  • 50、ubuntu18.04&20.04+CUDA11.1+cudnn11.3+TensorRT7.2+Deepsteam5.1+vulkan环境搭建
    基本思想:想学习一下TensorRT的使用,随笔记录一下;链接:https://pan.baidu.com/s/1uFOktdF-bHcDDsufIqmNSA 提取码:k55w 复制这段内容后打开百度网盘手机App,操作更方便哦记录......