YOLOv5改进 | Neck | 有效提升小目标检测效果【小白必备】

 秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转


💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡


专栏目录: 《YOLOv5入门 + 改进涨点》专栏介绍 & 专栏目录 |目前已有60+篇内容,内含各种Head检测头、损失函数Loss、Backbone、Neck、NMS等创新点改进


实时目标检测对于工业和研究领域都至关重要。在边缘设备上,巨大的模型难以满足实时检测的需求,而由大量深度可分离卷积构建的轻量级模型无法达到足够的准确度。本文介绍一种新的轻量级卷积技术,GSConv以减轻模型的同时保持准确度。GSConv在准确度与速度之间实现了极佳的权衡。此外,我们基于GSConv提供了一个设计建议,即Slim-Neck(SNs),以实现实时检测器更高的计算成本效益。文章在介绍主要的原理后,将手把手教学如何进行模块的代码添加和修改,并将修改后的完整代码放在文章的最后,方便大家一键运行,小白也可轻松上手实践。以帮助您更好地学习深度学习目标检测YOLO系列的挑战。   

专栏地址 YOLOv5改进+入门——持续更新各种有效涨点方法 点击即可跳转    

目录

1. 原理

2.  将Slim-Neck添加到YOLOv5中

2.1 Slim-Neck的代码实现

2.2 新增yaml文件

2.3 注册模块

2.4 执行程序

3. 完整代码分享

4. GFLOPs

5. 进阶

6. 总结


1. 原理

论文地址:Slim-neck by GSConv: A lightweight-design for real-time detector architectures——点击即可跳转

官方代码:官方代码仓库——点击即可跳转 

Slim-Neck (SNs) 是一种设计策略,旨在提高实时物体检测器的效率和准确性,特别是在计算资源有限的边缘设备上。这种方法的关键组成部分是 GSConv(Group Shuffle Convolution),旨在平衡计算成本和模型精度之间的权衡。

Slim-Neck 的主要原理和细节:

1. GSConv(Group Shuffle Convolution)

GSConv 是为提高轻量级卷积模型的性能而引入的核心技术。它通过结合 shuffling 机制,结合了标准卷积 (SC) 和深度可分离卷积 (DSC) 的优点:

  • 标准卷积 (SC):保持通道密集连接,保留更详细的特征表示,但计算成本更高。

  • 深度可分离卷积 (DSC):通过分离空间和通道计算来降低计算成本,但会牺牲一些特征表示能力。

GSConv 旨在通过以下方式保留 SC 和 DSC 的优势:

  • 对部分输入通道执行标准卷积。

  • 对剩余通道执行深度卷积。

  • 打乱并连接输出以混合两种卷积类型的信息。

2. 细颈设计 (SN)

细颈设计将 GSConv 应用于对象检测架构的“颈部”部分:

  • 主干:从输入图像中提取特征。

  • 颈部:融合来自不同层的特征(通常使用 FPN - 特征金字塔网络等技术)。

  • 头部:预测边界框和类别分数。

通过在颈部使用 GSConv,模型可以实现更好的特征融合,同时降低计算成本,因为颈部处理连接的特征图,其中 GSConv 的 shuffling 机制的优势最大化。这种设计最大限度地减少了 DSC 的负面影响,同时充分利用了其效率,使其适合边缘设备上的实时应用。

3. 使用 GSConv 的 Slim-Neck 的优势

  • 提高准确率:通过有效地结合密集和稀疏卷积运算,GSConv 增强了特征表示,从而提高了物体检测的准确性。

  • 计算效率:与始终使用标准卷积相比,GSConv 显着减少了参数数量和浮点运算 (FLOP),在速度和性能之间取得了良好的平衡。

  • 灵活性:GSConv 中的 shuffling 机制使其可以轻松集成到各种卷积神经网络架构中,而无需进行重大更改。

4. 实施和结果

使用 GSConv 的 Slim-Neck 设计的有效性已在多个比较实验中得到有力证明:

  • 结合 SN 的实时检测器实现了最先进的性能,速度和准确性显著提高(在 Tesla T4 上,SODA10M 数据集上 AP50 为 70.9%,速度约为 100 FPS)。

总之,使用 GSConv 的 Slim-Neck 设计代表了轻量级高效实时物体检测模型开发的重大进步,特别适合部署在计算资源有限的边缘设备上。该方法通过巧妙的改组机制充分利用标准和深度可分离卷积的优势,以保持高精度,同时降低计算需求。

