RT-DETR改进策略【Neck】| BiFPN:双向特征金字塔网络-跨尺度连接和加权特征融合
一、本文介绍
本文记录的是
基于BiFPN结构的RT-DETR颈部网络改进方法研究
。在
RT-DETR
的Neck颈部网络中使用的
FPN+PAN
的结构,但是
FPN
在融合不同输入特征时简单地将它们相加,没有区分不同特征的重要性;
PAN
虽然增加了额外的自底向上路径聚合网络,但参数和计算量较大。
为了解决这些问题,本文将颈部结构换成
BiFPN
,利用多尺度特征融合网络,使模型既能考虑不同输入特征的重要性,又能提高模型效率。
二、BiFPN介绍
EfficientDet
: 可扩展的高效物体检测
BiFPN(加权双向特征金字塔网络)
是该论文中提出的一种用于高效多尺度特征融合的网络结构,其设计原理和优势如下:
2.1、BiFPN原理
-
问题 formulation
:多尺度特征融合的目标是聚合不同分辨率的特征,给定多尺度特征列表
P
→
i
n
=
(
P
i
n
1
,
P
i
n
2
,
…
)
\overrightarrow{P}_{in} = (P_{in}^1, P_{in}^2, \ldots)
P
in
=
(
P
in
1
,
P
in
2
,
…
)
,其中
P
i
n
i
P_{in}^i
P
in
i
代表第
i
i
i
级的特征,目标是找到一个变换
f
f
f
,使得
P
→
o
u
t
=
f
(
P
→
i
n
)
\overrightarrow{P}_{out} = f(\overrightarrow{P}_{in})
P
o
u
t
=
f
(
P
in
)
。以传统的
FPN为例,它以自上而下的方式聚合多尺度特征,如 P 7 o u t = C o n v ( P 7 i n ) P_{7}^{out} = Conv(P_{7}^{in}) P 7 o u t = C o n v ( P 7 in ) , P 6 o u t = C o n v ( P 6 i n + R e s i z e ( P 7 o u t ) ) P_{6}^{out} = Conv(P_{6}^{in} + Resize(P_{7}^{out})) P 6 o u t = C o n v ( P 6 in + R es i ze ( P 7 o u t )) 等。
2.1.1 跨尺度连接
-
为了改进传统
FPN单向信息流的局限性,PANet增加了自底向上的路径聚合网络,其他研究也进一步探讨了跨尺度连接。通过研究FPN、PANet和NAS-FPN这三个网络的性能和效率,本文对跨尺度连接进行了优化: - 移除只有一个输入边缘的节点,因为这样的节点对旨在融合不同特征的特征网络贡献较小,从而得到简化的双向网络。
- 如果原始输入和输出节点在同一级别,添加一个额外的边缘,以便在不增加太多成本的情况下融合更多特征。
-
将每个双向(自上而下和自底向上)路径视为一个特征网络层,并多次重复该层以实现更多高级特征融合,从而命名为
双向特征金字塔网络(BiFPN)。
2.1.2 加权特征融合
当融合不同分辨率的特征时,常见的方法是先将它们调整到相同分辨率然后相加,但这种方法没有区分不同输入特征的重要性。为了解决这个问题,提出为每个输入添加一个额外的权重,让网络学习每个输入特征的重要性,并考虑了三种加权融合方法:
-
Unbounded fusion
:
O
=
∑
i
w
i
⋅
I
i
O = \sum_{i} w_{i} \cdot I_{i}
O
=
∑
i
w
i
⋅
I
i
,其中
w
i
w_{i}
w
i
是可学习的权重,可以是标量(每个特征)、向量(每个通道)或多维张量(每个像素)。通过权重归一化来限制每个权重的值范围,以避免训练不稳定。
-
Softmax - based fusion
:
O
=
∑
i
e
w
i
∑
j
e
w
j
⋅
I
i
O = \sum_{i} \frac{e^{w_{i}}}{\sum_{j} e^{w_{j}}} \cdot I_{i}
O
=
∑
i
∑
j
e
w
j
e
w
i
⋅
I
i
,对每个权重应用Softmax,使所有权重归一化为0到1之间的概率,表示每个输入的重要性。但在GPU硬件上会导致显著的减速。
-
Fast normalized fusion
:
O
=
∑
i
w
i
ϵ
+
∑
j
w
j
⋅
I
i
O = \sum_{i} \frac{w_{i}}{\epsilon + \sum_{j} w_{j}} \cdot I_{i}
O
=
∑
i
ϵ
+
∑
j
w
j
w
i
⋅
I
i
,通过在每个
w
i
w_{i}
w
i
后应用Relu确保
w
i
≥
0
w_{i} \geq 0
w
i
≥
0
,
ϵ
=
0.0001
\epsilon = 0.0001
ϵ
=
0.0001
是一个小值以避免数值不稳定。这种方法的每个归一化权重的值也落在0和1之间,且没有Softmax操作,因此更高效。
最终的BiFPN集成了双向跨尺度连接和快速归一化融合。例如,对于图2(d)中BiFPN在级别6的两个融合特征,计算方式为:
-
P
6
t
d
=
C
o
n
v
(
w
1
⋅
P
6
i
n
+
w
2
⋅
R
e
s
i
z
e
(
P
7
i
n
)
w
1
+
w
2
+
ϵ
)
P_{6}^{td} = Conv(\frac{w_{1} \cdot P_{6}^{in} + w_{2} \cdot Resize(P_{7}^{in})}{w_{1} + w_{2} + \epsilon})
P
6
t
d
=
C
o
n
v
(
w
1
+
w
2
+
ϵ
w
1
⋅
P
6
in
+
w
2
⋅
R
es
i
ze
(
P
7
in
)
)
-
P
6
o
u
t
=
C
o
n
v
(
w
1
′
⋅
P
6
i
n
+
w
2
′
⋅
P
6
t
d
+
w
3
′
⋅
R
e
s
i
z
e
(
P
5
o
u
t
)
w
1
′
+
w
2
′
+
w
3
′
+
ϵ
)
P_{6}^{out} = Conv(\frac{w_{1}' \cdot P_{6}^{in} + w_{2}' \cdot P_{6}^{td} + w_{3}' \cdot Resize(P_{5}^{out})}{w_{1}' + w_{2}' + w_{3}' + \epsilon})
P
6
o
u
t
=
C
o
n
v
(
w
1
′
+
w
2
′
+
w
3
′
+
ϵ
w
1
′
⋅
P
6
in
+
w
2
′
⋅
P
6
t
d
+
w
3
′
⋅
R
es
i
ze
(
P
5
o
u
t
)
)
为了进一步提高效率,使用深度可分离卷积进行特征融合,并在每次卷积后添加批量归一化和激活。
2.2、优势
-
通过优化跨尺度连接和加权特征融合,
BiFPN能够更好地融合多尺度特征,提高特征的表达能力。 -
与其他特征网络相比,
BiFPN在实现相似精度的情况下,使用更少的参数和计算量,提高了模型效率。 - 快速归一化融合方法与Softmax-based融合相比,具有非常相似的学习行为和精度,但在GPU上运行速度更快。
论文: https://arxiv.org/pdf/1911.09070.pdf
源码: https://github.com/google/automl/tree/master/efficientdet
三、BiFPN模块的实现代码
BiFPN模块
的实现代码如下:
import torch.nn as nn
import torch
class swish(nn.Module):
def forward(self, x):
return x * torch.sigmoid(x)
class BiFPN(nn.Module):
def __init__(self, length):
super().__init__()
self.weight = nn.Parameter(torch.ones(length, dtype=torch.float32), requires_grad=True)
self.swish = swish()
self.epsilon = 0.0001
def forward(self, x):
weights = self.weight / (torch.sum(self.swish(self.weight), dim=0) + self.epsilon)
weighted_feature_maps = [weights[i] * x[i] for i in range(len(x))]
stacked_feature_maps = torch.stack(weighted_feature_maps, dim=0)
result = torch.sum(stacked_feature_maps, dim=0)
return result
四、添加步骤
4.1 修改ultralytics/nn/modules/block.py
此处需要修改的文件是
ultralytics/nn/modules/block.py
block.py中定义了网络结构的通用模块
,我们想要加入新的模块就只需要将模块代码放到这个文件内即可。
将
BiFPN
模块代码添加到此文件下。
4.2 修改ultralytics/nn/modules/ init .py
此处需要修改的文件是
ultralytics/nn/modules/__init__.py
__init__.py
文件中定义了所有模块的初始化,我们只需要将
block.py
中的新的模块命添加到对应的函数即可。
Bi_FPN
在
block.py
中实现,所有要添加在
from .block import
:
from .block import (
C1,
C2,
...
BiFPN
)
4.3 修改ultralytics/nn/modules/tasks.py
在
tasks.py
文件中,需要在两处位置添加各模块类名称。
首先:在函数声明中引入
BiFPN
其次:在
parse_model函数
中注册
BiFPN
模块
elif m is BiFPN:
length = len([ch[x] for x in f])
args = [length]
五、yaml模型文件
5.1 模型改进⭐
在代码配置完成后,配置模型的YAML文件。
此处以
ultralytics/cfg/models/rtdetr/rtdetr-l.yaml
为例,在同目录下创建一个用于自己数据集训练的模型文件
rtdetr-l-BiFPN.yaml
。
将
rtdetr-l.yaml
中的内容复制到
rtdetr-l-BiFPN.yaml
文件下,修改
nc
数量等于自己数据中目标的数量。
📌 修改方法是将
BiFPN模块
替换
rtdetr
颈部网络中的
concat
,并修改结构。
# 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-P4/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-P5/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
- [3, 1, Conv, [256]] # 13-P3/8
- [7, 1, Conv, [256]] # 14-P4/16
- [12, 1, Conv, [256]] # 15-P5/32
- [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 16 P5->P4
- [[-1, 14], 1, BiFPN, []] # 17
- [-1, 3, RepC3, [256]] # 18-P4/16
- [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 19 P4->P3
- [[-1, 13], 1, BiFPN, []] # 20
- [-1, 3, RepC3, [256]] # 21-P3/8
- [1, 1, Conv, [256, 3, 2]] # 22 P2->P3
- [[-1, 13, 21], 1, BiFPN, []] # 23
- [-1, 3, RepC3, [256]] # 24-P3/8
- [-1, 1, Conv, [256, 3, 2]] # 25 P3->P4
- [[-1, 14, 18], 1, BiFPN, []] # 26
- [-1, 3, RepC3, [256]] # 27-P4/16
- [-1, 1, Conv, [256, 3, 2]] # 28 P4->P5
- [[-1, 15], 1, BiFPN, []] # 29
- [-1, 3, RepC3, [256]] # 30-P5/32
- [[24, 27, 28], 1, RTDETRDecoder, [nc, 256, 300, 4, 8, 3]] # Detect(P3, P4, P5)
六、成功运行结果
分别打印网络模型可以看到
BiFPN模块
已经加入到模型中,并可以进行训练了。
rtdetr-l-BiFPN :
rtdetr-l-BiFPN summary: 649 layers, 31,294,304 parameters, 31,294,304 gradients, 131.0 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 3 1 131584 ultralytics.nn.modules.conv.Conv [512, 256]
14 7 1 262656 ultralytics.nn.modules.conv.Conv [1024, 256]
15 12 1 66048 ultralytics.nn.modules.conv.Conv [256, 256]
16 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
17 [-1, 14] 1 2 ultralytics.nn.AddModules.BiFPN.BiFPN [2]
18 -1 3 2101248 ultralytics.nn.modules.block.RepC3 [256, 256, 3]
19 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
20 [-1, 13] 1 2 ultralytics.nn.AddModules.BiFPN.BiFPN [2]
21 -1 3 2101248 ultralytics.nn.modules.block.RepC3 [256, 256, 3]
22 1 1 295424 ultralytics.nn.modules.conv.Conv [128, 256, 3, 2]
23 [-1, 13, 21] 1 3 ultralytics.nn.AddModules.BiFPN.BiFPN [3]
24 -1 3 2101248 ultralytics.nn.modules.block.RepC3 [256, 256, 3]
25 -1 1 590336 ultralytics.nn.modules.conv.Conv [256, 256, 3, 2]
26 [-1, 14, 18] 1 3 ultralytics.nn.AddModules.BiFPN.BiFPN [3]
27 -1 3 2101248 ultralytics.nn.modules.block.RepC3 [256, 256, 3]
28 -1 1 590336 ultralytics.nn.modules.conv.Conv [256, 256, 3, 2]
29 [-1, 15] 1 2 ultralytics.nn.AddModules.BiFPN.BiFPN [2]
30 -1 3 2101248 ultralytics.nn.modules.block.RepC3 [256, 256, 3]
31 [24, 27, 28] 1 3917684 ultralytics.nn.modules.head.RTDETRDecoder [1, [256, 256, 256], 256, 300, 4, 8, 3]
rtdetr-l-BiFPN summary: 649 layers, 31,294,304 parameters, 31,294,304 gradients, 131.0 GFLOPs