@huanghaian
2020-11-01T10:30:51.000000Z
字数 5710
阅读 3077
mmdetection
yolov3总体思想和yolov2没有任何区别,只不过引入了最新提升性能的组件,从而将yolov2提升到一个非常强的高度,属于yolov2的改进版本,只有在彻底理解了yolov2后才好理解yolov3。其相比v2主要改进可以简单归纳为:
(1) 基于retinanet算法引入了FPN和多尺度预测
(2) 基于主流resnet残差设计思想提出了新的骨架网络darknet53
(3) 不再将所有loss都认为是回归问题,而是分为分类和回归loss,更加符合主流设计思想
虽然yolov3是前yolo系列的改进,但是由于其以下突出特性,使其实际应用程度远远大于前yolo系列。可以说目前提到yolo,基本都是指的yolov3了。
由于其突出的重要性,故本文会结合mmdet中yolov3代码进行讲解,力图在熟悉算法原理的同时能够深入理解代码。而且mmdet目前还在快速发展,故解读的mmdet版本是2.5.0,截止时间是2020.10.31。
(上图可能要重画)
整个yolov3网络包括标准的backbone、neck和head三个部分。
CBL:Yolov3网络结构中的最小组件,由Conv+Bn+Leaky_relu三者组成
Res unit:借鉴Resnet网络中的残差结构,让网络可以构建的更深
ResX:由一个CBL和X个Res unit构成,是Yolov3中的大组件。每个Res unit前面的CBL都起到下采样的作用,因此经过5次Res模块后,得到的特征图是608->304->152->76->38->19大小
(1) backbone
每个ResX中包含1+2*X个卷积层,因此整个主干网络Backbone中一共包含,再加上一个FC全连接层,即可以组成一个Darknet53分类网络。将avgpool+fc部分去掉就是常说的darknet53骨架网络。
相比其余主流网络,性能如下:
代码层面darknet53配置为:
arch_settings = {
53: ((1, 2, 8, 8, 4), ((32, 64), (64, 128), (128, 256), (256, 512),
(512, 1024)))
}
darknet53和标准的resnet一样,也是分为stem+stage+head三个部分
yolov3只需要最后三个stage的输出,故backbone输出是三个不同大小的特征图,其stride=8,16,32。整个darknet53配置如下:
backbone=dict(type='Darknet', depth=53, out_indices=(3, 4, 5))
out_indices表示最后三个stage的特征图输出索引。
(2) neck
这里的neck模块是指FPN层,其输入是backbone的三个尺度输出特征图,输出也是和输入wh一样的三个尺度,只不过内部进行了从高层特征和相邻低层特征进行上采样后concat进行融合操作,从而对输入特征图进行增强。其配置为:
neck=dict(
type='YOLOV3Neck',
num_scales=3,
in_channels=[1024, 512, 256],
out_channels=[512, 256, 128]),
其结构简图如下:
假设输入的三个特征图命名为P3~P5,stride分别是8/16/32。
(1) 对P5直接采用5个CBL模块得到O5输出,输出通道是通过out_channels指定的
(2) 对O5先进行1x1卷积将通道变换成和P4一样,然后进行2倍最近邻上采样,然后和P4特征进行concat融合,最后也是经过5个CBL模块得到O4输出
(3) 对O4先进行1x1卷积将通道变换成和P3一样,然后进行2倍最近邻上采样,然后和P3特征进行concat融合,最后也是经过5个CBL模块得到O3输出
此时就得到三个不同尺度的经过融合后的特征图O3~O5,stride分别是8/16/32。
(3) head
对neck模块输出的O3~O5都采用3x3的CBL模块+1x1的卷积进行通道变换即可,此时就可以得到三个尺度预测层了。每一层输出特征图shape为(b,num_anchor*(xywh+conf+cls_num))。yolov3每个预测层都是3个anchor,是通过kmean聚类算出来的,按照从大到小anchor尺度排列,分别检测大物体和小物体。
可以发现相比yolov2,其将单尺度预测修改为了多尺度输出,并且额外引入了FPN进行特征融合增强。
其规则和yolov2完全相同,只不过任何一个gt bbox和anchor计算iou的时候是会考虑三个预测层的anchor,而不是将gt bbox和每个预测层单独匹配。假设某个gt bbox和第二个输出层的某个anchor是最大iou,那么就仅仅该anchor负责对应gt bbox,其余所有anchor都是负样本。同样的yolov3也需要基于预测值计算忽略样本。
在coco数据集上通过kmean算法聚类得到的9个anchor如下所示:
base_sizes=[[(116, 90), (156, 198), (373, 326)],
[(30, 61), (62, 45), (59, 119)],
[(10, 13), (16, 30), (33, 23)]]
可以看出其是原图尺度,每个输出层的每个特征图位置都包括三个anchor,并且按照预测从大到小物体的输出层顺序排列,对应特征图stride是从大到小即O5~O3。假设输入图片大小是608x608,那么一共包括19x19x3+38x38x3+76x76x3=22743个anchor。
为了详细说明yolov3的正负样本定义规则,需要先回顾下yolov2的规则,其可以简要归纳为:
这样就得到了每个anchor的正负和忽略样本属性,其中正样本用于训练分类和回归分支,正负样本用于训练置信度分支,忽略样本忽略。
现在从yolov2的单尺度预测变成了yolov3多尺度预测,其匹配规则为:
可以发现相比yolov2,仅仅是第一步有一点点区别。但是需要特别注意的是mmdet中的yolov3的正负样本分配策略和原始论文不太一样,具体是:
assigner=dict(
type='GridAssigner', pos_iou_thr=0.5, neg_iou_thr=0.5, min_pos_iou=0)
看起来比较复杂,我们可以仔细分析下
通过上面的分析,我们可以知道mmdet中的实现始终缺少了第3步:忽略样本定义。我猜测原因是为了复用mmdet的max iou assigner策略。
这个规则和yolov2完全相同。正样本的xy预测值是相对当前网格左上角的偏移,而wh预测值是gt bbox的wh除以anchor的wh(注意wh是在特征图尺度算还是原图尺度算是一样的,解码还原时候注意下就行),然后取log得到。
和yolov2不同的是,对于分类和置信度预测值,其采用的不是l2 loss,而是bce loss,而回归分支依然是l2 loss。
疑问:为啥要将分类和置信度预测值的loss将l2 loss换成bce loss?
解答:首先这是主流做法,分类问题一般都是用ce或者bce loss,而在retinanet里面提到采用bce loss进行多分类可以避免类间竞争,对于coco这种数据集是很有好处的;其次在机器学习中知道对于逻辑回归问题采用bce loss是一个凸优化问题,理论上优化速度比l2 loss快;而且分类输出就是一个概率分布,采用分类常用loss是最常规做法,没必要统一成回归问题
和yolov2一样,置信度分支的label可以设置为1或者iou值。但是在标准yolov3配置中,默认是1。通过第三方用户对比实验发现原因可能是:在训练时,有些预测框与真实框的iou极限值很难达到1,假设置信度以0.7作为标签,最后学到的数值是0.5,0.6,那么假设推理时的分值阈值就不能设置的比较高,否则对于coco这类存在大量小物体的数据很容易出现漏检。而如果设置label设置为1可以提高召回率,此时置信度就不具有反映预测的准确率的作用了,有利有弊吧!在mmdet的yolov3代码中,置信度分支的label为1。
到目前为止,我们可以发现相比原始yolov3,mmdet中的复现少了以下操作:
按我个人理解,这两个策略还是有用的,特别是第二个对于小物体的mAP应该有很大提升。
yolov3的训练过程和v2完全相同。
推理流程为:
可以看出,yolov3性能强于ssd系列,但是比faster rcnn差,这是正常的,yolo系列算法的主打是高速,在如此快的检测速度下依然有不错的mAP。
yolov3可以认为是当前目标检测算法思想的集大成者,其通过引入主流的残差设计、FPN和多尺度预测,将one-stage目标检测算法推到了一个速度和精度平衡的新高度。由于其高速高精度的特性,在实际应用中通常都是首选算法。