Yolov5学习笔记5——v5.0源码剖析——Backbone部分2

netron得到yolov5s的框架结构图如下,可以非常直观的得到关于backbone部分的网络结构图。

:所使用的代码版本为 2020年11月24日发布的Yolov5-master。

YOLOv5s结构图如下所示:

Backbone结构图

  • backbone的意义是:在不同图像细粒度上聚合并形成图像特征的卷积神经网络;

  • backbone所需的主要模块在common.py里面可以找到。

从整体结构中我们抽出backbone部分学习:

backbone的结构图如下:

Netron打开yolov5s.pt导出的BackBone部分的框架图如下:

backbone对应的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# YOLOv5 backbone
backbone:
# [from, number, module, args]
[[-1, 1, Focus, [64, 3]], # 0-P1/2
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
[-1, 3, BottleneckCSP, [128]],
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 9, BottleneckCSP, [256]],
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, BottleneckCSP, [512]],
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 1, SPP, [1024, [5, 9, 13]]],
[-1, 3, BottleneckCSP, [1024, False]], # 9
]

从上述代码中我们可以看到backbone由如下组成:

BACKBONE =FOCUS(1个)+CONV (1个)+BCSP(3个)+CONV (1个)+BCSP(9个)+CONV (1个)+SPP(1个)+BCSP(1个)

  • 注:这里的BCSP相当于CSP

Backbone源码解析

Focus模块

Focus()函数在common.py中,对应的源码如下:

1
2
3
4
5
6
7
8
class Focus(nn.Module):
# Focus wh information into c-space
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
super(Focus, self).__init__()
self.conv = Conv(c1 * 4, c2, k, s, p, g, act)

def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2)
return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))

根据前述代码我们知道,输入的图片尺寸为640×640×3,而Focus()函数的功能为:将640 × 640 × 3的图像输入Focus结构,采用切片操作,先变成320 × 320 × 12的特征图,再经过3 × 3的卷积操作,输出通道32,最终变成320 × 320 × 32的特征图

Focus模块的结构图如下:

CBL模块(CBH)

该部分对应的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Conv(nn.Module):
# Standard convolution
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
super(Conv, self).__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
self.bn = nn.BatchNorm2d(c2)
self.act = nn.Hardswish() if act else nn.Identity()

def forward(self, x):
return self.act(self.bn(self.conv(x)))

def fuseforward(self, x):
return self.act(self.conv(x))

CONV是一个标准的卷积模块。

从源码第5 6 7行可以看出,激活函数变成了Hardwish(),实际上这里不应该叫CBL模块了,应该是CBH模块。

  • conv:来自于代码的torch.nn.Conv2d,是一个卷积操作
  • bn:来自于代码的torch.nn.BatchNorm2d:归一化处理,使batch里面的feature map 满足均值为1,方差为0 的正太分布
  • Hardswish:激活函数
    故:CBL=CONV+BN+Hardswish

BottleneckCSP模块

3个BCSP相当于是几个标准的Bottleneck的堆叠+几个标准卷积层。

  • BottleneckCSP的网络结构如下:

  • 残差组件Resunit的网络结构如下:

  • BottleneckCSP的源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class BottleneckCSP(nn.Module):
    # CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
    super(BottleneckCSP, self).__init__()
    c_ = int(c2 * e) # hidden channels
    self.cv1 = Conv(c1, c_, 1, 1)
    self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
    self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
    self.cv4 = Conv(2 * c_, c2, 1, 1)
    self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3)
    self.act = nn.LeakyReLU(0.1, inplace=True)
    self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])

    def forward(self, x):
    y1 = self.cv3(self.m(self.cv1(x)))
    y2 = self.cv2(x)
    return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1))))

:nn.sequential将所有的块链接在一起。self.bn = nn.BatchNorm2d(2 * c_)就是concat 块,cv2,cv3对应于图中的concat;

  • Resunit的源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Bottleneck(nn.Module):
    # Standard bottleneck
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
    super(Bottleneck, self).__init__()
    c_ = int(c2 * e) # hidden channels
    self.cv1 = Conv(c1, c_, 1, 1)
    self.cv2 = Conv(c_, c2, 3, 1, g=g)
    self.add = shortcut and c1 == c2

    def forward(self, x):
    return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

:cv1、cv2对应于图中的CBL模块,add不变。

SPP某块

  • SPP模块,就是常说的空间金字塔池化模块,分别采用5/9/13的最大池化,在进行concat融合,提高感受野。

  • SPP的输入时512×512×20,经过1×1的卷积层后输出256×20×20,然后经过并列的三个Maxpool进行下采样,将结果与其初始特征相加,输出1024×20×20,最后用512的卷积核将其恢复到512×20×20.

  • SPP模块的结构图如下:

  • SPP模块对应的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class SPP(nn.Module):
    # Spatial pyramid pooling layer used in YOLOv3-SPP
    def __init__(self, c1, c2, k=(5, 9, 13)):
    super(SPP, self).__init__()
    c_ = c1 // 2 # hidden channels
    self.cv1 = Conv(c1, c_, 1, 1)
    self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
    self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])

    def forward(self, x):
    x = self.cv1(x)
    return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))