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

2,275次阅读
没有评论

共计 6096 个字符,预计需要花费 16 分钟才能阅读完成。

1 网络结构

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

 

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

 

1.1、Backbone

Backbone 网络是检测网络的主干,网络提取出图像的高中低层的特征。
yolov5 的 backbone 主要使用了 Foucus 和 CSP 和 SPP 结构组成。

(1)CSP 结构(Cross Stage Partial)

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

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

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):      
# 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 的讨论区。

 

 

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

                         Focus 原理图

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。

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

LeakyReLUa(x)=max(ax, x),激活函数有一个参数 a,控制着 leaks 的数量,斜率非常的小,但是能够保证神经元不会挂掉。
在 pytorch 里有相关的函数可以使用 torch.nn.LeakyReLU(0.1,inplace=True),为了更好理解下面有实现的代码。
def leaky_relu(z, alpha=0.01):
     return np.maximum(alpha*z, z)
(4)SPP 层:Spatial Pyramid Pooling(空间金字塔池化)

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

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

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

# 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))

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。

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

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 的正负样本。

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

关于 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。

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

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

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

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

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  
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      
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 从入门到部署》系列将会 介绍:实践部分——如何使用 YOLOv5 训练 自己的数据集。

正文完
 0
一诺
版权声明:本站原创文章,由 一诺 于2021-02-17发表,共计6096字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
验证码