秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转
💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡
专栏目录: 《YOLOv5入门 + 改进涨点》专栏介绍 & 专栏目录 |目前已有60+篇内容,内含各种Head检测头、损失函数Loss、Backbone、Neck、NMS等创新点改进
实时目标检测对于工业和研究领域都至关重要。在边缘设备上,巨大的模型难以满足实时检测的需求,而由大量深度可分离卷积构建的轻量级模型无法达到足够的准确度。本文介绍一种新的轻量级卷积技术,GSConv,以减轻模型的同时保持准确度。GSConv在准确度与速度之间实现了极佳的权衡。此外,我们基于GSConv提供了一个设计建议,即Slim-Neck(SNs),以实现实时检测器更高的计算成本效益。文章在介绍主要的原理后,将手把手教学如何进行模块的代码添加和修改,并将修改后的完整代码放在文章的最后,方便大家一键运行,小白也可轻松上手实践。以帮助您更好地学习深度学习目标检测YOLO系列的挑战。
目录
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
改进后的GFLOPs
现在手上没有卡了,等过段时候有卡了把这补上,需要的同学自己测一下
5. 进阶
可以结合损失函数或者卷积模块进行多重改进
YOLOv5改进 | 损失函数 | EIoU、SIoU、WIoU、DIoU、FocuSIoU等多种损失函数——点击即可跳转
6. 总结
Slim-Neck的主要原理是通过引入GSConv(组通道卷积)技术,优化轻量级实时目标检测模型的性能。GSConv结合了标准卷积(SC)和深度可分离卷积(DSC)的优点,通过在部分输入通道上执行标准卷积,在剩余通道上执行深度卷积,然后将输出进行混合和连接,实现了通道信息的充分融合。Slim-Neck设计将GSConv应用于检测器的“颈部”部分,这部分负责融合不同层的特征。通过这种方式,Slim-Neck在保持高特征表示能力的同时,大幅降低了计算成本,达到了精度和速度之间的最佳平衡。该方法在多项实验中表现出色,显著提高了模型的实时检测能力,非常适合资源受限的边缘设备部署。