相关系列:
目标检测 | yolov1 原理和介绍
目标检测 | yolov2/yolo9000 原理和介绍
目标检测 | yolov3 原理和介绍
目标检测 | yolov4 原理和介绍
目标检测 | yolov5 原理和介绍
目标检测 | yolov6 原理和介绍
目标检测 | yolov7 原理和介绍
目标检测 | yolov8 原理和介绍
目标检测 | yolov9 原理和介绍
目标检测 | yolov10 原理和介绍
论文链接:https://arxiv.org/abs/1612.08242
时间:2016年
作者:Joseph Redmon
 作者首先在YOLOv1的基础上提出了改进的YOLOv2,然后提出了一种检测与分类联合训练方法,使用这种联合训练方法在COCO检测数据集和ImageNet分类数据集上训练出了YOLO9000模型,其可以检测超过9000多类物体。所以,这篇文章其实包含两个模型:YOLOv2和YOLO9000,不过后者是在前者基础上提出的,两者模型主体结构是一致的。YOLOv2相比YOLOv1做了很多方面的改进,这也使得YOLOv2的mAP有显著的提升,并且YOLOv2的速度依然很快,保持着自己作为one-stage方法的优势.

1. 简介

 YOLOv2(也称为YOLO9000)是目标检测领域的重大改进,它在YOLOv1的基础上引入了多项关键技术,显著提升了检测性能。以下是YOLOv2的一些核心改进点:

  1. 批量归一化(Batch Normalization):YOLOv2在每个卷积层后都添加了批量归一化层,这有助于加快模型的收敛速度,并降低过拟合的风险,从而提升了模型性能。
  2. 高分辨率分类器(High Resolution Classifier):YOLOv2在ImageNet数据集上使用更高分辨率的图像进行预训练,以适应目标检测中的高分辨率输入,进一步提高了模型的检测精度。
  3. 带有Anchor Boxes的卷积(Convolutional With Anchor Boxes):YOLOv2引入了Anchor Boxes机制,通过在特征图的每个网格中预测多个不同尺寸的边界框,提高了模型对不同尺寸目标的检测能力。
  4. 维度聚类(Dimension Clusters):YOLOv2使用K-means聚类算法自动确定Anchor Boxes的最佳尺寸,这比手动选择尺寸更加合理和有效。
  5. 直接位置预测(Direct Location Prediction):YOLOv2改进了边界框的位置预测方式,不再使用全连接层,而是直接在卷积层上进行预测,这有助于保留更多的空间信息。
  6. 细粒度特征(Fine-Grained Features):YOLOv2通过Passthrough层将不同层级的特征图进行连接,以捕获更细粒度的特征信息,进一步提升了检测精度。
  7. 多尺度训练(Multi-Scale Training):YOLOv2采用不同尺寸的图像进行训练,增强了模型对不同尺寸输入的适应性,提高了模型的鲁棒性。
  8. 新的网络结构Darknet-19:YOLOv2采用了新的网络Darknet-19作为其主干网络,该网络结构更简单、更高效,有助于提高模型的运行速度。
  9. 新的损失函数:YOLOv2对损失函数进行了改进,例如使用二元交叉熵损失来代替传统的softmax损失,有助于解决类别不平衡问题。
  10. 联合分类和检测的训练方法:YOLOv2提出了一种新的训练方法,允许模型在目标检测和分类任务上同时进行训练,这使得模型能够检测出更多种类的目标。

2. 改进策略

 YOLOv1虽然检测速度很快,但是在检测精度上却不如R-CNN系检测方法,YOLOv1在物体定位方面(localization)不够准确,并且召回率(recall)较低。YOLOv2共提出了几种改进策略来提升YOLO模型的定位准确度和召回率,从而提高mAP,YOLOv2在改进中遵循一个原则:保持检测速度,这也是YOLO模型的一大优势。YOLOv2的改进策略如图所示,可以看出,大部分的改进方法都可以比较显著提升模型的mAP。
在这里插入图片描述

2.1 Batch Normalization

 Batch Normalization可以提升模型收敛速度,而且可以起到一定正则化效果,降低模型的过拟合。在YOLOv2中,每个卷积层后面都添加了Batch Normalization层,并且不再使用droput。使用Batch Normalization后,YOLOv2的mAP提升了 2.4 % 2.4\% 2.4%

