RT-DETR改进策略【Neck】| GSConv+Slim Neck:混合深度可分离卷积和标准卷积的轻量化网络设计
一、本文介绍
本文记录的是
利用
GsConv
优化
RT-DETR
的颈部网络
。
深度可分离卷积(DSC)
在轻量级模型中被广泛使用,但其在计算过程中会分离输入图像的通道信息,导致特征表示能力明显低于
标准卷积(SC)
,而
GsConv
采用混合策略,使
DSC
的输出通过打乱特征更接近
SC
,从而优化模型的性能。本文利用
GsConv+Slim Neck
改进
RT-DETR
的颈部网络,
使其在提升特征表示能力的同时降低计算成本和内存占用。
二、GsConv介绍
Slim-neck by GSConv
: 实时检测器架构的轻量级设计
GsConv
是一种新的轻量级卷积技术,其设计原理、优势如下:
2.1、设计原理
-
为了减轻深度神经网络的高计算成本,许多轻量级模型使用
深度可分离卷积(DSC)来减少参数和浮点运算(FLOPs),但深度可分离卷积的缺点是在计算过程中分离了输入图像的通道信息,导致特征表示能力低于标准卷积(SC)。 -
为了缓解
DSC的固有缺陷,GsConv采用了一种混合策略,通过对SC和DSC生成的特征进行打乱(shuffle),使DSC的输出尽可能接近SC。具体来说,GsConv使用SC(通道密集卷积)生成的特征渗透到DSC生成的特征的每一部分,通过均匀混合来允许SC的信息充分混合到深度可分离卷积的输出中,从而尽可能地保留特征之间的隐藏连接。
Slim-neck
结构:
2.2、优势
-
精度提升
:通过添加
DSC层和打乱操作,增强了非线性表达能力,从而使轻量级卷积的表示能力尽可能接近SC,在精度上有显著提升。 -
计算成本降低
:
GSConv在保持较低时间复杂度的情况下,能以更少的计算成本捕获更多的空间和通道特征。 -
适应性强
:
GSConv灵活且易于适应,可根据需要添加简单的辅助分支来完成特定设计,进一步扩展其应用范围。例如,可以添加坐标编码辅助分支来优化检测精度,或者在辅助分支上使用大核大小的DSC来解决浅网络难以捕获足够感受野的问题。
论文: https://arxiv.org/pdf/2206.02424
源码: https://github.com/AlanLi1997/Slim-neck-by-GSConv
三、GSConv的实现代码
GSConv模块
的实现代码如下:
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)
四、添加步骤
4.1 改进点
模块改进方法
:加入
GSConv
模块和
VoVGSCSP
模块。
添加后如下:
注意❗:在
5.2和5.3小节
中需要声明的模块名称为:
GSConv
和
VoVGSCSP
。
五、添加步骤
5.1 修改ultralytics/nn/modules/block.py
此处需要修改的文件是
ultralytics/nn/modules/block.py
block.py中定义了网络结构的通用模块
,我们想要加入新的模块就只需要将模块代码放到这个文件内即可。
将
GSConv
和
VoVGSCSP
模块代码添加到此文件下。
5.2 修改ultralytics/nn/modules/ init .py
此处需要修改的文件是
ultralytics/nn/modules/__init__.py
__init__.py
文件中定义了所有模块的初始化,我们只需要将
block.py
中的新的模块命添加到对应的函数即可。
GSConv
和
VoVGSCSP
在
block.py
中实现,所有要添加在
from .block import
:
from .block import (
C1,
C2,
...
GSConv,
VoVGSCSP
)
5.3 修改ultralytics/nn/modules/tasks.py
在
tasks.py
文件中,需要在两处位置添加各模块类名称。
首先:在函数声明中引入
GSConv
和
VoVGSCSP
其次:在
parse_model函数
中注册
GSConv
和
VoVGSCSP
模块
六、yaml模型文件
6.1 模型改进⭐
在代码配置完成后,配置模型的YAML文件。
此处以
ultralytics/cfg/models/rt-detr/rt-detr-l.yaml
为例,在同目录下创建一个用于自己数据集训练的模型文件
rt-detr-l-GSConv.yaml
。
将
rt-detr-l.yaml
中的内容复制到
rt-detr-l-GSConv.yaml
文件下,修改
nc
数量等于自己数据中目标的数量。
此处是将
rt-detr-l
的
颈部网络
修改成
GSConv+Slim Neck
,原因是:
为了加速预测的计算,CNN中的馈送图像几乎必须在 Backbone 中经历类似的转换过程:空间信息逐步向通道传输。并且每次特征图的空间(宽度和高度)压缩和通道扩展都会导致语义信息的部分丢失。密集卷积计算最大限度地保留了每个通道之间的隐藏连接,而稀疏卷积则完全切断了这些连接。
GSConv
尽可能地保留这些连接。但是如果在模型的所有阶段都使用它,模型的网络层会更深,深层会加剧对数据流的阻力,显著增加推理时间。当这些特征图走到
Neck
时,它们已经变得细长(通道维度达到最大,宽高维度达到最小),不再需要进行变换。因此,更好的选择是仅在
Neck
使用
GSConv
。在这个阶段,使用
GSConv
处理concatenated feature maps刚刚好:冗余重复信息少,不需要压缩,注意力模块效果更好。
使用
VoV-GSCSP模块
则降低了计算和网络结构的复杂性,但保持了足够的精度。
# Ultralytics YOLO 🚀, AGPL-3.0 license
# RT-DETR-l object detection model with P3-P5 outputs. For details see https://docs.ultralytics.com/models/rtdetr
# Parameters
nc: 1 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov8n-cls.yaml' will call yolov8-cls.yaml with scale 'n'
# [depth, width, max_channels]
l: [1.00, 1.00, 1024]
backbone:
# [from, repeats, module, args]
- [-1, 1, HGStem, [32, 48]] # 0-P2/4
- [-1, 6, HGBlock, [48, 128, 3]] # stage 1
- [-1, 1, DWConv, [128, 3, 2, 1, False]] # 2-P3/8
- [-1, 6, HGBlock, [96, 512, 3]] # stage 2
- [-1, 1, DWConv, [512, 3, 2, 1, False]] # 4-P3/16
- [-1, 6, HGBlock, [192, 1024, 5, True, False]] # cm, c2, k, light, shortcut
- [-1, 6, HGBlock, [192, 1024, 5, True, True]]
- [-1, 6, HGBlock, [192, 1024, 5, True, True]] # stage 3
- [-1, 1, DWConv, [1024, 3, 2, 1, False]] # 8-P4/32
- [-1, 6, HGBlock, [384, 2048, 5, True, False]] # stage 4
head:
- [-1, 1, Conv, [256, 1, 1, None, 1, 1, False]] # 10 input_proj.2
- [-1, 1, AIFI, [1024, 8]]
- [-1, 1, Conv, [256, 1, 1]] # 12, Y5, lateral_convs.0
- [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- [7, 1, GSConv, [256, 1, 1]] # 14 input_proj.1
- [[-2, -1], 1, Concat, [1]]
- [-1, 3, VoVGSCSP, [512]] # 16, fpn_blocks.0
- [-1, 1, Conv, [256, 1, 1]] # 17, Y4, lateral_convs.1
- [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- [3, 1, GSConv, [256, 1, 1]] # 19 input_proj.0
- [[-2, -1], 1, Concat, [1]] # cat backbone P4
- [-1, 3, VoVGSCSP, [512]] # X3 (21), fpn_blocks.1
- [-1, 1, GSConv, [256, 3, 2]] # 22, downsample_convs.0
- [[-1, 17], 1, Concat, [1]] # cat Y4
- [-1, 3, VoVGSCSP, [512]] # F4 (24), pan_blocks.0
- [-1, 1, GSConv, [256, 3, 2]] # 25, downsample_convs.1
- [[-1, 12], 1, Concat, [1]] # cat Y5
- [-1, 3, VoVGSCSP, [512]] # F5 (27), pan_blocks.1
- [[21, 24, 27], 1, RTDETRDecoder, [nc]] # Detect(P3, P4, P5)
七、成功运行结果
分别打印网络模型可以看到
GSConv模块
和
VoVGSCSP
已经加入到模型中,并可以进行训练了。
rt-detr-l-GSConv :
rtdetr-l-GSConv summary: 975 layers, 42,171,075 parameters, 42,171,075 gradients, 119.2 GFLOPs
from n params module arguments
0 -1 1 25248 ultralytics.nn.modules.block.HGStem [3, 32, 48]
1 -1 6 155072 ultralytics.nn.modules.block.HGBlock [48, 48, 128, 3, 6]
2 -1 1 1408 ultralytics.nn.modules.conv.DWConv [128, 128, 3, 2, 1, False]
3 -1 6 839296 ultralytics.nn.modules.block.HGBlock [128, 96, 512, 3, 6]
4 -1 1 5632 ultralytics.nn.modules.conv.DWConv [512, 512, 3, 2, 1, False]
5 -1 6 1695360 ultralytics.nn.modules.block.HGBlock [512, 192, 1024, 5, 6, True, False]
6 -1 6 2055808 ultralytics.nn.modules.block.HGBlock [1024, 192, 1024, 5, 6, True, True]
7 -1 6 2055808 ultralytics.nn.modules.block.HGBlock [1024, 192, 1024, 5, 6, True, True]
8 -1 1 11264 ultralytics.nn.modules.conv.DWConv [1024, 1024, 3, 2, 1, False]
9 -1 6 6708480 ultralytics.nn.modules.block.HGBlock [1024, 384, 2048, 5, 6, True, False]
10 -1 1 524800 ultralytics.nn.modules.conv.Conv [2048, 256, 1, 1, None, 1, 1, False]
11 -1 1 789760 ultralytics.nn.modules.transformer.AIFI [256, 1024, 8]
12 -1 1 66048 ultralytics.nn.modules.conv.Conv [256, 256, 1, 1]
13 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
14 7 1 134784 ultralytics.nn.AddModules.GSConv.GSConv [1024, 256, 1, 1]
15 [-2, -1] 1 0 ultralytics.nn.modules.conv.Concat [1]
16 -1 3 4553472 ultralytics.nn.AddModules.GSConv.VoVGSCSP [512, 512]
17 -1 1 131584 ultralytics.nn.modules.conv.Conv [512, 256, 1, 1]
18 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
19 3 1 69248 ultralytics.nn.AddModules.GSConv.GSConv [512, 256, 1, 1]
20 [-2, -1] 1 0 ultralytics.nn.modules.conv.Concat [1]
21 -1 3 4553472 ultralytics.nn.AddModules.GSConv.VoVGSCSP [512, 512]
22 -1 1 593536 ultralytics.nn.AddModules.GSConv.GSConv [512, 256, 3, 2]
23 [-1, 17] 1 0 ultralytics.nn.modules.conv.Concat [1]
24 -1 3 4553472 ultralytics.nn.AddModules.GSConv.VoVGSCSP [512, 512]
25 -1 1 593536 ultralytics.nn.AddModules.GSConv.GSConv [512, 256, 3, 2]
26 [-1, 12] 1 0 ultralytics.nn.modules.conv.Concat [1]
27 -1 3 4553472 ultralytics.nn.AddModules.GSConv.VoVGSCSP [512, 512]
28 [21, 24, 27] 1 7500515 ultralytics.nn.modules.head.RTDETRDecoder [1, [512, 512, 512]]
rtdetr-l-GSConv summary: 975 layers, 42,171,075 parameters, 42,171,075 gradients, 119.2 GFLOPs