【YOLOv5改进系列(4)】高效涨点----添加可变形卷积DCNv2

在这里插入图片描述



在这里插入图片描述

👀🎉📜系列文章目录

【YOLOv5改进系列(1)】高效涨点----使用EIoU、Alpha-IoU、SIoU、Focal-EIOU替换CIou
【YOLOv5改进系列(2)】高效涨点----Wise-IoU详细解读及使用Wise-IoU(WIOU)替换CIOU
【YOLOv5改进系列(3)】高效涨点----Optimal Transport Assignment:OTA最优传输方法

🚀🚀🚀前言

目前可变形卷积一共有4个版本,分别是DCNv1~DCNv4,在本文中使用的是带有可变形卷积DCNv2的方法替换yolov5-v6.0版本中的C3模块。本次使用的数据集是热轧钢带的六种典型表面缺陷数据集,通过在backbone的不同位置添加可变形卷积进行训练,经过实验测试结果,最后的map@0.5提高了近5个百分点。


一、1️⃣ 什么是可变形卷积

🚀概念
可变形卷积指卷积核在每个元素上增加了一个参数方向参数,这样卷积核就能在训练过程中扩展到很大的范围。它允许卷积核在空间上进行动态调整,以适应目标对象的形变或者复杂结构。

传统的卷积操作是固定的,卷积核的权重在整个图像上是不变的,而可变形卷积则引入了额外的参数,用于控制卷积核的采样位置。这些额外的参数通常是由一个学习模块来学习得到的,通常称为偏移量(offset)。在进行可变形卷积时,卷积核的采样位置会根据输入特征图的内容进行动态调整,从而实现对目标对象形状的适应性。

🔥为什么要使用可变形卷积?
卷积核的目的是为了提取输入物的特征。传统的卷积核通常是固定尺寸、固定大小的。这种卷积核存在的最大问题就是,对于未知的变化适应性差,泛化能力差。网络内部缺乏能够解决这些问题的模块,这会产生显著的问题,同一 CNN 层的激活单元的感受野尺寸都相同,这对于编码位置信息的浅层神经网络并不可取,因为不同的位置可能对应有不同尺度或者不同形变的物体,这些层需要能够自动调整尺度或者感受野的方法。再比如,目标检测虽然效果很好但是都依赖于基于特征提取的边界框,这并不是最优的方法,尤其是对于非网格状的物体而言。

✨因此,希望卷积核可以根据实际情况调整本身的形状,更好的提取输入的特征。

  • 更好的感受野覆盖:由于卷积核的采样位置可以自适应地调整,因此可变形卷积可以更好地适应目标对象的形状和结构,从而提高感受野的覆盖能力。
  • 对目标形变的鲁棒性:可变形卷积可以处理目标对象形变的情况,例如目标的畸变、变形或者姿态变化等,从而提高了模型的鲁棒性和泛化能力。
  • 更强的表达能力:通过引入额外的偏移参数,可变形卷积可以增加模型的表达能力,使其能够更好地捕捉目标对象的细微特征和结构信息。

二、2️⃣如何在yolov5中添加DCNv2模块

2.1 🎓 修改common.py模块

找到models文件下的common.py文件,将鼠标定位到该文件的最后一行代码。

在这里插入图片描述
📌将下面修改的的可变形卷积DCNv2模块代码粘贴到最后一行,这段代码的作用就是构建带有DCNv2的C3模块

# ====================添加DCN V2=====================
class DCNv2(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1,
                 padding=1, dilation=1, groups=1, deformable_groups=1):
        super(DCNv2, self).__init__()

        self.in_channels = in_channels
        self.out_channels = out_channels
        self.kernel_size = (kernel_size, kernel_size)
        self.stride = (stride, stride)
        self.padding = (padding, padding)
        self.dilation = (dilation, dilation)
        self.groups = groups
        self.deformable_groups = deformable_groups

        self.weight = nn.Parameter(
            torch.empty(out_channels, in_channels, *self.kernel_size)
        )
        self.bias = nn.Parameter(torch.empty(out_channels))

        out_channels_offset_mask = (self.deformable_groups * 3 *
                                    self.kernel_size[0] * self.kernel_size[1])
        self.conv_offset_mask = nn.Conv2d(
            self.in_channels,
            out_channels_offset_mask,
            kernel_size=self.kernel_size,
            stride=self.stride,
            padding=self.padding,
            bias=True,
        )
        self.bn = nn.BatchNorm2d(out_channels)
        self.act = Conv.default_act
        self.reset_parameters()

    def forward(self, x):
        offset_mask = self.conv_offset_mask(x)
        o1, o2, mask = torch.chunk(offset_mask, 3, dim=1)
        offset = torch.cat((o1, o2), dim=1)
        mask = torch.sigmoid(mask)
        x = torch.ops.torchvision.deform_conv2d(
            x,
            self.weight,
            offset,
            mask,
            self.bias,
            self.stride[0], self.stride[1],
            self.padding[0], self.padding[1],
            self.dilation[0], self.dilation[1],
            self.groups,
            self.deformable_groups,
            True
        )
        x = self.bn(x)
        x = self.act(x)
        return x

    def reset_parameters(self):
        n = self.in_channels
        for k in self.kernel_size:
            n *= k
        std = 1. / math.sqrt(n)
        self.weight.data.uniform_(-std, std)
        self.bias.data.zero_()
        self.conv_offset_mask.weight.data.zero_()
        self.conv_offset_mask.bias.data.zero_()


class Bottleneck_DCN(nn.Module):
    # Standard bottleneck
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = DCNv2(c_, c2, 3, 1, groups=g)
        self.add = shortcut and c1 == c2

    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))