解释:BN层是把神经元的输出减去均值除以标准差,变成以0为均值,标准差为1得分布。
强行把神经元输出集合在0附近原因是:一些激活函数比如sigmod激活函数或者双曲正切激活函数在0附近是非饱和区,如果输出太大或者太小,会陷入激活函数的饱和区,意味着梯度消失,会难以训练。经过BN之后,原本很分散的数据都集中到了0得附近,对于双曲正切函数来说在0附近有较大得梯度。经过BN层之后,双峰的图像数据在中间也有较好的体现,可以更好地保留原始数据。
CNN在训练过程中网络每层输入的分布一直在改变, 会使训练过程难度加大,对网络的每一层的输入(每个卷积层后)都做了归一化,这样网络就不需要每层都去学数据的分布,收敛会更快。

2.2 High Resolution Classifier 高分辨率分类器

 目前大部分的检测模型都会在先在ImageNet分类数据集上预训练模型的主体部分(CNN特征提取器),由于历史原因,ImageNet分类模型基本采用大小为 224 × 224 224 \times 224 224×224的图片作为输入,分辨率相对较低,不利于检测模型。所以YOLOv1在采用 224 × 224 224 \times 224 224×224 分类模型预训练后,将分辨率增加至 448 × 448 448\times 448 448×448,并使用这个高分辨率在检测数据集上finetune。但是直接切换分辨率,检测模型可能难以快速适应高分辨率。所以YOLOv2增加了在ImageNet数据集上使用 448 × 448 448\times 448 448×448输入来finetune分类网络这一中间过程(10 epochs),这可以使得模型在检测数据集上finetune之前已经适用高分辨率输入。使用高分辨率分类器后,YOLOv2的mAP提升了约 4 % 4\% 4%

2.3 Convolutional With Anchor Boxes 带锚框的卷积

 在YOLOv1中,输入图片最终被划分为 7 × 7 7 \times 7 7×7 网格,每个单元格预测2个边界框。YOLOv1最后采用的是全连接层直接对边界框进行预测,其中边界框的宽与高是相对整张图片大小的,而由于各个图片中存在不同尺度和长宽比(scales and ratios)的物体,YOLOv1在训练过程中学习适应不同物体的形状是比较困难的,这也导致YOLOv1在精确定位方面表现较差。YOLOv2借鉴了Faster R-CNN中RPN网络的先验框(anchor boxes,prior boxes,SSD也采用了先验框)策略。RPN对CNN特征提取器得到的特征图(feature map)进行卷积来预测每个位置的边界框以及置信度(是否含有物体),并且各个位置设置不同尺度和比例的先验框,所以RPN预测的是边界框相对于先验框的offsets值(其实是transform值,详细见Faster R_CNN论文),采用先验框使得模型更容易学习。所以YOLOv2移除了YOLOv1中的全连接层而采用了卷积和anchor boxes来预测边界框。为了使检测所用的特征图分辨率更高,移除其中的一个pool层。在检测模型中,YOLOv2不是采用 448 × 448 448 \times 448 448×448 图片作为输入,而是采用 416 × 416 416 \times 416 416×416 大小。因为YOLOv2模型下采样的总步长为 32 32 32 ,对于 416 × 416 416 \times 416 416×416 大小的图片,最终得到的特征图大小为 13 × 13 13\times 13 13×13,维度是奇数,这样特征图恰好只有一个中心位置。对于一些大物体,它们中心点往往落入图片中心位置,此时使用特征图的一个中心点去预测这些物体的边界框相对容易些。所以在YOLOv2设计中要保证最终的特征图有奇数个位置。对于YOLOv1,每个cell都预测 2 2 2个boxes,每个boxes包含 5 5 5个值: ( x , y , w , h , c ) (x,y,w,h,c) (x,y,w,h,c),前 4 4 4个值是边界框位置与大小,最后一个值是置信度(confidence scores,包含两部分:含有物体的概率以及预测框与ground truth的IOU)。但是每个cell只预测一套分类概率值(class predictions,其实是置信度下的条件概率值),供 2 2 2个boxes共享。YOLOv2使用了anchor boxes之后,每个位置的各个anchor box都单独预测一套分类概率值,这和SSD比较类似(但SSD没有预测置信度,而是把background作为一个类别来处理)。
 使用anchor boxes之后,YOLOv2的mAP有稍微下降(这里下降的原因,我猜想是YOLOv2虽然使用了anchor boxes,但是依然采用YOLOv1的训练方法)。YOLOv1只能预测 98 98 98个边界框( 7 × 7 × 2 7 \times 7 \times 2 7×7×2),而YOLOv2使用anchor boxes之后可以预测上千个边界框( 13 × 13 13 \times 13 13×13anchors_num )。所以使用anchor boxes之后,YOLOv2的召回率大大提升,由原来的 81 % 81\% 81%升至 88 % 88\% 88%

