改进YOLOv5系列:基于互补搜索技术和新颖架构设计组合MobileNetV3结构作为Backbone主干网络,打造不同的检测器

时间:2022-11-29 20:55:12
  • ????统一使用 YOLOv5 代码框架,结合不同模块来构建不同的YOLO目标检测模型。
  • ????包含大量的改进方式,降低改进难度,改进点包含【Backbone特征主干】【Neck特征融合】【Head检测头】【注意力机制】【IoU损失函数】【NMS】【Loss计算方式】【自注意力机制】、【数据增强部分】【标签分配策略】、【激活函数】等各个部分

一、MobileNetV3论文理论部分

改进YOLOv5系列:基于互补搜索技术和新颖架构设计组合MobileNetV3结构作为Backbone主干网络,打造不同的检测器
论文展示了基于互补搜索技术和新颖架构设计组合的下一代 MobileNet。 MobileNetV3 通过硬件感知网络架构搜索 (NAS) 的组合与 NetAdapt 算法相结合,针对手机 CPU 进行了调整,然后通过新颖的架构进步进行了改进。本文开始探索自动搜索算法和网络设计如何协同工作以利用互补方法改进整体技术水平。通过这个过程,我们创建了两个新的 MobileNet 模型以供发布:MobileNetV3-Large 和 MobileNetV3-Small,它们分别针对高资源和低资源用例。然后对这些模型进行调整并将其应用于对象检测和语义分割的任务。对于语义分割(或任何密集像素预测)的任务,我们提出了一种新的高效分割解码器 Lite Reduced Atrous Spatial Pyramid Pooling (LR-ASPP)。我们在移动分类、检测和分割方面取得了最先进的成果。与 MobileNetV2 相比,MobileNetV3-Large 在 ImageNet 分类上的准确度提高了 3.2%,同时延迟减少了 15%。与 MobileNetV2 相比,MobileNetV3-Small 的准确度提高了 4.6%,同时延迟降低了 5%。 MobileNetV3-Large 检测速度快 25%,准确率与 MobileNetV2 在 COCO 检测上大致相同。对于 Cityscapes 分割,MobileNetV3-Large LR-ASPP 比 MobileNetV2 R-ASPP 快 30%,精度相似。

具体论文细节;可以参考论文:https://arxiv.org/abs/1905.02244

网络架构

改进YOLOv5系列:基于互补搜索技术和新颖架构设计组合MobileNetV3结构作为Backbone主干网络,打造不同的检测器

实验

改进YOLOv5系列:基于互补搜索技术和新颖架构设计组合MobileNetV3结构作为Backbone主干网络,打造不同的检测器

二、YOLOv5配置

YOLOv5添加mobile.yaml配置文件

增加以下yolov5_mobile.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

backbone:
  # [from, number, module, args]
  [[-1, 1, conv_bn_hswish, [16, 2]],                              # 0-p1/2

   [-1, 1, MobileNetV3_InvertedResidual, [16,  16, 3, 2, 1, 0]],  # 1-p2/4

   [-1, 1, MobileNetV3_InvertedResidual, [24,  72, 3, 2, 0, 0]],  # 2-p3/8
   [-1, 1, MobileNetV3_InvertedResidual, [24,  88, 3, 1, 0, 0]],  # 3

   [-1, 1, MobileNetV3_InvertedResidual, [40,  96, 5, 2, 1, 1]],  # 4-p4/16
   [-1, 1, MobileNetV3_InvertedResidual, [40, 240, 5, 1, 1, 1]],  # 5
   [-1, 1, MobileNetV3_InvertedResidual, [40, 240, 5, 1, 1, 1]],  # 6
   [-1, 1, MobileNetV3_InvertedResidual, [48, 120, 5, 1, 1, 1]],  # 7
   [-1, 1, MobileNetV3_InvertedResidual, [48, 144, 5, 1, 1, 1]],  # 8

   [-1, 1, MobileNetV3_InvertedResidual, [96, 288, 5, 2, 1, 1]],  # 9-p5/32
   [-1, 1, MobileNetV3_InvertedResidual, [96, 576, 5, 1, 1, 1]],  # 10
   [-1, 1, MobileNetV3_InvertedResidual, [96, 576, 5, 1, 1, 1]],  # 11

   [-1, 1, SPPF, [1024, 5]],  # 12
  ]
        
# YOLOv5 v6.0 head
head:
  [[-1, 1, Conv, [512, 1, 1]], # 13
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 8], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, C3, [512, False]],  # 16

   [-1, 1, Conv, [256, 1, 1]], # 17
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 3], 1, Concat, [1]],  # cat backbone P3
   [-1, 3, C3, [256, False]],  # 20 (P3/8-small)

   [-1, 1, Conv, [256, 3, 2]],
   [[-1, 17], 1, Concat, [1]], # cat head P4
   [-1, 3, C3, [512, False]],  # 23 (P4/16-medium)

   [-1, 1, Conv, [512, 3, 2]],
   [[-1, 13], 1, Concat, [1]],  # cat head P5
   [-1, 3, C3, [1024, False]],  # 26 (P5/32-large)

   [[20, 23, 26], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]

核心代码

./models/common.py文件增加以下模块

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 = nn.Hardswish()

    def forward(self, x):
        return self.act(self.bn(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] #mg

        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),
                nn.Hardswish() if use_hs else nn.ReLU(),
                # Squeeze-and-Excite #mg
                SeBlock(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),
                nn.Hardswish() if use_hs else nn.ReLU(),
                # 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
                SeBlock(hidden_dim) if use_se else nn.Sequential(),
                nn.Hardswish() if use_hs else nn.ReLU(),
                # 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

class SeBlock(nn.Module):
    def __init__(self, in_channel, reduction=4):
        super().__init__()
        self.Squeeze = nn.AdaptiveAvgPool2d(1)

        self.Excitation = nn.Sequential()
        self.Excitation.add_module('FC1', nn.Conv2d(in_channel, in_channel // reduction, kernel_size=1))  #mg
        self.Excitation.add_module('ReLU', nn.ReLU())
        self.Excitation.add_module('FC2', nn.Conv2d(in_channel // reduction, in_channel, kernel_size=1))
        self.Excitation.add_module('Sigmoid', nn.Sigmoid())

    def forward(self, x):
        y = self.Squeeze(x)
        ouput = self.Excitation(y)
        return x*(ouput.expand_as(x))

其他配置

找到./models/yolo.py文件下里的parse_model函数,将类名加入进去

for i, (f, n, m, args) in enumerate(d[‘backbone’] + d[‘head’]):`内部
对应位置 下方只需要增加 代码

参考代码

elif m in [MobileNetV3_InvertedResidual, conv_bn_hswish]:
            c1, c2 = ch[f], args[0]
            if c2 != no:  # if not output
                c2 = make_divisible(c2 * gw, 8)
            args = [c1, c2, *args[1:]]

运行代码

python train.py --cfg yolov5_mobile.yaml