解决问题:YOLOv5主干特征提取网络采用C3结构,带来较大的参数量,检测速度较慢,应用受限,在某些真实的应用场景如移动或者嵌入式设备,如此大而复杂的模型时难以被应用的。首先是模型过于庞大,面临着内存不足的问题,其次这些场景要求低延迟,或者说响应速度要快,想象一下自动驾驶汽车的行人检测系统如果速度很慢会发生什么可怕的事情。所以,研究小而高效的CNN模型在这些场景至关重要,至少目前是这样,尽管未来硬件也会越来越快。本文尝试将主干特征提取网络替换为更轻量的MobileNet网络,以实现网络模型的轻量化,平衡速度和精度。
一、MobileNetV3简介
MobileNetV3,是谷歌在2019年3月21日提出的轻量化网络架构,在前两个版本的基础上,加入神经网络架构搜索(NAS)和h-swish激活函数,并引入SE通道注意力机制,性能和速度都表现优异,受到学术界和工业界的追捧。
引用大佬的描述:MobileNet V3 = MobileNet v2 + SE结构 + hard-swish activation +网络结构头尾微调
摘要: 我们提出下一代的mobilenet基于一种互补搜索技术的组合作为一种新颖的建筑设计。MobileNetV3调到手机cpu通过硬件组合感知网络架构搜索(NAS)的补充NetAdapt算法,然后进行改进通过新颖的建筑进步。这篇论文从探索如何自动搜索算法和网络工作设计可以一起利用互补的工作提高整体技术水平的方法。通过在这个过程中,我们创建了两个新的MobileNet模型:MobileNetV3-Large和MobileNetV3-Small针对高资源和低资源用例。这些然后调整模型并将其应用于对象检测和语义分割任务。为了完成任务语义分割(或任何密集像素预测),我们提出了一种新的高效分割解码器空间金字塔池(LR-ASPP)。我们实现新状态的艺术结果移动分类,检测和分割。MobileNetV3-Large则高出3.2%准确的ImageNet分类,同时减少延迟与MobileNetV2相比减少了20%。MobileNetV3-Small是与MobileNetV2模型相比,准确率提高了6.6%具有相当的延迟。MobileNetV3-Large检测与MobileNetV2在COCO检测上的精度大致相同,速度快了25%以上。MobileNetV3-Large
LRASPP在类似情况下比MobileNetV2 R-ASPP快34%城市景观分割的准确性。论文地址:https://arxiv.org/abs/1905.02244.pdf
代码:https://github.com/LeBron-Jian/DeepLearningNote
MobileNetV1&MobileNetV2&MobileNetV3总结
MobileNet V3 相关技术如下:
1,用 MnasNet 搜索网络结构
2,用 MobileNetV1 的深度可分离卷积
3,用 MobileNetV2 的倒置残差线性瓶颈结构
4,引入轻量级注意力 SE模块
5,使用新的激活函数 h-swish(x)
6,网络搜索中利用两个策略:资源受限的 NAS 和 NetAdapt
7,修改MobileNet V2 最后部分减小计算
二、YOLOv5结合MobileNetV3_small
方 法:
第一步修改common.py,增加MobileNetV3模块。
class StemBlock(nn.Module):
def __init__(self, c1, c2, k=3, s=2, p=None, g=1, act=True):
super(StemBlock, self).__init__()
self.stem_1 = Conv(c1, c2, k, s, p, g, act)
self.stem_2a = Conv(c2, c2 // 2, 1, 1, 0)
self.stem_2b = Conv(c2 // 2, c2, 3, 2, 1)
self.stem_2p = nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)
self.stem_3 = Conv(c2 * 2, c2, 1, 1, 0)
def forward(self, x):
stem_1_out = self.stem_1(x)
stem_2a_out = self.stem_2a(stem_1_out)
stem_2b_out = self.stem_2b(stem_2a_out)
stem_2p_out = self.stem_2p(stem_1_out)
out = self.stem_3(torch.cat((stem_2b_out, stem_2p_out), 1))
return out
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):
y = self.sigmoid(x)
return x * y
class SELayer(nn.Module):
def __init__(self, channel, reduction=4):
super(SELayer, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
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)
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_InvertedResidual(nn.Module):
def __init__(self, inp, oup, hidden_dim, kernel_size, stride, use_se, use_hs):
super(MobileNetV3_InvertedResidual, 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(),
# Eca_layer(hidden_dim) if use_se else nn.Sequential(),#1.13.2022
# 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(),
# Eca_layer(hidden_dim) if use_se else nn.Sequential(), # 1.13.2022
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
第二步:将yolo.py中注册模块。
if m in [Conv,MobileNetV3_InvertedResidual,ShuffleNetV2_InvertedResidual,
]:
# 加入h_sigmoid,h_swish,SELayer,conv_bn_hswish,MobileNetV3_InvertedResidual五个模块
第三步:修改yaml文件
backbone:
# MobileNetV3-large
# [from, number, module, args]
[[-1, 1, conv_bn_hswish, [16, 2]], # 0-p1/2
[-1, 1, MobileNetV3_InvertedResidual, [ 16, 16, 3, 1, 0, 0]], # 1-p1/2
[-1, 1, MobileNetV3_InvertedResidual, [ 24, 64, 3, 2, 0, 0]], # 2-p2/4
[-1, 1, MobileNetV3_InvertedResidual, [ 24, 72, 3, 1, 0, 0]], # 3-p2/4
[-1, 1, MobileNetV3_InvertedResidual, [ 40, 72, 5, 2, 1, 0]], # 4-p3/8
[-1, 1, MobileNetV3_InvertedResidual, [ 40, 120, 5, 1, 1, 0]], # 5-p3/8
[-1, 1, MobileNetV3_InvertedResidual, [ 40, 120, 5, 1, 1, 0]], # 6-p3/8
[-1, 1, MobileNetV3_InvertedResidual, [ 80, 240, 3, 2, 0, 1]], # 7-p4/16
[-1, 1, MobileNetV3_InvertedResidual, [ 80, 200, 3, 1, 0, 1]], # 8-p4/16
[-1, 1, MobileNetV3_InvertedResidual, [ 80, 184, 3, 1, 0, 1]], # 9-p4/16
[-1, 1, MobileNetV3_InvertedResidual, [ 80, 184, 3, 1, 0, 1]], # 10-p4/16
[-1, 1, MobileNetV3_InvertedResidual, [112, 480, 3, 1, 1, 1]], # 11-p4/16
[-1, 1, MobileNetV3_InvertedResidual, [112, 672, 3, 1, 1, 1]], # 12-p4/16
[-1, 1, MobileNetV3_InvertedResidual, [160, 672, 5, 1, 1, 1]], # 13-p4/16
[-1, 1, MobileNetV3_InvertedResidual, [160, 960, 5, 2, 1, 1]], # 14-p5/32 原672改为原算法960
[-1, 1, MobileNetV3_InvertedResidual, [160, 960, 5, 1, 1, 1]], # 15-p5/32
]
根据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
修改的时候,需要注意/8,/16,/32等位置特征图的变换
同样的,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)
]
三、YOLOv5结合MobileNetV3_large
MobileNetV3_large和MobileNetV3_small区别在于yaml文件中head中concat连接不同,深度因子和宽度因子不同。接下来我们就直接改动yaml的部分,其余参考上面步骤。
然后根据MobileNetv3的网络结构来修改配置文件。
修改后代码如下:
# 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之间,可以作为模型对比,凸显自己模型优势。
结 果:如果训练之后发现掉点纯属正常现象,因为轻量化网络在提速减少计算量的同时会降低精度。
注:主干网络的替换不仅仅是适用改进YOLOv5,也可以改进其他的YOLO网络以及目标检测网络,比如YOLOv4、v3等。