2.4 Dimension Clusters 维度集群

 在Faster R-CNN和SSD中,先验框的维度(长和宽)都是手动设定的,带有一定的主观性。如果选取的先验框维度比较合适,那么模型更容易学习,从而做出更好的预测。因此,YOLOv2采用k-means聚类方法对训练集中的边界框做了聚类分析。因为设置先验框的主要目的是为了使得预测框与ground truth的IOU更好,所以聚类分析时选用box与聚类中心box之间的IOU值作为距离指标:
d (  box  ,  centroid  ) = 1 − IOU ⁡ (  box  ,  centroid  ) d(\text { box }, \text { centroid })=1-\operatorname{IOU}(\text { box }, \text { centroid }) d( box , centroid )=1IOU( box , centroid )

2.5 New Network: Darknet-19

 YOLOv2采用了一个新的基础模型(特征提取器),称为Darknet-19,包括 19 19 19个卷积层和 5 5 5个maxpooling层。Darknet-19与VGG16模型设计原则是一致的,主要采用 3 × 3 3 \times 3 3×3 卷积,采用 2 × 2 2 \times 2 2×2 的maxpooling层之后,特征图维度降低2倍,而同时将特征图的channles增加两倍。与NIN(Network in Network)类似,Darknet-19最终采用global avgpooling做预测,并且在 3 × 3 3 \times 3 3×3 卷积之间使用 1 × 1 1 \times 1 1×1 卷积来压缩特征图channles以降低模型计算量和参数。Darknet-19每个卷积层后面同样使用了batch norm层以加快收敛速度,降低模型过拟合。在ImageNet分类数据集上,Darknet-19的top-1准确度为 72.9 % 72.9\% 72.9%,top-5准确度为 91.2 % 91.2\% 91.2%,但是模型参数相对小一些。使用Darknet-19之后,YOLOv2的mAP值没有显著提升,但是计算量却可以减少约 33 % 33\% 33%
darknet-19模型结构

2.6 Direct location prediction 直接位置预测

 YOLOv2边界框的解码过程修改。约束了边界框的位置预测值使得模型更容易稳定训练,结合聚类分析得到先验框与这种预测方法,YOLOv2的mAP值提升了约 5 % 5\% 5%
预测边界框中心点相对于对应cell左上角位置的相对偏移值。将网格归一化为 1 × 1 1 \times 1 1×1,坐标控制在每个网格内,同时配合sigmod函数将预测值转换到 0 ∼ 1 0\sim1 01之间的办法,做到每一个Anchor只负责检测周围正负一个单位以内的目标box。

 YOLOv2借鉴RPN网络使用anchor boxes来预测边界框相对先验框的offsets。边界框的实际中心位置 ( x , y ) (x, y) (x,y) ,需要根据预测的坐标偏移值 ( t x , t y ) (t_\text x,t_\text y) (tx,ty) ,先验框的尺度 ( w a , h a ) (w_\text a,h_\text a) (wa,ha) 以及中心坐标 ( x a , y a ) (x_\text a,y_\text a) (xa,ya) (特征图每个位置的中心点)来计算:
x = ( t x ∗ w a ) − x a x=(t_\text x * w_\text a) - x_\text a x=(txwa)xa
y = ( t y ∗ h a ) − y a y=(t_\text y * h_\text a) - y_\text a y=(tyha)ya
 但是上面的公式是无约束的,预测的边界框很容易向任何方向偏移,如当 t x = 1 t_\text x=1 tx=1 时边界框将向右偏移先验框的一个宽度大小,而当 t x = − 1 t_\text x=-1 tx=1 时边界框将向左偏移先验框的一个宽度大小,因此每个位置预测的边界框可以落在图片任何位置,这导致模型的不稳定性,在训练时需要很长时间来预测出正确的offsets。所以,YOLOv2弃用了这种预测方式,而是沿用YOLOv1的方法,就是预测边界框中心点相对于对应cell左上角位置的相对偏移值,为了将边界框中心点约束在当前cell中,使用sigmoid函数处理偏移值,这样预测的偏移值在 ( 0 , 1 ) (0,1) (0,1)范围内(每个cell的尺度看做 1 1 1)。总结来看,根据边界框预测的4个offsets t x , t y , t w , t h t_\text x,t_\text y,t_\text w,t_\text h tx,ty,tw,th ,可以按如下公式计算出边界框实际位置和大小:
