YOLOv5模型改进策略源码示例

YOLOv5模型改进策略源码示例

YOLO目标检测算法作为单阶段目标检测算法的代表在各个领域都有广泛的应用,在前几篇文章中我们已经对YOLO的Backbone、Neck、Head进行了较为详细的解读,这篇文章主要是从添加注意力机制来提升YOLO检测效果的角度出发,深入探讨如何将不同的注意力机制加入到YOLO中。

1 几点必要知识

1.1 YOLO网络结构

Backbone解读
Neck解读
Head解读

1.2 注意力机制

Channel Attention通道注意力网络结构、源码解读系列一
BAM&DAN原文、结构、源码详解

1.3 修改哪?

YOLO的网络结构主要由yolo文件夹下的models文件夹下的相关配置文件及脚本进行解析,其中比较重要的几个部分:
(1)yaml配置文件:配置文件中包含网络结构的组成部分,修改网络结构就是对配置文件进行修改,如yolov5s.yaml原始文件内容:

# YOLOv5  by Ultralytics, GPL-3.0 license

# Parameters
nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # 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:
  # [from, number, module, args]
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 6, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],#256 16 16
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, SPPF, [1024, 5]],  # 9
  ]

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

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

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

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

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

(2)common.py:yaml配置文件中的网络结构组成部分的细节代码主要存在与common.py中,如Conv模块、C3模块、SPPF模块。也就是说,如果我们要往网络结构中添加新的模块,不仅要在yaml配置文件中列出,更要在common.py中定义好,这样在后续的网络结构解析过程中才不会报错。
(3)yolo.py:yolo这个脚本是根据上述两个内容进行网络结构解析的关键部分,主要依靠parse_model()函数实现。在parse_model函数内部,它会根据配置文件传入的模块去查找这部分代码,将其按照网络结构的顺序加入到总体结构中,所以我们需要注意一点:common.py中新添加的模块也需要在yolo的parse_model中指出来,比如我们要新添加CBAM模块,那就应该在parse_model的if判断中这么写(最后加上CBAM):

if m in [Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,
         BottleneckCSP, C3, C3TR, C3SPP, C3Ghost,CBAM]:
    c1, c2 = ch[f], args[0]

2 添加CBAM模块

2.1 修改网络结构配置文件

如果我们要在Backbone中加入一层新的网络,那么势必会造成总体网络数量的增长,所以要注意加入后的后续网络序列顺序问题:原来SPPF是第9层,我们在SPPF之前加入CBAM,那么SPPF及其之后的网络序列都发生了变化,即网络序列+1,那么在concat和Detect的时候我们就要注意,CBAM模块之后的序列+1,CBAM之前的不用变。当然,有些时候不改也不会报错,但其实与原来的网络结构是不符的,会导致训练结果不具有说服力,具体配置文件:

# YOLOv5  by Ultralytics, GPL-3.0 license

# Parameters
nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # 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:
  # [from, number, module, args]
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2
#   [-1, 1, DepthSeperabelConv2d, [32, 3, 2]],  # 1-P2/4
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 6, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, CBAM,[1024]],# 新添加的模块,序列为9,
   [-1, 1, SPPF, [1024, 5]],  # 10
  ]

# YOLOv5 v6.0 head
head:
  [[-1, 1, Conv, [512, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, C3, [512, False]],  # 14

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

   [-1, 1, Conv, [256, 3, 2]],
   [[-1, 15], 1, Concat, [1]],  # cat head P4,注意这里有变化
   [-1, 3, C3, [512, False]],  # 21 (P4/16-medium)

   [-1, 1, Conv, [512, 3, 2]],
   [[-1, 11], 1, Concat, [1]],  # cat head P5,注意这里有变化
   [-1, 3, C3, [1024, False]],  # 24 (P5/32-large)

   [[18, 21, 24], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5),注意这里有变化
  ]

2.2 common.py中定义网络的新结构

这里不多赘述,对注意力机制有疑问的同学可以移步之前的注意力机制专题讲解。

class ChannelAttention(nn.Module):
    def __init__(self, in_planes, ratio=16):
        super(ChannelAttention, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.max_pool = nn.AdaptiveMaxPool2d(1)
        self.f1 = nn.Conv2d(in_planes, in_planes // ratio, 1, bias=False)
        self.relu = nn.ReLU()
        self.f2 = nn.Conv2d(in_planes // ratio, in_planes, 1, bias=False)
        self.sigmoid = nn.Sigmoid()
    def forward(self, x):
        avg_out = self.f2(self.relu(self.f1(self.avg_pool(x))))
        max_out = self.f2(self.relu(self.f1(self.max_pool(x))))
        out = self.sigmoid(avg_out + max_out)
        return out
class SpatialAttention(nn.Module):
    def __init__(self, kernel_size=7):
        super(SpatialAttention, self).__init__()
        assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
        padding = 3 if kernel_size == 7 else 1
        self.conv = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
        self.sigmoid = nn.Sigmoid()
    def forward(self, x):
        avg_out = torch.mean(x, dim=1, keepdim=True)
        max_out, _ = torch.max(x, dim=1, keepdim=True)
        x = torch.cat([avg_out, max_out], dim=1)
        x = self.conv(x)
        return self.sigmoid(x)

class CBAM(nn.Module):
    # CSP Bottleneck with 3 convolutions
    def __init__(self, c1, c2, ratio=16, kernel_size=7):  # ch_in, ch_out, number, shortcut, groups, expansion
        super(CBAM, self).__init__()
        self.channel_attention = ChannelAttention(c1, ratio)
        self.spatial_attention = SpatialAttention(kernel_size)
    def forward(self, x):
        out = self.channel_attention(x) * x
        out = self.spatial_attention(out) * out
        return out

2.3 在yolo中加入新的结构

if m in [Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,
         BottleneckCSP, C3, C3TR, C3SPP, C3Ghost,CBAM]:
    c1, c2 = ch[f], args[0]

2.4 验证网络结构是否完成修改

在yolo.py中添加main函数进行检验:

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--cfg', type=str, default='这里填修改后的yaml文件路径,如yolov5s_cbam.yaml', help='model.yaml')
    parser.add_argument('--device', default='0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    parser.add_argument('--profile', action='store_true', help='profile model speed')
    opt = parser.parse_args()
    opt.cfg = check_yaml(opt.cfg)  # check YAML
    print_args(FILE.stem, opt)
    set_logging()
    device = select_device(opt.device)

    # Create model
    model = Model(opt.cfg).to(device)
    model.train()

    # Profile
    if opt.profile:
        img = torch.rand( 1, 3, 640, 640).to(device)
        print(img.shape)
        y = model(img, profile=True)

如果修改无误,那么将会得到新的网络输出:
YOLOv5模型改进策略源码示例_第1张图片

你可能感兴趣的:(YOLOv5,人工智能,目标检测,深度学习)