2.  将Slim-Neck添加到YOLOv5中

2.1 Slim-Neck的代码实现

关键步骤一: 在yolov5/models/下面新建slim-neck.py并粘贴下面的代码

import torch
import torch.nn as nn
import math
 
 

def autopad(k, p=None, d=1):
    """
    Pads kernel to 'same' output shape, adjusting for optional dilation; returns padding size.

    `k`: kernel, `p`: padding, `d`: dilation.
    """
    if d > 1:
        k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k]  # actual kernel-size
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # auto-pad
    return p


class Conv(nn.Module):
    # Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)
    default_act = nn.SiLU()  # default activation

    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
        """Initializes a standard convolution layer with optional batch normalization and activation."""
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()

    def forward(self, x):
        """Applies a convolution followed by batch normalization and an activation function to the input tensor `x`."""
        return self.act(self.bn(self.conv(x)))

    def forward_fuse(self, x):
        """Applies a fused convolution and activation function to the input tensor `x`."""
        return self.act(self.conv(x))

    
 
class DWConv(Conv):
    """Depth-wise convolution."""
 
    def __init__(self, c1, c2, k=1, s=1, d=1, act=True):  # ch_in, ch_out, kernel, stride, dilation, activation
        """Initialize Depth-wise convolution with given parameters."""
        super().__init__(c1, c2, k, s, g=math.gcd(c1, c2), d=d, act=act)
 
class GSConv(nn.Module):
    # GSConv https://github.com/AlanLi1997/slim-neck-by-gsconv
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
        super().__init__()
        c_ = c2 // 2
        self.cv1 = Conv(c1, c_, k, s, p, g, d, Conv.default_act)
        self.cv2 = Conv(c_, c_, 5, 1, p, c_, d, Conv.default_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.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)
 
class GSConvns(GSConv):
    # GSConv with a normative-shuffle https://github.com/AlanLi1997/slim-neck-by-gsconv
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):
        super().__init__(c1, c2, k, s, p, g, act=True)
        c_ = c2 // 2
        self.shuf = nn.Conv2d(c_ * 2, c2, 1, 1, 0, bias=False)
 
    def forward(self, x):
        x1 = self.cv1(x)
        x2 = torch.cat((x1, self.cv2(x1)), 1)
        # normative-shuffle, TRT supported
        return nn.ReLU()(self.shuf(x2))
 
class GSBottleneck(nn.Module):
    # GS Bottleneck https://github.com/AlanLi1997/slim-neck-by-gsconv
    def __init__(self, c1, c2, k=3, s=1, e=0.5):
        super().__init__()
        c_ = int(c2*e)
        # for lighting
        self.conv_lighting = nn.Sequential(
            GSConv(c1, c_, 1, 1),
            GSConv(c_, c2, 3, 1, act=False))
        self.shortcut = Conv(c1, c2, 1, 1, act=False)
 
    def forward(self, x):
        return self.conv_lighting(x) + self.shortcut(x)
 
class GSBottleneckns(GSBottleneck):
    # GS Bottleneck https://github.com/AlanLi1997/slim-neck-by-gsconv
    def __init__(self, c1, c2, k=3, s=1, e=0.5):
        super().__init__(c1, c2, k, s, e)
        c_ = int(c2*e)
        # for lighting
        self.conv_lighting = nn.Sequential(
            GSConvns(c1, c_, 1, 1),
            GSConvns(c_, c2, 3, 1, act=False))
        
class GSBottleneckC(GSBottleneck):
    # cheap GS Bottleneck https://github.com/AlanLi1997/slim-neck-by-gsconv
    def __init__(self, c1, c2, k=3, s=1):
        super().__init__(c1, c2, k, s)
        self.shortcut = DWConv(c1, c2, k, s, act=False)
 
class VoVGSCSP(nn.Module):
    # VoVGSCSP module with GSBottleneck
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c1, c_, 1, 1)
        self.gsb = nn.Sequential(*(GSBottleneck(c_, c_, e=1.0) for _ in range(n)))
        self.res = Conv(c_, c_, 3, 1, act=False)
        self.cv3 = Conv(2 * c_, c2, 1)
 
    def forward(self, x):
        x1 = self.gsb(self.cv1(x))
        y = self.cv2(x)
        return self.cv3(torch.cat((y, x1), dim=1))
 