b x = σ ( t x ) + c x b_\text x = \sigma(t_\text x) + c_\text x bx=σ(tx)+cx
b y = σ ( t y ) + c y b_\text y = \sigma(t_\text y) + c_\text y by=σ(ty)+cy
b w = p w e t w / W b_\text w = p_\text w e^{tw} / W bw=pwetw/W
b h = p h e t h / H b_\text h = p_\text h e^{th} / H bh=pheth/H
 如果再将上面的4个值分别乘以图片的宽度和长度(像素点值)就可以得到边界框的最终位置和大小了。这就是YOLOv2边界框的整个解码过程。约束了边界框的位置预测值使得模型更容易稳定训练,结合聚类分析得到先验框与这种预测方法,YOLOv2的mAP值提升了约 5 % 5\% 5%
具有维度先验和位置的边界框

2.7 Fine-Grained Features 细粒度特征

 YOLOv2的输入图片大小为 416 × 416 416 \times 416 416×416 ,经过5次maxpooling之后得到 13 × 13 13 \times 13 13×13 大小的特征图,并以此特征图采用卷积做预测。
大小的特征图对检测大物体是足够了,但是对于小物体还需要更精细的特征图(Fine-Grained Features)。因此SSD使用了多尺度的特征图来分别检测不同大小的物体,前面更精细的特征图可以用来预测小物体。YOLOv2提出了一种passthrough层来利用更精细的特征图。YOLOv2所利用的Fine-Grained Features是 26 × 26 26\times26 26×26 大小的特征图(最后一个maxpooling层的输入),对于Darknet-19模型来说就是大小为 26 × 26 × 512 26 \times 26 \times 512 26×26×512 的特征图。passthrough层与ResNet网络的shortcut类似,以前面更高分辨率的特征图为输入,然后将其连接到后面的低分辨率特征图上。前面的特征图维度是后面的特征图的2倍,passthrough层抽取前面层的每个 2 × 2 2 \times 2 2×2 的局部区域,然后将其转化为channel维度,对于 26 × 26 × 512 26 \times 26 \times512 26×26×512 的特征图,经passthrough层处理之后就变成了 13 × 13 × 2048 13 \times 13 \times 2048 13×13×2048 的新特征图(特征图大小降低 4 4 4倍,而channles增加 4 4 4倍,图为一个实例),这样就可以与后面的 13 × 13 × 1024 13 \times 13 \times 1024 13×13×1024 特征图连接在一起形成 13 × 13 × 3072 13 \times 13 \times 3072 13×13×3072 大小的特征图,然后在此特征图基础上卷积做预测。在YOLO的C源码中,passthrough层称为reorg layer。在TensorFlow中,可以使用tf.extract_image_patches或者tf.space_to_depth来实现passthrough层:

out = tf.extract_image_patches(in, [1, stride, stride, 1], [1, stride, stride, 1], [1,1,1,1], padding="VALID")
// or use tf.space_to_depth
out = tf.space_to_depth(in, 2)

passthrough层实例
 另外,作者在后期的实现中借鉴了ResNet网络,不是直接对高分辨特征图处理,而是增加了一个中间卷积层,先采用 64 64 64 1 × 1 1\times1 1×1 卷积核进行卷积,然后再进行passthrough处理,这样 26 × 26 × 512 26\times26\times512 26×26×512 的特征图得到 13 × 13 × 256 13\times13\times256 13×13×256 的特征图。这算是实现上的一个小细节。使用Fine-Grained Features之后YOLOv2的性能有 1 % 1\% 1%的提升。

