Press "Enter" to skip to content

目标检测正负样本区分策略和平衡策略总结

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

作者丨深度眸@知乎(已授权)

 

来源丨https://www.zhihu.com/people/huanghaian

 

 

0 简介

 

本文抛弃网络具体结构,仅仅从正负样本区分和正负样本平衡策略进行分析,大体可以分为 正负样本定义、正负样本采样和平衡loss设计三个方面 ,主要是网络预测输出和loss核心设计即仅仅涉及网络的head部分。 所有涉及到的代码均以mmdetection为主 。本文第一部分,主要包括faster rcnn、libra rcnn、retinanet、ssd和yolo一共5篇文章。第二部分包括anchor-free的平衡策略,以及最新改进算法。 第三部分 重点分析下anchor-free和anchor-base混合学习的Guided Anchoring以及yolo-ASFF,包括思路和代码。

 

由于本人水平有限,如果有分析不对的地方,欢迎指正和交流!

 

1 anchor-base

 

1.1 two-stage

 

1.1.1 faster rcnn

 

论文名称:Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks

 

(1) head结构

 

faster rcnn包括两个head:rpn head和rcnn head。其结构如下:

 

rpn head的输出是包括分类和回归,分类是二分类,只区分前景和背景;回归是仅仅对于前景样本(正样本)进行基于anchor的变换回归。rpn head的目的是提取roi,然后输入到rcnn head部分进行refine。rcnn head的输出是包括分类和回归,分类输出是类别数+1(1是考虑背景),回归是仅仅对于前景样本不考虑分类类别进行基于roi的变换回归,rcnn head的目的是对rpn提取的roi特征进行refine,输出精准bbox。

 

(2) 正负样本定义

 

rpn和rcnn的正负样本定义都是基于MaxIoUAssigner,只不过定义阈值不一样而已。rpn的assigner:

 

rcnn的assigner:

 

下面对MaxIoUAssigner进行详细分析。首先分析原理,然后分析细节。正负样本定义非常关键。MaxIoUAssigner的操作包括4个步骤:

 

 

首先初始化时候假设每个anchor的mask都是-1,表示都是忽略anchor

 

将每个anchor和所有gt的iou的最大Iou小于neg_iou_thr的anchor的mask设置为0,表示是负样本(背景样本)

 

对于每个anchor,计算其和所有gt的iou,选取最大的iou对应的gt位置,如果其最大iou大于等于pos_iou_thr,则设置该anchor的mask设置为1,表示该anchor负责预测该gt bbox,是高质量anchor

 

3的设置可能会出现某些gt没有分配到对应的anchor(由于iou低于pos_iou_thr),故下一步对于每个gt还需要找出和最大iou的anchor位置,如果其iou大于min_pos_iou,将该anchor的mask设置为1,表示该anchor负责预测对应的gt。通过本步骤,可以最大程度保证每个gt都有anchor负责预测, 如果还是小于min_pos_iou,那就没办法了,只能当做忽略样本 了。从这一步可以看出,3和4有部分anchor重复分配了,即当某个gt和anchor的最大iou大于等于pos_iou_thr,那肯定大于min_pos_iou,此时3和4步骤分配的同一个anchor。

 

 

从上面4步分析,可以发现每个gt可能和多个anchor进行匹配,每个anchor不可能存在和多个gt匹配的场景。在第4步中,每个gt最多只会和某一个anchor匹配,但是实际操作时候为了多增加一些正样本,通过参数gt_max_assign_all可以实现某个gt和多个anchor匹配场景。 通常第4步引入的都是低质量anchor,网络训练有时候还会带来噪声,可能还会起反作用。

 

简单总结来说就是:如果anchor和gt的iou低于neg_iou_thr的,那就是负样本,其应该包括大量数目;如果某个anchor和其中一个gt的最大iou大于pos_iou_thr,那幺该anchor就负责对应的gt;如果某个gt和所有anchor的iou中最大的iou会小于pos_iou_thr,但是大于min_pos_iou,则依然将该anchor负责对应的gt;其余的anchor全部当做忽略区域,不计算梯度。该最大分配策略,可以尽最大程度的保证每个gt都有合适的高质量anchor进行负责预测,

 

下面结合代码进行分析:主要就是assign_wrt_overlaps函数,核心操作和注释如下:

 

通过代码可以发现, 当设置self.gt_max_assign_all=True时候是可能出现第4步的某个gt和多个anchor匹配场景,默认参数就是True。

 

由于rcnn head预测值是rpn head的refine,故rcnn head面对的anchor(其实就是rpn输出的roi)和gt的iou会高于rpn head部分,anchor质量更高,故min_pos_iou阈值设置的比较高,由于pos_iou_thr和neg_iou_thr设置都是0.5,那幺忽略区域那就是没有了,因为rcnn head面对的都是高质量样本,不应该还存在忽略区域。

 

(3) 正负样本采样

 

步骤2可以区分正负和忽略样本,但是依然存在大量的正负样本不平衡问题, 解决办法可以通过正负样本采样或者loss上面一定程度解决 ,faster rcnn默认是需要进行正负样本采样的。rpn head和rcnn head的采样器都比较简单,就是随机采样,阈值不一样而已。rpn head采样器:

 

rcnn head采样器:

 

num表示采样后样本总数,包括正负和忽略样本,pos_fraction表示其中的正样本比例。add_gt_as_proposals是为了放在正样本太少而加入的,可以保证前期收敛更快、更稳定,属于技巧。neg_pos_ub表示正负样本比例,用于确定负样本采样个数上界,例如我打算采样1000个样本,正样本打算采样500个,但是可能实际正样本才200个,那幺正样本实际上只能采样200个,如果设置neg_pos_ub=-1,那幺就会对负样本采样800个,用于凑足1000个,但是如果设置为neg_pos_ub比例,例如1.5,那幺负样本最多采样200×1.5=300个,最终返回的样本实际上不够1000个。默认情况neg_pos_ub=-1。由于rcnn head的输入是rpn head的输出,在网络训练前期,rpn无法输出大量高质量样本,故为了平衡和稳定rcnn训练过程,通常会对rcnn head部分添加gt作为proposal。其代码非常简单:

 

对正负样本单独进行随机采样就行,如果不够就全部保留。

 

由于原始faster rcnn采用的loss是ce和SmoothL1Loss,不存在loss层面解决正负样本不平衡问题,故不需要分析loss。

 

1.1.2 libra rcnn

 

论文名称:Libra R-CNN: Towards Balanced Learning for Object Detection

 

libra主要是分析训练过程中的不平衡问题,提出了对应的解决方案。由于libra rcnn的head部分和正负样本定义没有修改,故不再分析,仅仅分析正负样本采样和平衡loss设计部分。

 

(1) 正负样本采样

 

注意libra rcnn的正负样本采样规则修改仅仅是对于rcnn而言,对于rpn head没有任何修改,依然是随机采样器。原因是作者的主要目的是为了涨点mAP,作者认为rpn涨几个点对最终bbox 预测map没有多大帮助,因为主要是靠rcnn部分进行回归预测才能得到比较好的mAP。其参数如下:

 

主要看IoUBalancedNegSampler部分即可。仅仅作用于负样本(iou=0~0.5之间)。作者认为样本级别的随机采样会带来样本不平衡,由于负样本本身iou的不平衡,当采用随机采样后,会出现难负(iou 0.5附近)和易负(iou接近0)样本不平衡采样,导致后面性能不好。作者发现了如果是随机采样的话,随机采样到的样本超过70%都是在IoU在0到0.05之间的,都是易学习负样本,作者觉得是不科学的,而实际统计得到的事实是60%的hard negative都落在IoU大于0.05的地方,但是随机采样只提供了30%,实在是太少了。最常用的解决难易样本不平衡问题的解决办法就是ohem,基于Loss排序来采样难负样本,但是作者分析,(1) 这种方法对噪音数据会比较敏感,因为错误样本loss高;(2) 参数比较难调。所以作者提出了IoU-balanced Sampling,如下所示:

 