class VoVGSCSPns(VoVGSCSP):
    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.gsb = nn.Sequential(*(GSBottleneckns(c_, c_, e=1.0) for _ in range(n)))
 
class VoVGSCSPC(VoVGSCSP):
    # cheap VoVGSCSP module with GSBottleneck
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
        super().__init__(c1, c2)
        c_ = int(c2 * 0.5)  # hidden channels
        self.gsb = GSBottleneckC(c_, c_, 1, 1)

Slim-Neck处理图像的主要流程包括以下几个关键步骤:

1. 输入图像

首先,将输入图像送入模型的骨干网络(backbone)进行特征提取。骨干网络通常是预训练的卷积神经网络(CNN),如ResNet、MobileNet等。

2. 特征提取

骨干网络提取不同层次的特征图。通常,较低层的特征图包含更多的空间细节,而较高层的特征图包含更多的语义信息。

3. 特征融合

在特征提取之后,进入Slim-Neck设计的关键部分:

  • GSConv(组通道卷积):将来自骨干网络的特征图输入到GSConv层。GSConv在部分通道上执行标准卷积,在剩余通道上执行深度卷积,然后将这两部分的输出进行通道混合(shuffle)和连接。

  • 特征金字塔网络(FPN):GSConv处理后的特征图通过FPN进行进一步融合,以便在不同尺度上检测物体。

4. 多尺度特征融合

FPN通过自顶向下的路径和横向连接将高层特征图逐步上采样并与低层特征图融合,生成一系列多尺度特征图。这些多尺度特征图包含丰富的空间和语义信息,有助于检测不同大小的目标。

5. 目标检测头

将多尺度特征图输入到目标检测头(head)。检测头通常包含分类分支和回归分支:

  • 分类分支:预测每个候选区域属于每个类别的概率。

  • 回归分支:预测每个候选区域的边界框坐标。

6. 非极大值抑制(NMS)

对检测头输出的候选区域进行后处理,应用非极大值抑制(NMS)去除冗余的检测框,只保留最高置信度的框。

7. 输出结果

最终,模型输出处理后的目标检测结果,包括目标的类别和位置。

通过这个流程,Slim-Neck设计能够在保证高检测精度的同时,大幅降低计算成本,使其适用于实时目标检测任务,特别是在计算资源有限的边缘设备上。

2.2 新增yaml文件

关键步骤二在下/yolov5/models下新建文件 yolov5_slimneck.yaml并将下面代码复制进去

  •  目标检测yaml文件 
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# Slim-neck by GSConv: A better design paradigm of detector architectures for autonomous vehicle
# Parameters
nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.25  # layer channel multiple
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 v6.0 backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 6, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, SPPF, [1024, 5]],  # 9
  ]

# YOLOv5 v6.0 head
head:
  [[-1, 1, GSConv, [512, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, VoVGSCSP, [512, False]],  # 13

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

   [-1, 1, GSConv, [256, 3, 2]],
   [[-1, 14], 1, Concat, [1]],  # cat head P4
   [-1, 3, VoVGSCSP, [512, False]],  # 20 (P4/16-medium)

   [-1, 1, GSConv, [512, 3, 2]],
   [[-1, 10], 1, Concat, [1]],  # cat head P5
   [-1, 3, VoVGSCSP, [1024, False]],  # 23 (P5/32-large)

   [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]
  • 语义分割yaml文件
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# Slim-neck by GSConv: A better design paradigm of detector architectures for autonomous vehicle
# Parameters
nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.25  # layer channel multiple
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 v6.0 backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 6, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, SPPF, [1024, 5]],  # 9
  ]