2.8 Multi-Scale Training 多尺度训练

 由于YOLOv2模型中只有卷积层和池化层,所以YOLOv2的输入可以不限于 416 × 416 416\times416 416×416 大小的图片。为了增强模型的鲁棒性,YOLOv2采用了多尺度输入训练策略,具体来说就是在训练过程中每间隔一定的iterations之后改变模型的输入图片大小。由于YOLOv2的下采样总步长为 32 32 32,输入图片大小选择一系列为 32 32 32倍数的值: { 320 , 352 , . . . , 608 } \{320,352,...,608\} {320,352,...,608},输入图片最小为 320 × 320 320\times320 320×320 ,此时对应的特征图大小为 10 × 10 10\times10 10×10 (不是奇数了,确实有点尴尬),而输入图片最大为 608 × 608 608\times608 608×608 ,对应的特征图大小为 19 × 19 19\times19 19×19 。在训练过程,每隔 10 10 10个iterations随机选择一种输入图片大小,然后只需要修改对最后检测层的处理就可以重新训练。
多尺度训练
 采用Multi-Scale Training策略,YOLOv2可以适应不同大小的图片,并且预测出很好的结果。在测试时,YOLOv2可以采用不同大小的图片作为输入,在VOC 2007数据集上的效果如下图所示。可以看到采用较小分辨率时,YOLOv2的mAP值略低,但是速度更快,而采用高分辨输入时,mAP值更高,但是速度略有下降,对于 544 × 544 544\times544 544×544 ,mAP高达 78.6 % 78.6\% 78.6%。注意,这只是测试时输入图片大小不同,而实际上用的是同一个模型(采用Multi-Scale Training训练)。
yolov2早voc数据集上的性能对比

2.9 总结

 总结来看,虽然YOLOv2做了很多改进,但是大部分都是借鉴其它论文的一些技巧,如Faster R-CNN的anchor boxes,YOLOv2采用anchor boxes和卷积做预测,这基本上与SSD模型(单尺度特征图的SSD)非常类似了,而且SSD也是借鉴了Faster R-CNN的RPN网络。从某种意义上来说,YOLOv2和SSD这两个one-stage模型与RPN网络本质上无异,只不过RPN不做类别的预测,只是简单地区分物体与背景。在two-stage方法中,RPN起到的作用是给出region proposals,其实就是作出粗糙的检测,所以另外增加了一个stage,即采用R-CNN网络来进一步提升检测的准确度(包括给出类别预测)。而对于one-stage方法,它们想要一步到位,直接采用“RPN”网络作出精确的预测,要因此要在网络设计上做很多的tricks。YOLOv2的一大创新是采用Multi-Scale Training策略,这样同一个模型其实就可以适应多种大小的图片了。

3. 训练

YOLOv2的训练主要包括三个阶段。

  • 第一阶段就是先在ImageNet分类数据集上预训练Darknet-19,此时模型输入为 224 × 224 224 \times 224 224×224 ,共训练 160 160 160个epochs。
  • 第二阶段将网络的输入调整为 448 × 448 448 \times 448 448×448 ,继续在ImageNet数据集上finetune分类模型,训练 10 10 10个epochs,此时分类模型的top-1准确度为 76.5 % 76.5\% 76.5%,而top-5准确度为 93.3 % 93.3\% 93.3%
  • 第三个阶段就是修改Darknet-19分类模型为检测模型,并在检测数据集上继续finetune网络。网络修改包括(网路结构可视化):移除最后一个卷积层、global avgpooling层以及softmax层,并且新增了三个 3 × 3 × 2014 3 \times 3 \times 2014 3×3×2014卷积层,同时增加了一个passthrough层,最后使用 1 × 1 1 \times1 1×1 卷积层输出预测结果,输出的channels数为: n u m _ a n c h o r s × ( 5 + n u m _ c l a s s e s ) { num\_anchors \times (5 + num\_classes) } num_anchors×(5+num_classes),和训练采用的数据集有关系。
     由于anchors数为5,对于VOC数据集输出的channels数就是 125 125 125,而对于COCO数据集则为 425 425 425。这里以VOC数据集为例,最终的预测矩阵为 T { T } T(shape为 ( b a t c h _ s i z e , 13 , 13 , 125 ) (batch\_size,13,13,125) (batch_size,13,13,125)),可以先将其reshape为 ( b a t c h _ s i z e , 13 , 13 , 5 , 25 ) (batch\_size,13,13,5,25) (batch_size,13,13,5,25) ,其中 T [ : , : , : , : , 0 : 4 ] T[ : , : , : , : , 0 : 4] T[:,:,:,:,0:4] 为边界框的位置和大小 ( t x , t y , t w , t h ) (t_\text x, t_\text y, t_\text w, t_\text h) (tx,ty,tw,th) T [ : , : , : , : , 4 ] T[:, :, :, :,4] T[:,:,:,:,4] 为边界框的置信度,而 T [ : , : , : , : , 5 : ] T[: , : , : , : , 5 : ] T[:,:,:,:,5:]为类别预测值。
    yolov2训练过程
    yolov2结构示意图

