Yolo 3 【1】是一款2018年推出的简单、高效的目标检测算法,它仅用卷积层,实现了一个全卷积网络(Fully Convolutional Network,FCN)。在它的整个设计和实现中,体现了许多深度学习概念和技巧,是一个很值得学习的、小而全的样例。为此,我写了一个小结,方便自己以后可以经常学习和复习。
Yolo 3的结构如下图:
图1、Yolo 3 的模型结构,摘自【3】(该图用Netron作出)
其实现结构,大致可以分为3个部分:
Yolo 3的特征提取部分采用Darknet-53,由于它在做downsample时,没有采用pooling方法,而是采用了抽取卷积(stride=2) 的方法,有效抑制了底层信息的丢失。在yolo 3方案中,它总共做了5次stride,原图为416416,经过5次stride后最终输出的特征map只有1313,如下图:
图2 Darknet-53【1】
Yolo 3将最后的Avgpool和后续分类用的层去掉,只保留了前面的网络,在训练时,可以直接利原来作为分类时训练好的网络作为初始值,这样可大大提高训练效率。
在图2中,用红色框起来的部分就是stride=2的抽取部分,一共有五次,得到了不同尺度的特征图,在后面进行对象检测时,用了倒数三个尺度:1313,2626和52*52。
Yolo 3对Yolo 1和Yolo 2的改进地方还有采用了ResNet结构,即在不同层之间有跨接的skip channels。这种被称为残余网络的结构,就是将前层的特征通过skip channel直接馈送至后层,与后层输出相加,形成最终的模块输出,如图1的绿色模块。在绿色模块中,有数字,如:Res1、Res4,这表示该模块重复了几次,每个Res模块在图1均有结构图示。据称,这样的结构可以减少中间层对信息的损耗,使深度网络在多层迭代时,不至于出现网络衰退的情况(所谓网络衰退指的是,因为网络层数的增加而导致的网络性能下降的情况),因而当前许多深度网络均采用ResNet思想。
另外,Yolo 3在backbone改进上还采用了BN,即批处理归一化(BatchNormal)。BN放在Conv与ReLU之间,用于调整Conv输出的feature数值。由于ReLU的特征函数在0处有一个阈值,小于0的值被直接抹去,而大于0的值被一成不变地保留下来,由此实现神经元激励函数的非线性。我们考虑两个极端情况,若Conv输出的值都大于0,或都小于0,则ReLU则完全失去其作用,因而,我们对Conv的输出是需要关注的。BN就是这样一个方法,将其输出值进行了归一化,使其对称地分布在0的两侧,从而达到提高ReLU性能的目的。(如果用三极管放大电路来理解之,其实很容易,一方面要调整直流偏置,另一方面约束输入信号的幅度,这便是BN的目的。)
另外我想,在此还可以加入谱正则,以约束Conv的权重矩阵的取值,使网络的泛化能力更强,不过,这个我还没有测试过。
特征金字塔是有【4】提出来的一个用于多尺度目标检测模型,其结构如图:
图3 特征金字塔,摘自【4】
由图3,可以看到predict是对不同尺度Feature map进行的,具体在YOLO 3中,这样的Feature map尺度分别为1313、2626、5252。图3所示的特征金字塔结构,其实可以在图1中找到,就是图1中间“DBL+上采样+Concat”部分。其中,尺度为1313的Feature map是Darknet-53顶层输出,它主要用于检测大尺度目标;它经过upsample后加到2626 Feature map上,这个复合Feature map不仅具有了顶层信息,还有更丰富的底层信息,非常适合用于检测中间尺度目标;最后,汇合了顶层和中层信息的 Feature map再一次upsample,形成了5252特征图,它与Backbone的52*52特征图相加,形成了用于predict小目标的Feature map。
整个过程就如同爬了一次金字塔,然后再下来,前者是逐步提取全局特征,提炼抽象信息的过程;后者则相反,将顶层的全局信息逐层扩散到下层,下层的检测也可以利用顶层的抽象全局信息,真是做到了信息一点也不浪费,其思想真当好好记留。
在【1】中,主要篇幅都是用于此,可见输出部分其设计作者是很花了一定心思的。
由上面叙述,我们已经知道最后用于目标检测的特征图分别为1313、2626和5252,而检测的输出是:1313B85、 2626B85、5252B85。其中,B是每个点阵的点能检测的目标数量,85维是由4个坐标维,加一个objectness score维,再加80个分类score维所构成的。
Yolo 3之所以被称为是FCN,是因为其输出predict是基于阵列的矢量输出,或者这样说吧,它输出是一个阵列,与输入原图位置相关的一个阵列,阵列的每一个点是一个矢量,如下图:
图4 prediction 的结构,摘自【2】
如图4,顶图是原图;中间图是用于predict的特征图,比如是顶部特征图(1313),其中每一个元素(图中的小红框)对应一个矢量;下图表示特征矢量,该矢量维度是B(4+1+c),对于Yolo 3原模型,B=3,表示每个grid可以预测3个目标,4是表示bounding box的坐标,1表示objectness score,而c=80,表示可用于80个类的分类。
在图1中,y1、y2和y3就对应这几个predictions。
再详细的解释,在各博客中,在论文里都有详细说明,尤其是【2】值得一看。
在【1】中有一节称为“Things We Tried That Didn’t Work”,翻译过来就是:有些东西我们做了,但没有效果,我觉得挺有意思的,“他山之石,可以攻玉”,因此摘抄于此。
We tried lots of stuff while we were working on YOLOv3. A lot of it didn’t work. Here’s the stuff we can remember.(深度网络的实现要靠反复的实验,耗时耗力,而且不一定成功,用“炼丹”来形容它真不为过。)
1、Anchor box x, y offset predictions. We tried using the normal anchor box prediction mechanism where you predict the x, y offset as a multiple of the box width or height using a linear activation. We found this formulation decreased model stability and didn’t work very well.(用box的长、宽比例系数来作为anchor box 坐标x、y的prediction,效果不好,因此Yolo v3没有用这个归一化值,而是直接预测其偏移量,当然用了sigmoid函数,保证回归预测的数值在一定范围,我想其中原因可能是因为anchor box的类型太多,因此要回归出归一化的比例系数不太容易,而统一直接用偏移量,要简单一些。)
2、Linear x, y predictions instead of logistic. We tried using a linear activation to directly predict the x, y offset instead of the logistic activation. This led to a couple point drop in mAP.(这里应该将的是perdition时,所用的激活函数,文章中说使用线性激活函数的效果不如用logistic 激活函数,会导致mAP指标的下降。由此可见,在回归拟合的最后一步用logistic activation作为输出,可能非线性更好一些导致输出更好一些吧。)
3、Focal loss. We tried using focal loss. It dropped our mAP about 2 points. YOLOv3 may already be robust to the problem focal loss is trying to solve because it has separate objectness predictions and conditional class predictions. Thus for most examples there is no loss from the class predictions? Or something? We aren’t totally sure.(Focal loss,是在训练时动态调整sample对Loss影响的一种简单方法,即将容易判别的sample对loss贡献比重下降,不容易判别的sample提高其loss比重。Focal loss一经提出,就被广泛应用,但也不是万能的灵药,至少在yolo 3处就是这样。)
4、Dual IOU thresholds and truth assignment. Faster R-CNN uses two IOU thresholds during training. If a prediction overlaps the ground truth by .7 it is as a positive example, by [.3 .7] it is ignored, less than .3 for all ground truth objects it is a negative example. We tried a similar strategy but couldn’t get good results.(因为深度学习无法用精确的数学语言来解释,所以在训练网络时无法预知哪一种方法是有效的,只能靠碰。这里讲到的Dual IOU方法,在训练时,只选用IOU大于0.7和小于0.3的samples,即能明显区别的那部分samples,太好分别的那部分( 即在区间[.3 .7] 中)ignore掉,其做法与Focal Loss思想截然相反,Focal Loss要对不太好分的加大权重,好分的减少权重。作者两种方法都试验过,发现都没有太大的效果,干脆就用 IOU=0.5 作为阈值就算了,效果反而更好。有时真的说不清,或许下一次再用,说不定就好了,真是望天打卦。)
深度网络的结构设计给的我感觉就如同中医开药,比如:熬夜上火,就要喝凉茶,没有什么精确的数学的推导和证明,只有经验的推理,然后就是实验,行就行了,不行再换一种方法。不过有时也有一些让人眼前一亮的数学模型解释,如谱正则,如WGAN的Wasserstein距离。在这些精彩的解释后面,大都跟着一个正则或是pooling,我们似乎可以得到一个结论:多层神经网络复杂度是足够适应任务拟合需求的,比如前面的Darknet-53,在目标检测任务上已经足够,不需要再增加过多的层数,而整个算法的关键在于:如何在复杂的参数空间找的最优解,使用正则、pooling等方法是我们在寻优过程中找到的有效规则,人为地控制寻优的过程,给无约束的梯度寻优,添加约束项。这个地方应该是未来研究的重点。
另外,大部分目标检测算法,皆可以归类为FCN,比如这里的Yolo系列,以及前面一篇博客的CornerNet,其实都是利用深度网络提取特征,然后用回归拟合得到一个prediction。它们的差别只是预测的内容和含义(即矢量编码的含义),以及拟合输出矩阵的大小不同罢了。其实,在FCN的输出拟合矩阵中,其输出点阵本身就反映了二维位置信息,这是由CNN赋予它的receptive field(接受域)决定的,而其上的编码是复合的,有多种用途,我给它起了一个名字——二维编码,以区别于传统分类网络的编码,我们也给它取个名字——一维线性编码(编码本身没有二维空间属性,只是为了方便一字排开)。由此看来,在设计网络输出时,也可从输出编码角度考虑考虑:输出是不是可以设计为多维复合编码?多维编码是不是更有效率呢?
最后,推荐看看[2],该博文不仅将Yolo 3的原理讲得很透彻,而且对实现代码也有详细分析,值得推荐!
【1】 YOLOv3: An Incremental Improvement,
【2】 How to implement a YOLO (v3) object detector from scratch in PyTorch: Part 1 https://blog.paperspace.com/how-to-implement-a-yolo-object-detector-in-pytorch/
【3】yolo系列之yolo v3【深度解析】 https://blog.csdn.net/leviopku/article/details/82660381
【4】T.-Y. Lin, P. Dollar, R. Girshick, K. He, B. Hariharan, and S. Belongie. Feature pyramid networks for object detection. In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, pages 2117–2125, 2017. 2, 3