之前的两篇YOLO的发展历史
YOLOv1 https://blog.csdn.net/u010712012/article/details/85116365
YOLOv2 https://blog.csdn.net/u010712012/article/details/85274711

2018年又出现了YOLOv3,相比于SSD,FasterRCNN,RetinaNet,速度都是更快的,作者很皮的把YOLOv3的五角星打在了没有横坐标的第二象限,藐视其他目标检测算法。
这才是目标检测YOLOv3的真实面目
yolo_v3作为yolo系列目前最新的算法,对之前的算法既有保留又有改进。先分析一下yolo_v3上保留的东西:

  • “分而治之”,从yolo_v1开始,yolo算法就是通过划分单元格来做检测,只是划分的数量不一样。
  • 采用"leaky ReLU"作为**函数。
  • 端到端进行训练。一个loss function搞定训练,只需关注输入端和输出端。
  • 从yolo_v2开始,yolo就用batch normalization作为正则化、加速收敛和避免过拟合的方法,把BN层和leaky relu层接到每一层卷积层之后。
  • 多尺度训练。在速度和准确率之间tradeoff。想速度快点,可以牺牲准确率;想准确率高点儿,可以牺牲一点速度。

YOLOV3模型

这里借鉴了一位大佬的博客:https://blog.csdn.net/leviopku/article/details/82660381

这才是目标检测YOLOv3的真实面目
上图表示了yolo_v3整个yolo_body的结构,没有包括把输出解析整理成咱要的[box, class, score]。对于把输出张量包装成[box, class, score]那种形式,还需要写一些脚本,但这已经在神经网络结构之外了。

为了让yolo_v3结构图更好理解,对图1做一些补充解释:

DBL: 如图1左下角所示,也就是代码中的Darknetconv2d_BN_Leaky,是yolo_v3的基本组件。就是卷积+BN+Leaky relu。对于v3来说,BN和leaky relu已经是和卷积层不可分离的部分了(最后一层卷积除外),共同构成了最小组件。

resn:n代表数字,有res1,res2, … ,res8等等,表示这个res_block里含有多少个res_unit。这是yolo_v3的大组件,yolo_v3开始借鉴了ResNet的残差结构,使用这种结构可以让网络结构更深(从v2的darknet-19上升到v3的darknet-53,前者没有残差结构)。对于res_block的解释,可以在图1的右下角直观看到,其基本组件也是DBL。

concat:张量拼接。将darknet中间层和后面的某一层的上采样进行拼接。拼接的操作和残差层add的操作是不一样的,拼接会扩充张量的维度,而add只是直接相加不会导致张量维度的改变。

可以借鉴netron来分析网络层,整个yolo_v3_body包含252层,组成如下:
这才是目标检测YOLOv3的真实面目
根据上表可以得出,对于代码层面的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。

1. backbone

整个v3结构里面,是没有池化层和全连接层的。前向传播过程中,张量的尺寸变换是通过改变卷积核的步长来实现的,比如stride=(2, 2),这就等于将图像边长缩小了一半(即面积缩小到原来的1/4)。在yolo_v2中,要经历5次缩小,会将特征图缩小到原输入尺寸的1/251/2^5 ,即1/32。输入为416x416,则输出为13x13(416/32=13)。yolo_v3也和v2一样,backbone都会将输出特征图缩小到输入的1/32。所以,通常都要求输入图片是32的倍数。可以对比v2和v3的backbone看看:(DarkNet-19 与 DarkNet-53)
这才是目标检测YOLOv3的真实面目
yolo_v2中对于前向过程中张量尺寸变换,都是通过最大池化来进行,一共有5次。而v3是通过卷积核增大步长来进行,也是5次。(darknet-53最后面有一个全局平均池化,在yolo-v3里面没有这一层,所以张量维度变化只考虑前面那5次)。
这也是416x416输入得到13x13输出的原因。从图2可以看出,darknet-19是不存在残差结构(resblock,从resnet上借鉴过来)的,和VGG是同类型的backbone(属于上一代CNN结构),而darknet-53是可以和resnet-152正面刚的backbone,看下表:
这才是目标检测YOLOv3的真实面目
从上表也可以看出,darknet-19在速度上仍然占据很大的优势。其实在其他细节也可以看出(比如bounding box prior采用k=9),yolo_v3并没有那么追求速度,而是在保证实时性(fps>60)的基础上追求performance。不过前面也说了,你要想更快,还有一个tiny-darknet作为backbone可以替代darknet-53,在官方代码里用一行代码就可以实现切换backbone。搭用tiny-darknet的yolo,也就是tiny-yolo在轻量和高速两个特点上,显然是state of the art级别,tiny-darknet是和squeezeNet正面刚的网络,详情可以看下表:
这才是目标检测YOLOv3的真实面目