4. 损失函数

 先验框匹配(样本选择)以及训练的损失函数论文中没提,不过默认按照YOLOv1的处理方式也是可以处理,参考YOLO在TensorFlow上的实现darkflow(见yolov2/train.py),发现它就是如此处理的:和YOLOv1一样,对于训练图片中的ground truth,若其中心点落在某个cell内,那么该cell内的5个先验框所对应的边界框负责预测它,具体是哪个边界框预测它,需要在训练中确定,即由那个与ground truth的IOU最大的边界框预测它,而剩余的4个边界框不与该ground truth匹配。YOLOv2同样需要假定每个cell至多含有一个grounth truth,而在实际上基本不会出现多于1个的情况。与ground truth匹配的先验框计算坐标误差、置信度误差(此时target为1)以及分类误差,而其它的边界框只计算置信度误差(此时target为0)。YOLOv2和YOLOv1的损失函数一样,为均方差函数。
yolov2损失函数参考

在这里插入图片描述
 首先 W W W, H H H 分别指的是特征图 ( 13 × 13 ) (13\times13) (13×13)的宽与高,而
指的是先验框数目(这里是5),各个 λ \lambda λ 值是各个loss部分的权重系数。第一项loss是计算background的置信度误差,但是哪些预测框来预测背景呢,需要先计算各个预测框和所有ground truth的IOU值,并且取最大值Max_IOU,如果该值小于一定的阈值(YOLOv2使用的是0.6),那么这个预测框就标记为background,需要计算noobj的置信度误差。第二项是计算先验框与预测宽的坐标误差,但是只在前 12800 12800 12800个iterations间计算,我觉得这项应该是在训练前期使预测框快速学习到先验框的形状。第三大项计算与某个ground truth匹配的预测框各部分loss值,包括坐标误差、置信度误差以及分类误差。先说一下匹配原则,对于某个ground truth,首先要确定其中心点要落在哪个cell上,然后计算这个cell的 5 5 5个先验框与ground truth的IOU值(YOLOv2中 b i a s _ m a t c h = 1 bias\_match=1 bias_match=1),计算IOU值时不考虑坐标,只考虑形状,所以先将先验框与ground truth的中心点都偏移到同一位置(原点),然后计算出对应的IOU值,IOU值最大的那个先验框与ground truth匹配,对应的预测框用来预测这个ground truth。在计算obj置信度时, t a r g e t = 1 target=1 target=1,但与YOLOv1一样而增加了一个控制参数rescore,当其为1时,target取预测框与ground truth的真实IOU值(cfg文件中默认采用这种方式)。对于那些没有与ground truth匹配的先验框(与预测框对应),除去那些Max_IOU低于阈值的,其它的就全部忽略,不计算任何误差。这点在YOLOv3论文中也有相关说明:YOLO中一个ground truth只会与一个先验框匹配(IOU值最好的),对于那些IOU值超过一定阈值的先验框,其预测结果就忽略了。这和SSD与RPN网络的处理方式有很大不同,因为它们可以将一个ground truth分配给多个先验框。尽管YOLOv2和YOLOv1计算loss处理上有不同,但都是采用均方差来计算loss。另外需要注意的一点是,在计算boxes的 w w w h h h 误差时,YOLOv1中采用的是平方根以降低boxes的大小对误差的影响,而YOLOv2是直接计算,但是根据ground truth的大小对权重系数进行修正: λ _ c o o r d _ s c a l e ∗ ( 2 − t r u t h w × t r u t h h ) \lambda\_coord\_scale * (2 - truth_w\times truth_h) λ_coord_scale(2truthw×truthh)(这里w和h都归一化到 ( 0 , 1 ) (0,1) (0,1)),这样对于尺度较小的boxes其权重系数会更大一些,可以放大误差,起到和YOLOv1计算平方根相似的效果.

