一、本文介绍
本文记录的是利用ExtraDW
优化YOLOv9
中的RepNCSPELAN4
,详细说明了优化原因,注意事项等。ExtraDW
是MobileNetv4
模型中提出的新模块,允许以低成本增加网络深度和感受野,具有ConvNext和IB的组合优势。可以在提高模型精度的同时降低一定量的模型参数。
文章目录
- 一、本文介绍
- 二、UIB介绍
- 2.1 UIB结构设计
- 2.2 ExtraDW结构组成
- 2.3 ExtraDW特点
- 三、ExtraDW的实现代码
- 四、添加步骤
- 4.1 修改common.py
- 4.1.1 基础模块1
- 4.1.2 创新模块2⭐
- 4.2 修改yolo.py
- 五、yaml模型文件
- 5.1 模型改进版本一
- 5.2 模型改进版本二⭐
- 六、成功运行结果
二、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) // 2conv.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 convclass 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 convself.start_dw_kernel_size = start_dw_kernel_sizeif self.start_dw_kernel_size:stride_ = stride if not middle_dw_downsample else 1self._start_dw_ = conv2d(in_channels, in_channels, kernel_size=start_dw_kernel_size, stride=stride_, groups=in_channels, act=False)# expansion with 1x1 convsexpand_filters = make_divisible(in_channels * expand_ratio, 8)self._expand_conv = conv2d(in_channels, expand_filters, kernel_size=1)# middle depthwise convself.middle_dw_kernel_size = middle_dw_kernel_sizeif self.middle_dw_kernel_size:stride_ = stride if middle_dw_downsample else 1self._middle_dw = conv2d(expand_filters, expand_filters, kernel_size=middle_dw_kernel_size, stride=stride_, groups=expand_filters)# projection with 1x1 convsself._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-elandef __init__(self, c1, c2, c3, c4, c5=1): # ch_in, ch_out, number, shortcut, groups, expansionsuper().__init__()self.c = c3//2self.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
数量等于自己数据中目标的数量。
📌 模型的修改方法是将骨干网络中的所有RepNCSPELAN4模块
替换成UIBRepNCSPELAN4模块
。
# 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, UIBRepNCSPELAN4, [256, 128, 64, 1]], # 3 修改此处# avg-conv down[-1, 1, ADown, [256]], # 4-P3/8# elan-2 block[-1, 1, UIBRepNCSPELAN4, [512, 256, 128, 1]], # 5 修改此处# avg-conv down[-1, 1, ADown, [512]], # 6-P4/16# elan-2 block[-1, 1, UIBRepNCSPELAN4, [512, 512, 256, 1]], # 7 修改此处# avg-conv down[-1, 1, ADown, [512]], # 8-P5/32# elan-2 block[-1, 1, UIBRepNCSPELAN4, [512, 512, 256, 1]], # 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)]
六、成功运行结果
分别打印网络模型可以看到UniversalInvertedBottleneckBlock模块
和UIBRepNCSPELAN4
已经加入到模型中,并可以进行训练了。
yolov9-c-UIB:
from n params module arguments 0 -1 1 0 models.common.Silence [] 1 -1 1 1856 models.common.Conv [3, 64, 3, 2] 2 -1 1 73984 models.common.Conv [64, 128, 3, 2] 3 -1 1 102144 models.common.UniversalInvertedBottleneckBlock[128, 256, 0, 3, True, 1, 2] 4 -1 1 164352 models.common.ADown [256, 256] 5 -1 1 400896 models.common.UniversalInvertedBottleneckBlock[256, 512, 0, 3, True, 1, 2] 6 -1 1 656384 models.common.ADown [512, 512] 7 -1 1 2138624 models.common.UniversalInvertedBottleneckBlock[512, 512, 5, 3, True, 1, 4] 8 -1 1 656384 models.common.ADown [512, 512] 9 -1 1 2138624 models.common.UniversalInvertedBottleneckBlock[512, 512, 5, 3, True, 1, 4] 10 -1 1 656896 models.common.SPPELAN [512, 512, 256] 11 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest'] 12 [-1, 7] 1 0 models.common.Concat [1] 13 -1 1 3119616 models.common.RepNCSPELAN4 [1024, 512, 512, 256, 1] 14 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest'] 15 [-1, 5] 1 0 models.common.Concat [1] 16 -1 1 912640 models.common.RepNCSPELAN4 [1024, 256, 256, 128, 1] 17 -1 1 164352 models.common.ADown [256, 256] 18 [-1, 13] 1 0 models.common.Concat [1] 19 -1 1 2988544 models.common.RepNCSPELAN4 [768, 512, 512, 256, 1] 20 -1 1 656384 models.common.ADown [512, 512] 21 [-1, 10] 1 0 models.common.Concat [1] 22 -1 1 3119616 models.common.RepNCSPELAN4 [1024, 512, 512, 256, 1] 23 5 1 131328 models.common.CBLinear [512, [256]] 24 7 1 393984 models.common.CBLinear [512, [256, 512]] 25 9 1 656640 models.common.CBLinear [512, [256, 512, 512]] 26 0 1 1856 models.common.Conv [3, 64, 3, 2] 27 -1 1 73984 models.common.Conv [64, 128, 3, 2] 28 -1 1 212864 models.common.RepNCSPELAN4 [128, 256, 128, 64, 1] 29 -1 1 164352 models.common.ADown [256, 256] 30 [23, 24, 25, -1] 1 0 models.common.CBFuse [[0, 0, 0]] 31 -1 1 847616 models.common.RepNCSPELAN4 [256, 512, 256, 128, 1] 32 -1 1 656384 models.common.ADown [512, 512] 33 [24, 25, -1] 1 0 models.common.CBFuse [[1, 1]] 34 -1 1 2857472 models.common.RepNCSPELAN4 [512, 512, 512, 256, 1] 35 -1 1 656384 models.common.ADown [512, 512] 36 [25, -1] 1 0 models.common.CBFuse [[2]] 37 -1 1 2857472 models.common.RepNCSPELAN4 [512, 512, 512, 256, 1] 38[31, 34, 37, 16, 19, 22] 1 21542822 DualDDetect [1, [512, 512, 512, 256, 512, 512]]
yolov9-c-UIB summary: 764 layers, 49004454 parameters, 49004422 gradients, 224.6 GFLOPs
在将UniversalInvertedBottleneckBlock模块
替换RepNCSPELAN4模块
后,模型参数量和计算量均有减少,更加轻量。
yolov9-c-UIB-2:
from n params module arguments 0 -1 1 0 models.common.Silence [] 1 -1 1 1856 models.common.Conv [3, 64, 3, 2] 2 -1 1 73984 models.common.Conv [64, 128, 3, 2] 3 -1 1 231808 models.common.UIBRepNCSPELAN4 [128, 256, 128, 64, 1] 4 -1 1 164352 models.common.ADown [256, 256] 5 -1 1 901888 models.common.UIBRepNCSPELAN4 [256, 512, 256, 128, 1] 6 -1 1 656384 models.common.ADown [512, 512] 7 -1 1 3031552 models.common.UIBRepNCSPELAN4 [512, 512, 512, 256, 1] 8 -1 1 656384 models.common.ADown [512, 512] 9 -1 1 3031552 models.common.UIBRepNCSPELAN4 [512, 512, 512, 256, 1] 10 -1 1 656896 models.common.SPPELAN [512, 512, 256] 11 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest'] 12 [-1, 7] 1 0 models.common.Concat [1] 13 -1 1 3119616 models.common.RepNCSPELAN4 [1024, 512, 512, 256, 1] 14 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest'] 15 [-1, 5] 1 0 models.common.Concat [1] 16 -1 1 912640 models.common.RepNCSPELAN4 [1024, 256, 256, 128, 1] 17 -1 1 164352 models.common.ADown [256, 256] 18 [-1, 13] 1 0 models.common.Concat [1] 19 -1 1 2988544 models.common.RepNCSPELAN4 [768, 512, 512, 256, 1] 20 -1 1 656384 models.common.ADown [512, 512] 21 [-1, 10] 1 0 models.common.Concat [1] 22 -1 1 3119616 models.common.RepNCSPELAN4 [1024, 512, 512, 256, 1] 23 5 1 131328 models.common.CBLinear [512, [256]] 24 7 1 393984 models.common.CBLinear [512, [256, 512]] 25 9 1 656640 models.common.CBLinear [512, [256, 512, 512]] 26 0 1 1856 models.common.Conv [3, 64, 3, 2] 27 -1 1 73984 models.common.Conv [64, 128, 3, 2] 28 -1 1 212864 models.common.RepNCSPELAN4 [128, 256, 128, 64, 1] 29 -1 1 164352 models.common.ADown [256, 256] 30 [23, 24, 25, -1] 1 0 models.common.CBFuse [[0, 0, 0]] 31 -1 1 847616 models.common.RepNCSPELAN4 [256, 512, 256, 128, 1] 32 -1 1 656384 models.common.ADown [512, 512] 33 [24, 25, -1] 1 0 models.common.CBFuse [[1, 1]] 34 -1 1 2857472 models.common.RepNCSPELAN4 [512, 512, 512, 256, 1] 35 -1 1 656384 models.common.ADown [512, 512] 36 [25, -1] 1 0 models.common.CBFuse [[2]] 37 -1 1 2857472 models.common.RepNCSPELAN4 [512, 512, 512, 256, 1] 38[31, 34, 37, 16, 19, 22] 1 21542822 DualDDetect [1, [512, 512, 512, 256, 512, 512]]
yolov9-c-UIB-2 summary: 1058 layers, 51420966 parameters, 51420934 gradients, 241.5 GFLOPs