一、本文介绍
本文记录的是利用ExtraDW
优化YOLOv9
中的RepNCSPELAN4
,详细说明了优化原因,注意事项等。ExtraDW
是MobileNetv4
模型中提出的新模块,允许以低成本增加网络深度和感受野,具有ConvNext和IB的组合优势。可以在提高模型精度的同时降低一定量的模型参数。
文章目录
二、UIB介绍
Universal Inverted Bottleneck(UIB)
通用反向瓶颈结构。
2.1 UIB结构设计
-
基于
MobileNetV4
UIB
建立在MobileNetV4
之上,即采用深度可分离卷积
和逐点
扩展及投影的反向瓶颈
结构。- 在
反向瓶颈块(IB)
中引入两个可选的深度可分离卷积
,一个在扩展层之前,另一个在扩展层和投影层之间。
-
UIB有四种可能的实例化形式:
- Inverted Bottleneck (IB):对扩展后的特征激活进行空间混合,以增加成本为代价提供更大的模型容量。
- ConvNext:通过在扩展之前进行空间混合,使用更大的核尺寸实现更便宜的空间混合。
- ExtraDW:文中引入的新变体,允许以低成本增加网络深度和感受野,具有
ConvNext
和IB
的组合优势。 - FFN:由两个
1x1逐点卷积(PW)
组成的栈,中间有激活和归一化层。
2.2 ExtraDW结构组成
结构组成:
- 在
IB块
中加入两个可选的深度可分离卷积
,一个在扩展层之前,另一个在扩展层和投影层之间。
2.3 ExtraDW特点
-
灵活性:
- 在每个网络阶段,可以灵活地进行空间和通道混合的权衡调整,根据需要扩大感受野,并最大化计算利用率,增强模型对输入特征的感知能力。
-
效率提升:
- 提供了一种廉价增加网络深度和感受野的方式。相比其他结构,它在增加网络深度和感受野的同时,不会带来过高的计算成本。
- 在论文中,与其他注意力机制结合时,能有效提高模型的运算强度,减少内存访问需求,从而提高模型效率。
论文:http://arxiv.org/abs/2404.10518
源码:https://github.com/tensorflow/models/blob/master/official/vision/modeling/backbones/mobilenet.py
三、ExtraDW的实现代码
ExtraDW模块
的实现代码如下:参考代码
def conv2d(in_channels, out_channels, kernel_size=3, stride=1, groups=1, bias=False, norm=True, act=True):
conv = nn.Sequential()
padding = (kernel_size - 1) // 2
conv.append(nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, bias=bias, groups=groups))
if norm:
conv.append(nn.BatchNorm2d(out_channels))
if act:
conv.append(nn.ReLU6())
return conv
class UniversalInvertedBottleneckBlock(nn.Module):
def __init__(self, in_channels, out_channels, start_dw_kernel_size, middle_dw_kernel_size, middle_dw_downsample,
stride, expand_ratio):
"""An inverted bottleneck block with optional depthwises.
Referenced from here https://github.com/tensorflow/models/blob/master/official/vision/modeling/layers/nn_blocks.py
"""
super(UniversalInvertedBottleneckBlock, self).__init__()
# starting depthwise conv
self.start_dw_kernel_size = start_dw_kernel_size
if self.start_dw_kernel_size:
stride_ = stride if not middle_dw_downsample else 1
self._start_dw_ = conv2d(in_channels, in_channels, kernel_size=start_dw_kernel_size, stride=stride_, groups=in_channels, act=False)
# expansion with 1x1 convs
expand_filters = make_divisible(in_channels * expand_ratio, 8)
self._expand_conv = conv2d(in_channels, expand_filters, kernel_size=1)
# middle depthwise conv
self.middle_dw_kernel_size = middle_dw_kernel_size
if self.middle_dw_kernel_size:
stride_ = stride if middle_dw_downsample else 1
self._middle_dw = conv2d(expand_filters, expand_filters, kernel_size=middle_dw_kernel_size, stride=stride_, groups=expand_filters)
# projection with 1x1 convs
self._proj_conv = conv2d(expand_filters, out_channels, kernel_size=1, stride=1, act=False)
# expand depthwise conv (not used)
# _end_dw_kernel_size = 0
# self._end_dw = conv2d(out_channels, out_channels, kernel_size=_end_dw_kernel_size, stride=stride, groups=in_channels, act=False)
def forward(self, x):
if self.start_dw_kernel_size:
x = self._start_dw_(x)
# print("_start_dw_", x.shape)
x = self._expand_conv(x)
# print("_expand_conv", x.shape)
if self.middle_dw_kernel_size:
x = self._middle_dw(x)
# print("_middle_dw", x.shape)
x = self._proj_conv(x)
# print("_proj_conv", x.shape)
return x
四、添加步骤
4.1 修改common.py
此处需要修改的文件是models/common.py
common.py中定义了网络结构的通用模块
,我们想要加入新的模块就只需要将模块代码放到这个文件内即可。
4.1.1 基础模块1
模块改进方法1️⃣:直接加入UniversalInvertedBottleneckBlock模块
。
将上方的实现代码粘贴到common.py
文件夹下,UniversalInvertedBottleneckBlock模块
添加后如下:
注意❗:在4.2小节
中的yolo.py
文件中需要声明的模块名称为:UniversalInvertedBottleneckBlock
。
4.1.2 创新模块2⭐
模块改进方法2️⃣:基于UniversalInvertedBottleneckBlock
的RepNCSPELAN4
。
相较方法一中的直接插入UIB模块
,利用UIB模块
对卷积等其他模块进行改进,其新颖程度会更高一些,训练精度可能会表现的更高。
第二种改进方法是对YOLOv9
中的RepNCSPELAN4模块
进行改进。UIB
中的ExtraDW模块
与 RepNCSPELAN4
结合后,可以为YOLOv9
提供更丰富的特征表示,更好地调整特征的空间分布和通道信息,使得模型能够更有效地聚焦于目标相关的特征,减少无关信息的干扰,进而提高检测精度。
改进代码如下:
class UIBRepNCSPELAN4(nn.Module):
# csp-elan
def __init__(self, c1, c2, c3, c4, c5=1): # ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
self.c = c3//2
self.cv1 = Conv(c1, c3, 1, 1)
self.cv2 = nn.Sequential(RepNCSP(c3//2, c4, c5), UniversalInvertedBottleneckBlock(c4, c4, 3, 3, True, 1, 6))
self.cv3 = nn.Sequential(RepNCSP(c4, c4, c5), UniversalInvertedBottleneckBlock(c4, c4, 5, 3, True, 1, 4))
self.cv4 = Conv(c3+(2*c4), c2, 1, 1)
def forward(self, x):
y = list(self.cv1(x).chunk(2, 1))
y.extend((m(y[-1])) for m in [self.cv2, self.cv3])
return self.cv4(torch.cat(y, 1))
def forward_split(self, x):
y = list(self.cv1(x).split((self.c, self.c), 1))
y.extend(m(y[-1]) for m in [self.cv2, self.cv3])
return self.cv4(torch.cat(y, 1))
注意❗:在4.2小节
中的yolo.py
文件中需要声明的模块名称为:UIBRepNCSPELAN4
。
4.2 修改yolo.py
此处需要修改的文件是models/yolo.py
yolo.py用于函数调用
,我们只需要将common.py
中定义的新的模块名添加到parse_model函数
下即可。
UniversalInvertedBottleneckBlock模块
以及UIBRepNCSPELAN4模块
添加后如下:
五、yaml模型文件
5.1 模型改进版本一
在代码配置完成后,配置模型的YAML文件。
此处以models/detect/yolov9-c.yaml
为例,在同目录下创建一个用于自己数据集训练的模型文件yolov9-c-UIB.yaml
。
将yolov9-c.yaml
中的内容复制到yolov9-c-UIB.yaml
文件下,修改nc
数量等于自己数据中目标的数量。
在骨干网络中,将四个RepNCSPELAN4模块
替换成UniversalInvertedBottleneckBlock模块
,注意修改函数中的参数。还需要注意的是,由于PAN+FPN的颈部模型结构存在,层之间的匹配也要记得修改,维度要匹配上。
# YOLOv9
# parameters
nc: 1 # number of classes
depth_multiple: 1.0 # model depth multiple
width_multiple: 1.0 # layer channel multiple
#activation: nn.LeakyReLU(0.1)
#activation: nn.ReLU()
# anchors
anchors: 3
# YOLOv9 backbone
backbone:
[
[-1, 1, Silence, []],
# conv down
[-1, 1, Conv, [64, 3, 2]], # 1-P1/2
# conv down
[-1, 1, Conv, [128, 3, 2]], # 2-P2/4
# elan-1 block
[-1, 1, UniversalInvertedBottleneckBlock, [256, 0, 3, True, 1, 2]], # 3 修改此处
# avg-conv down
[-1, 1, ADown, [256]], # 4-P3/8
# elan-2 block
[-1, 1, UniversalInvertedBottleneckBlock, [512, 0, 3, True, 1, 2]], # 5 修改此处
# avg-conv down
[-1, 1, ADown, [512]], # 6-P4/16
# elan-2 block
[-1, 1, UniversalInvertedBottleneckBlock, [512, 5, 3, True, 1, 4]], # 7 修改此处
# avg-conv down
[-1, 1, ADown, [512]], # 8-P5/32
# elan-2 block
[-1, 1, UniversalInvertedBottleneckBlock, [512, 5, 3, True, 1, 4]], # 9 修改此处
]
# YOLOv9 head
head:
[
# elan-spp block
[-1, 1, SPPELAN, [512, 256]], # 10
# up-concat merge
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 7], 1, Concat, [1]], # cat backbone P4
# elan-2 block
[-1, 1, RepNCSPELAN4, [512, 512, 256, 1]], # 13
# up-concat merge
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 5], 1, Concat, [1]], # cat backbone P3
# elan-2 block
[-1, 1, RepNCSPELAN4, [256, 256, 128, 1]], # 16 (P3/8-small)
# avg-conv-down merge
[-1, 1, ADown, [256]],
[[-1, 13], 1, Concat, [1]], # cat head P4
# elan-2 block
[-1, 1, RepNCSPELAN4, [512, 512, 256, 1]], # 19 (P4/16-medium)
# avg-conv-down merge
[-1, 1, ADown, [512]],
[[-1, 10], 1, Concat, [1]], # cat head P5
# elan-2 block
[-1, 1, RepNCSPELAN4, [512, 512, 256, 1]], # 22 (P5/32-large)
# multi-level reversible auxiliary branch
# routing
[5, 1, CBLinear, [[256]]], # 23
[7, 1, CBLinear, [[256, 512]]], # 24
[9, 1, CBLinear, [[256, 512, 512]]], # 25
# conv down
[0, 1, Conv, [64, 3, 2]], # 26-P1/2
# conv down
[-1, 1, Conv, [128, 3, 2]], # 27-P2/4
# elan-1 block
[-1, 1, RepNCSPELAN4, [256, 128, 64, 1]], # 28
# avg-conv down fuse
[-1, 1, ADown, [256]], # 29-P3/8
[[23, 24, 25, -1], 1, CBFuse, [[0, 0, 0]]], # 30
# elan-2 block
[-1, 1, RepNCSPELAN4, [512, 256, 128, 1]], # 31
# avg-conv down fuse
[-1, 1, ADown, [512]], # 32-P4/16
[[24, 25, -1], 1, CBFuse, [[1, 1]]], # 33
# elan-2 block
[-1, 1, RepNCSPELAN4, [512, 512, 256, 1]], # 34
# avg-conv down fuse
[-1, 1, ADown, [512]], # 35-P5/32
[[25, -1], 1, CBFuse, [[2]]], # 36
# elan-2 block
[-1, 1, RepNCSPELAN4, [512, 512, 256, 1]], # 37
# detection head
# detect
[[31, 34, 37, 16, 19, 22], 1, DualDDetect, [nc]], # DualDDetect(A3, A4, A5, P3, P4, P5)
]
5.2 模型改进版本二⭐
此处同样以models/detect/yolov9-c.yaml
为例,在同目录下创建一个用于自己数据集训练的模型文件yolov9-c-UIB-2.yaml
。
将yolov9-c.yaml
中的内容复制到yolov9-c-UIB-2.yaml
文件下,修改nc
数量等于自己数据中目标的数量。