5. 标签分配

YOLOv2的标签分配策略如下:

  • 网格数量的增加:YOLOv2将图像划分为 13 × 13 13\times13 13×13的网格,相比于YOLOv1的 7 × 7 7\times7 7×7网格,更多的网格意味着每个网格中多个目标中心落在一个网格的情况更少,从而减少了漏检的可能性 。
  • 引入Anchor Box机制:YOLOv2在每个网格中分配 5 5 5个不同尺寸的anchor box,通过训练集聚类得到。对于每个ground truth(GT),首先确定其中心落在哪个网格,然后与该网格对应的 5 5 5个bbox计算IOU,选择IOU最大的bbox负责该GT的预测,即该bbox为正样本 。
  • 正负样本的确定:将每一个bbox与所有的GT计算IOU,若Max_IOU小于IOU阈值,则该bbox为负样本,其余的bbox忽略。这样,一个grid中的多个bbox可以分别预测不同的类别,与YOLOv1相比,YOLOv2是以bbox(anchor)为单位进行类别预测的 。
  • 边框回归方式:YOLOv2预测基于grid的偏移量 ( t x , t y ) (t_x, t_y) (tx,ty)和基于anchor的偏移量 ( t w , t h ) (t_w, t_h) (tw,th),具体体现在loss函数中。这种方法结合了YOLOv1的直接回归和Faster R-CNN的基于anchor的偏移量回归思想 。
  • 损失函数的调整:YOLOv2在损失函数的计算中,对于confidence_loss,真值为0和 I o U ( G T , a n c h o r ) IoU(GT, anchor) IoU(GT,anchor)的值,同时在v2中confidence的预测值施加了sigmoid函数。此外,前12800步会优化预测的 ( x , y , w , h ) (x,y,w,h) (x,y,w,h)与anchor的 ( x , y , w , h ) (x,y,w,h) (x,y,w,h)的距离加上预测的 ( x , y , w , h ) (x,y,w,h) (x,y,w,h)与GT的 ( x , y , w , h ) (x,y,w,h) (x,y,w,h)的距离,12800步之后就只优化预测的 ( x , y , w , h ) (x,y,w,h) (x,y,w,h)与GT的 ( x , y , w , h ) (x,y,w,h) (x,y,w,h)的距离,这样的设计有助于在一开始预测不准确时,使用anchor加速训练 。

 这些改进使得YOLOv2在目标检测任务中相比于YOLOv1有了显著的性能提升,尤其是在减少漏检和提高召回率方面 。

代码参考

  • 定义YOLOv2的主体网络结构Darknet-19:
def darknet(images, n_last_channels=425):
    """Darknet19 for YOLOv2"""
    net = conv2d(images, 32, 3, 1, name="conv1")
    net = maxpool(net, name="pool1")
    net = conv2d(net, 64, 3, 1, name="conv2")
    net = maxpool(net, name="pool2")
    net = conv2d(net, 128, 3, 1, name="conv3_1")
    net = conv2d(net, 64, 1, name="conv3_2")
    net = conv2d(net, 128, 3, 1, name="conv3_3")
    net = maxpool(net, name="pool3")
    net = conv2d(net, 256, 3, 1, name="conv4_1")
    net = conv2d(net, 128, 1, name="conv4_2")
    net = conv2d(net, 256, 3, 1, name="conv4_3")
    net = maxpool(net, name="pool4")
    net = conv2d(net, 512, 3, 1, name="conv5_1")
    net = conv2d(net, 256, 1, name="conv5_2")
    net = conv2d(net, 512, 3, 1, name="conv5_3")
    net = conv2d(net, 256, 1, name="conv5_4")
    net = conv2d(net, 512, 3, 1, name="conv5_5")
    shortcut = net
    net = maxpool(net, name="pool5")
    net = conv2d(net, 1024, 3, 1, name="conv6_1")
    net = conv2d(net, 512, 1, name="conv6_2")
    net = conv2d(net, 1024, 3, 1, name="conv6_3")
    net = conv2d(net, 512, 1, name="conv6_4")
    net = conv2d(net, 1024, 3, 1, name="conv6_5")
    # ---------
    net = conv2d(net, 1024, 3, 1, name="conv7_1")
    net = conv2d(net, 1024, 3, 1, name="conv7_2")
    # shortcut
    shortcut = conv2d(shortcut, 64, 1, name="conv_shortcut")
    shortcut = reorg(shortcut, 2)
    net = tf.concat([shortcut, net], axis=-1)
    net = conv2d(net, 1024, 3, 1, name="conv8")
    # detection layer
    net = conv2d(net, n_last_channels, 1, batch_normalize=0,
                 activation=None, use_bias=True, name="conv_dec")
    return net
  • 对Darknet-19模型输出的解码
