RT-DETR改进策略【卷积层】| ICCV-2023 SAFM 空间自适应特征调制模块 对ResNetLayer进行二次创新
一、本文介绍
本文记录的是
利用空间自适应特征调制模块SAFM优化RT-DETR的目标检测方法研究
。
SAFM
通过更好地利用特征信息来实现模型性能和效率的平衡。本文通过
二次创新ResNetLayer
,
能够动态选择代表性特征,并结合局部上下文信息,提升模型的检测精度。
二、SAFM 原理介绍
高效图像超分辨率的空间自适应特征调制
2.1 设计出发点
- 融合卷积与自注意力机制优势 :现有的基于深度学习的图像超分辨率(SR)模型存在一些问题。一方面,大型的基于卷积神经网络(CNN)的模型如RCAN虽取得良好性能,但计算成本高,难以部署;另一方面,视觉Transformer(ViT)虽在某些任务中表现出探索非局部特征交互的优势,但自注意力机制计算昂贵,不利于高效SR设计。因此,需要一种既能吸收CNN的高效性又能具备类似Transformer适应性的机制,这促使了SAFM模块的设计,旨在通过融合两者的优点来实现高效的SR。
- 提升模型对特征的利用能力 :在高效SR的研究方向中,现有的方法在提升模型效率时往往难以兼顾性能,而SAFM模块希望通过更好地利用特征信息来实现模型性能和效率的平衡。通过动态选择代表性特征,并结合局部上下文信息,提升模型在超分辨率任务中的表现。
2.2 原理
- 引入长距离交互和动态建模能力 :借鉴ViT中多头自注意力(MHSA)机制实现长距离特征交互和动态空间加权的能力,将其引入到卷积操作中。通过并行和独立的计算,让每个头处理输入的不同尺度信息,然后聚合这些特征生成注意力图,用于对输入特征进行空间自适应调制。
- 多尺度特征生成与选择 :为了学习非局部特征交互,首先对归一化后的输入特征进行通道分割,分为四组组件。其中第一组通过 3 × 3 3×3 3 × 3 深度卷积处理,其余部分通过池化操作进行单独采样。并且在输入特征上应用自适应最大池化操作来收集信息,以选择具有判别性的特征。之后将这些不同尺度的特征在通道维度上进行拼接,并通过 1 × 1 1×1 1 × 1 卷积进行聚合,得到一个综合的特征表示。最后通过GELU非线性函数对其进行归一化,得到注意力图,用于自适应地调制输入特征。
2.3 结构
2.3.1 多尺度特征生成单元(MFGU)
- 包含多个分支,其中一个分支是 3 × 3 3×3 3 × 3 深度卷积对第一组特征进行处理。
- 其他分支通过池化操作对特征进行不同程度的下采样,然后再通过上采样操作恢复到原始分辨率,以获取不同尺度的特征信息。
2.3.2 特征聚合与调制
-
将
MFGU输出的不同尺度特征在通道维度上进行拼接,然后通过 1 × 1 1×1 1 × 1 卷积进行聚合。 -
对聚合后的特征通过
GELU函数进行归一化,得到注意力图,再通过逐元素相乘的方式自适应地调制输入特征。
2.4 优势
- 更好的性能与效率平衡 :与其他轻量级SR方法相比,如在Set5数据集上进行 × 2 S R ×2SR × 2 SR 时,所提方法在模型复杂度和重建性能之间取得了更好的平衡,在减少模型参数和计算量(FLOPs)的同时,能够获得与其他先进方法相当的性能。
- 有效利用特征信息 :通过局部归因图(LAM)和扩散指数(DI)的比较可知,该模块能够比其他基于CNN的高效SR模型利用更多的特征信息,从而有助于提升重建性能。
- 降低计算成本 :在卷积操作中引入类似自注意力机制的能力时,通过多尺度特征表示和并行独立计算,在保持对特征有效利用的同时,维持了较低的计算成本。
论文: https://openaccess.thecvf.com/content/ICCV2023/papers/Sun_Spatially-Adaptive_Feature_Modulation_for_Efficient_Image_Super-Resolution_ICCV_2023_paper.pdf
源码: https://github.com/sunny2109/SAFMN
三、SAFM的实现代码
SAFM模块
的实现代码如下:
import torch
import torch.nn as nn
import torch.nn.functional as F
class DMlp(nn.Module):
def __init__(self, dim, growth_rate=2.0):
super().__init__()
hidden_dim = int(dim * growth_rate)
self.conv_0 = nn.Sequential(
nn.Conv2d(dim, hidden_dim, 3, 1, 1, groups=dim),
nn.Conv2d(hidden_dim, hidden_dim, 1, 1, 0)
)
self.act = nn.GELU()
self.conv_1 = nn.Conv2d(hidden_dim, dim, 1, 1, 0)
def forward(self, x):
x = self.conv_0(x)
x = self.act(x)
x = self.conv_1(x)
return x
class SAFM(nn.Module):
def __init__(self, dim=36):
super(SAFM, self).__init__()
self.linear_0 = nn.Conv2d(dim, dim * 2, 1, 1, 0)
self.linear_1 = nn.Conv2d(dim, dim, 1, 1, 0)
self.linear_2 = nn.Conv2d(dim, dim, 1, 1, 0)
self.lde = DMlp(dim, 2)
self.dw_conv = nn.Conv2d(dim, dim, 3, 1, 1, groups=dim)
self.gelu = nn.GELU()
self.down_scale = 8
self.alpha = nn.Parameter(torch.ones((1, dim, 1, 1)))
self.belt = nn.Parameter(torch.zeros((1, dim, 1, 1)))
def forward(self, f):
_, _, h, w = f.shape
y, x = self.linear_0(f).chunk(2, dim=1)
x_s = self.dw_conv(F.adaptive_max_pool2d(x, (h // self.down_scale, w // self.down_scale)))
x_v = torch.var(x, dim=(-2, -1), keepdim=True)
x_l = x * F.interpolate(self.gelu(self.linear_1(x_s * self.alpha + x_v * self.belt)), size=(h, w),
mode='nearest')
y_d = self.lde(y)
return self.linear_2(x_l + y_d)
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 = SAFM(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.cv2(self.cv1(x))) + self.shortcut(x))
class ResNetLayer_SAFM(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 改进点⭐
模块改进方法
:直接加入
SAFM模块
(
第五节讲解添加步骤
)。
SAFM模块
添加后如下:
基于
SAFM模块
的
ResNetLayer
。
第二种改进方法是对
RT-DETR
中的
ResNetLayer模块
进行改进,并将
SAFM
在加入到
ResNetLayer
模块中。
改进代码如下:
首先添加如下代码改进
ResNetBlock
模块,并将
ResNetLayer
重命名为
ResNetLayer_SAFM
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 = SAFM(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.cv2(self.cv1(x))) + self.shortcut(x))
class ResNetLayer_SAFM(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)
注意❗:在
第五小节
中需要声明的模块名称为:
ResNetLayer_SAFM
。
五、添加步骤
5.1 修改一
① 在
ultralytics/nn/
目录下新建
AddModules
文件夹用于存放模块代码
② 在
AddModules
文件夹下新建
SAFM.py
,将
第三节
中的代码粘贴到此处
5.2 修改二
在
AddModules
文件夹下新建
__init__.py
(已有则不用新建),在文件内导入模块:
from .SAFM import *
5.3 修改三
在
ultralytics/nn/modules/tasks.py
文件中,需要在两处位置添加各模块类名称。
首先:导入模块
其次:在
parse_model函数
中注册
ResNetLayer_SAFM
模块
六、yaml模型文件
6.1 模型改进版本⭐
此处以
ultralytics/cfg/models/rt-detr/rtdetr-resnet50.yaml
为例,在同目录下创建一个用于自己数据集训练的模型文件
rtdetr-ResNetLayer_SAFM.yaml
。
将
rtdetr-resnet50.yaml
中的内容复制到
rtdetr-resnet50-ResNetLayer_SAFM.yaml
文件下,修改
nc
数量等于自己数据中目标的数量。
📌 模型的修改方法是将
骨干网络
中的所有
ResNetLayer模块
替换成
ResNetLayer_SAFM模块
。
# 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_SAFM, [3, 64, 1, True, 1]] # 0
- [-1, 1, ResNetLayer_SAFM, [64, 64, 1, False, 3]] # 1
- [-1, 1, ResNetLayer_SAFM, [256, 128, 2, False, 4]] # 2
- [-1, 1, ResNetLayer_SAFM, [512, 256, 2, False, 6]] # 3
- [-1, 1, ResNetLayer_SAFM, [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_SAFM
已经加入到模型中,并可以进行训练了。
**rtdetr-ResNetLayer_SAFM **:
rtdetr-ResNetLayer_SAFM summary: 607 layers, 51,311,095 parameters, 51,311,095 gradients, 167.2 GFLOPs
from n params module arguments
0 -1 1 9536 ultralytics.nn.AddModules.DynamicConv.ResNetLayer_DynamicConv[3, 64, 1, True, 1]
1 -1 1 328332 ultralytics.nn.AddModules.DynamicConv.ResNetLayer_DynamicConv[64, 64, 1, False, 3]
2 -1 1 1913872 ultralytics.nn.AddModules.DynamicConv.ResNetLayer_DynamicConv[256, 128, 2, False, 4]
3 -1 1 11443224 ultralytics.nn.AddModules.DynamicConv.ResNetLayer_DynamicConv[512, 256, 2, False, 6]
4 -1 1 22846476 ultralytics.nn.AddModules.DynamicConv.ResNetLayer_DynamicConv[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_SAFM summary: 593 layers, 55,796,195 parameters, 55,796,195 gradients, 115.0 GFLOPs