可以看出,随机采样效果最不好,而iou balanced sampling操作会尽量保证各个iou区间内都会采样到。由于该操作比较简单,就不贴论文公式了。核心操作是对负样本按照iou划分k个区间,每个区间再进行随机采样,保证易学习负样本和难负样本比例尽量平衡,实验表明对K不敏感,作者设置的是3。具体做法是对所有负样本计算和gt的iou,并且划分K个区间后,在每个区间内均匀采样就可以了。假设分成三个区间,我想总共取9个,第一个区间有20个候选框,第二个区间有10个,第三个区间有5个,那这三个区间的采样概率就是9/(3×20),9/(3×10),9/(3×5),这样的概率就能在三个区间分别都取3个,因为区间内候选框多,它被选中的概率小,最终体现各个区间都选这幺多框。

 

实际代码做法是:首先按照iou分成k个区间,先尝试在不同区间进行随机采样采相同多数目的样本,如果不够就全部采样;进行一轮后,如果样本数不够,再剩下的样本中均匀随机采样。例如假设总共有1000个候选负样本(区间1:800个,区间2:120个,区间3:80个),分为3个区间,总共想取333个,那幺理论上每个区间是111个,首先第一次在不同区间均匀采样,此时区间1可以采样得到111个,区间2也可以得到111个,区间3不够,所以全部保留;然后不够的样本数,在剩下的(800-111)+(120-111)+0个里面随机取31个,最终补齐333个。核心代码如下:

 

意思是在各个区间内,如果够数目就随机采样,如果不够那就剩下的负样本里面全部采样。

 

(2) 平衡回归loss

 

原始的faster rcnn的rcnn head,使用的回归loss是smooth l1,作者认为这个依然存在不平衡。作者分析是:loss解决Classification和Localization的问题,属于多任务loss,那幺就存在一个平衡权重,一般来说回归权重会大一些,但一味的提高regression的loss其实会让outlier的影响变大(类似于OHEM中的noise label),outlier外点样本这里作者认为是样本损失大于等于1.0,这些样本会产生巨大的梯度不利于训练过程,小于的叫做inliers。 平衡回归loss的目的是既不希望放大外点对梯度的影响,又要突出内点中难负样本的梯度,从而实现对外点容忍,对内点区分难负样本的作用 。为此作者在smooth l1的基础上进行重新设计,得到Balanced L1 Loss。核心操作就是想要得到一个当样本在附近产生稍微大点的梯度的函数。首先smooth l1的定义如下:

 

其梯度如下:

 

为了突出难样本梯度,需要重新设计梯度函数,作者想到了如下函数:

 

梯度公式可以实现上述任务。然后反向计算就可以得到Loss函数了。为了保证连续,还需要增加(9)的限制。

 

左边是梯度曲线,右边是loss曲线,可以看出非常巧妙。

 

1.2 one-stage

 

1.2.1 focal loss

 

论文名称:Feature Pyramid Networks for Object Detection

 

该论文也叫做retinanet,是目前非常主流的FPN目标检测one-stage网络结构,本文主要是提出了一个focal loss来对难易样本进行平衡,属于平衡loss范畴。

 

(1) 网络结构

 

由于该网络结构非常流行,故这里仅仅简要说明下,不做具体分析。

 

图(d)即为retinanet的网络结构。主要特点是:(1) 多尺度预测输出;(2) 采用FPN结构进行多层特征图融合。网络进行多尺度预测,尺度一共是5个,每个尺度共享同一个head结构,但是分类和回归分支是不共享权重的。

 

为了方便和faster rcnn进行对比,下面再次贴出rpn结构,并解释参数含义。

 

1. 共同部分

 

anchor_strides表示对应的特征图下采样次数,由于retinanet是从stage1开始进行多尺度预测,故其stride比rpn大一倍;anchor_ratios表示anchor比例,一般是1:1,1:2和2:1三种;

 

2. 不同部分

 

rpn中的anchor_scales表示每个特征尺度上anchor的base尺度,例如这里是8,表示设定的anchor大小是8_[4,8,16,32,64],可以看出每个预测层是1个size * 3个比例,也就是每个预测层是3个anchor;而retianet是不同的,scales_pre_octave=3表示每个尺度上有3个scale size,分别是__,而octave_base_scale=4,意思其实和rpn的anchor_scales意思一样,但是这里换个名字是因为retinanet的scale值是固定的,就一个值,而rpn可能是多个值;通过上面的设置,retinanet的每个预测层都有scale_pre_octivate_len(anchor_ratios)个anchor,这里是9个,是非常多的,anchor的大小是octave_base_scale * [8,16,32,64,128]。可以明显发现retinanet正负样本不平衡问题比faster rcnn更加严重。

 

(2) 正负样本定义

 

retinanet是one-stage算法,其采用的正负样本定义操作是MaxIoUAssigner,阈值定义和rpn不一样,更加严格。如下所示:

 

min_pos_iou=0,可以保证每个GT一定有对应的anchor负责预测。0.4以下是负样本,0.5以上且是最大Iou的anchor是正样本0.4~0.5之间的anchor是忽略样本。其不需要正负样本采样器,因为其是通过平衡分类loss来解决的。

 

(3) 平衡分类loss

 

FocalLoss是本文重点,是用于处理分类分支中大量正负样本不平衡问题,或者说大量难易样本不平衡问题。作者首先也深入分析了OHEM的不足:它通过对loss排序,选出loss最大的example来进行训练,这样就能保证训练的区域都是hard example,这个方法的缺陷,是把所有的easy example(包括easy positive和easy negitive)都去除掉了,造成easy positive example无法进一步提升训练的精度(表现的可能现象是预测出来了,但是bbox不是特别准确),而且复杂度高影响检测效率。故作者提出一个简单且高效的方法: Focal Loss焦点损失函数 ,用于替代OHEM,功能是一样的,需要强调的是: FL本质上解决的是将大量易学习样本的loss权重降低,但是不丢弃样本,突出难学习样本的loss权重,但是因为大部分易学习样本都是负样本,所以顺便解决了正负样本不平衡问题 。其是根据交叉熵改进而来,本质是 dynamically scaled cross entropy loss ,直接按照loss decay掉那些easy example的权重,这样使训练更加bias到更有意义的样本中去,说通俗点就是一个解决 分类问题中类别不平衡、分类难度差异 的一个 loss。

 

上面的公式表示label必须是one-hot形式。只看图示就很好理解了,对于任何一个类别的样本,本质上是希望学习的概率为1,当预测输出接近1时候,该样本loss权重是很低的,当预测的结果越接近0,该样本loss权重就越高。而且相比于原始的CE,这种差距会进一步拉开。由于大量样本都是属于well-classified examples,故这部分样本的loss全部都需要往下拉。其简单思想版本如下:

 

1.2.2 yolov2 or yolov3

 

论文名称:YOLOv3: An Incremental Improvement

 

yolov2和yolov3差不多,主要是网络有差异,不是我们分析的重点,下面以yolov3为例。

 

(1) head结构

 

yolov3也是多尺度输出,每个尺度有3个anchor。对于任何一个分支都是输出[anchor数×(x,y,w,h,confidence,class类别数)h’,w’]。 需要注意的是,其和faster rcnn或者ssd不一样,其类别预测是不考虑背景的,所以才多引入了一个confidence的概念,该分支用于区分前景和背景。 ,所以最复杂的设计就在condidence上面了。

 

(2) 正负样本定义

 

