前言:Hello大家好,我是小哥谈。MobileNetV3是由Google团队在2019年提出的一种卷积神经网络结构,其目标是在保持高性能的同时减少计算延时。MobileNetV3相比于前一版本(MobileNetV2)在性能上有明显的提升。根据原论文,在ImageNet分类任务中,MobileNetV3的正确率提升了3.2%,同时计算延时降低了20%。MobileNetV3通过使用NAS搜索参数重新设计了耗时层结构,这也是其与之前版本的主要区别之一。🌈
前期回顾:
YOLOv5算法改进(1)— 如何去改进YOLOv5算法
YOLOv5算法改进(2)— 添加SE注意力机制
YOLOv5算法改进(3)— 添加CBAM注意力机制
YOLOv5算法改进(4)— 添加CA注意力机制
YOLOv5算法改进(5)— 添加ECA注意力机制
YOLOv5算法改进(6)— 添加SOCA注意力机制
YOLOv5算法改进(7)— 添加SimAM注意力机制
目录
🚀1.论文
🚀2.MobileNetV3网络架构
🚀3.YOLOv5结合MobileNetV3_small
💥💥步骤1:在common.py中添加MobileNetV3模块
💥💥步骤2:在yolo.py文件中加入类名
💥💥步骤3:创建自定义yaml文件
💥💥步骤4:验证是否加入成功
💥💥步骤5:修改train.py中的'--cfg'默认参数
🚀4.YOLOv5结合MobileNetV3_large
💥💥步骤3:创建自定义yaml文件
🚀1.论文
MobileNetV3 是由 Google 团队在 2019 年提出的轻量化网络模型。传统的卷积神经网络,内容需求大、运算量大,无法在移动设备以及嵌入式设备上运行,为了解决这一问题,MobileNet网络应运而生。MobileNetV3在移动端图像分类、目标检测、语义分割等任务上均取得了优秀的表现。MobileNetV3采用了很多新的技术,包括针对通道注意力的Squeeze-and-Excitation模块、NAS搜索方法等,这些方法都有利于进一步提升网络的性能。🌱
MobileNetV3的整体架构基本沿用了MobileNetV2的设计,采用了轻量级的深度可分离卷积和残差块等结构,依然是由多个模块组成,但是每个模块得到了优化和升级,包括瓶颈结构、SE模块和NL模块。MobileNetV3在ImageNet 分类任务中正确率上升了 3.2%,计算延时还降低了20%。🌱
整体来说MobileNetV3有两大创新点:
- 互补搜索技术组合:由资源受限的NAS执行模块级搜索,NetAdapt执行局部搜索。
- 网络结构改进:将最后一步的平均池化层前移并移除最后一个卷积层,引入h-swish激活函数。
MobileNetV3 有两个版本,MobileNetV3-Small 与 MobileNetV3-Large 分别对应对计算和存储要求低和高的版本。🌺
论文题目:《Searching for MobileNetV3》
论文地址: https://arxiv.org/abs/1905.02244
代码实现: mirrors / xiaolai-sqlai / mobilenetv3 · GitCode
🚀2.MobileNetV3网络架构
下图为MobileNetV3的网络结构图,large和small的整体结构一致,区别就是基本单元bneck的个数以及内部参数上,主要是通道数目。🌳
MobileNetV3-Large版本参数:
MobileNetV3-Small版本参数:
说明:♨️♨️♨️
上表为具体的参数设置,其中bneck是网络的基本结构。SE代表是否使用通道注意力机制。NL代表激活函数的类型,包括HS(h-swish),RE(ReLU)。NBN 代表没有BN操作。s 是stride的意思,网络使用卷积stride操作进行降采样,没有使用pooling操作。
MobileNetV3 相较于 MobileNetV2,主要有以下改进和优化:
🍀更高的准确率:MobileNetV3在ImageNet数据集上的Top-1准确率达到了75.2%,比MobileNetV2的72.0%要高。
🍀更快的推理速度:MobileNetV3通过增加Squeeze-and-Excitation模块和hard-swish非线性激活函数,提高了模型的计算效率,加快了推理速度。
🍀更少的参数和计算量:MobileNetV3在保持准确率不变的情况下,参数数量和计算量比MobileNetV2都要少。
🍀支持多种模型结构:MobileNetV3除了提供标准版模型外,还提供了Small模型和Large模型,可以根据不同的场景和需求选择合适的模型。
🍀支持自适应网络:MobileNetV3加入了Squeeze-and-Excite模块和自适应网络结构,可以根据输入图像的尺寸和分辨率自适应地调整模型的结构和参数,从而实现更好的模型泛化能力和适应性。
🍀支持权重重要性筛选:MobileNetV3提供了权重重要性筛选工具,可以定量地筛选出对模型性能影响最大的参数,从而实现更高效的模型压缩和优化。
总的来说,MobileNetV3 相对于 MobileNetV2,在准确率、推理速度、参数数量、计算量、模型结构和适应性等方面都有了显著的提升和改进。🌴
🚀3.YOLOv5结合MobileNetV3_small
💥💥步骤1:在common.py中添加MobileNetV3模块
将下面MobileNetV3模块的代码复制粘贴到common.py文件的末尾。
# Mobilenetv3Small
# ——————MobileNetV3——————
class h_sigmoid(nn.Module):
def __init__(self, inplace=True):
super(h_sigmoid, self).__init__()
self.relu = nn.ReLU6(inplace=inplace)
def forward(self, x):
return self.relu(x + 3) / 6
class h_swish(nn.Module):
def __init__(self, inplace=True):
super(h_swish, self).__init__()
self.sigmoid = h_sigmoid(inplace=inplace)
def forward(self, x):
return x * self.sigmoid(x)
class SELayer(nn.Module):
def __init__(self, channel, reduction=4):
super(SELayer, self).__init__()
# Squeeze操作
self.avg_pool = nn.AdaptiveAvgPool2d(1)
# Excitation操作(FC+ReLU+FC+Sigmoid)
self.fc = nn.Sequential(
nn.Linear(channel, channel // reduction),
nn.ReLU(inplace=True),
nn.Linear(channel // reduction, channel),
h_sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size()
y = self.avg_pool(x)
y = y.view(b, c)
y = self.fc(y).view(b, c, 1, 1) # 学习到的每一channel的权重
return x * y
class conv_bn_hswish(nn.Module):
"""
This equals to
def conv_3x3_bn(inp, oup, stride):
return nn.Sequential(
nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
nn.BatchNorm2d(oup),
h_swish()
)
"""
def __init__(self, c1, c2, stride):
super(conv_bn_hswish, self).__init__()
self.conv = nn.Conv2d(c1, c2, 3, stride, 1, bias=False)
self.bn = nn.BatchNorm2d(c2)
self.act = h_swish()
def forward(self, x):
return self.act(self.bn(self.conv(x)))
def fuseforward(self, x):
return self.act(self.conv(x))
class MobileNetV3(nn.Module):
def __init__(self, inp, oup, hidden_dim, kernel_size, stride, use_se, use_hs):
super(MobileNetV3, self).__init__()
assert stride in [1, 2]
self.identity = stride == 1 and inp == oup
# 输入通道数=扩张通道数 则不进行通道扩张
if inp == hidden_dim:
self.conv = nn.Sequential(
# dw
nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride, (kernel_size - 1) // 2, groups=hidden_dim,
bias=False),
nn.BatchNorm2d(hidden_dim),
h_swish() if use_hs else nn.ReLU(inplace=True),
# Squeeze-and-Excite
SELayer(hidden_dim) if use_se else nn.Sequential(),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
else:
# 否则 先进行通道扩张
self.conv = nn.Sequential(
# pw
nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
nn.BatchNorm2d(hidden_dim),
h_swish() if use_hs else nn.ReLU(inplace=True),
# dw
nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride, (kernel_size - 1) // 2, groups=hidden_dim,
bias=False),
nn.BatchNorm2d(hidden_dim),
# Squeeze-and-Excite
SELayer(hidden_dim) if use_se else nn.Sequential(),
h_swish() if use_hs else nn.ReLU(inplace=True),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
def forward(self, x):
y = self.conv(x)
if self.identity:
return x + y
else:
return y
具体如下图所示:
💥💥步骤2:在yolo.py文件中加入类名
首先在yolo.py文件中找到parse_model函数这一行,加入h_sigmoid,h_swish,SELayer,conv_bn_hswish,MobileNetV3这五个模块。
💥💥步骤3:创建自定义yaml文件
在models文件夹中复制yolov5s.yaml,粘贴并重命名为yolov5s_MobileNetV3.yaml。
然后根据 MobileNetV3的网络架构来修改配置文件。
根据网络结构,我们可以看到MobileNetV3模块包含六个参数,即[out_ch,hidden_ch,kernel_size,stride,use_se,use_hs]。
这六个参数的含义如下:
out_ch:输出通道
hidden_ch:表示在Inverted residuals中的扩张通道数。
kernel_size:卷积核大小
stride:步长
use_se:表示是否使用了SELayer,使用了是1,不使用是0。
use_hs:表示使用h_swish还是ReLU,使用h_swish是1,使用ReLU是0。
同样的,head部分这几个concat的层也要做修改:
yaml文件修改后的完整代码如下:
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# Parameters
nc: 80 # number of classes
depth_multiple: 1.0 # model depth multiple
width_multiple: 1.0 # layer channel multiple
anchors:
- [10,13, 16,30, 33,23] # P3/8
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32
# Mobilenetv3-small backbone
# MobileNetV3_InvertedResidual [out_ch, hid_ch, k_s, stride, SE, HardSwish]
backbone:
# [from, number, module, args]
[[-1, 1, conv_bn_hswish, [16, 2]], # 0-p1/2 320*320
[-1, 1, MobileNetV3, [16, 16, 3, 2, 1, 0]], # 1-p2/4 160*160
[-1, 1, MobileNetV3, [24, 72, 3, 2, 0, 0]], # 2-p3/8 80*80
[-1, 1, MobileNetV3, [24, 88, 3, 1, 0, 0]], # 3 80*80
[-1, 1, MobileNetV3, [40, 96, 5, 2, 1, 1]], # 4-p4/16 40*40
[-1, 1, MobileNetV3, [40, 240, 5, 1, 1, 1]], # 5 40*40
[-1, 1, MobileNetV3, [40, 240, 5, 1, 1, 1]], # 6 40*40
[-1, 1, MobileNetV3, [48, 120, 5, 1, 1, 1]], # 7 40*40
[-1, 1, MobileNetV3, [48, 144, 5, 1, 1, 1]], # 8 40*40
[-1, 1, MobileNetV3, [96, 288, 5, 2, 1, 1]], # 9-p5/32 20*20
[-1, 1, MobileNetV3, [96, 576, 5, 1, 1, 1]], # 10 20*20
[-1, 1, MobileNetV3, [96, 576, 5, 1, 1, 1]], # 11 20*20
]
# YOLOv5 v6.0 head
head:
[[-1, 1, Conv, [96, 1, 1]], # 12 20*20
[-1, 1, nn.Upsample, [None, 2, 'nearest']], # 13 40*40
[[-1, 8], 1, Concat, [1]], # cat backbone P4 40*40
[-1, 3, C3, [144, False]], # 15 40*40
[-1, 1, Conv, [144, 1, 1]], # 16 40*40
[-1, 1, nn.Upsample, [None, 2, 'nearest']],# 17 80*80
[[-1, 3], 1, Concat, [1]], # cat backbone P3 80*80
[-1, 3, C3, [168, False]], # 19 (P3/8-small) 80*80
[-1, 1, Conv, [168, 3, 2]], # 20 40*40
[[-1, 16], 1, Concat, [1]], # cat head P4 40*40
[-1, 3, C3, [312, False]], # 22 (P4/16-medium) 40*40
[-1, 1, Conv, [312, 3, 2]], # 23 20*20
[[-1, 12], 1, Concat, [1]], # cat head P5 20*20
[-1, 3, C3, [408, False]], # 25 (P5/32-large) 20*20
[[19, 22, 25], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
💥💥步骤4:验证是否加入成功
在yolo.py文件里,配置我们刚才自定义的yolov5s_MobileNetV3.yaml。
然后运行yolo.py,得到结果。
我们和原始的yolov5s.yaml进行对比。
可以看到替换主干网络为MobileNetV3之后层数变多了,可以学习到更多的特征,参数量由原来的700多万减少到500多万,大幅度减少了,GFLOPS也由16.4变为12.1。💯💯💯
💥💥步骤5:修改train.py中的'--cfg'默认参数
在train.py文件中找到 parse_opt函数,然后将第二行'--cfg'的default改为 'models/yolov5s_MobileNetV3.yaml',然后就可以开始进行训练了。🎈🎈🎈
🚀4.YOLOv5结合MobileNetV3_large
MobileNetV3_large 和 MobileNetV3_small的区别在于 yaml 文件中 head 中的 concat 连接不同,即深度因子和宽度因子不同。
步骤1和步骤2与上面的相同,我们直接开始步骤3,修改yaml文件部分。
💥💥步骤3:创建自定义yaml文件
在models文件夹中复制yolov5s.yaml,粘贴并重命名为yolov5s_MobileNetV3_large.yaml。
然后根据 MobileNetV3的网络架构来修改配置文件。
yaml文件修改后的完整代码如下:
# Parameters
nc: 20 # number of classes
depth_multiple: 1.0 # model depth multiple
width_multiple: 1.0 # layer channel multiple
anchors:
- [10,13, 16,30, 33,23] # P3/8
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32
# YOLOv5 v6.0 backbone
backbone:
[[-1, 1, conv_bn_hswish, [16, 2]], # 0-p1/2
[-1, 1, MobileNetV3, [ 16, 16, 3, 1, 0, 0]], # 1-p1/2
[-1, 1, MobileNetV3, [ 24, 64, 3, 2, 0, 0]], # 2-p2/4
[-1, 1, MobileNetV3, [ 24, 72, 3, 1, 0, 0]], # 3-p2/4
[-1, 1, MobileNetV3, [ 40, 72, 5, 2, 1, 0]], # 4-p3/8
[-1, 1, MobileNetV3, [ 40, 120, 5, 1, 1, 0]], # 5-p3/8
[-1, 1, MobileNetV3, [ 40, 120, 5, 1, 1, 0]], # 6-p3/8
[-1, 1, MobileNetV3, [ 80, 240, 3, 2, 0, 1]], # 7-p4/16
[-1, 1, MobileNetV3, [ 80, 200, 3, 1, 0, 1]], # 8-p4/16
[-1, 1, MobileNetV3, [ 80, 184, 3, 1, 0, 1]], # 9-p4/16
[-1, 1, MobileNetV3, [ 80, 184, 3, 1, 0, 1]], # 10-p4/16
[-1, 1, MobileNetV3, [112, 480, 3, 1, 1, 1]], # 11-p4/16
[-1, 1, MobileNetV3, [112, 672, 3, 1, 1, 1]], # 12-p4/16
[-1, 1, MobileNetV3, [160, 672, 5, 1, 1, 1]], # 13-p4/16
[-1, 1, MobileNetV3, [160, 960, 5, 2, 1, 1]], # 14-p5/32 原672改为原算法960
[-1, 1, MobileNetV3, [160, 960, 5, 1, 1, 1]], # 15-p5/32
]
# YOLOv5 v6.0 head
head:
[ [ -1, 1, Conv, [ 256, 1, 1 ] ],
[ -1, 1, nn.Upsample, [ None, 2, 'nearest' ] ],
[ [ -1, 13], 1, Concat, [ 1 ] ], # cat backbone P4
[ -1, 1, C3, [ 256, False ] ], # 13
[ -1, 1, Conv, [ 128, 1, 1 ] ],
[ -1, 1, nn.Upsample, [ None, 2, 'nearest' ] ],
[ [ -1, 6 ], 1, Concat, [ 1 ] ], # cat backbone P3
[ -1, 1, C3, [ 128, False ] ], # 17 (P3/8-small)
[ -1, 1, Conv, [ 128, 3, 2 ] ],
[ [ -1, 20 ], 1, Concat, [ 1 ] ], # cat head P4
[ -1, 1, C3, [ 256, False ] ], # 20 (P4/16-medium)
[ -1, 1, Conv, [ 256, 3, 2 ] ],
[ [ -1, 16 ], 1, Concat, [ 1 ] ], # cat head P5
[ -1, 1, C3, [ 512, False ] ], # 23 (P5/32-large)
[ [ 23, 26, 29 ], 1, Detect, [ nc, anchors ] ], # Detect(P3, P4, P5)
]
网络运行结果:
由结果可以看到,MobileNetV3_large模型比MobileNetV3_small多了更多的MobileNet_Block结构,参差倒置结构中通道数维度也增大了许多,速度比YOLOv5s慢近一半,但是参数更少,效果介乎MobileNetV3_small和YOLOv5s之间,可以作为模型对比,凸显自己模型的优势。🍃🍃🍃
说明:♨️♨️♨️
如果训练之后发现掉点纯属正常现象,因为轻量化网络在提速减少计算量的同时,也会降低精度。