YOLOV2全面解读+YOLOV3全面解读! |
本篇论文是YOLO作者为了改进原有的YOLO算法所写的。
YOLO有两个缺点:
因此YOLOv2主要是要在这两方面做提升。另外YOLOv2并不是通过加深或加宽网络达到效果提升,反而是简化了网络。
下面就是使用的一些手段。
BN(Batch Normalization:批次归一化)层简单讲就是对网络的每一层的输入都做了归一化,这样网络就不需要每层都去学数据的分布,收敛会快点。作者在YOLOv2中为每个卷积层都添加了BN层,由于BN可以规范模型,所以加入BN后就把dropout去掉了,实验证明添加了BN层可以提高2%的mAP。
现在基本跑个分类或目标检测模型都不会从随机初始化所有参数开始,所以一般都是用预训练(详细介绍参考:yolov1可以参考: 预训练(pre-training/trained)与微调!
)的网络来fine-tuning自己的网络,而且预训练的网络基本上都是在ImageNet数据集上跑的,一方面数据量大,另一方面训练时间久,而且也比较容易得到。
YOLOV2做了以下改变:
Faster R-CNN中anchor box的大小和比例是按经验设定的,然后网络会在训练过程中调整anchor box的尺寸。如果一开始就能选择到合适尺寸的anchor box,那肯定可以帮助网络更好地预测。所以作者采用k-means的方式对训练集的bounding boxes做聚类,试图找到合适的anchor box。
作者发现采用标准的k-means(即用欧式距离来衡量差异),在box的尺寸比较大的时候其误差也更大,而我们希望的是误差和box的尺寸没有太大关系。所以通过IOU定义了距离函数,使得误差和box的大小无关
注意:设置先验框的主要目的是为了使得预测框与ground truth的IOU更好,所以聚类分析师使用box与聚类中的box之间的IOU值作为距离指标。
在VOC和COCO数据集上的聚类分析结果,随着聚类中心数目的增加,平均IOU值(各个边界框与聚类中心的IOU的平均值)是增加的,但是综合考虑模型复杂度和召回率,作者最终选取5个聚类中心作为先验框。
对于两个数据集,5个先验框的width和height如下:
COCO: (0.57273, 0.677385), (1.87446, 2.06253), (3.33843, 5.47434), (7.88282, 3.52778), (9.77052, 9.16828)
VOC: (1.3221, 1.73145), (3.19275, 4.00944), (5.05587, 8.09892), (9.47112, 4.84053), (11.2364, 10.0071)
这里先验框的大小具体指什么作者在论文中并没有说明,从代码实现上看,应该是相对于13*13的特征图的大小。
实验对比:
这部分细节很多,作者在引入anchor box的时候遇到的第二个问题:模型不稳定,尤其是在训练刚开始的时候。作者认为这种不稳定主要来自预测box的中心坐标 ( x , y ) (x,y) (x,y)值。
在基于region proposal的目标检测算法中,是通过预测 t x t_{x} tx 和 t y t_{y} ty 来得到 ( x , y ) (x,y) (x,y) 值,也就是预测的是offsets。
论文这里公式是错的,应该是“+”号。依据是下文中的例子,以及Faster R-CNN中的公式。
细粒度特征
YOLOv2中只有卷积层和池化层,因此不需要固定的输入图片的大小。为了让模型更有鲁棒性,作者引入了多尺度训练。就是在训练过程中,每迭代一定的次数,改变模型的输入图片大小。
注意:这一步是在检测数据集上fine tuning时候采用的,不要跟前面在Imagenet数据集上的两步预训练分类模型混淆。
具体操作:
网络包含19个卷积层和5个max pooling层,而在YOLOV1中采用的GooleNet,包含24个卷积层和2个全连接层,因此Darknet-19整体上卷积卷积操作比YOLOV1中用的GoogleNet要少,这是计算量减少的关键。最后用average pooling层代替全连接层进行预测。
这部分前面有提到,就是训练处理的小trick。这里的Training for Classification都是在ImageNet上进行预训练。
YOLOV2的训练主要包括三个阶段:
第三阶段: 修改Darknet-19分类模型为检测模型,并在检测数据集上继续fine tuning网络。
其中网络修改包括: 移除最后一个卷积层、global avgerage pooling层以及softmax层,新增了三个3×3卷积层,同时增加了一个passthrough层,最后使用 1×1卷积层输出预测结果。输出通道数计算如下。对于VOC数据,每个 c e l l cell cell预测num=5个bounding box,每个bounding box有5个坐标值和20个类别值,所以每个cell有125个filter。即: f i l t e r n u m = n u m ∗ ( c l a s s e s + 5 ) = 5 ∗ ( 20 + 5 ) = 125 filter_{num} = num * (classes + 5) = 5 * (20 + 5) = 125 filternum=num∗(classes+5)=5∗(20+5)=125
注意:
YOLOv2的结构示意图如下:
论文里没有说明先验框匹配和loss是怎么做的,所以有很多细节就需要去挖掘一下!
查资料有人参考YOLO在TensorFlow上的实现darkflow(见yolov2/train.py)给出了解释(见文后的参考,写的非常好,这里为了连贯我直接贴出来):
对应源码,参考YOLOV2–region层源码分析
1、W,H分别指的是特征图(13×13)的宽与高;
2、A指的是先验框数目(这里是5);
3、各个 λ λ λ 值是各个 l o s s loss loss的权重系数,参考YOLOV1的loss;
4、第一项 loss 是计算background的置信度误差,但是哪些预测框来预测背景呢,需要先计算各个预测框和所有ground truth的IOU值,并且取最大值Max_IOU,如果该值小于一定的阈值(YOLOV2使用的是0.6),那么这个预测框就标记为background,需要计算noobj的置信度误差;
5、第二项是计算先验框与预测宽的坐标误差,但是只在前12800个iterations间计算,我觉得这项应该是在训练前期使预测框快速学习到先验框的形状;
6、第三大项计算与某个ground- truth匹配的预测框各部分loss值,包括坐标误差、置信度误差以及分类误差。
先说一下匹配原则,对于某个ground-truth,首先要确定其中心点要落在哪个cell上,然后计算这个cell的5个先验框与ground-truth的IOU值(YOLOV2中bias_match=1),计算IOU值时不考虑坐标,只考虑形状,所以先将先验框与ground truth的中心点都偏移到同一位置(原点),然后计算出对应的IOU值,IOU值最大的那个先验框与ground truth匹配,对应的预测框用来预测这个ground truth。
在计算obj置信度时,在YOLOV1中target=1,而YOLOV2增加了一个控制参数rescore,当其为1时,target取预测框与ground truth的真实IOU值。对于那些没有与ground truth匹配的先验框(与预测框对应),除去那些Max_IOU低于阈值的,其它的就全部忽略,不计算任何误差。这点在YOLOV3论文中也有相关说明:YOLO中一个ground truth只会与一个先验框匹配(IOU值最好的),对于那些IOU值超过一定阈值的先验框,其预测结果就忽略了。这和SSD与RPN网络的处理方式有很大不同,因为它们可以将一个ground truth分配给多个先验框。
尽管YOLOV2和YOLOV1计算loss处理上有不同,但都是采用均方差来计算loss。
另外需要注意的一点是,在计算boxes的和误差时,YOLOV1中采用的是平方根以降低boxes的大小对误差的影响,而YOLOV2是直接计算,但是根据ground truth的大小对权重系数进行修正: l . c o o r d l.coord l.coord_ s c a l e ∗ ( 2 − t r u t h . w ∗ t r u t h . h ) scale * (2 - truth.w*truth.h) scale∗(2−truth.w∗truth.h),这样对于尺度较小的boxes其权重系数会更大一些,起到和YOLOv1计算平方根相似的效果。
带标注的检测数据集量比较少,而带标注的分类数据集量比较大,因此YOLO9000主要通过结合分类和检测数据集使得训练得到的检测模型可以检测约9000类物体。
本博客主要参考大神:木盏:yolo系列之yolo v3,在这里表示感谢!
yolov3作为yolo系列目前最新的算法,对之前的算法既有保留又有改进。先分析一下yolov3上保留的东西:
yolo每一代的提升很大一部分决定于backbone网络的提升,从yolov2的darknet-19到yolov3的darknet-53。yolov3还提供替换backbone——tiny darknet。要想性能牛叉,backbone可以用Darknet-53,要想轻量高速,可以用tiny-darknet。总之,yolo就是天生“灵活”,所以特别适合作为工程算法。
当然,yolov3在之前的算法上保留的点不可能只有上述几点。由于本文章主要针对yolov3进行剖析,不便跑题,下面切入正题。
图1. yolov3结构图 |
---|
|
yolo系列里面,作者只在v1的论文里给出了结构图,而v2和v3的论文里都没有结构图,这使得读者对后两代yolo结构的理解变得比较难。but,对于yolo学习者来说,脑子里没有一个清晰的结构图,就别说自己懂yolo了。为了让yolov3结构图更好理解,我对上图做一些补充解释:
整个v3结构里面,是没有池化层和全连接层的。前向传播过程中,张量的尺寸变换是通过改变卷积核的步长来实现的,比如stride=(2, 2),这就等于将图像边长缩小了一半(即面积缩小到原来的1/4)。在yolov2中,要经历5次缩小,会将特征图缩小到原输入尺寸的 1 / 2 5 1 / 2^{5} 1/25即1/32。输入为416x416
,则输出为13x13(416/32=13)
。yolov3也和v2一样,backbone都会将输出特征图缩小到输入的1/32。所以,通常都要求输入图片是32的倍数。可以对比v2和v3的backbone看看:
图2 | 图2 |
---|---|
|
|
yolov2中对于前向过程中张量尺寸变换,都是通过最大池化来进行,一共有5次。 | yolov3是通过卷积核增大步长来进行,也是5次 |
注意: darknet-53最后面有一个全局平均池化,在yolov3里面没有这一层,所以张量维度变化只考虑前面那5次。这也是416x416输入得到(416/32=13)13x13输出的原因。从上图可以看出,darknet-19是不存在残差结构(resblock,从resnet上借鉴过来)的,和VGG是同类型的backbone(属于上一代CNN结构),而darknet-53是可以和resnet-152正面刚的backbone,看下表:
从上表也可以看出,darknet-19在速度上仍然占据很大的优势。其实在其他细节也可以看出(比如bounding box prior采用k=9),yolov3并没有那么追求速度,而是在保证实时性(fps>36)的基础上追求performance。不过前面也说了,你要想更快,还有一个tiny-darknet作为backbone可以替代darknet-53,在官方代码里用一行代码就可以实现切换backbone。搭用tiny-darknet的yolo,也就是tiny-yolo在轻量和高速两个特点上,显然是state of the art级别,tiny-darknet是和squeezeNet正面刚的网络,详情可以看下表:
所以,有了yolov3,就真的用不着yolov2了,更用不着yolov1了。这也是yolo官方网站,在v3出来以后,就没提供v1和v2代码下载链接的原因了。
值得关注的是输出张量: yolo v3输出了3个不同尺度的feature map,如下图所示的y1, y2, y3。这也是v3论文中提到的为数不多的改进点:predictions across scales;这个借鉴了FPN(feature pyramid networks):采用多尺度来对不同size的目标进行检测,越精细的grid cell就可以检测出越精细的物体。
图3. yolov3输出张量 |
---|
|
其中:y1,y2和y3的深度都是255,边长的规律是13:26:52;对于COCO类别而言,有80个种类,所以每个box应该对每个种类都输出一个概率。yolov3设定的是每个网格单元预测3个box,所以每个box需要有(x, y, w, h, confidence)
五个基本参数,然后还要有80个类别的概率。所以3×(5+80)=255
。这个255就是这么来的。(还记得yolo v1的输出张量吗? 7x7x30,只能识别20类物体,而且每个cell只能预测2个box,和v3比起来就像老人机和iphoneX一样)。
yolov3用上采样的方法来实现这种多尺度的feature map,可以结合图1和图2右边来看:图1中concat连接的两个张量是具有一样尺度的(两处拼接分别是26x26尺度拼接和52x52尺度拼接,通过(2, 2)上采样来保证concat拼接的张量尺度相同);
作者并没有像SSD那样直接采用backbone中间层的处理结果作为feature map的输出,而是和后面网络层的上采样结果进行一个拼接之后的处理结果作为feature map。为什么这么做呢?我感觉是有点玄学在里面,一方面避免和其他算法做法重合,另一方面这也许是试验之后并且结果证明更好的选择,再者有可能就是因为这么做比较节省模型size的。这点的数学原理不用去管,知道作者是这么做的就对了。
Bounding Box Prediction
具体可以参考作者博客;对于v3而言,在prior这里的处理有明确解释:选用的b-box priors 的k=9,对于tiny-yolo的话,k=6。 priors都是在数据集上聚类得来的,有确定的数值,如下:
10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
每个anchor prior(名字叫anchor prior,但并不是用anchor机制)就是两个数字组成的,一个代表高度另一个代表宽度。
v3对b-box进行预测的时候,采用了logistic regression,这一波操作sao得就像RPN中的线性回归调整b-box。v3每次对b-box进行predict时,输出和v2一样都是 ( t x , t y , t w , t h , t o ) \left(t_{x}, t_{y}, t_{w}, t_{h}, t_{o}\right) (tx,ty,tw,th,to);然后通过公式1计算出绝对的 ( x , y , w , h , c ) (x, y, w, h, c) (x,y,w,h,c)。
logistic回归用于对anchor包围的部分进行一个目标性评分(objectness score),即这块位置是目标的可能性有多大。这一步是在predict之前进行的,可以去掉不必要anchor,可以减少计算量。作者在论文种的描述如下:
If the bounding box prior is not the best but does overlap a ground truth object by more than
some threshold we ignore the prediction, following[17]. We use the threshold of 0.5. Unlike [17]
our system only assigns one bounding box prior for each ground truth object.
# 如果模板框不是最佳的即使它超过我们设定的阈值,我们还是不会对它进行predict。
不同于faster R-CNN的是,yolov3只会对1个prior进行操作,也就是那个最佳prior。 而logistic回归就是用来从9个anchor priors中找到objectness score(目标存在可能性得分)最高的那一个。logistic回归就是用曲线对prior相对于 objectness score映射关系的线性建模。
疑问解答和说明:
了解v3输出的输出是至关重要的。
第一点,9个anchor会被三个输出张量平分的。根据大中小三种size各自取自己的anchor。
第二点,每个输出y在每个自己的网格都会输出3个预测框,这3个框是9除以3得到的,这是作者设置
的,我们可以从输出张量的维度来看,13x13x255。255是怎么来的呢,3*(5+80)。80表示80个种类,5表
示位置信息和置信度,3表示要输出3个prediction。在代码上来看,3*(5+80)中的3是直接由
num_anchors//3得到的。
第三点,作者使用了logistic回归来对每个anchor包围的内容进行了一个目标性评分(objectness score)。
根据目标性评分来选择anchor prior进行predict,而不是所有anchor prior都会有输出。
对掌握Yolo来讲,loss function不可谓不重要。在v3的论文里没有明确提所用的损失函数,确切地说,yolo系列论文里面只有yolo v1明确提了损失函数的公式。对于yolo这样一种讨喜的目标检测算法,就连损失函数都非常讨喜。在v1中使用了一种叫sum-square error的损失计算方法,就是简单的差方相加而已。我们知道,在目标检测任务里,有几个关键信息是需要确定的: ( x , y ) , ( w , h ) , c l a s s , c o n f i d e n c e (x, y),(w, h),class,confidence (x,y),(w,h),class,confidence
根据关键信息的特点可以分为上述四类,损失函数应该由各自特点确定。最后加到一起就可以组成最终的loss_function了,也就是一个loss_function搞定端到端的训练。可以从代码分析出v3的损失函数,同样也是对以上四类,不过相比于v1中简单的总方误差,还是有一些调整的:
xy_loss = object_mask * box_loss_scale * K.binary_crossentropy(raw_true_xy, raw_pred[..., 0:2],
from_logits=True)
wh_loss = object_mask * box_loss_scale * 0.5 * K.square(raw_true_wh - raw_pred[..., 2:4])
confidence_loss = object_mask * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True) + \
(1 - object_mask) * K.binary_crossentropy(object_mask, raw_pred[..., 4:5],
from_logits=True) * ignore_mask
class_loss = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[..., 5:], from_logits=True)
xy_loss = K.sum(xy_loss) / mf
wh_loss = K.sum(wh_loss) / mf
confidence_loss = K.sum(confidence_loss) / mf
class_loss = K.sum(class_loss) / mf
loss += xy_loss + wh_loss + confidence_loss + class_loss
以上是一段keras框架描述的yolo v3 的loss_function代码。忽略恒定系数不看,可以从上述代码看出:除了w, h的损失函数依然采用总方误差之外,其他部分的损失函数用的是二值交叉熵。最后加到一起。那么这个binary_crossentropy又是个什么玩意儿呢?就是一个最简单的交叉熵而已,一般用于二分类,这里的两种二分类类别可以理解为"对和不对"这两种。关于binary_crossentropy的公式详情可参考博文【常见损失函数;误差计算之MSE,Cross Enropy】
主要参考以下作者的博客,在这里表示真诚的感谢!