yolo系列的正负样本定义比较简单,原则和MaxIoUAssigner(固定anchor和gt值计算)非常类似,但是更加简单粗暴: 保证每个gt bbox一定有一个唯一的anchor进行对应 ,匹配规则就是IOU最大,而没有考虑其他乱七八糟的。具体就是:对于某个ground truth,首先要确定其中心点要落在哪个cell上,然后计算这个cell的每个anchor与ground truth的IOU值,计算IOU值时不考虑坐标,只考虑形状(因为anchor没有坐标xy信息),所以先将anchor与ground truth的中心点都移动到同一位置(原点),然后计算出对应的IOU值,IOU值最大的那个先验框anchor与ground truth匹配,对应的预测框用来预测这个ground truth。这个匹配规则和ssd和faster rcnn相比,简单很多, 其没有啥阈值的概念 。对于分类分支和bbox回归分支,采用上述MaxIoU分配原则,可以 保证每个gt bbox一定有唯一的anchor进行负责预测,而不考虑阈值 ,即使某些anchor与gt的匹配度不高也负责,而 faster rcnn里面的MaxIoUAssigner是可能由于anchor设置不合理导致某个gt没有anchor进行对应,而变成忽略区域的 。可以看出这种分配制度会导致正样本比较少。对于confidence分支,其在上述MaxIoU分配原则下,还需要从负样本中划分出额外的忽略区域。因为有些anchor虽然没有和gt有最大iou,但是其iou依然很高,如果作为正样本来对待,由于质量不是很高以及为了和分类、回归分支的正样本定义一致,所以不适合作为正样本,但是如果作为负样本那也不合适,毕竟iou很大,这部分位置的anchor就应该设置为忽略区域,一般忽略iou阈值是0.7即将负样本中的iou大于0.7中的anchor设置为忽略区域 (需要特别注意一个细节:此处的iou是每个位置的anchor预测值和所有gt计算iou,而不是固定的anchor和所有gt计算iou,因为此处需要考虑位置信息,faster rcnn系列不需要这幺算的原因是faster rcnn是每个位置都会预测xywh,而yolo系列是基于grid网格预测,xy和wh预测是分开来的,所以会更复杂一些) 。总结就是:

 

1.基于max iou分配准则,区分正负样本

 

2.在负样本范围内,将iou(基于anchor预测值和gt计算)大于忽略阈值的anchor定义为忽略区域,实时改变

 

3.此时就区分出了正、负和忽略anchor样本,正anchor用于分类、回归分支学习,正负anchor用于confidence分支学习,忽略区域不考虑。

 

对于yolov3,由于是多尺度预测,故还有一个细节需要注意: 首先需要将gt利用 max iou原则分配到不同的预测层上去,然后在每个层上单独计算正负样本和忽略样本,也就是和faster rcnn不一样的地方是yolov3不存在某个gt会分配到多个层进行预测的可能性,而是一定是某一层负责的 。但是不同的具体代码实现时候可能会有些许差别。

 

(3) loss

 

由于其采用的是普通的bce分类Loss和smooth l1 回归loss,故不再进行分析。

 

1.2.3 ssd

 

论文地址:SSD: Single Shot MultiBox Detector

 

(1) head结构

 

ssd是最典型的多尺度预测结构,是非常早期的网络。

 

其ssd300的head结构如下:

 

可以看出,ssd一共包括6个尺度输出,每个尺度的strides可以从anchor_strides中看出来,basesize_ratio_range表示正方形anchor的min_size和max_size,anchor_ratios表示每个预测层的anchor个数,以及比例。有点绕,下面具体分析。为了方便设置anchor,作者设计了一个公式来生成anchor,具体为:

 

k为特征图索引,m为5,而不是6,因为第一层输出特征图Conv4_3比较特殊,是单独设置的,表示anchor大小相对于图片的比例,和是比例的最小和最大值,论文中设置min=0.2(ssd300中,coco数据集设置为0.15,voc数据集设置为0.2),max=0.9,但是实际上代码不是这样写的。实际上是:对于第一个特征图Conv4_3,其先验框的尺度比例一般设置为,故第一层的=0.1,输入是300,故conv4_3的min_size=30。对于从第二层开始的特征图,则利用上述公式进行线性增加,然后再乘以图片大小,可以得到各个特征图的尺度为60,111,162,213,264。最后一个特征图conv9_2的size是直接计算的,300*105/100=315。以上计算可得每个特征的min_size和max_size,如下:

 

计算得到min_size和max_size后,需要再使用宽高比例因子来生成更多比例的anchor,一般选取,但是对于比例为1的先验框,作者又单独多设置了一种比例为1,的尺度,所以一共是6种尺度。但是在实现时,Conv4_3,Conv8_2和Conv9_2层仅使用4个先验框,它们不使用长宽比为3,1/3的先验框,每个单元的先验框的中心点分布在各个单元的中心。具体细节如下:

 

以feature map上每个点的中点为中心(offset=0.5),生成一些列同心的prior box(然后中心点的坐标会乘以step,相当于从feature map位置映射回原图位置)。

 

正方形prior box最小边长为和最大边长为:min_size和

 

根据aspect ratio,会生成2个长方形,长宽为

 

目的是保存在该比例下,面积不变。以fc7为例,前面知道其min_size=60,max_size=111,由于其需要6种比例,故生成过程是:

 

第一种比例,(min_size,min_size)=(60,60)

 

第二种比例, ,

 

第三种比例, ,

 

第四种比例,(\sqrt{60×110},\sqrt{60×110})$

 

不管哪个框架实现,核心思想都是一样,但是可能某些数据的设置不一样。下面以mmdetection为例:

 

(2) 正负样本定义

 

ssd采用的正负样本定义器依然是MaxIoUAssigner,但是由于参数设置不一样,故有了不同的解释。

 

其定义anchor与gt的iou小于0.5的就全部认为是负样本,大于0.5的最大iou样本认为是正样本anchor,同时由于min_pos_iou=0以及gt_max_assign_all=False,可以发现该设置的结果是 每个gt可能和多个anchor匹配上,匹配阈值比较低,且每个gt一定会和某个anchor匹配上,不可能存在gt没有anchor匹配的情况,且没有忽略样本 。总结下意思就是:

 

 

anchor和所有gt的iou都小于0.5,则认为是负样本

 

anchor和某个gt的最大iou大于0.5,则认为是正样本

 

gt和所有anchor的最大iou值,如果大于0.0,则认为该最大iou anchor是正样本

 

没有忽略样本

 

 

(3) 平衡分类loss

 

由于正负样本差距较大,如果直接采用ce和smooth l1训练,效果可能不太好,比较样本不平衡严重。故作者的ce loss其实采用了ohem+ce的策略,通过train_cfg.neg_pos_ratio=3来配置负样本是正样本的3倍。

 

核心就是按照分类loss进行topk,得到3倍的负样本进行反向传播。

 

2 anchor-free

 

2.1 fcos

 

论文名称:FCOS: Fully Convolutional One-Stage Object Detection

 

FCOS堪称anchor free论文的典范,因为其结构主流,思路简单清晰,效果蛮好,故一直是后续anchor free的基准对比算法。FCOS的核心是将输入图像上的位置作为anchor point的中心点,并且对这些anchor point进行回归。

 

(1) head结构

 

fcos的骨架和neck部分是标准的resnet+fpn结构,和retinanet完全相同。如下所示:

 

我们仅仅考虑head部分。除去center-ness分支,则可以看出和retinanet完全相同。其配置如下:

 

可以看出一共5个尺度进行预测,每个尺度的stride可以从strides中看出来。由于其是anchor free论文,故少了很多复杂参数。

 

(2) 正负样本定义

 

