YOLOv1,you only look once
- 1. 网络简介
- 2. 网络设计思路
- 3. 网络结构
- 4. 网络训练和检测的技巧
- 5. 网络的优缺点
其实经典的CNN如AlexNet,VGG,GoogLeNet和ResNet部分的搭建和训练都已经完成了,但是还是想之后结合论文再一起介绍,所以这次先来个比较有难度的任务,目标检测YOLOv1网络的实现。要自己动手实现网络,第一步当然是阅读论文,论文阅读是能让你最快了解某个网络框架思路的方式,遇到论文中不太懂的或文中解释比较简单的内容,可以借助google或baidu来加深理解。不得不说,YOLOv1的论文写的实在太简略了,以致于你单看论文容易理解,但是自己实现的时候会发现很多细节隐藏在论文的各个角落,真的是论文里每一句话都是一个细节。另外,要想复现网络,参考源代码是必不可少的,源码虽然不如论文那么直观,但是阅读一遍源码和阅读论文相比,前者对网络的理解帮助是非常显著的。源码的传送门
这类文献阅读的博客,我不会简单的把论文翻译一遍,其实这样除了锻炼英语翻译能力以外,意义也不大。我就按自己对文章的理解,将论文里的重点提出来然后归纳一下,如果对某些部分不太理解的读者,可以参阅论文原文“You Only Look Once: Unified, Real-Time Object Detection"
1. 网络简介
YOLO网络名称就很直观,“you only look once”,这句话是相对于RCNN和传统计算机视觉算法中,针对目标检测问题的共同特点,即都包含一个先验的目标区域划定的步骤,在RCNN中就是Resion proposal。其实也正常,要解决目标检测问题,最直观的想法当然就是在图片中框出n个框(或者使用滑窗法),然后对每个框进行检测,是不是包含物体?包含什么类别的物体?然后再根据物体的真实框对预测得出的框进行调整。这样,就相当于你需要将一张图像分割成多张子图像,然后对每张子图像进行单独处理。
而YOLO不一样,它是一次性就把一张图片输入网络,然后得到预测结果,预测结果中就包含所有该网络对目标的预测信息(包含目标框信息(bounding box,下文用bbox简称)和目标类别信息),只需要从中选择合适的,去除多余无效的信息即可。所以它相当于只看了图像一次就处理出了结果。
论文引言中主要介绍了YOLO的三个特点:
1).快,飞一般的快
YOLO网络的设计初衷就是希望目标检测任务可以在类似自动驾驶等领域应用,那么就需要很好的实时性。而YOLO由于没有过多的额外处理,没有使用滑窗法产生大量的待定框,因此网络在inference的时候相比于RCNN等方式效率非常高。
2).视野更广
YOLO网络由于直接对全图进行处理,所以网络提取的信息是具有全局性的,而滑窗法等方式,其信息只能局限于对应的窗口。所以,论文中提到,YOLO的背景检测误差相比于Fast RCNN网络来说,减少了一半以上。
3).泛化能力更强
论文中提到,YOLO网络学习了目标的泛化特征,也就是网络对于图中什么东西可能是目标物体(而不管类别)是有一定理解的。因此,在输入训练集中不存在的物体或者是艺术图像时,其效果要比RCNN更好。但是此处我感觉,只是相对于其他网络来说要更好,但是实际上泛化能力还是不够强,这一点在YOLOv2版本中就有很大的改善,体现出YOLO网络思路的泛化潜力是很强的。
2. 网络设计思路
YOLOv1的网络思路大致如下:
- 首先,利用经典的图像分类网络思路,进行图像的特征提取;
- 然后,将提取到的特征再进行训练,得到一组网络的输出结果;
- 训练时,将最终结果和样本标签进行加工,然后计算loss值,再进行梯度更新;
- inference时,直接根据网络得到的最终结果,经过NMS等算法选出符合条件的bbox,即最终结果。
上述思路流程看似很简单,但是其中有三个关键问题,作者针对这些问题做了大量的工作:
1).网络的输出结果到底代表了什么?(组成结构?)
论文里提到,YOLOv1的网络将图像划分为 S × S S×S S×S 个网格(如上最左图,论文中设定 S = 7 S=7 S=7),每个网格都会预测 B B B 个预测框(bounding box,如上面的中上图,论文中设定 B = 2 B=2 B=2,即每个网格都预测两个bbox,共有 7 × 7 × 2 = 98 7×7×2=98 7×7×2=98个网格),并且每个网格都会得到 C C C 个类别的条件概率,且设定一个网格最终只预测一个类别(如上面的中下图,每种颜色代表每个网格预测的一个类别,YOLOv1主要使用VOC数据集,因此 C = 20 C=20 C=20)。
其实这几句话就已经表明了网络的输出结果,只是需要稍微再说的详细一些。
我们假设,网络的输出结果是维度尺寸为 S × S × ( B × 5 + C ) = 7 × 7 × 30 S×S×(B×5+C)=7×7×30 S×S×(B×5+C)=7×7×30的矩阵:
- 为什么论文说YOLOv1将原图像划分为 7 × 7 7×7 7×7的网格?其实是因为他们的网络最终输出的结果是 7 × 7 7\times7 7×7的feature map,相比于原图像 448 × 448 448\times448 448×448的尺寸,最终的feature map上每个 1 × 1 1\times1 1×1像素点相当于对应原图像 64 × 64 64\times64 64×64的网格,所以说原图像被划分为 7 × 7 7\times7 7×7的网格。
- 7 × 7 7\times7 7×7的每个网格,都包含30个通道( 7 × 7 × 30 7\times7\times30 7×7×30),前10个通道分别对应 B × 5 B\times5 B×5,也就是2个bounding box的信息,每个bounding box都有5个信息表示,分别是 ( p x , p y , w , h , c o n f i d e n c e ) (px,py,w,h,confidence) (px,py,w,h,confidence),前四个是表示bounding box的位置信息,具体说明见下图所示,其中 x c , y c x_c,y_c xc,yc是预测框的中心坐标, x g , y g x_g,y_g xg,yg是预测框中心点所在网格(图中红色网格)的左上角点的坐标,gridsize是网格大小, w b , h b w_b,h_b wb,hb是预测框的宽和高以及 H,W分别是图像的高和宽。计算关系如下:
I). px,py:预测框的中心落在某个网格(gridcell)中,px,py是该中心点坐标在这个网格中的相对位置,值域为0~1
p x = x c − x g g r i d s i z e p y = y c − y g g r i d s i z e px=\frac{x_c-x_g}{gridsize}\quad py=\frac{y_c-y_g}{gridsize} px=gridsizexc−xgpy=gridsizeyc−yg
II). w,b:标准化后的预测框的宽和高,值域为0~1
w = w b W h = h b H w = \frac{w_b}{W} \quad h = \frac{h_b}{H} w=Wwbh=Hhb
III). confidence:作者将其设置为置信度,用来表示当前bbox可能存在目标物体(不分类别)的概率与目标物体的真实bbox(也称为ground truth)的IOU的乘积
c o n f i d e n c e = P r ( O b j e c t ) × I O U p r e d t r u t h confidence = Pr(Object)\times IOU_{pred}^{truth} confidence=Pr(Object)×IOUpredtruth
这样,每个bbox就有5个信息,2个bbox就对应需要10个通道的信息来表示。
- 剩下的20个通道,就和图像分类任务的softmax层输出的含义一样,就是代表20个预测类别的概率,用 c i ( i = 0 , 1 , . . . , 19 ) c_i(i=0,1,...,19) ci(i=0,1,...,19)来表示。
通过上述说明,就可以清楚的知道YOLO网络的输出到底是什么了,其实就是 7 × 7 × 30 7\times7\times30 7×7×30的数据,每个 1 × 1 × 30 1\times1\times30 1×1×30的数据都代表1个网格预测的2个bounding box的信息,每个网格只预测一个物体类别。另外需要说明的是,实际上YOLO网络最终的输出层是全连接层,所以输出的数据其实是 7 × 7 × 30 = 1470 7\times7\times30=1470 7×7×30=1470的一维数据,而不是那么立体的三维数据,不过其实对输出数据reshape一下就可以了。
再稍微提一下,为什么设计的bbox位置信息那么麻烦,不直接使用bbox左上角点的位置和右下角点的位置,或者直接使用预测框中心坐标 x c , y c x_c,y_c xc,yc和对应的宽高w,h呢?理由很简单,一切都是为了降低训练难度!首先,有CNN卷积网络的基础的读者都知道,将数据标准化后再进行训练,训练效果是会好很多的。这里就对bbox的 w b , h b w_b,h_b wb,hb进行了标准化。而bbox中心坐标 x c , y c x_c,y_c xc,yc的处理其实也是一种标准化,把中心坐标转变为网格内部的相对位置坐标。使用这种标准化比将bbox中心坐标转变为相对整张图像位置的相对位置坐标效果更好,因为作者希望的是每个网格只负责寻找中心落在当前网格内的bbox,而不是希望每个网格都寻找全局范围内的bbox。可见,这样对于网络训练来说,降低了每个网格预测任务的难度,那么网络训练起来应该也更容易收敛。
2).在计算loss前,要对训练样本的标签做什么处理?
其实上文说到的 7 × 7 × 30 7\times7\times30 7×7×30的输出数据,每个数据代表什么含义,都是人为自己定义的,也就是说,是作者希望图片输入到YOLO网络中,输出的1470维数据代表的上述含义。深度学习的训练其实是对网络的一个引导过程,所以,想要网络按你所想的产生输出,就需要设计好对应的标签(label),来引导网络,这也是对网络训练的目的。
YOLOv1使用的主要数据集是VOC2012,该数据集只提供了每张图片的名称及图片中标注出来的真实bounding box的信息(cls,x,y,w,h),表示该bbox的物体类别,中心位置x,y(绝对坐标,即图中像素坐标),以及bbox的宽和高。每张图片标注的bbox数量不定,假设为n,那么每张图片的标签数据就是 n × 5 n\times5 n×5 的维度,要怎么将这个标签数据转换为 7 × 7 × 30 7\times7\times30 7×7×30的YOLO标签的数据格式呢?其实理解了网络输出数据的结构就很清楚了,步骤如下:
- 初始化一个全零的 7 × 7 × 30 7\times7\times30 7×7×30矩阵M,对每个bbox的5个信息做如下操作:
- 通过 f l o o r ( x W × g r i d s i z e ) , f l o o r ( y H × g r i d s i z e ) floor(\frac{x}{W\times gridsize}),floor(\frac{y}{H\times gridsize}) floor(W×gridsizex),floor(H×gridsizey)计算出bbox中心位置所在的网格位置 ( i , j ) (i,j) (i,j),其中floor是向下取整函数。
- 将bbox的 ( x c , y c , w b , h b ) (x_c,y_c,w_b,h_b) (xc,yc,wb,hb)数据标准化为 ( p x , p y , w , h ) (px,py,w,h) (px,py,w,h)数据
- 将 M ( i , j , 0 : 10 ) M(i,j,0:10) M(i,j,0:10)的前10个通道设置为 ( p x , p y , w , h , 1 , p x , p y , w , h , 1 ) (px,py,w,h,1,px,py,w,h,1) (px,py,w,h,1,px,py,w,h,1),也就是此处置信度都置为1,因为此时还不知道预测框的位置信息,所以没法计算IOU,之后在计算loss的时候,根据预测框的信息可以计算出IOU后,再将IOU数值乘以该处的置信度即可得到标签的置信度。
- 将 M ( i , j , 10 : 30 ) M(i,j,10:30) M(i,j,10:30)的后20个通道中,与类别cls对应的下标序号的 c c l s = 1 c_{cls}=1 ccls=1,其余的 c i = 0 c_i=0 ci=0(不需要改变)。
- 重复上述操作,将图中每个bbox信息都填入矩阵M中,即可得到当前图片对应的YOLO训练标签的格式。
3).损失函数是如何设计的?
YOLO的损失函数是它最有特点,也是看论文最不容易理解的部分(因为解释的太少了又没有案例),所以推荐结合论文和源码的loss函数一起看,比较容易懂。当然,我这里也是会进行解释的。
首先,损失函数如上图所示,粗略看下来好像就是简单的MSEloss函数,其实事实也是这样,只是其中包含很多小细节。YOLO的损失函数是分为四部分的:
- bbox的定位误差:也就是图中的前面两行,可以看到,按本文的变量描述,简单来说其实就是
( p x − p x ^ ) 2 + ( p y − p y ^ ) 2 + ( w − w ^ ) 2 + ( h − h ^ ) 2 (px-\hat{px})^2+(py-\hat{py})^2+(\sqrt{w}-\sqrt{\hat{w}})^2+(\sqrt{h}-\sqrt{\hat{h}})^2 (px−px^)2+(py−py^)2+(w −w^ )2+(h −h^ )2
但是,其中有几个细节需要说明,假设网络输出的预测数据为 p r e d 7 × 7 × 30 pred_{7\times7\times30} pred7×7×30,对应样本标签为 l a b e l 7 × 7 × 30 label_{7\times7\times30} label7×7×30:
- 第一,bbox的宽和高都开了根号再进行MSEloss计算,原因是因为YOLO网络训练下来发现对小目标的检测效果不太好,所以作者将bbox的宽和高开了根号,因为 w , h ∈ ( 0 , 1 ) w,h\in(0,1) w,h∈(0,1),所以根号后数值都变大了,也就相当于提高了w,h的损失权值,且w,h越小的bbox,它对损失值的贡献权值就更大;
- 第二,论文中的 1 i j o b j 1_{ij}^{obj} 1ijobj是啥意思?它表示第i个网格的第j个bbox是负责预测物体的。这么说还是不够清晰。其实就是当ground truth提供的bbox的中心落在网格 ( i x , i y ) (i_x,i_y) (ix,iy)上(即 l a b e l [ i x , i y , 4 ] = 1 label[i_x,i_y,4]=1 label[ix,iy,4]=1),且该网格预测的两个bbox(即 p r e d [ i x , i y , 0 : 4 ] 和 p r e d [ i x , i y , 5 : 9 ] pred[i_x,i_y,0:4]和pred[i_x,i_y,5:9] pred[ix,iy,0:4]和pred[ix,iy,5:9]与ground truth的bbox(即 l a b e l [ i x , i y , 0 : 4 ] label[i_x,i_y,0:4] label[ix,iy,0:4])分别计算IOU,得到iou1,iou2,取其大者(我们只希望一个网格中只有一个bbox是负责预测物体的)。此时,iou较大的bbox对应的 1 i j o b j = 1 1_{ij}^{obj}=1 1ijobj=1,不满足上述条件的网格的bbox,它们对应的 1 i j o b j = 0 1_{ij}^{obj}=0 1ijobj=0。说白了,如果样本标签提供了多少个真实bbox,那么该样本的网络输出只有同样数量的bbox会进行bbox的定位误差计算,这些bbox就是真实bbox中心所在的网格中,预测的2个bbox中,与真实bbox的iou更大的那个bbox,会产生bbox定位误差的损失。
- 第三,论文中的 λ c o o r d \lambda_{coord} λcoord 是用来调整各部分损失权重的系数。通过第二点的分析可知,其实网络预测的98个bbox中,只有少数几个会产生bbox的定位误差,而其他所有的bbox都是产生另一部分的误差,如果不调整损失权重的话,很可能导致网络对bbox的位置信息预测效果很差。所以,作者设置 λ c o o r d = 5 \lambda_{coord}=5 λcoord=5 。
- 负责预测物体的bbox置信度误差:也就是图中的第三行,就是置信度的MSEloss。其中 1 i j o b j 1_{ij}^{obj} 1ijobj含义和上面相同。另外,根据上文样本标签处理的说明可知,这里应当计算满足 1 i j o b j = 1 1_{ij}^{obj}=1 1ijobj=1的bbox与真实bbox的 I O U i j IOU_{ij} IOUij,然后将标签的置信度设置为 C i ^ = I O U i j \hat{C_i}=IOU_{ij} Ci^=IOUij,再进行损失计算。
- 不负责预测物体的bbox误差:也就是图中第四行,其中 1 i j n o o b j = 1 − 1 i j o b j 1_{ij}^{noobj}=1-1_{ij}^{obj} 1ijnoobj=1−1ijobj,也就是不负责预测物体的bbox,只计算其置信度误差,这里每个bbox对应的标签的置信度如下:
C i ^ = { 0 , 该 b b o x 所 在 的 网 格 不 包 含 g r o u n d t r u t h 的 b b o x 的 中 心 I O U p r e d t r u t h , 该 b b o x 所 在 的 网 格 包 含 g r o u n d t r u t h 的 b b o x 的 中 心 \hat{C_i}=\begin{cases}0,\quad该bbox所在的网格不包含ground truth的bbox的中心\\ IOU_{pred}^{truth},\quad该bbox所在的网格包含ground truth的bbox的中心\end{cases} Ci^={0,该bbox所在的网格不包含groundtruth的bbox的中心IOUpredtruth,该bbox所在的网格包含groundtruth的bbox的中心
上文提到了,包含ground truth的bbox中心的网格,会预测出2个bbox,其中一个 1 i j o b j = 1 1_{ij}^{obj}=1 1ijobj=1负责预测物体边框,另一个则被淘汰不负责预测物体边框,但是它的置信度误差的计算和那些不包含ground truth的bbox中心的网格对应的bbox是有所不同的。另外,需要注意,不负责预测物体的bbox是占大多数的,所以它的损失权重要调低,作者设置 λ n o o b j = 0.5 \lambda_{noobj}=0.5 λnoobj=0.5。
- 网格预测的物体分类误差:也就是图中最后一行,其中 1 i o b j 1_{i}^{obj} 1iobj的含义和 1 i j o b j 1_{ij}^{obj} 1ijobj几乎一样。只是每个网格只预测一个物体类别,所以只要网格包含ground truth的bbox中心,那么该网格就要计算对应的分类误差。这里分类误差简单地使用MSEloss来处理,而不是像常规的分类问题那样使用交叉熵损失函数来处理。
3. 网络结构
在第二节我们说到,YOLOv1的网络设计是分为两部分,一部分用于图片的特征提取(feature extraction),另一部分是用来完成目标检测的任务。YOLOv1的特征提取部分是基于VGG网络,上图就是论文中展示的完整网络结构,总共有24个卷积层和2个全连接层。其中,前20个卷积层用于图像的特征提取,后4个卷积层+2个全连接层用于目标检测。可以看到,其中也有利用 1 × 1 1\times1 1×1卷积层进行通道整合和降维减少参数的操作。除了最后一层全连接层外,其他层都是用leaky ReLU激活函数进行激活,它和ReLU函数很像,只是负数部分的激活函数也具有一个很小的斜率,而不是为0。
另外,需要说明的是,最后一个卷积层的输出,图中显示的是 7 × 7 × 30 7\times7\times30 7×7×30的数据结构,其实这是reshape之后的结果,也就是说,最后一个卷积层其实是输入通道为4096,输出通道为1470的线性全连接层,然后将1470个输出reshape成 7 × 7 × 30 7\times7\times30 7×7×30即可。
最后提一句,作者当时设计YOLOv1的时候,BN层还不是标配,所以作者没有用上,当时ResNet也还没出来。所以在第4章内容中可以看到,作者为了能让上述网络训练起来是花了很大心思的。但经过测试发现,增加BN层,并在最后的全连接层后面将输出全部经过Sigmoid激活一下(因为网络的输出其实值域都是0~1,用sigmoid激活一下可以保证网络输出都是0-1的数值,而不会出现负数),很容易就可以训练起来了。
4. 网络训练和检测的技巧
1).网络训练
- YOLOv1网络分段训练:作者先将前20层卷积层拿出来,后面连接一个池化层和全连接层,用该网络在ImageNet数据集上进行图像分类任务的训练,训练了一周左右。目的是为了让YOLO网络的前20层具备很好的特征提取能力。然后将预训练好的20层网络的参数导入YOLOv1网络中,再使用VOC2007和VOC2012数据集进行完整网络的目标检测训练。
- 数据增广及防止过拟合:在两个全连接层之间采用p=0.5的dropout层防止过拟合。数据增广:1).使用±20%的图像缩放和平移;2).在0.5~1.5的比例之间随机调整图像的饱和度;做数据增广的时候要注意,如果图像发生了位置变换(如平移等),注意要对应调整样本标签的bbox信息。
- 训练参数调整:作者目标检测的任务训练了135个epoch,batch_size=64,momentum=0.9,decay=0.0005。学习率(learning rate)是动态的,起始先用0.001,然后在第一个epoch训练的过程中慢慢提升到0.01,这么做是因为网络训练如果一开始学习率太大很容易发散。之后,就稳定的使用0.01训练75个epoch,然后用0.001训练30个epoch,最后用0.0001训练最后30个epoch。
2).网络预测(inference)
正如上文所述,由于YOLO只需要允许一个神经网络即可进行预测,因此YOLO的目标检测速度是非常快的。网络对一张图片进行预测,会得到一个 7 × 7 × 30 7\times7\times30 7×7×30的张量,包含98个bbox信息。在检测时,我们使用每个bbox的confidence信息,乘以该bbox所在网格的所有20个条件类别概率 c i c_i ci,论文将这两者的乘积叫做“class-specific confidence score"。该数值整合了某个bbox中存在物体的概率、该物体属于某个类别的概率以及该bbox与真实bbox之间的IOU(重合度)三种信息。
将98个bbox的位置信息取出来(4个),并求出每个bbox对于所有类别的class-specific confidence score(20个),就可以得到一个维度为(98,24)的数据,对这个数据进行筛选,去除class-specific confidence score太小的bbox(每个bbox只需要查看其最大的class-specific confidence score即可),再进行非极大值抑制(NMS,non-maxinum suppression)算法,可以筛除重合度很高的框。
最后,98个bbox筛选后剩下的bbox,就是最终的结果,可以在图上绘制出来查看效果。
3).目标检测的评价指标
YOLOv1使用的是VOC数据集的评价指标,也就是mAP,这个指标还是比较复杂的,这里就不展开了。给大家推荐一个很好的github项目,那里面有很详细的指标介绍,还有他计算mAP的源码,有兴趣可以深入了解计算过程,没兴趣的读者可以直接使用他的代码计算mAP,前提是要知道需要输入什么数据进他的API里。目标检测指标介绍github项目传送门
5. 网络的优缺点
具体和其他网络的对比就不详细展开了,论文里都有,包括实验数据集、实验所用的超参数、实验结果等等。这里就简述一下YOLO的优缺点。
优点:
- 最大的优点就是快。YOLO的出现让实时目标检测成为了可能,但是网络效率高的代价就是,其精度相对于目标检测顶尖的网络来说略有降低。但是相比于它的速度提升来说,这点精度降低不值一提。
- 网络结构很简单直观。YOLO网络看起来好像和普通的卷积神经网络没什么区别,实际上也没什么区别,所以网络结构就非常平易近人。不像RCNN有各种复杂的分支,每个分支还要用不同的算法做不同的事。但是YOLO网络的难点在于它在网络背后的设计思想,表明言简意赅,背后是大神满满的心血。
缺点:
- YOLOv1网络有很强的空间约束性。因为每个网格只预测中心点在该网格内部的bbox,且每个网格其实最终只预测一个bbox和一个类别,所以如果有多个物体中心落在同一个网格里,那就网络预测效果就会很差。另外,由于最终的网格是 7 × 7 7\times7 7×7的,相比于输入图像 448 × 448 448\times448 448×448来说,其实是很小的尺度了,所以每个网格的感受野比较大,那么相对来说对大物体的检测就比较理想,而对小物体的检测就比较棘手。
- 网络训练时,使用的标签是训练集中包含的bbox信息,那么网络在预测和训练集中的bbox结构差别很大新目标框时,效果就会很差。比如说,训练集里全都是瘦高瘦高的bbox,那么网络就很难预测出矮胖的框。
- 最后,尽管损失函数已经花费很大心思进行了设计,但是还是存在一些不足之处。损失函数对待尺寸较大的bbox和尺寸较小的bbox是比较相同的。但是,大的bbox如果预测偏移一些,和小bbox预测偏移一些,对于iou的影响是完全不同的。所以损失的主要来源是bbox的定位误差。
YOLOv1的理论部分结束,接下来就是从零开始的代码复现过程,你准备好了吗?