学习资源站

RT-DETR改进策略【卷积层】GnConv:一种通过门控卷积和递归设计来实现高效、可扩展、平移等变的高阶空间交互操作-

RT-DETR改进策略【卷积层】| GnConv:一种通过门控卷积和递归设计来实现高效、可扩展、平移等变的高阶空间交互操作

一、本文介绍

本文记录的是 利用 GnConv 优化 RT-DETR 的目标检测方法研究 RT-DETR 在进行目标检测时,需要进行信息融合。 GnConv 可以考虑更高阶的空间交互, 能够更好地捕捉特征之间的复杂关系,从而增强特征融合的效果 ,提高模型对目标的检测能力。



二、HorNet原理介绍

HorNet : 利用递归门控卷积实现高效高阶空间交互

GnConv(Recursive Gated Convolution,递归门控卷积) 是论文中提出的一种高效操作,用于实现长期和高阶空间交互,其设计原理、计算公式和优势如下:

2.1、GnConv设计原理

  • 输入自适应交互与门控卷积 Vision Transformer 的成功主要依赖于对视觉数据中空间交互的适当建模,与简单使用静态卷积核聚合相邻特征的CNN不同, Vision Transformer 应用多头自注意力来动态生成权重以混合空间标记,但自注意力关于输入大小的二次复杂度在很大程度上阻碍了其应用,尤其是在需要更高分辨率特征图的下游任务中。在这项工作中,作者寻求一种更有效和高效的方法来执行空间交互,使用 门控卷积(gConv) 来实现输入自适应的空间混合。
  • 高阶交互与递归门控 :在通过 gConv 实现了高效的一阶空间交互后,作者设计了 递归门控卷积(GnConv) 来通过引入高阶交互进一步增强模型容量。具体来说,首先使用多个线性投影层( ϕ i n \phi_{in} ϕ in )获得一组投影特征( p 0 p_0 p 0 q k q_k q k ),然后通过递归的方式执行门控卷积( p k + 1 = f k ( q k ) ⊙ g k ( p k ) / α p_{k + 1} = f_k(q_k) \odot g_k(p_k) / \alpha p k + 1 = f k ( q k ) g k ( p k ) / α ),其中 f k f_k f k 是一组深度卷积层, g k g_k g k 用于匹配不同阶的维度,最后将最后一次递归步骤的输出 q n q_n q n 送入投影层 ϕ o u t \phi_{out} ϕ o u t 以获得 g n C o n v g^{n}Conv g n C o n v 的结果。从递归公式可以看出, p k p_k p k 的交互阶数在每一步后都会增加1,因此 g n C o n v gnConv g n C o n v 实现了 n n n 阶空间交互。
  • 大核卷积与长期交互 :为了使 G n C o n v GnConv G n C o n v 能够捕捉长期交互,作者采用了两种实现方式来处理深度卷积 f f f
    • 7×7卷积 :7×7是 Swin Transformers ConvNext 的默认窗口/核大小,研究表明该核大小在ImageNet分类和各种下游任务中产生良好性能,因此作者遵循此配置以公平地与代表性的 Vision Transformer 和现代CNN进行比较。
    • 全局滤波器(GF) :GF层通过将频域特征与可学习的全局滤波器相乘,相当于在空间域中进行具有全局核大小和循环填充的卷积。作者使用了GF层的修改版本,即处理一半的通道与全局滤波器相乘,另一半与3×3深度卷积相乘,并仅在后期阶段使用GF层以保留更多局部细节。

2.2、GnConv计算公式

门控卷积输出

