RetinaNet
Last updated
Last updated
RetinaNet 是 FAIR 出品,目标检测领域意义重大的精品算法,其犀利的问题解决思路和简洁优雅的网络设计,深深的影响了整个目标检测算法的发展方向,到目前为止依然是 one-stage 主流算法。其核心创新点可以简单归纳为:
深入分析了何种原因导致 one-stage 检测器精度低于 two-stage 检测器
针对上述问题,提出了一种简单但是极其实用的 Focal Loss 焦点损失函数,并且 focal 思想可以推广到其他领域
针对目标检测特定问题,专门设计了一个 RetinaNet 网络,结合 Focal Loss 使得 one-stage 检测器在精度上能够达到乃至超过 two-stage 检测器,在速度上和一阶段相同
目标检测算法一般分为 two-stage 和 one-stage,通常 two-stage 检测器精度较高、速度较慢,而 one-stage 检测器速度较快、精度较低。作者试图分析何种原因导致 one-stage 检测器精度低于 two-stage 检测器算法?通过实验分析发现原因是:极度不平衡的正负(前景背景)样本比例。对于 one-stage 检测器,例如 YOLO、SSD 等,anchor 近似于滑动窗口的方式铺设在原图上,由于每张图片 gt bbox 比较少,会导致正负样本极度不平衡,而且绝大部分样本都是易学习样本 easy example(包括 easy positive 和 easy negative,但主要是 easy negative),这些易学习样本虽然每一个带来的 loss 会比较小,但是因为数量巨大,最终依然主导了 loss,导致最后训练出来的是一个比较差的模型,也就是梯度被易学习样本主导,且基本上都是易学习背景样本。
对于 Faster R-CNN 这类 two stage 模型,第一阶段的 RPN 可以过滤掉很大一部分负样本,最终第二阶段的检测模块只需要处理少量的候选框,这些后续框不是随机产生的,而是高质量样本,而且检测模块还采用正负样本固定比例抽样(比如 1:3)或者 OHEM 方法(online hard example mining)来进一步解决正负样本不平衡问题,所以在 two-stage 算法中正负样本不平衡问题没有 one-stage 那么严重。
常用的解决正负样本不平衡办法是 OHEM,它通过对 loss 排序,选出 loss 最大的前 topk 个样本来进行训练,这样就能保证训练的都是难样本。作者指出该方法的缺陷是把所有的 easy example 都去除掉了,造成易学习正样本无法进一步提升训练的精度(易学习正样本其实非常关键,在 pisa 论文中有详细分析),而且复杂度高影响检测效率。
为此作者提出一个简单且高效的方法:Focal Loss 焦点损失函数,用于替代 OHEM,功能是一样的。其解决了两个问题:样本不平衡问题以及突出难样本,需要强调的是:FL 本质上是将大量易学习样本的 loss 权重降低,突出难学习样本的 loss 权重,但是因为大部分易学习样本都是负样本,所以还有一个附加功能即克服正负样本不平衡问题。
为了说明 focal loss 思想,需要回顾下 ce loss 和加权 ce loss。
为了能够突出正负样本不平衡问题,可以引入加权 ce loss 即:
对于任何一个类别的样本,本质上是希望学习出概率为 1,当预测输出接近 1 时候,该样本 loss 权重是很低的,当预测的结果越接近 0,该样本 loss 权重就越高。而且相比于原始的 ce loss,这种差距会进一步拉开。由于大量样本都是属于 well-classified examples,故这部分样本的 loss 全部都需要往下拉,就可以达到聚焦难样本且不抛弃任何可能有用的样本效果。
我们可以自己通过仿真调整参数看下具体效果:
通过上述分析可以发现:focal loss 是根据交叉熵改进而来,本质是 dynamically scaled cross entropy loss,直接按照 loss decay 掉那些 easy example 的权重,这样使训练更加 bias 到更有意义的样本中去,说通俗点就是一个解决分类问题中类别不平衡、分类难度差异的一个 loss。
代码实现方面也比较简单:
focal loss 配合 RetinaNet 网络可以实现非常不错的 one-stage 检测性能。RetinaNet 网络是目前主流的 backbone+neck+head 设计模式。
(1) backbone
backbone 可以选择任意分类模型,默认是采用 resnet,其 4 个特征图,按照特征图从大到小排列,分别是 c2 c3 c4 c5,stride=4,8,16,32,考虑到计算量仅仅用了 c3 c4 c5 3 个输出特征图。
(2) neck
neck 模块是标准的 FPN 结构,其作用是特征融合,其细节是:先对这 c3 c4 c5 三层进行 1x1 改变通道,全部输出 256 个通道;然后经过从高层到底层的最近邻 2x 上采样+add 操作进行特征融合,细节见下图,最后对每个层进行 3x3 的卷积,得到 p3,p4,p5 特征图。
考虑到大物体可能感受野不够,故还需要构建两个额外的输出层 stride=64,128,首先对 c5 进行 3x3 卷积且 stride=2 进行下采样得到 P6,然后对 P6 进行同样的 3x3 卷积且 stride=2,得到 P7,整个 FPN 层都不含 BN 和 Relu。
(3) head
作者认为 one-stage 算法中 head 设计比较关键,对最终性能影响较大,相比于其余 one-stage 算法,retinanet 的 head 模块比较大,其输出头包括分类和检测 head 两个分支,且每个分支都包括 4 个卷积层,不进行参数共享,分类 head 输出通道是 num_class*K,检测 head 输出通道是 4*K,K 是 anchor 个数。虽然每个 head 的分类和回归分支权重不共享,但是 5 个输出特征图的 head 是权重共享的。
在介绍正负样本定义前,需要先说明下 anchor 设置规则:
retinanet 采用了密集 anchor 设定规则,每个输出特征图位置都输出 K=9 个 anchor,非常密集。其含义和 faster rcnn 里面完全相同,此处就不赘述了。
retinanet 匹配策略非常简单就是 iou 规则,和 faster rcnn 中的完全相同,仅仅阈值设置不同而已。配置如下:
大意是:
初始所有 anchor 都定义为忽略样本
遍历所有 anchor,每个 anchor 和所有 gt 的最大 iou 大于 0.5,则该 anchor 是正样本且最大 iou 对应的 gt 是匹配对象
遍历所有 anchor,每个 anchor 和所有 gt 的最大 iou 小于 0.4,则该 anchor 是背景
遍历所有 gt,每个 gt 和所有 anchor 的最大 iou 大于 0,则该对应的 anchor 也是正样本,负责对于 gt 的匹配,可以发现 min_pos_iou=0 表示每个 gt 都一定有 anchor 匹配,且会出现忽略样本,可能引入低质量 anchor
匹配情况可视化如下:
白色 bbox 是 gt 值,其余颜色是正样本 anchor。从 0-4 是从大特征图(检测小物体)到小特征图(检测大物体)的。可以发现图中有两个 gt bbox,其中网球排被分配到第 1 层负责预测,人分配到第 2 层负责预测了,可能存在某个 gt bbox 分配到多个层进行预测。
和 faster rcnn 中的 rpn 一样,为了使得各分支 Loss 更加稳定,需要对 gt bbox 进行编解码,其编解码过程也是基于 gt bbox 和 anchor box 的中心偏移+宽高比规则,对应的代码是:
虽然前面正负样本定义阶段存在极度不平衡,但是由于 focal loss 的引入可以在很大程度克服。故分类分支采用 focal loss,回归分支可以采用 l1 loss 或者 smooth l1 loss,实验效果表明 l1 loss 好一些。
在 Retinanet 中,其分类分支初始化 bias 权重设置非常关键。那么原因是啥?
简单来说就是对于一个分类任务,一个 batch 内部几乎全部是负样本,如果预测的时候没有偏向,那么 Loss 肯定会非常大,因为大部分输出都是错误的,现在强制设置预测为负类,这样开始训练时候 loss 会比较小,这个操作会影响初始训练过程。
我们可以通过对分类分支的特征图进行计算 norm max min 三个值,并打印操作就看出: 当 bias=0,也就是没有偏向,则开始训练时候:
当 bias 采用上述公式:
可以明显发现,设置 0.01 的参数后输出 tensor 的值基本上都是负数,符合预期。
在推理阶段,对 6 个输出 head 的预测首先取 top 1K 的预测值,然后用 0.05 的阈值过滤掉背景,此时得到的检测结果已经大大降低,此时再对检测结果的 box 分支进行解码,最后把输出 head 的检测结果拼接在一起,通过 IoU=0.5 的 NMS 过滤重叠框就得到最终结果。
(1) focal loss VS Ohem
可以看出在如此密集预测情况下,采用 ohem 策略会丢弃大量样本,导致训练过程不充分,效果明显不如 focal loss。
(2) 性能对比
RetinaNet 是非常优秀的目标检测算法,其提出的 focal loss 和 backbone+neck+head 网络构建模型深深影响了整个领域的发展。其没有花里胡哨的设计,没有太多的 trick,也不存在难以理解的地方,个人觉得应该成为每一位目标检测算法初学者的首选。
以二分类问题为例,输出通道是 1,如果 y=1 则表示正样本,y=0 表示负样本,p 是经过 sigmoid 后的输出概率值,可以发现其对正负样本的惩罚是一样的。为了统一成一个公式,可以设置 :
此时 loss 函数可以写成 。
对正负样本设置不同的 (一般正负权重加起来是 1)即可,可以通过交叉验证确定。但是上述损失函数只关注于正负样本类别不平衡问题,而无法关注难易样本不平衡问题,故 focal loss 的定义如下:
和是超参,并且两者相互影响,作者通过实验设置 、 。loss 可视化如下:
先看,随着增加,整个梯度会变大,也就是属于正负样本的加权参数,值越大,正样本的权重越大。再看 ,其具有 focal 效应,可以控制难易样本权重,值越大,对分类错误样本梯度越大(难样本权重大),focal 效应越大,这个参数非常关键。
默认为 0.01。这个操作非常关键,原因是 anchor 太多了,且没有 faster rcnn 里面的 sample 操作,故负样本远远大于正样本,也就是说分类分支,假设负样本:正样本数=1000:1,分类是 sigmod 输出,其输出的负数表示负样本 label,如果某个 batch 的分类输出都是负数,那么也就是预测全部是负类,这样算 loss 时候就会比较小,相当于强制输出的值偏向负类。