一.前沿
前面我们讲过了YOLOv1目标检测算法,请参考:
这篇文章我们仍然通俗的介绍下YOLOv2
,YOLOv2
又叫做YOLO9000
,顾名思义,就是能够检测出9000种类别(有点夸张了,实际上根本做不到,不过作者提到的思路倒是很值得借鉴)。
YOLOv2
对YOLOv1
做了很多的改进,如加了BN
,anchor
,多尺度训练等tricks。YOLOv2
是YOLO
系列承上启下的算法,可以说YOLO
算法真正的开始算是从YOLOv2
开始的,当然这也不是说YOLOv1
你不用了解了,了解YOLOv1
对学习后面的YOLO
系列具有很大的帮助。
YOLOv1
属于YOLO
系类的最初版本,存在着很多的问题,如recall比较低、准确度差、定位能力差、检测小目标和密集型目标性能很差等问题。我们看一下YOLOv1
跟他那个时代已经存在的目标检测模型进行比较。
上面讲了YOLOv1
存在四个问题,recall比较低、准确度差、定位能力差、检测小目标和密集型目标性能很差,于是YOLOv2
就尝试着对YOLOv1
进行改进,尝试解决这些问题。下面我们正式进入YOLOv2
的讲解。
二.YOLOv2
2.1.改进
YOLOv2
采取了各种各样的手段对YOLOv1
做改进:
- BN
- High Resolution Classifier(高分辨率的分类器)
- Anchor(通过聚类得到)
- Dimension Cluster
- Direct location prediction
- Fine-Grained Feature
- Multi-Scale Training
下面我们一个一个的来看上面的tricks分别都做了什么事情
2.1.1.BN
BN就是Batch Normalization
,批标准化,BN做了什么事情呢?实际上他就是对神经元的输出进行一个归一化的操作,把每个神经元的输出减去均值再除以标准差变成一个以0为均值,1为标准差的一个分布的过程。为什么要进行这个BN操作呢,不做行不行呢? 我们知道很多激活函数如:sigmoid,tanh激活函数,他们在0附近是非饱和区(关于什么是饱和区什么是非饱和区请参考:激活函数中的硬饱和,软饱和,左饱和和右饱和。如果神经元的输出太大或者太小的话就会陷入激活函数的饱和区,饱和区就意味着梯度消失(导数为0),导致模型难以训练,所以我们通过BN 强行的把神经元的输出集中到0附近。BN 是包含测试阶段和训练阶段的,尤其要注意测试阶段是怎么处理的,用不好反而适得其反,具体可以参考:Batch Normalization详解以及pytorch实验。通过上面的对BN层的介绍,我们不难得出BN层一般放在输出特征层的后面,激活函数的前面。总结一下BN层有哪些好处:
- 加快模型训练的收敛速度
- 改善梯度,远离非饱和区
- 可以使用大的学习率,使得模型对初始化不敏感(如果没有使用BN层就有可能导致神经元的输出都在非饱和区,所以经过BN之后对初始化的敏感度就降低了。)
- 起到正则化的作用,防止过拟合甚至可以去到dropout层。 这里留一个问题:BN和dropout都可以防止过拟合,为什么他们两个一块用不行?
具体可参考:BN和Dropout同时使用的问题
2.1.2.High Resolution Classifier
一般模型的训练都是以 224 × 224 224\times224 224×224尺寸大小在ImageNet上进行训练的,而我们的YOLOv1
模型最后的输入图像时 448 × 448 448\times448 448×448,如果一个模型原来是在小尺寸上进行训练的现在又在大尺寸上进行训练,那么网络就要学会切换这两种分辨率会带来模型性能的下降。YOLOv2
怎么解决这个问题呢?作者用了最简单粗暴方法,直接在分类图像数据集上以 448 × 448 448\times448 448×448的尺寸进行骨干网络的训练,让网络适应大分辨率。实际上作者训练的时候是只在分类图像训练骨干网络的最后10个eopchs切换到 448 × 448 448\times448 448×448,节省训练时间。通过使用High Resolution Classifier
,模型提高了3.5%mAP。
2.1.3anchor
anchor
是YOLOv2
的核心。YOLOv3
,YOLOv4
,YOLOv5
也是用anchor
机制,近期的一些目标检测算法开始使用anchor free
机制。下面我们来讲下什么是anchor
机制。我们在YOLOv1
里面没有使用anchor
机制,直接使用两个bounding box
,谁跟ground truth
的 I o U IoU IoU大,谁负责去拟合这个ground truth
。这个时候可能会遇到一个问题,这两个bounding box
是不受任何约束的,想怎么变怎么变,想多大就多大,想多宽就多宽,不受约束,你觉得让这样的完全不受限制的框去拟合ground truth好吗,他能够很准确的预测出待检测物体吗?我们不能说他不好,显然很难训练,没有给他一个限定范围,完全让他俩野蛮生长,这样做显然是不好的。于是在YOLOv2
里面就引入了anchor
机制,我们不让这两个bounding box
随机生长了,给他们一个限制,加个紧箍咒,给他们一个参考,这个参考就是anchor,又叫做先验框。如上图所示以两个anchor
为例,一个瘦高,一个矮胖分别负责预测不同形状的物体。这样的话bounding box
就不会随机生长了,就会有了自己的使命了,知道自己应该预测什么样的物体了。所以这个时候每个预测框只需要预测出它相较于这个anchor
的偏移量就行了。
这个时候你又该问了,偏移量又是个什么鬼?不急,带我娓娓道来,上面我们知道了什么是anchor
,所谓的anchor
就是预先设置好的一个限制框,预测框只能根据这个限制框进行预测。下面我们讲下偏移量。上图就是YOLOv2
给出的改进,通过预测偏移量代替直接预测bounding box
,你看到这可能一脸懵逼,这么多符号都是啥意思?我来解释下其中的含义:
- b x , b y , b w , b h b_{x},b_{y},b_{w},b_{h} bx,by,bw,bh:模型最终得到的检测结果。
- t x , t y , t w , t h t_{x},t_{y},t_{w},t_{h} tx,ty,tw,th:模型要预测的结果。
- c x , c y c_{x},c_{y} cx,cy:
grid
的左上角坐标。- p w , p h p_{w},p_{h} pw,ph:
anchor
的宽和高,因为anchor
是人为设定的,所以这两个值是固定的。
有没有发现上面少了一项,置信度。置信度的标签是 P r ( o b j e c t ) ∗ I o U ( b , o b j e c t ) = σ ( t o ) Pr(object)*IoU(b,object)=\sigma(t_{o}) Pr(object)∗IoU(b,object)=σ(to),这地方和YOLOv1
是一样的,只不过这地方的置信度经过了一个归一化操作。
通过上面anchor
机制,我们从直接预测bounding box
改为预测一个偏移量,这个偏移量是什么呢,他是不是就是一个基于anchor
框的宽和高和grid
的先验位置的偏移量,得到最终目标的位置,这种方法也叫作location prediction
。注意,因为物体的宽高可能很大,所以并没有对 t w , t h t_{w},t_{h} tw,th进行限制。只是把 t x , t y t_{x},t_{y} tx,ty中心坐标限制在了grid
里面了。刚才我们说了 t x , t y , t w , t h t_{x},t_{y},t_{w},t_{h} tx,ty,tw,th是模型要预测的值,那么这几个值应该怎么计算呢?
观察上面的图,我们可以计算出:
t x = l o g [ 0.07 1 − 0.07 ] = − 1.12 t y = l o g [ 0.116 1 − 0.116 ] = − 0.882 t w = l o g ( 320 370 ) = − 0.063 t h = l o g ( 380 280 ) = 0.132 t_{x}=log[\frac{0.07}{1-0.07}]=-1.12\\ t_{y}=log[\frac{0.116}{1-0.116}]=-0.882\\ t_{w}=log(\frac{320}{370})=-0.063\\ t_{h}=log(\frac{380}{280})=0.132 tx=log[1−0.070.07]=−1.12ty=log[1−0.1160.116]=−0.882tw=log(370320)=−0.063th=log(280380)=0.132
关于上面的0.07,0.116这些值是怎么来的,我知道你肯定有疑问。下面我们来看下。
我们知道 b x = s i g m o i d ( t x ) + c x b_{x}=sigmoid(t_{x})+c_{x} bx=sigmoid(tx)+cx,又因为 s i g m o i d ( t x ) = 1 1 + e − t x sigmoid(t_{x})=\frac{1}{1+e^{-t_{x}}} sigmoid(tx)=1+e−tx1,从而可以推出 t x = l o g ( b x − c x 1 − ( b x − c x ) ) t_{x}=log(\frac{b_{x}-c_{x}}{1-(b_{x}-c_{x})}) tx=log(1−(bx−cx)bx−cx),代入anchor
框的宽高、grid的坐标和预测框的宽高即可计算出 t x , t y , t w , t h t_{x},t_{y},t_{w},t_{h} tx,ty,tw,th四个值。
通过上面计算之后,要预测的值就变成了 t x , t y , t w , t h = − 1.12 , − 0.882 , − 0.063 , 0.132 t_{x},t_{y},t_{w},t_{h}=-1.12,-0.882,-0.063,0.132 tx,ty,tw,th=−1.12,−0.882,−0.063,0.132这是一个偏移量,这个时候再预测是不是比之前直接预测bounding box
容易多了。
通过上面的讲解,我相信你对anchor和偏移量已经有了了解了,下面我们回归正题,继续回到YOLOv2
。在实际训练和预测的时候,YOLOv2
是把整个图像给划分成 13 × 13 13\times13 13×13个grid
,每个grid
有5个anchor
,就是事先指定了5种大小不同的先验框,每个anchor
都对应一个预测框,而这个预测框只需要去负责预测输出他相对于他所在的anchor
偏移量就行了。如下图所示,如果要预测小女孩,人工标注框的中心点落在哪个grid里面就应该由哪个grid
产生的五个anchor
中与ground truth
的 I o U IoU IoU最大的那个anchor
去负责拟合ground truth
。而这个anchor
对应的预测框只需要预测他相较于这个anchor
的偏移量就行了,意思就是在这个anchor
的基础上进行预测。
我们知道每个grid
都有5个anchor
,ground truth
落在哪个grid
就由哪个grid
产生的五个anchor
中的一个来负责预测预测这个ground truth
,总共5个anchor
,那么由哪一个anchor
去负责预测呢?由与anchor
的 I o U IoU IoU最大的那个去负责预测。 这个anchor
对应的预测框只需要输出它相比于它所在的anchor
的偏移量就行了。
我们上面有讲到YOLOv2
最后输出的网格大小是 13 × 13 13\times13 13×13,这个网格的长宽都是奇数,是为了便于预测物体的只有一个中心的grid
,如果是偶数的话就会有好几个。
通过上面的介绍可以知道YOLOv2
模型输出的结构也改了,如下图所示,在YOLOv1
里面没有用anchor
,而是直接划分成 7 × 7 7\times7 7×7个grid,每个grid
输出两个bounding box
,每个bounding box
有两个未知参数和一个置信度,以及每个grid
还预测出了20个类别的条件类别概率。在YOLOv2
里面模型就变得稍微复杂一些,YOLOv1
里面的类别是由grid
负责,在YOLOv2
里面类别变成由anchor
负责了,每个grid
产生5个anchor
,每个anchor
除了产生4个定位参数和一个置信度参数之外还有20个类别的条件类别概率,所以在YOLOv2
里面每个anchor
都会产生25个数,总共输出 5 × 5 = 125 5\times5=125 5×5=125个数。即YOLOv2
总共输出 13 × 13 × 125 = 21125 13\times13\times125=21125 13×13×125=21125个数。最终的目标检测结果就是从这21125个数里面提取出来的。
下图可视化了YOLOv2的anchor的产生过程。
上面每个grid
将产生5个anchor
,你有没有疑问,为什么一定是5个,我产生10个不行吗?当然可以,只不过没必要,太浪费,导致网络参数太多。作者通过聚类的方法对PASCAL VOC
和COCO
数据集的长宽比进行了聚类操作,黑色框表示VOC数据集,紫色框表示COCO数据集。通过观察左图可以发现,聚类的长宽比种类越多,所覆盖的 I o U IoU IoU也越大,但是模型也会编的更复杂,作者在此取了个折中,直接取值为5,兼顾了准确度效率。长宽度如下图的有图所示。通过聚类的方法确定anchor
数目。比双阶段的目标检测,如Faster RCNN
手动去选择anchor
要科学很多,聚类产生anchor
一直到YOLOV5
还在用,后面就开始使用anchor free
了。
下面我们讲一下YOLOv2
的损失函数。
在YOLOv2
论文里面并没有提到上面的损失函数,网上大神根据YOLOv2
的代码整理出来的。
首先,我们看下损失函数的最外层,总共有三次求和,分别是遍历所有的宽,高和anchor
的个数。然后是这个求和符号后面总共有三项,每项前面还有一个条件系数,这个系数非0即1。系数后面又有个 λ \lambda λ,表示权重:
anchor
与ground truth
的 I o U IoU IoU是否小于0.6,代码中给的阈值Thresh
为0.6,他们是被抛弃的框,并不负责预测物体,他们的置信度越为0越好,括号里面为什么会有个负号?他表示是 ( 0 − b i j k o ) 2 (0-b_{ijk}^o)^2 (0−bijko)2,0就是那些被抛弃的框的标签, b i j k o b_{ijk}^o bijko表示预测框置信度。怎么计算 I o U IoU IoU? 让anchor
和ground truth
的中心点重合来计算,只看形状,不看位置,就是anchor的中心点不重合也把他移到重合位置去算,你是不是觉得这样做很离谱,所以作者在YOLOv3中又对其进行了改进,具体改进方法课参考我的另一篇博文, I o U IoU IoU的进化之路。IoU、GIoU、DIoU、CIoU四种损失函数总结。anchor
能够各司其职,尽快找到自己负责的物体,让模型能够更加的稳定。anchor
负责检测物体,一个grid
产生5个anchor
,这个负责检测物体的anchor
就是5个anchor
里面与ground truth
最大的那个anchor
,也就是一个ground truth
只分配给一个anchor
。这个地方你会不会有疑问,负责预测物体的grid
的里面的不是与 I o U IoU IoU最大但是又与ground truth
的 I o U IoU IoU大于0.6的那些anchor
怎么处理。或者你也可以这样理解,总共三类anchor
,第一类是与gouund truth
的 I o U IoU IoU最大的anchor
,用来负责预测物体,第二类是与gouund truth
的 I o U IoU IoU小于0.6的,也就是损失函数第一项,第三类是与gouund truth
的 I o U IoU IoU大于0.6但又不是与ground truth
的 I o U IoU IoU最大的那些anchor
怎么处理?作者怎么处理的呢?他是直接把第三类的给抛弃了。对于负责预测物体的这个anchor
总共要计算三项,分别是定位误差、置信度误差、分类误差。定位误差就是计算ground truth
的位置和预测框的位置的误差。置信度误差就是计算anchor
与标注框ground truth
的 I o U IoU IoU与预测框置信度得误差,这里anchor
与 I o U IoU IoU的标签是置信度标签,YOLOv1
也是这样设计的。而分类误差就是计算标注框类别和预测框类别的误差,并且遍历所有类别。 损失函数我们讲过了,下面我们来继续接着讲作者对YOLV1的不足之处做的改进
2.1.4.Fine-Grained Features(细粒度特征)
总结一句话就是长宽变为原来的一半,通道数变为原来的4倍。如下图的展示了一个可视化,就是网络中的Pass Throuth层。
2.1.5.Multi-Scale Training
在模型训练期把不同的图像大小输入到模型,这个地方你还不会有疑问,输入图像大小变了,我的网络模型不用调整吗?作者肯定考虑了这个问题,他用了一个全局平均池化层,对每个通道求平均来替换全连接层。如下图所示,速度跟图像尺寸成反比,精度跟输入图像尺寸成正比。
最后我们再来看张图,作者加了这些tricks到底有没有用。
上面我们讲的都是针对YOLOv2
为什么会更好,下面我们再来介绍下YOLOv2
为什么会更快。作者换了骨干网络,我们知道YOLOv1
作者使用的是VGG
骨干网络,很臃肿,在YOLOv2
里面换成了作者自己设计Darknet19
。
最后再讲下怎么检测更多的类别?
不是重点,简单了解即可,我们知道COCO
也不过就80个类别,作者是怎么做到能够检测出9000各类别的,是在夸大其词还是真的能够做到检测9000个类别?
YOLOv2
作者是这样做的,他把COCO
数据集和ImageNet
数据集进行联合训练,我们知道ImageNet
数据集有两万多个类别,但是呢ImageNet
又没有定位标签,只有分类标签,并且ImageNet
的分类细粒度还很高,比如狗这个类别,他是什么狗,是阿拉斯加还是二哈,还是金毛,拉布拉多等。于是作者把他两个联合起来训练让模型能够学习到更多细粒度的特征。主要就是让目标检测数据集学定位,分类数据集学识别。这个地方简单了解即可,这里不做过多介绍。
至此,我们的YOLOv2模型理论部分也基本上介绍完了,YOLOv3
的理论部分我们下篇文章再讲,在此,附一个YOLO
系列的文章链接:
YOLOv1目标检测算法——通俗易懂的解析
YOLOv3目标检测算法——通俗易懂的解析
YOLOv4目标检测算法——通俗易懂的解析
YOLOv5目标检测算法——通俗易懂的解析
欢迎各位大佬批评指正。