YOLO是一种端到端的目标检测模型。YOLO算法的基本思想是:首先通过特征提取网络对输入特征提取特征,得到特定大小的特征图输出。输入图像分成grid cell,接着如果真实框中某个object的中心坐标落在某个grid cell中,那么就由该grid cell来预测该object。每个object有固定数量的bounding box,YOLO v3中有三个bounding box,使用逻辑回归确定用来预测的回归框。
先分析一下yolo_v3上保留的东西:
l “分而治之”,从yolo_v1开始,yolo算法就是通过划分单元格来做检测,只是划分的数量不一样。
l 采用"leaky ReLU"作为激活函数。
l 端到端进行训练。一个loss function搞定训练,只需关注输入端和输出端。
l 从yolo_v2开始,yolo就用batch normalization作为正则化、加速收敛和避免过拟合的方法,把BN层和leaky relu层接到每一层卷积层之后。
l 多尺度训练。在速度和准确率之间tradeoff。想速度快点,可以牺牲准确率;想准确率高点儿,可以牺牲一点速度。
yolo每一代的提升很大一部分决定于backbone网络的提升,从v2的darknet-19到v3的darknet-53。yolo_v3还提供替换backbone——tiny darknet。要想性能牛叉,backbone可以用Darknet-53,要想轻量高速,可以用tiny-darknet。总之,yolo就是天生“灵活”,所以特别适合作为工程算法。
l DBL:代码中的Darknetconv2d_BN_Leaky,是yolo_v3的基本组件。就是卷积+BN+Leaky relu。
l resn:n代表数字,有res1,res2, … ,res8等等,表示这个res_block里含有多少个res_unit。
l concat:张量拼接。将darknet中间层和后面的某一层的上采样进行拼接。拼接的操作和残差层add的操作是不一样的,拼接会扩充张量的维度,而add只是直接相加不会导致张量维度的改变。
整个yolo_v3_body包含252层,组成如下:
根据表0可以得出,对于代码层面的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。
下面逐个分析
整个v3结构里面,是没有池化层和全连接层的。前向传播过程中,张量的尺寸变换是通过改变卷积核的步长来实现的,比如stride=(2, 2),这就等于将图像边长缩小了一半(即面积缩小到原来的1/4)。在yolo_v2中,要经历5次缩小,会将特征图缩小到原输入尺寸的1/2^5,即1/32。输入为416x416,则输出为13x13(416/32=13)。
yolo_v3也和v2一样,backbone都会将输出特征图缩小到输入的1/32。所以,通常都要求输入图片是32的倍数。yolo_v2中对于前向过程中张量尺寸变换,都是通过最大池化来进行,一共有5次。而v3是通过卷积核增大步长来进行,也是5次。(darknet-53最后面有一个全局平均池化,在yolo-v3里面没有这一层,所以张量维度变化只考虑前面那5次)。可以对比v2和v3的backbone看看:(DarkNet-19 与 DarkNet-53,)
Yolo_v3使用了darknet-53的前面的52层(没有全连接层),yolo_v3这个网络是一个全卷积网络,大量使用残差的跳层连接,并且为了降低池化带来的梯度负面效果,作者直接摒弃了POOLing,用conv的stride来实现降采样。在这个网络结构中,使用的是步长为2的卷积来进行降采样。
为了加强算法对小目标检测的精确度,YOLO v3中采用类似FPN的upsample和融合做法(最后融合了3个scale,其他两个scale的大小分别是26×26和52×52),在多个scale的feature map上做检测。
作者在3条预测支路采用的也是全卷积的结构,其中最后一个卷积层的卷积核个数是255,是针对COCO数据集的80类:3*(80+4+1)=255,3表示一个grid cell包含3个bounding box,4表示框的4个坐标信息,1表示objectness score。
所谓的多尺度就是来自这3条预测之路,y1,y2和y3的深度都是255,边长的规律是13:26:52。yolo v3设定的是每个网格单元预测3个box,所以每个box需要有(x, y, w, h, confidence)五个基本参数,然后还要有80个类别的概率,所以3×(5 + 80) = 255(即一行代表了一个网格,由三个“5+80”元素组成)。那么总共有(1313+2626+52*52)*3=10674个先验框,如下:
网络中作者进行了三次检测,分别是在32倍降采样,16倍降采样,8倍降采样时进行检测。在网络中使用up-sample(上采样)的原因:网络越深的特征表达效果越好。比如在进行16倍降采样检测,如果直接使用第四次下采样的特征来检测,这样就使用了浅层特征,这样效果一般并不好,从而添加32倍降采样后的特征,但深层特征的大小太小,因此yolo_v3使用了步长为2的up-sample(上采样),把32倍降采样得到的feature map的大小提升一倍,也就成了16倍降采样后的维度。同理8倍采样也是对16倍降采样的特征进行步长为2的上采样,这样就可以使用深层特征进行detection。
如下图所示,85层将13×13×256的特征上采样得到26×26×256,再将其与61层的特征拼接起来得到26×26×768。为了得到channel255,还需要进行一系列的3×3,1×1卷积操作,这样既可以提高非线性程度增加泛化性能提高网络精度,又能减少参数提高实时性。52×52×255的特征也是类似的过程。
对于Yolo v3关于bounding box的初始尺寸还是采用Yolo v2中的k-means聚类的方式来做。在yolo_v2和yolo_v3中,都采用了对图像中的object采用k-means聚类。 同样地,YOLO v3选择的默认框有9个。其尺寸可以通过k-means算法在数据集上聚类得到。在COCO数据集上,9个聚类是:(10×13);(16×30);(33×23);(30×61);(62×45); (59×119); (116×90); (156×198); (373×326)。三次检测,每次对应的感受野不同,32倍降采样的感受野最大,适合检测大的目标,所以在输入为416×416时,每个cell的三个anchor box为(116 ,90); (156 ,198); (373 ,326)。16倍适合一般大小的物体,anchor box为(30,61); (62,45); (59,119)。8倍的感受野最小,适合检测小目标,因此anchor box为(10,13); (16,30); (33,23)。所以当输入为416×416时,实际总共有(52×52+26×26+13×13)×3=10647个proposal box。
feature map中的每一个cell都会预测3个边界框(bounding box) ,每个bounding box都会预测三个东西:1. 每个框的位置微调量(4个值,中心坐标tx和ty,框的高度bh和宽度bw);2. 一个objectness prediction ;3. N个类别,coco数据集80类,voc20类。然后采用和v2相同的公式给出最终的预测:
Yolo v3采用直接预测相对位置的方法。预测出b-box中心点相对于网格单元左上角的相对坐标。直接预测出(tx,ty,tw,th,t0),然后通过以下坐标偏移公式计算得到b-box的位置大小和confidence。
下图9种先验框的尺寸,下图中蓝色框为聚类得到的先验框。黄色框式ground truth,红框是对象中心点所在的网格:
现在我们得到1W多个先验框的位置信息、置信度、类别概率向量,如下:
首先将类别概率中最大的类别用蓝色标出,
然后乘以置信度,得到联合概率,
然后用联合概率更换掉置信度,
然后设置阈值(阈值这个时候训练的时候设置低一些,测试的时候可以高一些),如果联合概率小于阈值,则将该预测框去掉,
然后将得到的先验框,按照联合概率的大小降序排列,
然后进行NMS,
NMS过程:
经过上述排序,我们知道“bb666”先验框的联合概率最高,不防假设“bb666”先验框预测类别“狗”。那么“bb666”先验框显示其预测类别是“狗”的把握最大,那么先将其拿出,放到新的地方储存区,
现在将“bb666”先验框和其他预测类别是“狗”的,进行NMS,假若IOU大于阈值,则将其删除(这个阈值尽量低一些,因为如果两只狗挨得很近,这时候如果太高,很可能只能框出一只狗)。如下图,与绿框“bb666”先验框进行NMS,其中蓝框和粉红框与绿框IOU大于阈值,则将其删除,下图的虚线框
这个时候假设剩余先验框中概率最大的是“bb95”,不妨假设其看到的是“自行车”(当然也有可能是上一个类别“狗”,也就是图中可能存在多个“狗”,而先验框预测的是另一个“狗”),那么将“bb95”先验框拿到新的存储区域, 还需要再对剩余的先验框按照联合概率重新排序 然后将“bb95”先验框和剩余的预测为“自行车”类别的先验框,进行相同的NMS操作, 这样一直循环,直到最后没有先验框,
YOLOv3重要改变之一:No more softmaxing the classes。YOLO v3现在对图像中检测到的对象执行多标签分类。
早期YOLO,作者曾用softmax获取类别得分并用最大得分的标签来表示包含再边界框内的目标,在YOLOv3中,这种做法被修正。softmax来分类依赖于这样一个前提,即分类是相互独立的,换句话说,如果一个目标属于一种类别,那么它就不能属于另一种。但是,当我们的数据集中存在人或女人的标签时,上面所提到的前提就是去了意义。这就是作者为什么不用softmax,而用logistic regression来预测每个类别得分并使用一个阈值来对目标进行多标签预测。比阈值高的类别就是这个边界框真正的类别。用简单一点的语言来说,其实就是对每种类别使用二分类的logistic回归,即你要么是这种类别要么就不是,然后便利所有类别,得到所有类别的得分,然后选取大于阈值的类别就好了。
作者使用了logistic回归来对每个anchor包围的内容进行了一个目标性评分(objectness score)。根据目标性评分来选择anchor prior进行predict,而不是所有anchor prior都会有输出。
相比于v1中简单的总方误差,还是有一些调整的:除了w, h的损失函数依然采用总方误差之外,其他部分的损失函数用的是二值交叉熵。最后加到一起。下面为代码中损失函数:
其中只有v1给出损失函数,其余版本未给出,可以去看源码。其余版本可能未必是原始的。当然也可以利用其他的损失函数,比如RetinaNet中的“focal loss”或者GIoU等。
训练过程:
利用预训练
利用检测网络,得到1W左右的先验框;对其进行NMS操作
对于剩余的利用LOSS,进行迭代网络参数
测试:
利用检测网络得到先验框
进行nms,得到结果
在第一层添加SPP层,也就是用不同的尺寸的池化层,分别池化,然后通道叠加,如下