2.输出Output

网络结构放大版本:
这才是目标检测YOLOv3的真实面目
三种不同的scale如下:
这才是目标检测YOLOv3的真实面目
yolo v3输出了3个不同尺度的feature map,如上图所示的y1, y2, y3。这也是v3论文中提到的为数不多的改进点:predictions across scales,这个借鉴了FPN(feature pyramid networks),采用多尺度来对不同size的目标进行检测,越精细的grid cell就可以检测出越精细的物体。

y1,y2和y3的深度都是255,边长的规律是13:26:52。对于COCO类别而言,有80个种类,所以每个box应该对每个种类都输出一个概率。yolo v3设定的是每个网格单元预测3个box,所以每个box需要有(x, y, w, h, confidence)五个基本参数,然后还要有80个类别的概率。所以3*(5 + 80) = 255。这个255就是这么来的。(还记得yolo v1的输出张量吗? 7x7x30,只能识别20类物体,而且每个cell只能预测2个box,和v3比起来就像老人机和Mate X一样)

v3用上采样的方法来实现这种多尺度的feature map,可以结合图1和图2右边来看,图1中concat连接的两个张量是具有一样尺度的(两处拼接分别是26x26尺度拼接和52x52尺度拼接,通过(2, 2)上采样来保证concat拼接的张量尺度相同)。作者并没有像SSD那样直接采用backbone中间层的处理结果作为feature map的输出,而是和后面网络层的上采样结果进行一个拼接之后的处理结果作为feature map。为什么这么做呢? 我感觉是有点玄学在里面,一方面避免和其他算法做法重合,另一方面这也许是试验之后并且结果证明更好的选择,再者有可能就是因为这么做比较节省模型size的。

3.some tricks

上文把yolo_v3的结构讨论了一下,下文将对yolo v3的若干细节进行剖析。

Bounding Box Prediction b-box预测手段是v3论文中提到的又一个亮点。先回忆一下v2的b-box预测:想借鉴faster R-CNN RPN中的anchor机制,但不屑于手动设定anchor prior(模板框),于是用维度聚类的方法来确定anchor box prior(模板框),最后发现聚类之后确定的prior在k=5也能够又不错的表现,于是就选用k=5。后来呢,v2又嫌弃anchor机制线性回归的不稳定性(因为回归的offset可以使box偏移到图片的任何地方),所以v2最后选用了自己的方法:直接预测相对位置。预测出b-box中心点相对于网格单元左上角的相对坐标。

这才是目标检测YOLOv3的真实面目
这才是目标检测YOLOv3的真实面目
yolo v2直接predict出(tx,ty,tw,th,tc)(t_x,t_y,t_w,t_h,t_c),并不像RPN中anchor机制那样去遍历每一个pixel。可以从上面的公式看出,b-box的位置大小和confidence都可以通过(tx,ty,tw,th,tc)(t_x,t_y,t_w,t_h,t_c)计算得来,v2相当直接predict出了b-box的位置大小和confidence。box宽和高的预测是受prior影响的,对于v2而言,b-box prior数为5,在论文中并没有说明抛弃anchor机制之后是否抛弃了聚类得到的prior(没看代码,所以我不能确定),如果prior数继续为5,那么v2需要对不同prior预测出和对于v3而言,在prior这里的处理有明确解释:选用的b-box priors 的k=9,对于tiny-yolo的话,k=6.

