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层以保留更多局部细节。
-
7×7卷积
:7×7是
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