考虑到anchor free预测值和anchor base不一样,故还是需要提前说下网络预测形式。FCOS是全卷积预测模式,对于cls分支,输出是h x w x (class+1),每个空间位置值为1,表示该位置有特定类别的gt bbox,对于回归分支,输出是h x w x 4,其4个值的含义是:

 

每个点回归的4个数代表距离4条边的距离,非常简单易懂。 和所有目标检测算法一样,需要提前定义好正负样本,同时由于是多尺度预测输出,还需要首先考虑gt由哪一个输出层具体负责 。作者首先设计了min_size和max_size来确定某个gt到底由哪一层负责,具体设置是0, 64, 128, 256, 512和无穷大,也就是说对于第1个输出预测层而言,其stride=8,负责最小尺度的物体,对于第1层上面的任何一个空间位置点,如果有gt bbox映射到特征图上,满足 0 < max(中心点到4条边的距离) < 64 ,那幺该gt bbox就属于第1层负责,其余层也是采用类似原则。总结来说就是第1层负责预测尺度在0~64范围内的gt,第2层负责预测尺度在64~128范围内的gt,其余类推。通过该分配策略就可以将不同大小的gt分配到最合适的预测层进行学习。

 

第二步是需要确定在每个输出层上面,哪些空间位置是正样本区域,哪些是负样本区域。原版的fcos的正负样本策略非常简单粗暴:在bbox区域内的都是正样本,其余地方都是负样本,而没有忽略样本区域。可想而知这种做法不友好,因为标注本身就存在大量噪声,如果bbox全部区域都作为正样本,那幺bbox边沿的位置作为正样本负责预测是难以得到好的效果的,显然是不太靠谱的(在文本检测领域,都会采用shrink的做法来得到正样本区域),所以后面又提出了 center sampling 的做法来确定正负样本,具体是: 引入了center_sample_radius(基于当前stride参数)的参数用于确定在半径范围内的样本都属于正样本区域,其余区域作为负样本,依然没有定义忽略样本 。默认配置center_sample_radius=1.5,如果第1层为例,其stride=8,那幺也就是说在该输出层上,对于任何一个gt,基于gt bbox中心点为起点,在半径为1.5*8=12个像素范围内都属于正样本区域。其核心代码如下:

 

如果需要进行中心采样,那幺基于采样半径比例×当前stride的范围内都属于正样本inside_gt_bbox_mask,其余样本全部属于负样本。可能很多人都有疑问:为啥不需要设置忽略区域?个人猜测原因可能是:1. 设置忽略区域,又需要增加一个超参;2.多了一个center-ness分支,可以很大程度抑制这部分区域对梯度的影响。3.间接的增加了正样本数目。不管咋说应该是作者实验后发现没有很大必要吧。

 

(3) 平衡loss设计

 

可以发现上述肯定存在大量正负样本不平衡问题,故作者对于分类分支采用了one-stage常用的 focal loss ;对于bbox回归问题,由于很多论文表明直接优化bbox比单独优化4个值更靠谱,故作者采用了GIOU loss来回归4个值,对于center-ness分支,采用的是CrossEntropyLoss,当做分类问题处理。

 

(4) 附加内容

 

center-ness作用比较大,从上面的正负样本定义就可以看出来,如果没有center-ness,对于所有正样本区域,其距离bbox中心不同远近的loss权重居然是一样的,这明显是违反直觉的,理论上应该越是远离Bbox中心的空间位置,其权重应该越小,作者实验也发现如果没有center-ness分支,会产生大量假正样本,导致很多虚检。center-ness本质就是对正样本区域按照距离gt bbox中心来设置权重,这是作者的做法,还有很多类似做法,不过有些是在Loss上面做文章,例如在ce loss基础上乘上一个类似预center-ness的权重来实现同样效果(例如Soft Anchor-Point Object Detection)。center-ness效果如下:

 

2.2 centernet

 

论文名称:Objects as Points

 

centernet也是非常流行的anchor-free论文, 其核心是:一些场景的cv任务例如2d目标检测、3d目标检测、深度估计和关键点估计等等任务都可以建模成以物体中心点学习,外加上在该中心点位置处额外学习一些各自特有属性的通用做法 。对于目标检测,可以将bbox回归问题建模成学习bbox中心点+bbox宽高问题,如下所示:

 

(1) head结构

 

centernet的输出也非常简单,其相比较于FCOS等算法,使用更大分辨率的输出特征图(缩放了4倍),本质上是因为其采用关键点检测思路做法,而关键点检测精度要高,通常是需要输出高分辨率特征图,同时不需要多尺度预测。其输出预测头包含3个分支,分别是

 

1. 分类分支h’ x w’ x (c+1),如果某个特定类的gt bbox的中心点落在某个位置上,那幺该通道的对应位置值设置为1,其余为0;

 

2. offset分支h’ x w’ x 2,用于学习量化偏差,图像下采样时,gt bbox的中心点会因数据是离散的而产生偏差,例如gt bbox的中心点坐标是101,而由于输入和输出相差4倍,导致gt bbox映射到特征图上坐标由25.25量化为了25,这就出现了101-25×4=1个pix的误差,如果下采样越大,那幺量化误差会越大,故可以使用offset分支来学习量化误差,这样可以提高预测精度。

 

3. 宽高分支h’ x w’ x 2,表示gt bbox的宽高。

 

其中1.2分支和cornetnet的做法完全相同。而 由于objects as points的建模方式和FCOS的建模方式不一样,故centernet的正负样本定义就会产生很大区别。主要是宽高分支的通道数是2,而不是4,也就是说其输入到宽高分支的正样本其实会非常少,必须是gt bbox的中心位置才是正样本,左右偏移位置无法作为正样本,也没有啥忽略样本的概念,这个是和FCOS的最大区别 。

 

(2) 正负样本定义

 

由于centernet特殊的建模方式,故其正负样本定义特别简单,不需要考虑多尺度、不需要考虑忽略区域,也不用考虑iou,正样本定义就是某个gt bbox中心落在哪个位置上,那幺那个位置就是正样本,其余位置全部是负样本。

 

(3) 平衡Loss设计

 

前面说过目标检测所有论文的样本平衡策略都主要包括正负样本定义、正负样本采样和平衡loss设计,不管啥目标检测算法都省不了,对于centernet,其正负样本定义非常简单,可以看出会造成极其严重的正负样本不平衡问题,然后也无法像two-stage算法一样设计正负样本采样策略,那幺平衡问题就必须要在loss上面解决。对于offset和宽高预测分支,其只对正样本位置进行监督,故核心设计就在平衡分类上面。对于分类平衡loss,首选肯定是focal loss了,但是还不够,focal loss的核心是压制大量易学习样本的权重,但是由于我们没有设置忽略区域,在正样本附近的样本,实际上非常靠近正样本,如果强行设置为0背景来学习,那其实相当于难负样本,focal loss会突出学这部分区域,导致loss难以下降、不稳定,同时也是没有必要的,因为我们的label虽然是0或者1的,但是在前向后处理时候是当做高斯热图(0~1之间呈现2d高斯分布特点)来处理的,我们学到最后的输出只要满足gt bbox中心值比附近区域大就行,不一定要学习出0或者1的图。

 

基于上述设定,在不修改分类分支label的情况下,在使用focal loss的情况下,作者的做法是对正样本附近增加惩罚,基于2d高斯分布来降低这部分权重,相当于起到了类似于忽略区域的作用。可以简单认为是focal focal loss。

 

其中

 

Y是label,0或者1,当Y=1时候,也就是正样本位置,就是标准的focal loss设定;其余区域,分为附近区域和外围区域,附近的定义采用了自适应宽高标准差的做法,在2d高斯分布内部的属于附近区域,外面的都称为外围背景区域,外围区域也是标准的focal loss定义。故作者设计更改的就是附近区域,也就是,可以发现如果去掉,就是标准的focal loss。 附近区域中越靠近中心点的惩罚Y越大,Loss权重 越小,表示该位置对于正还是负的区分越模糊 。

 

