YOLOv5 从入门到部署之:网络和损失函数

南山 AI约读社

Yolov5的网络结构主要由Backbone、Neck、Head构成,其中Backbone主要使用CSPdarknet+SPP结构,Neck使用PANet结,Head使用yolov3 head, yolov5提供了我们yolov5s、yolov5m、yolov5l、yolov5s的四种模型,这几种模型的网络结构相似,这里我们主要以yolov5s为例来分析yolov5的网络结构,yolov5s是四种网络中最小最基础的模型,其他的三种模型都是在加深深度和加宽宽度。这里我按照yolov5.yaml的模型文件自制了一个网络结构图,希望能够帮助大家更好的理解yolov5的网络结构。

file


1.1、Backbone

Backbone网络是检测网络的主干,网络提取出图像的高中低层的特征。

yolov5的backbone主要使用了Foucus和CSP和SPP结构组成。

(1)CSP结构(Cross Stage Partial)

CSP结构从网络结构设计的角度来解决以往工作在推理过程中需要很大计算量的问题,CSP结构认为推理计算过高的问题是由于网络优化中的梯度信息重复导致的。CSP结构通过将基础层的特征图划分为两个部分,然后通过CSP结构将它们合并,可以在能够实现更丰富的梯度组合的同时减少计算量。下面是CSP结构在各个网络中的表现。
file

Yolov5使用CSPDarknet作为Backbone,从输入图像中提取丰富的信息特征。CSPNet解决了其他大型卷积神经网络框架Backbone中网络优化的梯度信息重复问题,将梯度的变化从头到尾地集成到特征图中,因此减少了模型的参数量和FLOPS数值,既保证了推理速度和准确率,又减小了模型尺寸。

在yolov5中的CSP结构,yolov5采用了两种的CSP结构,第一种主要在Backbone中使用CSP_1(结构图中黄色)其中的Bottleneck就是采用Res 结构,第二种就是在Neck中使用CSP_2(结构图中绿色)其中的Bottleneck没有采用Res 结构。

CSP结构代码:common.pyc - BottleneckCSP

class BottleneckCSP(nn.Module):  

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)    #Conv = Conv2d + BatchNorm2d + LeakyReLU  
    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)]) #n个 Bottleneck  

def forward(self, x):  
    #特征图分为两个部分  
     #第一部分:先后经过 Conv(C+B+L) 和 n个 Bottleneck 以及 Conv2d 得到 y 1
    y1 = self.cv3(self.m(self.cv1(x)))    
    y2 = self.cv2(x)   #第二部分:经过卷积 conv 得到 y2  
    #合并两个部分:cat y1 和 y2, 然后进行BatchNorm2d标准化 和 LeakyReLU激活 再经过 Conv  
    return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1))))  


(2)Focus

Focus是一种对 feature map 的切片操作把宽度 w 和高度 h 的信息整合到 c 维度,具体来说就是将相距为 2 的四个位置进行堆叠到一个通道上去,因此长 h 和宽 w 都缩小两倍,通道 c 数增加了4倍,Focus模块设计用于降低FLOPS和提高速度,而不是提高mAP。

ov5github的讨论区。

file

                         Focus原理图

class Focus(nn.Module):  

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)  
    #::2 从0开始每两个取一个数, 1::2 从1开始每两个取一个数。第一维是c ,第二维是 w,第三维是 h  
    #x[..., ::2, ::2] 长宽从0开始相距2的位置也就是上图中的黄色0,x[..., 1::2, ::2] 宽从1 长从0开始相距2的位置也就是上图中的绿色1  
    #因此x[..., ::2, 1::2]为红色 2,x[..., 1::2, 1::2]为蓝色3,  
    #最后cat将这四个部分拼接到第一维 c,所以通道数c扩大四倍,w和h各缩小两倍  
    return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))  


(3)LeakyReLU

LeakyReLU是一种activation function,是ReLU的一种改进版本,要了解LeakyReLU,我们需要先回顾一下ReLU。ReLU(x) = max(0, x),ReLU是最常用的activation function,但ReLU会面临一个问题,在训练过程中部分神经元不会被激活,导致相应的参数永远不能被更新,我们称这种情况叫神经元挂掉,为了解决这种问题 LeakyReLU 提出了将 ReLU 的前半段设为 ax 而非0。
file

LeakyReLUa(x)=max(ax, x),激活函数有一个参数a,控制着leaks的数量,斜率非常的小,但是能够保证神经元不会挂掉。

在pytorch里有相关的函数可以使用 torch.nn.LeakyReLU(0.1,inplace=True),为了更好理解下面有实现的代码。

def leaky_relu(z, alpha=0.01):  

(4)SPP层:Spatial Pyramid Pooling(空间金字塔池化)
file

Spatial Pyramid Pooling 原理如上图,feature maps 是经过三个pooling窗口(蓝色,青绿,银灰的窗口) 进行pooling,将分别得到的结果在channel维度进行concat。SPP 可以增大感受野,有助于解决anchor和feature map的对齐问题。

SPP 的代码如下:  common.py - class SPP

1.  class SPP(nn.Module):  
  1. class SPP(nn.Module):
  2. Spatial pyramid pooling layer used in YOLOv3-SPP

  3. def init(self, c1, c2, k=(5, 9, 13)):
  4. super(SPP, self).init()
  5. c_ = c1 // 2 # hidden channels
  6. self.cv1 = Conv(c1, c_, 1, 1)
  7. self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
  8. self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])
  9. def forward(self, x):
  10. x = self.cv1(x)
  11. return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))

