YOLO V2是原作者在V1基础上做出改进后提出的。为了达到题目中所称的Better,Faster,Stronger的目标,主要改进点如下。当然,具体内容还是要深入论文。
下面,还是先把论文的摘要意译如下:
我们引入了YOLO 9000模型,它是实时物体检测的State of the art的工作,能够检测超过9K类目标。首先,我们对以前的工作(YOLO V1)做出了若干改进,使其成为了一种在实时检测方法内在PASCAL VOC和COCO上State of the art的效果。通过一种新颖的多尺度训练犯法(multi-scale training method), YOLO V2适用于不同大小尺寸输入的image,在精度和效率上达到了很好地trade-off。在67fps的速度下,VOC2007上达到了76.8mAP的精度。40fps时,达到了78.6mAP,已经超过了Faster RCNN with ResNet和SSD的精度,同时比它们更快!最后,我们提出了一种能够同时进行检测任务和分类任务的联合训练方法。使用这种方法,我们在COCO detection dataset和ImageNet classification dataset上同时训练模型。这种方法使得我们能够对那些没有label上detection data的数据做出detection的预测。我们使用ImageNet的detection任务做了验证。YOLO 9000在仅有200类中44类的detection data的情况下,仍然在ImageNet datection任务中取得了19.7mAP的成绩。对于不在COCO中的156类,YOLO9000成绩为16.0mAP。我们使得YOLO9000能够在保证实时的前提下对9K类目标进行检测。
根据论文结构的安排,将从Better,Faster和Stronger三个方面对论文提出的各条改进措施进行介绍。
在YOLO V1的基础上,作者提出了不少的改进来进一步提升算法的性能(mAP),主要改进措施包括网络结构的改进(第1,3,5,6条)和Anchor Box的引进(第3,4,5条)以及训练方法(第2,7条)。
Batch Normalization能够加快模型收敛,并提供一定的正则化。作者在每个conv层都加上了了BN层,同时去掉了原来模型中的drop out部分,这带来了2%的性能提升。
YOLO V1首先在ImageNet上以 224×224 224 × 224 大小图像作为输入进行训练,之后在检测任务中提升到 448×448 448 × 448 。这里,作者在训练完224大小的分类网络后,首先调整网络大小为 448×448 448 × 448 ,然后在ImageNet上进行fine tuning(10个epoch)。也就是得到了一个高分辨率的cls。再把它用detection上训练。这样,能够提升4%。
YOLO V1中直接在CNN后面街上全连接层,直接回归bounding box的参数。这里引入了Faster RCNN中的anchor box概念,不再直接回归bounding box的参数,而是相对于anchor box的参数。
作者去掉后面的fc层和最后一个max pooling层,以期得到更高分辨率的feature map。同时,shrink网络接受 416×416 416 × 416 (而不是448)大小的输入image。这是因为我们想要最后的feature map大小是奇数,这样就能够得到一个center cell(比较大的目标,更有可能占据中间的位置),相比与中间四个bbox来确定大物体的位置,使用中心的位置更加容易cover到大的物体。由于YOLO conv-pooling的效应是将image downsamplig 32倍,所以最后feature map大小为 416/32=13 416 / 32 = 13 。
与YOLO V1不同的是,我们不再对同一个grid cell下的bounding box统一产生一个数量为 C C 的类别概率,而是对于每一个bounding box都产生对应的 C C 类概率。因为在小物体检测方面,如果多个物体都在一个box当中,yolo1中只选择一个bbox,yolo2中可以分别预测每个box的类别。其他和YOLO V1一样的是,意义也完全一样。
使用anchor后,我们的精度accuracy降低了,不过recall上来了。(这也较好理解。原来每个grid cell内部只有2个bounding box,造成recall不高。现在recall高上来了,accuracy会下降一些)。
在引入anchor box后,一个问题就是如何确定anchor的位置和大小?Faster RCNN中是手工选定的,每隔stride设定一个anchor,并根据不同的面积比例和长宽比例产生9个anchor box。在本文中,作者使用了聚类方法对如何选取anchor box做了探究。这点应该是论文中很有新意的地方。
这里对作者使用的方法不再过多赘述,强调以下两点:
作者使用的聚类方法是K-Means;
相似性度量不用欧氏距离,而是用IoU,定义如下:
使用不同的 k k ,聚类实验结果如下,作者折中采用了 k=5 k = 5 。而且经过实验,发现当取 k=9 k = 9 时候,已经能够超过Faster RCNN采用的手工固定anchor box的方法。下图右侧图是在COCO和VOC数据集上 k=5 k = 5 的聚类后结果。这些box可以作为anchor box使用。
我们仍然延续了YOLO V1中的思路,预测box相对于grid cell的位置。因为使用原来的公式进行计算中心坐标的时候坐标可能出现在图片中的任何一个点,因此,使用sigmoid函数作为激活函数和cell位置的offset,使得最终输出值落在cell当中,是的模型bbox回归对位置敏感程度降低。
在output的feature map上,对于每个cell(共计 13×13 13 × 13 个),给出对应每个bounding box的输出 tx t x , ty t y , tw t w , th t h 。每个cell共计 k=5 k = 5 个bounding box。如何由这几个参数确定bounding box的真实位置呢?见下图。
确定bbox的位置
设该grid cell距离图像左上角的offset是 (cx,cy) ( c x , c y ) ,那么bounding box的位置和宽高计算如下。注意,box的位置是相对于grid cell的,而宽高是相对于anchor box的。
bounding box参数的计算方法
Darknet中的具体的实现代码如下(不停切换中英文输入实在是蛋疼,所以只有用我这蹩脚的英语来注释了。。。):
// get bounding box
// x: data pointer of feature map
// biases: data pointer of anchor box data
// biases[2*n] = width of anchor box
// biases[2*n+1] = height of anchor box
// n: output bounding box for each cell in the feature map
// index: output bounding box index in the cell
// i: `cx` in the paper
// j: 'cy' in the paper
// (cx, cy) is the offset from the left top corner of the feature map
// (w, h) is the size of feature map (do normalization in the code)
box get_region_box(float *x, float *biases, int n, int index, int i, int j, int w, int h)
{
box b;
// i <- cx, j <- cy
// index + 0: tx
// index + 1: ty
// index + 2: tw
// index + 3: th
// index + 4: to // not used here
// index + 5, +6, ..., +(C+4) // confidence of P(class c|Object), not used here
b.x = (i + logistic_activate(x[index + 0])) / w; // bx = cx+sigmoid(tx)
b.y = (j + logistic_activate(x[index + 1])) / h; // by = cy+sigmoid(ty)
b.w = exp(x[index + 2]) * biases[2*n] / w; // bw = exp(tw) * pw
b.h = exp(x[index + 3]) * biases[2*n+1] / h; // bh = exp(th) * ph
// 注意这里都做了Normalization,将值化到[0, 1],论文里面貌似没有提到
// 也就是说YOLO 用于detection层的bounding box大小和位置的输出参数都是相对值
return b;
}
顺便说一下对bounding box的bp实现。具体代码如下:
// truth: ground truth
// x: data pointer of feature map
// biases: data pointer of anchor box data
// n, index, i, j, w, h: same meaning with `get_region_box`
// delta: data pointer of gradient
// scale: just a weight, given by user
float delta_region_box(box truth, float *x, float *biases,
int n, int index, int i, int j, int w, int h,
float *delta, float scale)
{
box pred = get_region_box(x, biases, n, index, i, j, w, h);
// get iou of the bbox and truth
float iou = box_iou(pred, truth);
// ground truth of the parameters (tx, ty, tw, th)
float tx = (truth.x*w - i);
float ty = (truth.y*h - j);
float tw = log(truth.w*w / biases[2*n]);
float th = log(truth.h*h / biases[2*n + 1]);
// 这里是欧式距离损失的梯度回传
// 以tx为例。
// loss = 1/2*(bx^hat-bx)^2, 其中bx = cx + sigmoid(tx)
// d(loss)/d(tx) = -(bx^hat-bx) * d(bx)/d(tx)
// 注意,Darkent中的delta存储的是负梯度数值,所以下面的delta数组内数值实际是-d(loss)/d(tx)
// 也就是(bx^hat-bx) * d(bx)/d(tx)
// 前面的(bx^hat-bx)把cx约掉了(因为是同一个cell,偏移是一样的)
// 后面相当于是求sigmoid函数对输入自变量的梯度。
// 由于当初没有缓存 sigomid(tx),所以作者又重新计算了一次 sigmoid(tx),也就是下面的激活函数那里
delta[index + 0] = scale * (tx - logistic_activate(x[index + 0]))
* logistic_gradient(logistic_activate(x[index + 0]));
delta[index + 1] = scale * (ty - logistic_activate(x[index + 1]))
* logistic_gradient(logistic_activate(x[index + 1]));
// tw 相似,只不过这里的 loss = 1/2(tw^hat-tw)^2,而不是和上面一样使用bw^hat 和 bw
delta[index + 2] = scale * (tw - x[index + 2]);
delta[index + 3] = scale * (th - x[index + 3]);
return iou;
}
接下来,我们看一下bp的计算。主要涉及到决定bounding box大小和位置的四个参数的回归,以及置信度 to t o ,以及 C C 类分类概率。上面的代码中已经介绍了bounding box大小位置的四个参数的梯度计算。对于置信度 to t o 的计算,如下所示。
// 上面的代码遍历了所有的groundtruth,找出了与当前预测bounding box iou最大的那个
// 首先,我们认为当前bounding box没有responsible for any groundtruth,
// 那么,loss = 1/2*(0-sigmoid(to))^2
// => d(loss)/d(to) = -(0-sigmoid(to)) * d(sigmoid)/d(to)
// 由于之前的代码中已经将output取了sigmoid作用,所以就有了下面的代码
// 其中,logistic_gradient(y) 是指dy/dx|(y=y0)的值。具体来说,logistic_gradient(y) = (1-y)*y
l.delta[index + 4] = l.noobject_scale * ((0 - l.output[index + 4]) * logistic_gradient(l.output[index + 4]));
// 如果best iou > thresh, 我们认为这个bounding box有了对应的groundtruth,把梯度直接设置为0即可
if (best_iou > l.thresh) {
l.delta[index + 4] = 0;
}
这里额外要说明的是,阅读代码可以发现,分类loss的计算方法和V1不同,不再使用MSELoss,而是使用了交叉熵损失函数。对应地,梯度计算的方法如下所示。不过这点在论文中貌似并没有体现。
// for each class
for(n = 0; n < classes; ++n){
// P_i = \frac{exp^out_i}{sum of exp^out_j}
// SoftmaxLoss = -logP(class)
// ∂SoftmaxLoss/∂output = -(1(n==class)-P)
delta[index + n] = scale * (((n == class)?1 : 0) - output[index + n]);
if(n == class) *avg_cat += output[index + n];
}
这个trick是受Faster RCNN和SSD方法中使用多个不同feature map提高算法对不同分辨率目标物体的检测能力的启发,加入了一个pass-through层,直接将倒数第二层的 26×26 26 × 26 大小的feature map加进来。
在具体实现时,是将higher resolution(也就是 26×26 26 × 26 )的feature map stacking在一起。比如,原大小为 26×26×512 26 × 26 × 512 的feature map,因为我们要将其变为 13×13 13 × 13 大小,所以,将在空间上相近的点移到后面的channel上去,这部分可以参考Darknet中reorg_layer的实现。
使用这一扩展之后的feature map,提高了1%的性能提升。
在实际应用时,输入的图像大小有可能是变化的。我们也将这一点考虑进来。因为我们的网络是全卷积神经网络,只有conv和pooling层,没有全连接层,所以可以适应不同大小的图像输入。所以网络结构上是没问题的。
具体来说,在训练的时候,我们每隔一定的epoch(例如10)就随机改变网络的输入图像大小。由于我们的网络最终降采样的比例是 32 32 ,所以随机生成的图像大小为 32 32 的倍数,即 {320,352,…,608} { 320 , 352 , … , 608 } 。
在实际使用中,如果输入图像的分辨率较低,YOLO V2可以在不错的精度下达到很快的检测速度。这可以被用应在计算能力有限的场合(无GPU或者GPU很弱)或多路视频信号的实时处理。如果输入图像的分辨率较高,YOLO V2可以作为state of the art的检测器,并仍能获得不错的检测速度。对于目前流行的检测方法(Faster RCNN,SSD,YOLO)的精度和帧率之间的关系,见下图。可以看到,作者在30fps处画了一条竖线,这是算法能否达到实时处理的分水岭。Faster RCNN败下阵来,而YOLO V2的不同点代表了不同输入图像分辨率下算法的表现。对于详细数据,见图下的表格对比(VOC 2007上进行测试)。
不同检测方法的对比
不同检测方法的对比
在Better这部分的末尾,作者给出了一个表格,指出了主要提升性能的措施。例外是网络结构上改为带Anchor box的全卷积网络结构(提升了recall,但对mAP基本无影响)和使用新的网络(计算量少了~33%)。
不同改进措施的影响
这部分的改进为网络结构的变化。包括Faster RCNN在内的很多检测方法都使用VGG-16作为base network。VGG-16精度够高但是计算量较大(对于大小为 224×224 224 × 224 的单幅输入图像,卷积层就需要30.69B次浮点运算)。在YOLO V1中,我们的网络结构较为简单,在精度上不如VGG-16(ImageNet测试,88.0% vs 90.0%)。
在YOLO V2中,我们使用了一种新的网络结构Darknet-19(因为base network有19个卷积层)。和VGG相类似,我们的卷积核也都是 3×3 3 × 3 大小,同时每次pooling操作后channel数double。另外,在NIN工作基础上,我们在网络最后使用了global average pooling层,同时使用 1×1 1 × 1 大小的卷积核来做feature map的压缩(分辨率不变,channel减小,新的元素是原来相应位置不同channel的线性组合),同时使用了Batch Normalization技术。具体的网络结构见下表。Darknet-19计算量大大减小,同时精度超过了VGG-16。
Darknet-19的网络结构
在训练过程中,首先在ImageNet 1K上进行分类器的训练。使用数据增强技术(如随机裁剪、旋转、饱和度变化等)。和上面的讨论相对应,首先使用 224×224 224 × 224 大小的图像进行训练,再使用 448×448 448 × 448 的图像进行fine tuning,具体训练参数设置可以参见论文和对应的代码。这里不再多说。
然后,我们对检测任务进行训练。对网络结构进行微调,去掉最后的卷积层(因为它是我们当初为了得到1K类的分类置信度加上去的),增加 3 3 个 3×3 3 × 3 大小,channel数为 1024 1024 的卷积层,并每个都跟着一个 1×1 1 × 1 大小的卷积层,channel数由我们最终的检测任务需要的输出决定。对于VOC的检测任务来说,我们需要预测 5 5 个box,每个需要 5 5 个参数(相对位置,相对大小和置信度),同时 20 20 个类别的置信度,所以输出共 5×(5+20)=125 5 × ( 5 + 20 ) = 125 。从YOLO V2的yolo_voc.cfg文件中,我们也可以看到如下的对应结构:
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=1024
activation=leaky
[convolutional]
size=1
stride=1
pad=1
filters=125
activation=linear
同时,加上上文提到的pass-through结构。
转自https://xmfbit.github.io/2017/02/04/yolo-paper/