学习资源站

RT-DETR改进策略【Neck】GSConv+SlimNeck:混合深度可分离卷积和标准卷积的轻量化网络设计_rt-detr颈部网络-

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