# YOLOv5 v6.0 head
head:
  [[-1, 1, GSConv, [512, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, VoVGSCSP, [512, False]],  # 13

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

   [-1, 1, GSConv, [256, 3, 2]],
   [[-1, 14], 1, Concat, [1]],  # cat head P4
   [-1, 3, VoVGSCSP, [512, False]],  # 20 (P4/16-medium)

   [-1, 1, GSConv, [512, 3, 2]],
   [[-1, 10], 1, Concat, [1]],  # cat head P5
   [-1, 3, VoVGSCSP, [1024, False]],  # 23 (P5/32-large)

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

温馨提示:本文只是对yolov5基础上添加模块,如果要对yolov5n/l/m/x进行添加则只需要指定对应的depth_multiple 和 width_multiple。


# YOLOv5n
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.25  # layer channel multiple
 
# YOLOv5s
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # layer channel multiple
 
# YOLOv5l 
depth_multiple: 1.0  # model depth multiple
width_multiple: 1.0  # layer channel multiple
 
# YOLOv5m
depth_multiple: 0.67  # model depth multiple
width_multiple: 0.75  # layer channel multiple
 
# YOLOv5x
depth_multiple: 1.33  # model depth multiple
width_multiple: 1.25  # layer channel multiple

2.3 注册模块

关键步骤三在yolo.py的parse_model函数中注册 添加“GSConv, VoVGSCSP, VoVGSCSPC",

2.4 执行程序

在train.py中,将cfg的参数路径设置为yolov5_slimneck.yaml的路径

建议大家写绝对路径,确保一定能找到

🚀运行程序,如果出现下面的内容则说明添加成功🚀 

  0                -1  1      7040  models.common.Conv                      [3, 64, 6, 2, 2]
  1                -1  1     73984  models.common.Conv                      [64, 128, 3, 2]
  2                -1  3    156928  models.common.C3                        [128, 128, 3]
  3                -1  1    295424  models.common.Conv                      [128, 256, 3, 2]
  4                -1  6   1118208  models.common.C3                        [256, 256, 6]
  5                -1  1   1180672  models.common.Conv                      [256, 512, 3, 2]
  6                -1  9   6433792  models.common.C3                        [512, 512, 9]
  7                -1  1   4720640  models.common.Conv                      [512, 1024, 3, 2]
  8                -1  3   9971712  models.common.C3                        [1024, 1024, 3]
  9                -1  1   2624512  models.common.SPPF                      [1024, 1024, 5]
 10                -1  1    269568  models.slim_neck.GSConv                 [1024, 512, 1, 1]
 11                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, 'nearest']
 12           [-1, 6]  1         0  models.common.Concat                    [1]
 13                -1  3   2582272  models.slim_neck.VoVGSCSP               [1024, 512, 3, False]
 14                -1  1     69248  models.slim_neck.GSConv                 [512, 256, 1, 1]
 15                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, 'nearest']
 16           [-1, 4]  1         0  models.common.Concat                    [1]
 17                -1  3    652160  models.slim_neck.VoVGSCSP               [512, 256, 3, False]
 18                -1  1    298624  models.slim_neck.GSConv                 [256, 256, 3, 2]
 19          [-1, 14]  1         0  models.common.Concat                    [1]
 20                -1  3   2320128  models.slim_neck.VoVGSCSP               [512, 512, 3, False]
 21                -1  1   1187072  models.slim_neck.GSConv                 [512, 512, 3, 2]
 22          [-1, 10]  1         0  models.common.Concat                    [1]
 23                -1  3   9227776  models.slim_neck.VoVGSCSP               [1024, 1024, 3, False]        
 24      [17, 20, 23]  1    457725  Detect                                  [80, [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]], [256, 512, 1024]]
YOLOv5_slimneck summary: 557 layers, 43647485 parameters, 43647485 gradients, 97.6 GFLOPs

3. 完整代码分享

https://pan.baidu.com/s/1nQ0WyyAdBQycAo1qxZwt6w?pwd=325h

 提取码: 325h 

4. GFLOPs

关于GFLOPs的计算方式可以查看百面算法工程师 | 卷积基础知识——Convolution

未改进的GFLOPs

img

改进后的GFLOPs

现在手上没有卡了,等过段时候有卡了把这补上,需要的同学自己测一下

5. 进阶

可以结合损失函数或者卷积模块进行多重改进

YOLOv5改进 | 损失函数 | EIoU、SIoU、WIoU、DIoU、FocuSIoU等多种损失函数——点击即可跳转

6. 总结

Slim-Neck的主要原理是通过引入GSConv(组通道卷积)技术,优化轻量级实时目标检测模型的性能。GSConv结合了标准卷积(SC)和深度可分离卷积(DSC)的优点,通过在部分输入通道上执行标准卷积,在剩余通道上执行深度卷积,然后将输出进行混合和连接,实现了通道信息的充分融合。Slim-Neck设计将GSConv应用于检测器的“颈部”部分,这部分负责融合不同层的特征。通过这种方式,Slim-Neck在保持高特征表示能力的同时,大幅降低了计算成本,达到了精度和速度之间的最佳平衡。该方法在多项实验中表现出色,显著提高了模型的实时检测能力,非常适合资源受限的边缘设备部署。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

kay_545

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

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

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

打赏作者

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

抵扣说明:

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

余额充值