x ∈ R H W × C x \in \mathbb{R}^{H W \times C} x R H W × C 为输入特征,门控卷积 y = g C o n v ( x ) y = gConv(x) y = g C o n v ( x ) 的输出可写为:
[ p 0 H W × C , q 0 H W × C ] = ϕ i n ( x ) ∈ R H W × 2 C , y = f ( q 0 ) ⊙ p 0 ∈ R H W × C , y = ϕ o u t ( p 1 ) ∈ R H W × C , \begin{array}{r} {\left[p_{0}^{H W \times C}, q_{0}^{H W \times C}\right]=\phi_{in }(x) \in \mathbb{R}^{H W \times 2 C},} \\ y = f\left(q_{0}\right) \odot p_{0} \in \mathbb{R}^{H W \times C}, \\ y = \phi_{out }\left(p_{1}\right) \in \mathbb{R}^{H W \times C}, \end{array} [ p 0 H W × C , q 0 H W × C ] = ϕ in ( x ) R H W × 2 C , y = f ( q 0 ) p 0 R H W × C , y = ϕ o u t ( p 1 ) R H W × C ,

其中 ϕ i n \phi_{in} ϕ in ϕ o u t \phi_{out} ϕ o u t 是线性投影层以执行通道混合, f f f 是深度卷积。注意到 p 1 ( i , c ) = ∑ j ∈ Ω i w i → j c q 0 ( j , c ) p 0 ( i , c ) p_{1}^{(i, c)}=\sum_{j \in \Omega_{i}} w_{i \to j}^{c} q_{0}^{(j, c)} p_{0}^{(i, c)} p 1 ( i , c ) = j Ω i w i j c q 0 ( j , c ) p 0 ( i , c ) ,其中 Ω i \Omega_{i} Ω i 是以为 i i i 中心的局部窗口, w i → j w_{i \to j} w i j 代表卷积权重。

  • 递归门控卷积
    [ p 0 H W × C 0 , q 0 H W × C 0 , … , q n − 1 H W × C n − 1 ] = ϕ i n ( x ) ∈ R H W × ( C 0 + ∑ 0 ≤ k ≤ n − 1 C k ) , p k + 1 = f k ( q k ) ⊙ g k ( p k ) / α , k = 0 , 1 , … , n − 1 , \begin{aligned} &\left[p_{0}^{H W \times C_{0}}, q_{0}^{H W \times C_{0}}, \ldots, q_{n - 1}^{H W \times C_{n - 1}}\right]=\phi_{in }(x) \in \mathbb{R}^{H W \times\left(C_{0} + \sum_{0 \leq k \leq n - 1} C_{k}\right)}, \\ &p_{k + 1} = f_{k}\left(q_{k}\right) \odot g_{k}\left(p_{k}\right) / \alpha, k = 0, 1, \ldots, n - 1, \end{aligned} [ p 0 H W × C 0 , q 0 H W × C 0 , , q n 1 H W × C n 1 ] = ϕ in ( x ) R H W × ( C 0 + 0 k n 1 C k ) , p k + 1 = f k ( q k ) g k ( p k ) / α , k = 0 , 1 , , n 1 ,
    其中 g k g_{k} g k 的定义为: g k = { I d e n t i t y , k = 0 L i n e a r ( C k − 1 , C k ) , 1 ≤ k ≤ n − 1 g_{k}=\begin{cases}Identity, & k = 0 \\Linear\left(C_{k - 1}, C_{k}\right), & 1 \leq k \leq n - 1\end{cases} g k = { I d e n t i t y , L in e a r ( C k 1 , C k ) , k = 0 1 k n 1
  • 计算复杂度 :总FLOPs为: F L O P s ( g n C o n v ) < H W C ( 2 K 2 + 11 3 × C + 2 ) FLOPs\left(g^{n}Conv\right) < HW C\left(2K^{2} + \frac{11}{3} \times C + 2\right) F L OP s ( g n C o n v ) < H W C ( 2 K 2 + 3 11 × C + 2 ) ,其中 K K K 是深度卷积的核大小。

在这里插入图片描述

2.3、优势

  • 高效 :基于卷积的实现避免了自注意力的二次复杂度。在执行空间交互时逐渐增加通道宽度的设计也使能够以有限的复杂度实现更高阶的交互。
  • 可扩展 :将自注意力中的二阶交互扩展到任意阶,以进一步提高建模能力。由于不对空间卷积的类型做假设,因此(gnConv)与各种核大小和空间混合策略兼容。
  • 平移等变性 :完全继承了标准卷积的平移等变性,这为主要视觉任务引入了有益的归纳偏差,并避免了局部注意力带来的不对称性。

论文: https://arxiv.org/pdf/2207.14284
源码: https://github.com/raoyongming/HorNet

三、GnConv的实现代码

GnConv模块 的实现代码如下:


import torch.nn.functional as F
import torch
import torch.nn as nn

def get_dwconv(dim, kernel, bias):
    return nn.Conv2d(dim, dim, kernel_size=kernel, padding=(kernel-1)//2 ,bias=bias, groups=dim)

class gnConv(nn.Module):
    def __init__(self, dim, order=5, gflayer=None, h=14, w=8, s=1.0):
        super().__init__()
        self.order = order
        self.dims = [dim // 2 ** i for i in range(order)]
        self.dims.reverse()
        self.proj_in = nn.Conv2d(dim, 2*dim, 1)
 
        if gflayer is None:
            self.dwconv = get_dwconv(sum(self.dims), 7, True)
        else:
            self.dwconv = gflayer(sum(self.dims), h=h, w=w)
        
        self.proj_out = nn.Conv2d(dim, dim, 1)
 
        self.pws = nn.ModuleList(
            [nn.Conv2d(self.dims[i], self.dims[i+1], 1) for i in range(order-1)]
        )
        self.scale = s
 
    def forward(self, x, mask=None, dummy=False):
        # B, C, H, W = x.shape gnconv [512]by iscyy/air
        fused_x = self.proj_in(x)
        pwa, abc = torch.split(fused_x, (self.dims[0], sum(self.dims)), dim=1)
        dw_abc = self.dwconv(abc) * self.scale
        dw_list = torch.split(dw_abc, self.dims, dim=1)
        x = pwa * dw_list[0]
        for i in range(self.order -1):
            x = self.pws[i](x) * dw_list[i+1]
        x = self.proj_out(x)
 
        return x

def autopad(k, p=None, d=1):  # kernel, padding, dilation
    """Pad to 'same' shape outputs."""
    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):
        """Initialize Conv layer with given arguments including 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):
        """Apply convolution, batch normalization and activation to input tensor."""
        return self.act(self.bn(self.conv(x)))
 
    def forward_fuse(self, x):
        """Perform transposed convolution of 2D data."""
        return self.act(self.conv(x))

class ResNetBlock(nn.Module):
    """ResNet block with standard convolution layers."""

    def __init__(self, c1, c2, s=1, e=4):
        """Initialize convolution with given parameters."""
        super().__init__()
        c3 = e * c2
        self.cv1 = Conv(c1, c2, k=1, s=1, act=True)
        self.cv2 = Conv(c2, c2, k=3, s=s, p=1, act=True)
        self.cv3 = Conv(c2, c3, k=1, act=False)
        self.cv4 = gnConv(c2)
        self.shortcut = nn.Sequential(Conv(c1, c3, k=1, s=s, act=False)) if s != 1 or c1 != c3 else nn.Identity()

    def forward(self, x):
        """Forward pass through the ResNet block."""
        return F.relu(self.cv3(self.cv4(self.cv2(self.cv1(x)))) + self.shortcut(x))

class ResNetLayer_GnConv(nn.Module):
    """ResNet layer with multiple ResNet blocks."""

    def __init__(self, c1, c2, s=1, is_first=False, n=1, e=4):
        """Initializes the ResNetLayer given arguments."""
        super().__init__()
        self.is_first = is_first

        if self.is_first:
            self.layer = nn.Sequential(
                Conv(c1, c2, k=7, s=2, p=3, act=True), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
            )
        else:
            blocks = [ResNetBlock(c1, c2, s, e=e)]
            blocks.extend([ResNetBlock(e * c2, c2, 1, e=e) for _ in range(n - 1)])
            self.layer = nn.Sequential(*blocks)

    def forward(self, x):
        """Forward pass through the ResNet layer."""
        return self.layer(x)

四、创新模块

4.1 改进点⭐

模块改进方法
1️⃣ 加入 GnConv模块 GnConv模块 添加后如下:

在这里插入图片描述

2️⃣:加入基于 GnConv模块 ResNetLayer 。利用 GnConv 改进 ResNetLayer 模块, 替换其中的普通卷积可以使RT-DETR实现更高阶的空间交互,更好地捕捉特征之间的复杂关系,从而提高模型的建模能力。

改进代码如下:

首先添加如下代码改进 ResNetLayer 模块,并重命名为 ResNetLayer_GnConv

class ResNetLayer_GnConv(nn.Module):
    """ResNet layer with multiple ResNet blocks."""

    def __init__(self, c1, c2, s=1, is_first=False, n=1, e=4):
        """Initializes the ResNetLayer given arguments."""
        super().__init__()
        self.is_first = is_first

        if self.is_first:
            self.layer = nn.Sequential(
                Conv(c1, c2, k=7, s=2, p=3, act=True), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
            )
        else:
            blocks = [ResNetBlock(c1, c2, s, e=e)]
            blocks.extend([ResNetBlock(e * c2, c2, 1, e=e) for _ in range(n - 1)])
            self.layer = nn.Sequential(*blocks)

    def forward(self, x):
        """Forward pass through the ResNet layer."""
        return self.layer(x)

在这里插入图片描述

注意❗:在 5.2和5.3小节 中需要声明的模块名称为: ResNetLayer_GnConv


五、添加步骤

5.1 修改一

① 在 ultralytics/nn/ 目录下新建 AddModules 文件夹用于存放模块代码

② 在 AddModules 文件夹下新建 GnConv.py ,将 第三节 中的代码粘贴到此处

在这里插入图片描述

5.2 修改二

AddModules 文件夹下新建 __init__.py (已有则不用新建),在文件内导入模块: from .GnConv import *

在这里插入图片描述

5.3 修改三

ultralytics/nn/modules/tasks.py 文件中,需要在指定位置添加各模块类名称。

首先:导入模块

在这里插入图片描述

其次:在 parse_model函数 中注册 ResNetLayer_DySnakeConv 模块

在这里插入图片描述
在这里插入图片描述


六、yaml模型文件

6.1 模型改进版本一

在代码配置完成后,配置模型的YAML文件。

此处以 ultralytics/cfg/models/rt-detr/rtdetr-resnet50.yaml 为例,在同目录下创建一个用于自己数据集训练的模型文件 rtdetr-ResNetLayer_GnConv.yaml

rtdetr-resnet50.yaml 中的内容复制到 rtdetr-ResNetLayer_GnConv.yaml 文件下,修改 nc 数量等于自己数据中目标的数量。
骨干网络 中将 ResNetLayer 模块替换成 ResNetLayer_GnConv模块

# Ultralytics YOLO 🚀, AGPL-3.0 license
# RT-DETR-ResNet50 object detection model with P3-P5 outputs.

# 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, ResNetLayer_GnConv, [3, 64, 1, True, 1]] # 0
  - [-1, 1, ResNetLayer_GnConv, [64, 64, 1, False, 3]] # 1
  - [-1, 1, ResNetLayer_GnConv, [256, 128, 2, False, 4]] # 2
  - [-1, 1, ResNetLayer_GnConv, [512, 256, 2, False, 6]] # 3
  - [-1, 1, ResNetLayer_GnConv, [1024, 512, 2, False, 3]] # 4

head:
  - [-1, 1, Conv, [256, 1, 1, None, 1, 1, False]] # 5
  - [-1, 1, AIFI, [1024, 8]]
  - [-1, 1, Conv, [256, 1, 1]] # 7

  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [3, 1, Conv, [256, 1, 1, None, 1, 1, False]] # 9
  - [[-2, -1], 1, Concat, [1]]
  - [-1, 3, RepC3, [256]] # 11
  - [-1, 1, Conv, [256, 1, 1]] # 12

  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [2, 1, Conv, [256, 1, 1, None, 1, 1, False]] # 14
  - [[-2, -1], 1, Concat, [1]] # cat backbone P4
  - [-1, 3, RepC3, [256]] # X3 (16), fpn_blocks.1

  - [-1, 1, Conv, [256, 3, 2]] # 17, downsample_convs.0
  - [[-1, 12], 1, Concat, [1]] # cat Y4
  - [-1, 3, RepC3, [256]] # F4 (19), pan_blocks.0

  - [-1, 1, Conv, [256, 3, 2]] # 20, downsample_convs.1
  - [[-1, 7], 1, Concat, [1]] # cat Y5
  - [-1, 3, RepC3, [256]] # F5 (22), pan_blocks.1

  - [[16, 19, 22], 1, RTDETRDecoder, [nc]] # Detect(P3, P4, P5)


七、成功运行结果

分别打印网络模型可以看到 ResNetLayer_GnConv 已经加入到模型中,并可以进行训练了。

rtdetr-ResNetLayer_GnConv

rtdetr-ResNetLayer_GnConv summary: 737 layers, 47,754,451 parameters, 47,754,451 gradients, 144.9 GFLOPs

                   from  n    params  module                                       arguments                     
  0                  -1  1      9536  ultralytics.nn.AddModules.GnConv.ResNetLayer_GnConv[3, 64, 1, True, 1]           
  1                  -1  1    280368  ultralytics.nn.AddModules.GnConv.ResNetLayer_GnConv[64, 64, 1, False, 3]         
  2                  -1  1   1511808  ultralytics.nn.AddModules.GnConv.ResNetLayer_GnConv[256, 128, 2, False, 4]       
  3                  -1  1   8695424  ultralytics.nn.AddModules.GnConv.ResNetLayer_GnConv[512, 256, 2, False, 6]       
  4                  -1  1  18002560  ultralytics.nn.AddModules.GnConv.ResNetLayer_GnConv[1024, 512, 2, False, 3]      
  5                  -1  1    524800  ultralytics.nn.modules.conv.Conv             [2048, 256, 1, 1, None, 1, 1, False]
  6                  -1  1    789760  ultralytics.nn.modules.transformer.AIFI      [256, 1024, 8]                
  7                  -1  1     66048  ultralytics.nn.modules.conv.Conv             [256, 256, 1, 1]              
  8                  -1  1         0  torch.nn.modules.upsampling.Upsample         [None, 2, 'nearest']          
  9                   3  1    262656  ultralytics.nn.modules.conv.Conv             [1024, 256, 1, 1, None, 1, 1, False]
 10            [-2, -1]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 11                  -1  3   2232320  ultralytics.nn.modules.block.RepC3           [512, 256, 3]                 
 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                   2  1    131584  ultralytics.nn.modules.conv.Conv             [512, 256, 1, 1, None, 1, 1, False]
 15            [-2, -1]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 16                  -1  3   2232320  ultralytics.nn.modules.block.RepC3           [512, 256, 3]                 
 17                  -1  1    590336  ultralytics.nn.modules.conv.Conv             [256, 256, 3, 2]              
 18            [-1, 12]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 19                  -1  3   2232320  ultralytics.nn.modules.block.RepC3           [512, 256, 3]                 
 20                  -1  1    590336  ultralytics.nn.modules.conv.Conv             [256, 256, 3, 2]              
 21             [-1, 7]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 22                  -1  3   2232320  ultralytics.nn.modules.block.RepC3           [512, 256, 3]                 
 23        [16, 19, 22]  1   7303907  ultralytics.nn.modules.head.RTDETRDecoder    [1, [256, 256, 256]]          
rtdetr-ResNetLayer_GnConv summary: 737 layers, 47,754,451 parameters, 47,754,451 gradients, 144.9 GFLOPs