可能很多人有疑问: 明明label是0-1的,为啥学出来的会是高斯热图,而不是0-1热图? 我的分析是:如果不考虑附近区域,而是仅仅采用focal loss那幺确实应该学出来的最接近0-1热图,但是由于基于距离gt bbox中心远近不同,设置了不同的惩罚系数,导致网络学习时候对于这部分学习出来的值关注程度不一样,可能就会产生这种现象,举例来说:即使采用上述label,最完美的输出应该是0或1的,但是实际上很难,对于偏离gt bbox附近一点点的位置,假设其预测输出为0,那幺肯定是loss最低,但是可能训练不到那幺好,那幺由于其权重比较小,其学习出0.9,其实loss也不会太大,但是如果距离远一点的,其也学习出0.9,由于其loss权重比较大,就会迫使网络预测要变小一点点,例如变成0.8输出。基于这种权重分布,训练出来的热图可能就会按照1-loss权重的分布呈现,出现高斯热图。学习关键点中心,作者采用的是分类loss,不清楚如果直接采用回归loss,效果咋样,我觉得效果应该不会差。因为关键点检测一般都是采用l2回归loss直接监督在高斯热图上。并且因为宽高、offset分支其实也都是回归loss,三个分支都采用回归Loss不好吗?

 

还有一个问题:宽高和offset的监督仅仅在gt bbox中心位置,其余位置全部是忽略区域。这种做法其实很不鲁棒,也就是说bbox性能其实完全靠分类分支,如果分类分支学习的关键点有偏差,那幺由于宽高的特殊监督特性,可能会导致由于中心点定位不准而带来宽高不准的情况(特别的如果中心点预测丢失了,那幺宽高预测再准也没有用),后面有些论文有其他解决办法,

 

2.3 atss

 

论文名称:Bridging the Gap Between Anchor-based and Anchor-free Detection via Adaptive Training Sample Selection

 

本文写的非常好,作者试图分析问题:anchor-free和anchor-base算法的本质区别是啥?性能为啥不一样?最终结论是 其本质区别就在于正负样本定义不同 。只要我们能够统一正负样本定义方式,那幺anchor-free和anchor-base就没有啥实际区别了,性能也是非常一致的。 正负样本定义也是我写这篇文章的一个最大要说明的地方,因为其非常关键,要想彻底理解不同目标检测算法的区别,那幺对于正负样本定义必须要非常清楚 。

 

(1) head部分

 

由于这篇论文细节比较多,为了论文完整性,我这里大概说下,不重点描述。作者为了找出本质区别,采用了anchor-base经典算法retinanet以及anchor-free经典算法FCOS来说明,因为这两篇论文非常相似,最好进行对比。

 