class C3_DCN(C3):
    #  带有DCNv2的C3模块
    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)
        self.m = nn.Sequential(*(Bottleneck_DCN(c_, c_, shortcut, g, e=1.0) for _ in range(n)))

2.2 ✨修改yolo.py文件

📌首先找到models文件夹下的yolo.py,在该文件中找到parse_model网络解析函数,大概是在第300行左右。

在这里插入图片描述
📌在parse_model函数中需要在两处添加C3_DCN(也就是带有DCN的C3模块),添加位置如下:

在这里插入图片描述

2.3 ⭐️修改yolov5s.yaml文件

🚀找到models文件夹下的yolov5s.yaml配置文件,前面我们已经定义好C3_DCN模块,接下来就是需要将可变形卷积替换掉原始的C3模块。
☀️温馨提示:看过很多论文添加可变形卷积,一般是在backbone特征提取部分添加,如果有兴趣也可以在Neck特征融合部分添加可变形卷积。
我是将backbone的特征金字塔特征融合输出的三个部分C3模块替换成了C3_DCN可变形卷积,我添加的比较多,训练过程中的参数量也会大大增加,所以会增加训练时间,如果不想训练速度太慢,也可以只替换最后一个C3模块。
在这里插入图片描述

# YOLOv5 v6.0 backbone
backbone:
  # [from, number, module, args] 输出640*640
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2 320*320    64表示输出的channel,但是需要乘以width_multiple,6表示卷积核的大小,2表示padding大小,2表示stride大小
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4   160*160*128      2表示stride大小
   [-1, 3, C3, [128]],

   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8   80*80*256
   [-1, 6, C3_DCN, [256]],

   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16   40*40*512
   [-1, 9, C3_DCN, [512]],

   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32   20*20
   [-1, 3, C3_DCN, [1024]],

   [-1, 1, SPPF, [1024, 5]],  # 9
  ]

2.4 🎯训练可能报错结果

在将C3模块替换成C3_DCN模块之后可能会报一个错误,也是我遇到的,报错内容如:

Variable._execution_engine.run_backward( # Calls into the C++ engine to run the backward pass RuntimeError: adaptive_max_pool2d_backward_cuda does not have a deterministic implementation, but you set ‘torch.use_deterministic_algorithms(True)’. You can turn off determinism just for this operation, or you can use the ‘warn_only=True’ option

如果出现这个错误可以通过寻找报错来源可以通过修改train.py文件中的

init_seeds(opt.seed + 1 + RANK, deterministic=True)

将True改成False

init_seeds(opt.seed + 1 + RANK, deterministic=False)
  • opt.seed + 1 + RANK的目的是使每个进程具有不同的随机种子。通过在种子值中添加进程排名,可以确保每个进程使用不同的种子,从而生成独立的随机序列。
  • deterministic=True参数的作用是启用确定性算法

⚡️目前我也不知道为什么修改初始化随机种子就能解决这个问题。有大佬了解的可以评论区讨论。

三、3️⃣DCNv2实验结果

3.1 🎓 正常训练

F1置信度分数为0.71、map@0.5=0.779
在这里插入图片描述

3.2 ✨修改实验1

修改内容:只修改backbone网络中的最后一个C3模块,将其替换成C3_DCN。

backbone:
  # [from, number, module, args] 输出640*640
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2 320*320    64表示输出的channel,但是需要乘以width_multiple,6表示卷积核的大小,2表示padding大小,2表示stride大小
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4   160*160*128      2表示stride大小
   [-1, 3, C3, [128]],

   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8   80*80*256
   [-1, 6, C3, [256]],

   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16   40*40*512
   [-1, 9, C3, [512]],

   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32   20*20
   [-1, 3, C3_DCN, [1024]],

   [-1, 1, SPPF, [1024, 5]],  # 9
  ]

实验结果:F1置信度分数为0.76、map@0.5=0.801,两个评估指标都有所增长。

在这里插入图片描述

3.3 ⭐️修改实验2

修改内容:只修改backbone网络中的第2个和最后一个C3模块,将其替换成C3_DCN。

backbone:
  # [from, number, module, args] 输出640*640
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2 320*320    64表示输出的channel,但是需要乘以width_multiple,6表示卷积核的大小,2表示padding大小,2表示stride大小
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4   160*160*128      2表示stride大小
   [-1, 3, C3, [128]],

   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8   80*80*256
   [-1, 6, C3_DCN, [256]],

   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16   40*40*512
   [-1, 9, C3, [512]],

   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32   20*20
   [-1, 3, C3_DCN, [1024]],

   [-1, 1, SPPF, [1024, 5]],  # 9
  ]

实验结果F1置信度分数为0.76、map@0.5=0.819,两个评估指标都有所增长。

在这里插入图片描述

3.4 🎯修改实验3

修改内容:将backbone的特征金字塔特征融合输出的三个部分C3模块替换成了C3_DCN可变形卷积

backbone:
  # [from, number, module, args] 输出640*640
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2 320*320    64表示输出的channel,但是需要乘以width_multiple,6表示卷积核的大小,2表示padding大小,2表示stride大小
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4   160*160*128      2表示stride大小
   [-1, 3, C3, [128]],

   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8   80*80*256
   [-1, 6, C3_DCN, [256]],

   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16   40*40*512
   [-1, 9, C3_DCN, [512]],

   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32   20*20
   [-1, 3, C3_DCN, [1024]],

   [-1, 1, SPPF, [1024, 5]],  # 9
  ]

实验结果F1置信度分数为0.75、map@0.5=0.818,两个评估指标都有所增长。相较于修改实验2,这一次训练时间大大增加,但是评估指标并没有明显增加。
在这里插入图片描述


在这里插入图片描述

  • 21
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

慕溪同学

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

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

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

打赏作者

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

抵扣说明:

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

余额充值