1.2、Neck

Yolov5 的 Neck 部分采用了 PANet 结构,Neck 主要用于生成特征金字塔。特征金字塔会增强模型对于不同缩放尺度对象的检测,从而能够识别不同大小和尺度的同一个物体。

PANet 结构是在FPN的基础上引入了 Bottom-up path augmentation 结构。FPN主要是通过融合高低层特征提升目标检测的效果,尤其可以提高小尺寸目标的检测效果。Bottom-up path augmentation结构可以充分利用网络浅特征进行分割,网络浅层特征信息对于目标检测非常重要,因为目标检测是像素级别的分类浅层特征多是边缘形状等特征。PANet 在 FPN 的基础上加了一个自底向上方向的增强,使得顶层 feature map 也可以享受到底层带来的丰富的位置信息,从而提升了大物体的检测效果。如下图所示,(a)FPN backbone, (b)Bottom-up path augmentation。

file


1.3、Head

Head 进行最终检测部分,在yolov5.yaml 的相关配置如下:

[-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 26 (P5/32-large)

可以看到,YOLOv5 采用了与 YOLOv3 相同的head网络,都是 1*1 的卷积结构,并有三组output,输出的特征图大小分辨为:

  • bs * 255 * 80 * 80;

  • bs * 255 * 40 * 40;

  • bs * 255 * 20 * 20;

其中,bs是batch size,255 的计算方式为 [na * (nc + 1 + 4)] ,具体参数含义如下:

  • na(number of anchor) 为每组 anchor 的尺度数量(YOLOv5中一共有 3 组anchor,每组有3个尺度);

  • nc 为number of class (coco的class 为80);

  • 1 为前景背景的置信度score;

  • 4 为中心点坐标和宽高;

最后,输出的特征图上会应用锚定框,并生成带有类别概率、置信度得分和包围框的最终输出向量。

与YOLOv3不同的是,在 anchor 上 YOLOv5 跨网格匹配规则的方式来区分 anchor 的正负样本。

file

关于anchor的分配任务,在这篇文章anchor的机制回顾有仔细的介绍:

目标检测中的Anchor机制回顾

2 Loss Function

YOLOv5 采用了BECLogits 损失函数计算objectness score的损失,class probability score采用了交叉熵损失函数(BCEclsloss),bounding box采用了GIOU Loss。

GIoU Loss 用来计算bounding box的 Loss, GIOU 是在CVPR2019中,论文https://arxiv.org/pdf/1902.09630.pdf中提出。GIOU直接把IoU设为回归的 Loss。
file

上面公式的意思将两个任意框A,B,我们找到一个最小的封闭形状C,让C可以把A,B包含在内,接着计算C中没有覆盖A和B的面积占C总面积的比值,然后用A与B的IoU减去这个比值。与IoU类似,GIoU也可以作为一个距离,loss可以用
file

GIoU代码:general.py  bbox_iou 函数  返回值是GIoU值

iou = inter / union  # iou  inter是A和B的相交面积,union是A和B的面积之和 ,计算A和B的IOU  

iou = inter / union # iou inter是A和B的相交面积,union是A和B的面积之和 ,计算A和B的IOU
if GIoU or DIoU or CIoU: # 可以选择三种IOU ,yolov5默认采用GIOU
cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1) # convex (smallest enclosing box) width
ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1) # convex height
if GIoU: # Generalized IoU https://arxiv.org/pdf/1902.09630.pdf
c_area = cw * ch + 1e-16 # convex area 计算公式里的C 就是能够包含A和B的最小矩形面积
return iou - (c_area - union) / c_area # GIoU

GIoU Loss代码 : 在compute_loss 函数

# Regression  

Regression

pxy = ps[:, :2].sigmoid() 2. - 0.5 #预测框的中心点
pwh = (ps[:, 2:4].sigmoid()
2) * 2 anchors[i] #预测框的hw
pbox = torch.cat((pxy, pwh), 1).to(device) # predicted box
iou = bbox_iou(pbox.T, tbox[i], x1y1x2y2=False, CIoU=True) # iou(prediction, target)
lbox += (1.0 - iou).mean() # iou loss giou_loss = 1- giou

Classloss和Obj loss 代码:在compute_loss 函数  obj loss 采用BEC Logits loss,class loss 采用BCEcls loss二分类交叉熵损失。

    # Objectness  

# Objectness  
tobj[b, a, gj, gi] = (1.0 - model.gr) + model.gr * iou.detach().clamp(0).type(tobj.dtype)  # iou ratio  

# Classification  
if model.nc > 1:  # cls loss (only if multiple classes)  
    t = torch.full_like(ps[:, 5:], cn, device=device)  # targets  
    t[range(n), tcls[i]] = cp  
    lcls += BCEcls(ps[:, 5:], t)  # BCE  

# Append targets to text file  
# with open('targets.txt', 'a') as file:  
#     [file.write('%11.5g ' * 4 % tuple(x) + '\n') for x in torch.cat((txy[i], twh[i]), 1)]  

lobj += BCEobj(pi[..., 4], tobj) * balance[i] # obj loss


3 Next

《YOLOv5 来了!Pytorch 实现,支持 ONNX 和 CoreML》https://bbs.cvmart.net/topics/2345