YOLO V3 详解
概述
目前在做工业上的目标检测项目,用了很久的Yolov3算法模型,刚开始是基于Keras平台的,后来使用基于Darknet平台的训练。项目要结束了,终于有时间好好的整理下笔记。先推出算法网络的原理,后续写基于darknet的C++的部署和利用Opencv的dnn模块调用模型的C++部署。
网络介绍
Yolov3和V2、V1的比较
Yolov3算法相比YOLOV2和YOLOV1算法保留的功能有:
- “分而治之”,从yolov1开始,yolo算法就是通过划分单元格来做检测,只是划分的数量不一样。Yolov1分成7x7网格,yolov3分成13x13网格。
- 采用"leaky ReLU"作为激活函数。
- 端到端进行训练。一个损失函数(loss function)搞定训练,只需关注输入端和输出端。
- 从yolov2开始,yolo就用BN算法(batch normalization)作为正则化、加速收敛和避免过拟合,把BN层和leaky relu层接到每一层卷积层之后。
- 多尺度训练。在速度和准确率之间权衡。想速度快点,可以牺牲准确率;想准确率高点儿,可以牺牲一点速度。
- Yolo系列的每一代的提升很大一部分决定于神经网络(backbone)的提升,从v2的darknet-19到v3的darknet-53。Yolov3还提供替换神经网络backbone的tiny darknet网络。tiny darknet网络是darknet-53的轻量级网络。
Yolov3网络结构
Yolov3的网络结构如下所示:
如上所示,Yolov3的网络结构可以分为三个部分。分别为输入、基础网络Darknet-53和Yolov3的三个分支(y1,y2,y3)。其中Darknet-53网络中的DBL模块是卷积、BN算法、线性激活函数Leaky relu的总称,也是yolov3算法的最基本单元。
BN算法:Batch Normalization是2015年一篇论文中提出的数据归一化方法,往往用在深度神经网络中激活层之前。其作用可以加快模型训练时的收敛速度,使得模型训练过程更加稳定,避免梯度爆炸或者梯度消失。并且起到一定的正则化作用,几乎代替了Dropout。
核心公式为:
- 输入为数值集合( B B B),可训练参数 [ γ \gamma γ, β \beta β] ;
- BN的具体操作为:先计算的 B B B均值和方差,之后将 B B B集合的均值、方差变换为0和1(对应上式中 x i − u B σ B 2 + ϵ \frac{x_i - u_B}{\sqrt{\sigma^2_B + \epsilon}} σB2+ϵxi−uB),最后将 x i − u B σ B 2 + ϵ \frac{x_i - u_B}{\sqrt{\sigma^2_B + \epsilon}} σB2+ϵxi−uB乘以 γ \gamma γ再加上 β \beta β输出。[ γ \gamma γ, β \beta β]是可训练参数,参与整个网络的BP。
归一化的目的:将数据规整到统一区间,减少数据的发散程度,降低网络的学习难度。BN的精髓在于归一之后,使用作为还原参数,在一定程度上保留原数据的分布。
线性激活函数Leaky ReLU:是线性激活函数ReLu的改进版,它在负轴保留了非常小的常数leak,使得输入信息小于0时,信息没有完全丢掉,进行了相应的保留。即ReLU在取值小于零部分没有梯度,Leaky relu在取值小于0部分给一个很小的梯度。函数坐标图如下:
公式如下:
L
e
a
k
y
R
e
L
u
(
x
)
=
{
x
,
x
>
0
l
e
a
k
∗
x
x
<
0
Leaky ReLu(x)=\begin{cases}x,&x>0\\leak*x&x<0 \end{cases}
LeakyReLu(x)={x,leak∗xx>0x<0
其中leak是小数,例如leak=0.1。
将激活函数代入公式中:
- 将Leak ReLu看作神经网络中的一层,设第 l l l层输出的是 x l x^l xl,然后输入Leak ReLu激活函数输出的是 x l + 1 x^{l+1} xl+1。
- 设损失函数
L
L
L关于第l层输出的
x
l
x^l
xl偏导是
resn:n代表数字,有res1,res2, … ,res8等等,表示这个res_block里含有多少个res_unit。这是yolov3的大组件,yolov3开始借鉴了ResNet的残差结构,使用这种结构可以让网络结构更深(从v2的darknet-19上升到v3的darknet-53,前者没有残差结构)。对于res_block,可以在上图的右下角直观看到,其基本组件也是DBL。
concat:张量拼接。将darknet中间层和后面的某一层的上采样进行拼接。拼接的操作和残差层add的操作是不一样的,拼接会扩充张量的维度,而add只是直接相加不会导致张量维度的改变。
整个yolov3网络包含252层,组成如下:
从上图可以看出,对于代码层面的layers数量一共有252层,包括add层23层(主要用于res_block的构成,每个res_unit需要一个add层,一共有1+2+8+8+4=23层)。除此之外,BN层和LeakyReLU层数量完全一样(72层),在网络结构中的表现为:每一层BN后面都会接一层LeakyReLU。卷积层一共有75层,其中有72层后面都会接BN+LeakyReLU的组合构成基本组件DBL。看结构图,可以发现上采样和concat都有2次,和表格分析中对应上。每个res_block都会用上一个零填充,一共有5个res_block。
整个yolov3结构里面,是没有池化层和全连接层的。前向传播过程中,张量的尺寸变换是通过改变卷积核的步长来实现的,比如步长stride=(2, 2),这就等于将图像边长缩小了一半(即面积缩小到原来的1/4)。在yolov2中,要经历5次缩小,会将特征图缩小到原输入尺寸的 ,即1/32。输入为416x416,则输出为13x13(416/32=13)。Yolov3也和yolov2一样,神经网络backbone都会将输出特征图缩小到输入的1/32。所以,通常都要求输入图片是32的倍数。
下图所示为yolov2和yolov3的结构对比图。
yolov2中对于前向过程中张量尺寸变换,都是通过最大池化来进行,一共有5次。而yolov3是通过卷积核增大步长来进行,也是5次。(darknet-53最后面有一个全局平均池化,在yolov3里面没有这一层,所以张量维度变化只考虑前面那5次)。
yolov3输出了3个不同尺度的特征图feature map,这个借鉴了FPN(feature pyramid networks)网络,采用多尺度来对不同size的目标进行检测,越精细的grid cell就可以检测出越精细的物体。y1,y2和y3的深度都是255,边长的规律是13:26:52。对于COCO类别而言,有80个种类,所以每个box应该对每个种类都输出一个概率。yolov3设定的是每个网格单元预测3个box,所以每个box需要有(x, y, w, h, confidence)五个基本参数,然后还要有80个类别的概率。所以3*(5 + 80) = 255。Yolov3用上采样的方法来实现这种多尺度的特征图feature map,结合yolov3的网络结构图中concat连接的两个张量是具有一样尺度的(两处拼接分别是26x26尺度拼接和52x52尺度拼接,通过上采样来保证concat拼接的张量尺度相同)。
目标位置预测以及损失函数选择
位置预测结构图:
如上图所示,
b
x
b_x
bx和
b
y
b_y
by为检测框的中心点,
t
x
t_x
tx和
t
y
t_y
ty为上图左上角网格坐标(1,1)的偏移量。
σ
(
t
x
)
\sigma(t_x)
σ(tx)和
σ
(
t
y
)
\sigma(t_y)
σ(ty)为对应(y1,y2,y3)尺寸归一化后的偏移量。
c
x
c_x
cx和
c
y
c_y
cy为单元网格的距离,这里都为1。
p
w
p_w
pw和
p
y
p_y
py为anchor的宽、高与特征图(feature map)的宽、高的比值。yolov3算法利用k-mean算法对数据中的标记框进行聚类,生成9个anchor,每个输出(y1,y2,y3)各有三个anchor。
b
w
b_w
bw和
b
h
b_h
bh为检测框的宽和高。
Yolov3损失函数:
在YOLOV3中,Loss分成三个部分:
1、目标框位置(左上角和长宽)带来的误差,也即是box带来的loss。而在box带来的loss中又分为带来的BCE Loss以及带来的MSE Loss。
2、目标置信度带来的误差,也就是目标obj带来的loss(BCE Loss)。
3、类别带来的误差,也就是种类class带来的loss(类别数个BCE Loss)。
其中BCE Loss为二值交叉熵损失函数,MSE Loss为均方误差损失函数。2-
w
i
∗
h
i
w_i*h_i
wi∗hi为制衡值。因为当存在小目标的时候,小目标与anchor的IOU会相对变小,所以
w
i
∗
h
i
w_i*h_i
wi∗hi越小,2-
w
i
∗
h
i
w_i*h_i
wi∗hi越大,相当于制衡值。
在keras版本的yolov3训练时,有利用k-mean算法生成9个anchor,现在对k-mean做个简单介绍。
K-mean算法:聚类是基于数据中的模式将整个数据划分为组(也称为簇)的过程。K-Means算法是一种无监督分类算法,假设有无标签数据集:
x
=
∣
x
(
1
)
x
(
2
)
⋮
x
(
n
)
∣
x=\left| \begin{matrix}x^{(1)}\\x^{(2)}\\\vdots\\x^{(n)} \end{matrix} \right|
x=∣∣∣∣∣∣∣∣∣x(1)x(2)⋮x(n)∣∣∣∣∣∣∣∣∣
该算法的任务是将数据集聚类成
k
k
k个簇
C
=
C
1
,
C
2
,
⋯
,
C
k
.
C=C_1,C_2,\cdots,C_k.
C=C1,C2,⋯,Ck.最小损失函数为
E
=
∑
i
=
1
k
∑
x
ϵ
C
i
∣
∣
x
−
u
i
∣
∣
2
E=\sum_{i=1}^k\sum_{x\epsilon{C_i}}{||{x-u_i}||^2}
E=i=1∑kxϵCi∑∣∣x−ui∣∣2
其中
u
i
u_i
ui为簇
C
i
C_i
Ci的中心点:
u
i
=
1
∣
C
i
∣
∑
x
ϵ
C
i
x
u_i =\frac{1}{|C_i|}\sum_{x\epsilon{C_i}}x
ui=∣Ci∣1xϵCi∑x
要找到以上问题的最优解需要遍历所有可能的簇划分,K-Means算法使用贪心策略求得一个近似解,具体步骤如下:
- 在样本中随机选取k个样本点充当各个簇的中心点 u 1 , u 2 , ⋯ , u k . {u_1,u_2,\cdots,u_k.} u1,u2,⋯,uk.
- 计算所有样本点与各个簇中心之间的距离 d i s t ( x ( i ) , u j ) dist(x^{(i)},u_j) dist(x(i),uj),然后把样本点划入最近的簇中, x ( i ) ϵ u n e a r e s t x^{(i)}\epsilon{u_{nearest}} x(i)ϵunearest。
- 根据簇中已有的样本点,重新计算簇中心 u i = 1 ∣ C i ∣ ∑ x ϵ C i x u_i =\frac{1}{|C_i|}\sum_{x\epsilon{C_i}}x ui=∣Ci∣1xϵCi∑x
- 重复2、3
- 当样本点不更换簇中心,或者到达设定的迭代上限等停止迭代。
总结
在工程上使用yolov3有很多的技巧,包括部署时候,如何提高速度和准确率等。