def decode(detection_feat, feat_sizes=(13, 13), num_classes=80,
           anchors=None):
    """decode from the detection feature"""
    H, W = feat_sizes
    num_anchors = len(anchors)
    detetion_results = tf.reshape(detection_feat, [-1, H * W, num_anchors,
                                        num_classes + 5])

    bbox_xy = tf.nn.sigmoid(detetion_results[:, :, :, 0:2])
    bbox_wh = tf.exp(detetion_results[:, :, :, 2:4])
    obj_probs = tf.nn.sigmoid(detetion_results[:, :, :, 4])
    class_probs = tf.nn.softmax(detetion_results[:, :, :, 5:])

    anchors = tf.constant(anchors, dtype=tf.float32)

    height_ind = tf.range(H, dtype=tf.float32)
    width_ind = tf.range(W, dtype=tf.float32)
    x_offset, y_offset = tf.meshgrid(height_ind, width_ind)
    x_offset = tf.reshape(x_offset, [1, -1, 1])
    y_offset = tf.reshape(y_offset, [1, -1, 1])

    # decode
    bbox_x = (bbox_xy[:, :, :, 0] + x_offset) / W
    bbox_y = (bbox_xy[:, :, :, 1] + y_offset) / H
    bbox_w = bbox_wh[:, :, :, 0] * anchors[:, 0] / W * 0.5
    bbox_h = bbox_wh[:, :, :, 1] * anchors[:, 1] / H * 0.5

    bboxes = tf.stack([bbox_x - bbox_w, bbox_y - bbox_h,
                       bbox_x + bbox_w, bbox_y + bbox_h], axis=3)

    return bboxes, obj_probs, class_probs

6. YOLO9000

 YOLO9000是在YOLOv2的基础上提出的一种可以检测超过9000个类别的模型,其主要贡献点在于提出了一种分类和检测的联合训练策略。众多周知,检测数据集的标注要比分类数据集打标签繁琐的多,所以ImageNet分类数据集比VOC等检测数据集高出几个数量级。在YOLO中,边界框的预测其实并不依赖于物体的标签,所以YOLO可以实现在分类和检测数据集上的联合训练。对于检测数据集,可以用来学习预测物体的边界框、置信度以及为物体分类,而对于分类数据集可以仅用来学习分类,但是其可以大大扩充模型所能检测的物体种类。
 作者选择在COCO和ImageNet数据集上进行联合训练,但是遇到的第一问题是两者的类别并不是完全互斥的,比如"Norfolk terrier"明显属于"dog",所以作者提出了一种层级分类方法(Hierarchical classification),主要思路是根据各个类别之间的从属关系(根据WordNet)建立一种树结构WordTree,结合COCO和ImageNet建立的WordTree如下图所示:
基于coco和imageNet数据集建立的WorldTree
 WordTree中的根节点为"physical object",每个节点的子节点都属于同一子类,可以对它们进行softmax处理。在给出某个类别的预测概率时,需要找到其所在的位置,遍历这个path,然后计算path上各个节点的概率之积。
ImageNet与WordTree预测的对比
 在训练时,如果是检测样本,按照YOLOv2的loss计算误差,而对于分类样本,只计算分类误差。在预测时,YOLOv2给出的置信度就是 P r ( p h y s i c a l   o b j e c t ) Pr(physical \ object) Pr(physical object)
,同时会给出边界框位置以及一个树状概率图。在这个概率图中找到概率最高的路径,当达到某一个阈值时停止,就用当前节点表示预测的类别。
 通过联合训练策略,YOLO9000可以快速检测出超过9000个类别的物体,总体mAP值为 19.7 % 19.7\% 19.7%。我觉得这是作者在这篇论文作出的最大的贡献,因为YOLOv2的改进策略亮点并不是很突出,但是YOLO9000算是开创之举。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