每个anchor prior(名字叫anchor prior,但并不是用anchor机制)就是两个数字组成的,一个代表高度另一个代表宽度。v3对b-box进行预测的时候,采用了logistic regression。这一波操作6得就像RPN中的线性回归调整b-box。v3每次对b-box进行predict时,输出和v2一样都是(tx,ty,tw,th,tc)(t_x,t_y,t_w,t_h,t_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的是,yolo_v3只会对1个prior进行操作,也就是那个最佳prior。而logistic回归就是用来从9个anchor priors中找到objectness score(目标存在可能性得分)最高的那一个。logistic回归就是用曲线对prior相对于 objectness score映射关系的线性建模。

4.loss function

对掌握Yolo来讲,loss function不可谓不重要。在v3的论文里没有明确提所用的损失函数,确切地说,yolo系列论文里面只有yolo v1明确提了损失函数的公式。对于yolo这样一种讨喜的目标检测算法,就连损失函数都非常讨喜。在v1中使用了一种叫sum-square error的损失计算方法,就是简单的差方相加而已。想详细了解的可以看我篇头关于v1解释的博文。我们知道,在目标检测任务里,有几个关键信息是需要确定的:

(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又是个什么玩意儿呢?就是一个最简单的交叉熵而已,一般用于二分类,这里的两种二分类类别可以理解为"对和不对"这两种.

总结:

改进之处:

  • 多尺度预测 (类FPN)
  • 更好的基础分类网络(类ResNet)和分类器

分类器-类别预测:

YOLOv3不使用Softmax对每个框进行分类,主要考虑因素有两个:

  • Softmax使得每个框分配一个类别(score最大的一个),而对于Open Images这种数据集,目标可能有重叠的类别标签,因此Softmax不适用于多标签分类。
  • Softmax可被独立的多个logistic分类器替代,且准确率不会下降。
  • 分类损失采用binary cross-entropy loss.

多尺度预测

每种尺度预测3个box, anchor的设计方式仍然使用聚类,得到9个聚类中心,将其按照大小均分给3中尺度.

  • 尺度1: 在基础网络之后添加一些卷积层再输出box信息.
  • 尺度2: 从尺度1中的倒数第二层的卷积层上采样(x2)再与最后一个16x16大小的特征图相加,再次通过多个卷积后输出box信息.相比尺度1变大两倍.
  • 尺度3: 与尺度2类似,使用了32x32大小的特征图.

下一代YOLO长啥样?

mAP会继续提高。随着模型训练越来越高效,神经网络层级的不断加深,信息抽象能力的不断提高,以及一些小的修修补补,未来的目标检测应用mAP会不断提升。

实时检测会成为标配。目前所谓的“实时”,工业界是不认可的。为什么呢,因为学术圈的人,验证模型都是建立在TitanX或者Tesla这类强大的独立显卡上,而实际的潜在应用场景中,例如无人机/扫地/服务机器人/视频监控等,是不会配备这些“重型装备”的。所以,在嵌入式设备中,如FPGA,TX2,轻量级CPU上,能达到的实时,才是货真价实的。

模型小型化成为重要分支。类似于tiny YOLO的模型分支会受到更多关注。模型的小型化是应用到嵌入式设备的重要前提。而物联网机器人无人机等领域还是以嵌入式设备为主的。模型剪枝/二值化/权值共享等手段会更广泛的使用。

说点题外话:

YOLO让人联想到龙珠里的沙鲁(cell),不断吸收同化对手,进化自己,提升战斗力:YOLOv1吸收了SSD的长处(加了 BN 层,扩大输入维度,使用了 Anchor,训练的时候数据增强),进化到了YOLOv2;

吸收DSSD和FPN的长处, 仿ResNet的Darknet-53,仿SqueezeNet的纵横交叉网络,又进化到YOLO第三形态。

但是,我相信这一定不是最终形态。。。让我们拭目以待吧!

这才是目标检测YOLOv3的真实面目

参考:https://blog.csdn.net/leviopku/article/details/82660381
https://www.cnblogs.com/makefile/p/YOLOv3.html
https://zhuanlan.zhihu.com/p/35394369