由于FCOS是基于point进行预测,故可以认为就是一个anchor,为了公平对比,将retinanet的anchor也设置为1个(#A=1),将FCOS的训练策略移动到retinanet上面,可以发现性能依然retinanet低于fcos 0.8mAP。排除这个因素后,现在两个算法的区别是 1.正负样本定义;2.回归分支中从point回归还是从anchor回归 。从point回归就是指的每个点预测距离4条边的距离模式,而从anchor回归是指的retinanet那种基于anchor回归的模式。

 

后面有实验分析可以知道回归分支中从point回归还是从anchor回归对最终影响很小。ATSS的head部分结构如下:

 

可以看出所有的参数其实就是retinanet和fcos参数的合并而已,没有新增参数。

 

(2) 正负样本定义

 

正负样本定义是本文的讨论重点。作者首先详细分析了retinanet和fcos的正负样本定义策略的不同,首先这两个算法都是多尺度预测的,故其实都包括两个步骤:gt分配给哪一层负责预测;gt分配给哪一个位置anchor负责预测。retinanet完全依靠统一的iou来决定哪一层哪一个位置anchor负责预测,而fcos显式的分为两步:先利用scale ratio来确定gt分配到哪一层,然后利用center sampling策略来确定哪些位置是正样本。具体细节请参见的retinanet和fcos的正负样本定义总结。这两种操作的细微差别会导致如下情形:

 

对于1和2两个输出预测层,retinanet采用统一阈值iou,可以确定上图蓝色1位置是正样本,而对于fcos来说,有2个蓝色1,表明fcos的定义方式会产生更多的正样本区域。这种细微差距就会导致retinanet的性能比fcos低一些。

 

对于retinanet算法,正负样本定义采用iou阈值,回归分支采用原始的anchor变换回归模式(box),mAP=37.0,采用fcos的point模式是36.9,说明到底是point还是box不是关键因素。但是如果换成fcos的正负样本定义模式,mAP就可以上升为37.8,和fcos一致了,说明正负样本定义的不同是决定anchor-base和anchor-free的本质区别。

 

既然找到了本质问题,作者分析肯定是fcos的正负样本定义策略比retinanet好,但是fcos算法需要定义超参scale constraint,比较麻烦, 作者希望找到一种和fcos类似功能的正负样本定义算法,主要特定是几乎没有超参,或者说对超参不敏感,可以自适应,故作者提出ATSS算法 。由于作者的设计可以认为是fcos正负样本定义策略的改进版本,故mmdetection的代码中也是针对fcos来说的,具体就是除了正负样本定义策略和fcos不一样外,其余全部相同,所以我们也仅仅需要关注atss部分代码就行。

 

其只有一个参数topk,实验表明参数适当就行,不敏感。下面具体分析ATSS:

 

流程比较简单,但是需要注意, 由于依然需要计算iou,故anchor的设置不能少,只不过anchor仅仅用于计算正负样本区域而已,在计算loss时候还是anchor-free的,anchor默认设置就1个 。主要是理解思想:

 

 

对于每个gt bbox,在每一个预测层上采样tokp个基于l2距离的位置,作为候选;

 

计算gt和每个候选anchor的iou;计算所有iou的均值和方差,相加变成iou阈值(每个gt都有一个自适应的iou阈值);

 

遍历每个候选anchor,如果该anchor大于iou阈值,并且anchor中心位置在gt bbox内部,那幺这个就是正样本区域,其余全部是负样本。

 

 

均值(所有层的候选样本算出一个均值)代表了anchor对gt衡量的普遍合适度,其值越高,代表候选样本质量普遍越高,iou也就越大,而方差代表哪一层适合预测该gt bbox,方差越高越能区分层和层之间的anchor质量差异。均值和方差相加就能够很好的反应出哪一层的哪些anchor适合作为正样本。 一个好的anchor设计,应该是满足高均值、高方差的设定 。

 

如上图所示,(a)的阈值计算出来是0.612,采用该阈值就可以得到level3上面的才是正样本,是高均值高方差的。同样的如果anchor设置和gt不太匹配,计算出来的阈值为0.219,依然可以选择出最合适的正样本区域,虽然其属于低均值、低方差的。采用自适应策略依然可以得到相对好的正负样本,实现了自适应功能,至少可以保证每个GT一定有至少一个anchor负责对应。从这个设定来看 ,应该会出现某个gt在多个层上面都属于正样本区域,而没有限制必须在其中某个层预测,相当于增加了些正样本 。K默认是9。

 

(3) 平衡loss设定

 

其loss函数设计完全和fcos一样,不再赘述。

 

2.4 spad

 

论文名称:Soft Anchor-Point Object Detection

 

本文可以认为是anchor-free论文的改进。其首先指出目前anchor-free算法存在的问题:1.attention bias,2.feature selection,并提出了相应的soft策略。由于其主要是修改了正负样本定义策略,故是本文分析的重点。

 

(1) head

 

本文属于anchor point类算法,即每个点都学习距离4条边的距离,这是标准的densebox算法流程,本质上和FCOS一样:

 

和FCOS的区别就在于 1. 没有center-ness分支,2. 正样本区域的定义采用4条边向内shrink做法,而不是center sampling。

 

1.Attention bias

 

作者采用上面的网络进行训练,发现一个问题如下:

 

(b)是标准网络训练的分类confidence图,可以看出在靠近物体中心的四周会依然会产生大量confidence很高的输出,即没有清晰的边界,在训练过程中可能会抑制掉周围小的物体,导致小物体检测不出来或者检测效果很差。作者分析原因可能是在anchor-point方法在边界地方point进行回归,会存在特征不对齐问题或者说很难对齐。这个现象在FCOS没有加center-ness时候也出现了,FCOS的解决办法就是加入新的center-ness分支训练时候抑制掉,本文解决办法不一样,其通过加权方法。加权后效果如(c)。

 

2.Feature selection

 

特性选择问题其实是指的对于任何一个gt bbox,到底采用何种策略分配到不同的层级进行预测?目前目标检测的做法是基于启发式人工准则将实例分配到金字塔层次(retinanet),或将每个实例严格限制为单个层次(fcos),从而可能会导致特征能力的不充分利用。

 

作者通过训练发现:虽然每个GT只在特定层进行回归,但是其学出来的特征图是相似的,如上图所示,也就是说: 一个以上金字塔等级的特征可以共同为特定实例的检测做出贡献,但是来自不同等级的贡献程度应该有所不同 。这就是本文的出发点, 不再强行判断哪一层进行回归了,而是每一层都进行回归,但是让网络自行学习到金字塔层级的权重 。说了半天,作者仔细谈到了这两个问题,但思考下可以发现说的都是同一个问题,都是正负样本定义问题。 attention bias说的是对于特定输出层内的正负样本定义问题,feature selection说的则是不同输出层间的正负样本定义问题,我们以前说的正负样本定义都是hard,这里解决办法都是soft。

 

(2) 正负样本定义

 

对于特定输出层内的soft正负样本定义问题,作者的解决办法是引入类似center-ness一样的权重,属于Loss层面改进,我们放在(3) 平衡loss分析;对于不同输出层间的soft正负样本定义问题,作者采用的是采用网络自动学习soft权重的做法。注意:输出层内hard正负样本定义,作者采用的依然和FCOS相同的策略,只不过正样本区域是通过4条边shrink得到,而不是center sampling。输出层间hard正负样本定义,作者没有用FCOS的分配策略,而是提出了新的soft代替hard。本文不再强行判断哪一输出层进行gt bbox回归了,而是每一层都进行所有gt bbox回归,但是让网络自行学习到金字塔层级的权重。

 

引入一个简单的网络来学习金字塔权重,具体做法是:利用gt bbox,映射到对应的特征图层,然后利用roialign层提出特征,在采用简单的分类器输出每个层级的权重。由于金字塔层数是5,故fc的输出是5。label设置上,这个思想的提出是参考FSAF(Feature Selective Anchor-Free Module for Single-Shot Object Detection)的做法,核心思想是不再人为定义具体是哪一层负责预测GT,而是根据loss最小的动态选择的那个层。本文借鉴上述思想,也是自动选择最适合的层。meta选择网络的独热码label不是人为提前计算好的,而是根据输出softmax层的值或者说loss来选择的,哪个输出节点loss最小,那幺Label就设置为1,其余为0。整个meta选择网络和目标检测网络联合训练。

 

(3) 平衡loss设计

 

对于特定输出层内的soft正负样本定义问题,作者的解决办法是引入类似center-ness一样的权重,来对距离gt bbbx中心点进行惩罚。中心点权重最大,往外依次减少,其权重图如fcos里面所示:

 

从本质上来说和centernet的解决办法一模一样。

 

看过前面centernet 平衡分类loss分析的人可能有疑问:在centernet里面,其对focal loss的权重设置是在半径范围内,除中心点外,离中心点越近,权重最小,往外依次增加;但是这里的设计是正好相反的,其是离中心点越近,权重越大,往外依次减少。看起来是矛盾的?其实不是,千万不要忘记了label设计规则,centernet的分类label设置是除了中心点是1外,其余全部为0,而本文的label设置是不仅仅中心点是1,shrink附近区域的点也是1,其余全部是0,也就是说模糊区域中,centernet出发点认为是背景样本,而本文出发点认为是前景样本,所以其权重设置规则是正好相反的。

 

看到上图,就可以知道本文所有的操作了。对于特定层内的正负样本定义,原先是hard,现在通过引入权重变成了soft;对于层级间的正负样本定义,原先只会分配到特定层,其余层都0,现在引入了soft操作,权重是自己学到的。

 

通过网络学习到的层级间的权重,可以发现符合作者设定。

 

3 Guided Anchoring

 

论文名称:Region Proposal by Guided Anchoring

 

3.1 核心思想

 

ga这篇论文我觉得做的蛮好,先不说最终效果提升多少个mAP,他的出发点是非常不错的。anchor-base的做法都需要预设anchor,特别是对于one-stage而言,anchor设置的好坏对结果影响很大,因为anchor本身不会改变,所有的预测值都是基于anchor进行回归,一旦anchor设置不太好,那幺效果肯定影响很大。而对于two-stage而言,好歹还有一个rcnn层,其可以对RPN的输出roi(动态anchor)进行回归,看起来影响稍微小一点。不管是one stage还是two-stage,不管咋预测,肯定都是基于语义信息来预测的,在bbox内部的区域激活值肯定较大,这种语义信息正好可以指导anchor的生成,也就是本文的出发点: 通过图像特征来指导 anchor 的生成。通过预测 anchor 的位置和形状,来生成稀疏而且形状任意的 anchor。 可以发现此时的anchor就是动态的了。如果将faster rcnn进行改造,将RPN层替换为ga层,那肯定也是可以的,如果将retinanet或者yolo的预测层替换为ga,那其实就完全变成了anchor-free了。但是作者采用了一种更加优雅的实现方式,其采用了一种可以直接插入当前anchor-base网络中进行anchor动态调整的做法,而不是替换掉原始网络结构,属于锦上添花,从此anchor-base就变成了anchor-base混合anchor-free了(取长补短),我觉得这就是一个不错的进步。

 

3.2 网络设计

 

作者是以retinanet为例,但是可以应用于所有anchor-base论文中。核心操作就是在预测xywh的同时,新增两条预测分支,一条分支是loc(batch,anchor_num * 1,h,w),用于区分前后景,目标是预测哪些区域应该作为中心点来生成 anchor,是二分类问题,这个非常好理解,另一条分支是shape(batch,anchor_num * 2,h,w),用于预测anchor的形状。一旦训练好了,那幺应该anchor会和语义特征紧密联系,如下所示:

 

其测试流程为:

 

 

对于任何一层,都会输出4条分支,分别是anchor的loc_preds,anchor的shape_preds,原始retinanet分支的cls_scores和bbox_preds

 

使用阈值将loc_preds预测值切分出前景区域,然后提取前景区域的shape_preds,然后结合特征图位置,concat得到4维的guided_anchors(x,y,w,h)

 

此时的guided_anchors就相当于retinanet里面的固定anchor了,然后和原始retinanet流程完全相同,基于guided_anchors和cls_scores、bbox_preds分支就可以得到最终的bbox预测值了。

 

 

可以发现和原始retinanet相比,就是多了anchor预测分支,得到动态anchor后,那就是正常的retinanet预测流程了。

 

3.3 loss设计

 

主要就是anchor的loc_preds和shape_preds的loss设计。

 

(1) loc_preds

 

anchor的定位模块非常简单,就是个二分类问题,希望学习出前景区域。这个分支的设定和大部分anchor-free的做法是一样的(例如fcos)。

 

 

首先对每个gt,利用FPN中提到的roi重映射规则,将gt映射到不同的特征图层上

 

定义中心区域和忽略区域比例,将gt落在中心区域的位置认为是正样本,忽略区域是忽略样本(模糊样本),其余区域是背景负样本,这种设定规则很常用,没啥细说的,如图所示:

 

 

 

采用focal loss进行训练

 

 

(2) loc_shape

 

loc_shape分支的目标是给定 anchor 中心点,预测最佳的长和宽,这是一个回归问题。先不用管作者咋做的,我们可以先思考下可以如何做,首先预测宽高,那肯定是回归问题,采用l1或者smooth l1就行了,关键是label是啥?还有哪些位置计算Loss?我们知道retinanet计算bbox 分支的target算法就是利用MaxIoUAssigner来确定特征图的哪些位置anchor是正样本,然后将这些anchor进行bbox回归。现在要预测anchor的宽高,当然也要确定这个问题。

 

第一个问题:如何确定特征图的哪些位置是正样本区域?,注意作者采用的anchor个数其实是1(作者觉得既然是动态anchor,那幺个数其实影响不会很大,设置为1是可以的错),也就是说问题被简化了,只要确定每个特征图的每个位置是否是正样本即可。 要解决这个问题其实非常容易,做法非常多 ,完全可以按照anchor-free的做法即可,例如FOCS,其实就是loc_preds分支如何确定正负样本的做法即可,确定中心区域和忽略区域。将中心区域的特征位置作为正样本,然后直接优化预测输出的anchor shape和对应gt的iou即可。但是论文没有这幺做,我觉得直接按照fcos的做法来确定正样本区域,然后回归shape,是完全可行。本文做法是采用了ApproxMaxIoUAssigner来确定的,ApproxMaxIoUAssigner和MaxIoUAssigner非常相似,仅仅多了一个Approx,其核心思想是:利用原始retinanet的每个位置9个anchor设定,计算9个anchor和gt的iou,然后在9个anchor中采用max操作,选出每个位置9个iou中最高的iou值,然后利用该iou值计算后续的MaxIoUAssigner,此时就可以得到每个特征图位置上哪些位置是正样本了。 简单来说,ApproxMaxIoUAssigner和MaxIoUAssigner的区别就仅仅是ApproxMaxIoUAssigner多了一个将9个anchor对应的iou中取最大iou的操作而已 。

 

对于第二个问题:正样本位置对应的shape target是啥,其实得到了每个位置匹配的gt,那幺对应的target肯定就是Gt值了。该分支的loss是bounded iou loss,公式如下:

 

上面写的非常简陋,很多细节没有写,放在1.5节代码分析中讲解。

 

3.4 结果

 

可以看出非常符合预期。

 

3.5 代码分析

 

(1) head

 

本文代码分析以retinanet为主,网络骨架和neck就没啥说的了,直接截图:

 

就是ResNet+FPN结构,输出5个分支进行预测。stride为[8, 16, 32, 64, 128]。对于head部分,可以对比retinanet的head部分进行查看:

 

左边是GARetinaHead,右边是RetinaHead,可以看出配置除了loss有区别外,都是一样的。head部分的forward非常简单,和retinanet相比就是多了两个shape_pred, loc_pred分支:

 

关于feature_adaption的作用作者在论文分享中:Guided Anchoring: 物体检测器也能自己学 Anchor( https://zhuanlan.zhihu.com/p/55854246 )说的很清楚了,我就不写了。反正forward后就可以4个分支输出。

 

(2) loss计算

 

(2-1) anchor的loc分支loss计算

 

loc输出特征图大小是(batch,1,h,w),本质上是一个二分类器,用于找出语义前景区域。

 

核心参数是中心区域占据比例center_ratio=0.2,忽略区域占比ignore_ratio=0.5.首先需要计算loc分支的target,方便后面计算loss,对应的函数是ga_loc_target,由于代码比较多,不太好写,我只能写个大概流程出来。

 

有了每个特征图上,每个位置是正负还是忽略样本的结果,就可以针对预测的Loc特征图计算focal loss了:

 

(2-2) anchor的shape分支loss计算

 

这个分支稍微复杂一点点。首先我们要先熟悉下train_cfg

 

对于GA分支,主要包括ApproxMaxIoUAssigner和RandomSampler,而原始的retinanet分类和回归分支没有任何改变,不再赘述。ApproxMaxIoUAssigner是用来近似计算shape特征图分支上哪些位置负责预测gt bbox,而 RandomSampler主要目的不是用来平衡正负样本的,因为shape分支监督的只有正样本,没有啥平衡问题 。上述两个类非常关键,理解清楚了才能理解最核心的shape target计算过程。

 

(1) 计算5个特征图上,每个位置9个anchor,组成anchor_list

 

这个函数和其他anchor-base的anchor生成过程完全相同,就是利用特征图大小、anchor设置得到所有预设anchor的(x0,y0,x1,y1)坐标

 

(2) 计算每个特征图的每个位置上shape预测的基数

 

可能这个不好理解。其实在原文中作者指出shape分支直接预测gt bbox的宽高值不太稳定,因为数值波动范围比较大,为了稳定,作者回归的shape预测值实际上是在某个缩放系数下的值,具体是:

 

,s是每个特征图的stride,w就是原始gt bbox宽,dw才是shape分支的预测值。上述的squares_list存储的就是每层特征图的每个位置的基数,用于还原shape预测值到真实比例,由于anchor=1,且对于任意层而言是固定的,所以在代码实现上作者也用了anchor_generate代码来实现

 

可以明显发现approx_generators是每个位置9个anchor,而square_generators每个位置是1个anchor,且是正方形。为了方便区分,你可以认为squares_list存储的就是每一层的还原基数而已,和anchor没啥关系的,只不过可以等价实现而已。而guided_anchors_list其实就是在squares_list基础上结合loc预测和shape预测得到的动态anchor,用于训练原始的retinanet的分类和回归分支。而如何利用squares_list、loc_pred和shape_pred得到最终的动态anchor,做法非常简单,如下所示(测试阶段也是这个流程):

 

(3) 计算shape target

 

在得到squares_list、approxs_list和gt_bboxes,下面核心就是计算shape target了。函数是:

 

计算shape target的流程包括2步:

 

1、确定哪些位置是正样本,通过ApproxMaxIoUAssigner类实现;

 

2. 每个正样本位置的label,通过RandomSampler实现。

 

这两个操作看起来和faster rcnn的rpn阶段的loss计算相同,其实仅仅思想相同而已,但是实现差别还是很大的。

 

假设你已经了解了MaxIoUAssigner的实现过程,而ApproxMaxIoUAssigner的实现是继承自MaxIoUAssigner的,其核心差别是:

 

而MaxIoUAssigner仅仅有以下一行而已:

 

overlaps = bbox_overlaps(gt_bboxes, bboxes)

 

ApproxMaxIoUAssigner的做法是对于每个位置,先计算9个anchor和gt bbox的iou,然后max选择最大iou,将anchor=9变成anchor=1(因为预测就是只有1个anchor),然后在这个overlaps基础上再进行MaxIoUAssigner分配机制,从而确定哪些位置是正样本,同时会记录每个位置负责的gt bbox索引,方便后面计算。下一步是RandomSampler函数,但是要非常注意:retinanet的是RandomSampler调用过程:

 

assign_result = bbox_assigner.assign(anchors, gt_bboxes,
                                     gt_bboxes_ignore, gt_labels)

 

而ga分支的调用过程是:

 

# 确定了哪些位置是正样本区域后,需要加入正样本区域的squares值。注意这里加入的anchor不是近似anchor,而是squares
sampling_result = bbox_sampler.sample(assign_result, squares, gt_bboxes)

 

函数内部实现是一样的,但是由于传入的参数不一样,所有解释就不一样了。第一行的输入是anchor和gt bbox,意思是随机采样正负样本,并且同时将gt bbox作为label,后面直接计算回归loss,但是注意ga分支的输入是assign_result和squares,而不是assign_result和gt bbox,也不是assign_result和approxs。也就是说咱们 暂时把每个正样本位置shape预测值的target认为是squares值,后面还会进一步操作。

 

到这里为止,就已经知道了shape_target返回的各个值含义了。bbox_anchors_list存储的是所有正样本位置的squares值,bbox_gts_list是对应的gt bbox值,在得到所有需要的值后终于可以开始计算shape target的loss了。

 

bbox_anchor其实就是bbox_anchors_list,将bbox_anchors_list和shape_pred经过delta2bbox就还原得到真实的bbox预测值了,只不过由于本分支仅仅用于预测shape,故bbox_deltas的前两个维度一直是0,loss_shape是BoundedIoULoss。

 

到此,核心代码就分析完了。稍微难理解的就是shape target的计算过程了。

 

4 yolo-asff

 

论文名称:Learning Spatial Fusion for Single-Shot Object Detection

 

源码地址: https://github.com/ruinmessi/ASFF

 

简介

 

先贴性能:

 

首先可以看出是非常强的。虽然本文题目重点是说ASFF层的牛逼地方,但是我觉得看本文,重点不在这里,而是yolov3的强baseline。

 

通过一些训练技巧,将yolov3从33.0mAP,提升到38.8mAP,我觉得这个才是重点需要学习的地方。这其实反映出一个明显问题: 骨架的改进固然重要,但是训练技巧绝对要引起重视 ,很多新提出的算法搞了半天提升了1个mAP点,还不如从训练技巧上面想点办法来的快(在人脸检测领域就会针对人脸尺度问题针对性的提出大量训练技巧,是非常高效的),毕竟训练技巧只会影响训练过程,对推理没有任何额外负担,何乐不为。

 

ASFF

 

本文先分析不那幺重要的部分,即本文最大改进ASFF(Adaptively Spatial Feature Fusion)操作,其实熟悉BiFpn的人应该马上就能get到idea了,我觉得本质上没有啥区别。FPN操作是一个非常常用的用于对付大小尺寸物体检测的办法,作者指出FPN的缺点是不同尺度之间存在语义gap,举例来说基于iou准则,某个gt bbox只会分配到某一个特定层,而其余层级对应区域会认为是背景( 但是其余层学习出来的语义特征其实也是连续相似的,并不是完全不能用的 ),如果图像中包含大小对象,则不同级别的特征之间的冲突往往会占据要素金字塔的主要部分,这种不一致会干扰训练期间的梯度计算,并降低特征金字塔的有效性。

 

一句话就是: 目前这种concat或者add的融合方式不够科学。本文觉得应该自适应融合,自动找出最合适的融合特征 ,如下所示:

 

简要思想就是:原来的FPN add方式现在变成了add基础上多了一个可学习系数,该参数是自动学习的,可以实现自适应融合效果,类似于全连接参数。ASFF具体操作包括 identically rescaling和adaptively fusing。定义FPN层级为l,为了进行融合,对于不同层级的特征都要进行上采样或者下采样操作,用于得到同等空间大小的特征图,上采样操作是1×1卷积进行通道压缩,然后双线性插值得到;下采样操作是对于1/2特征图是采样3 × 3 convolution layer with a stride of 2,对于1/4特征图是add a 2-stride max pooling layer然后引用stride 卷积。Adaptive Fusion

 

下面讲解具体操作:

 

(1) 首先对于第l级特征图输出cxhxw,对其余特征图进行上下采样操作,得到同样大小和channel的特征图,方便后续融合

 

(2) 对处理后的3个层级特征图输出,输入到1x1xn的卷积中(n是预先设定的),得到3个空间权重向量,每个大小是nxhxw

 

(3) 然后通道方向拼接得到3nxhxw的权重融合图

 

(4) 为了得到通道为3的权重图,对上述特征图采用1x1x3的卷积,得到3xhxw的权重向量

 

(5) 在通道方向softmax操作,进行归一化,将3个向量乘加到3个特征图上面,得到融合后的cxhxw特征图

 

(6) 采用3×3卷积得到输出通道为256的预测输出层

 

ASFF层学习得到的各种特征可视化效果如下:

 

强baseline

 

YOLOv3包括darknet53骨架网络和3层特征金字塔网络构成的3个尺度输出。

 

首先采用基于Bag of freebies for training object detection neural networks里面提出的训练策略来改进性能,主要包括 the mixup algorithm , the cosinelearning rate schedule和 the synchronized batch normalization。 其次,由于最新论文表示iou loss对于边界框回归效果好,故作者也额外引入了一个 iou loss来优化bbox 。最后,由于GA论文(Region proposal by guided anchoring)指出采用语义向导式的anchor策略可以得到更好的结果,故作者也引入了 GA 操作来提升性能。可以看出,结合这些策略后,在coco上面可以得到38.8的mAP,速度仅仅慢了一点点(多了GA操作),可谓是非常强大,这也反应出训练策略对最终性能的影响非常大。

 

the mixup algorithm , the cosinelearning rate schedule和 the synchronized batch normalization这三个策略非常常见,没啥好说的。额外引入一个iou loss也是常规操作。我们重点分析GA的实现。为啥要重点分析GA操作呢? 因为作者实现的GA和原始论文的GA有点不同,很值得分析。

 

loss代码分析

 

对应的代码是YOLOv3Head.py

 

首先和GA一样,也是有FeatureAdaption,用于对动态anchor特征进行自适应。

 

(1) 初始化函数

 

anchors里面存储的就是原始yolov3的9组anchor,但是特别需要注意的是这里实现的anchor个数是4个,而不是GA原文的1个anchor,这个小差别会导致后面代码有些差别。按照GA论文做法,其实1个anchor就足够了。而且需要注意 这里的GA分支没有输出Loc,仅仅有shape预测。原因是loc分支的目的仅仅是用于进行前后景提取,是个二分类问题,但是由于yolo有confidence分支,其有前后景提取功能,故不再需要loc分支。 loss函数方面,就是多了shape预测的IOUWH_loss函数,以及bbox回归额外引入的IOUloss,其余相同。

 

(2) forward函数

 

(2-1) 输出预测值

 

对ASFF层输出的每一层特征图xin,进行GA分支推理,并且经过Feature_adaption,得到最终的bbox预测输出output,由于wh预测肯定是大于0的,故作者采用了exp函数强制大于0.此时就得到了**wh_pred(batch,4,2,h,w)和output(batch,4,5+class,h,w)**。

 

(2-2) 将预测anchor变换到原图尺度,得到guide anchor

 

(2-3) 前向模式下基于预测anchor直接进行回归即可得到最终bbox

 

(2-4) 训练模式下准备target先准备数据:

 

计算匹配anchor和confidence mask

 

理解下面的操作才是理解了本文核心:

 

前面所有准备都好了后,就是计算loss了:

 

可以看出前两个loss是额外加入的。

 

GA的引入可以实现:

 

(1) 在anchor设置不合理时候,动态引导anchor预测分支得到更好的anchor

 

(2) 在anchor设置合理时候,可以加速收敛,且可以进一步refine 默认anchor

 

我觉得本文的GA实现过程非常好,思路清晰,很好理解,可以实现anchor-base混合anchor-free,发挥各自的优势。后续会进行各种对比实验,验证GA在yolo中的引导作用。

 

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注