MENU

目标检测系列三:奇技淫巧

May 24, 2019 • Read: 5527 • 数据竞赛,目标检测阅读设置

背景

国内的数据竞赛真的缺乏交流,还是喜欢kaggle的kernel和讨论区,真硬核!这里分享一下我总结的一些目标检测中会用到的“奇淫技巧”,牵扯到代码的我就直接拿 mmdetection[1]来举例了,修改起来比较简单。这些都是跟随郑烨、梁爽、元梵、杨胜、亚光、徐大哥等大佬一起学习的一部分思路,感谢各位大佬不嫌弃小弟,带我不断起飞,哈哈哈。

1.模型选择

近一年多以来目标检测领域没有太大的动静,即使最近一段时间的 Anchor Free 和神经网络搜索框架比较热,但都没有太大的革新,当前检测竞赛圈的通用配置还是 Cascade-R-CNN + ResNeXt/ResNet系列+FPN+DCN 2,毕竟二阶段为王,详情参考链接 cascade_rcnn_dconv_c3-c5_r50_fpn,根据自己的需要进行修改,如果没有修改源码的能力,那就只能用官方给出的配置了。然后就是一些根据实验结果的调整了,由于商汤没有开源 SeNet系列的训练模型(避免误会,这里指的是商汤未在mmd中开源,并没有senet的所属关系),所以如果有卡的话可以自己搞。

2.数据预处理

如果你 baseline 选的准,那么基本上已经领先一大部分人了,但是如果在数据预处理过程中没有搞好,那基本上就跟TOP系列无缘了,毕竟在数据处理上能够领先的大佬,后面炼丹的技术也绝对不差。这里分为以下几个部分聊一聊数据这方面的策略:

2.1 数据扩充

如果说待检测目标具有旋转不变性,那这里就可以对目标做上下翻转、左右反转、90°*3旋转等操作;如果目标中存在模糊的情况,在扩充的时候也可以适当做一些高斯模糊什么的;对于颜色抖动、锐度变化、随机缩放等这些操作,我实验的过程中也很难界定他们的效果,而且跟队友做相同实验时,所起的作用也不一样,总结来说,有的时候真的是随机上分。

更详细的附带代码的数据扩充请移步:目标检测系列二:数据增广

2.2 mixup

mixup的意思就是将两张图按照一定的比例混合在一起,详情移步论文:Bag of Freebies for Training Object Detection Neural Networks[4]。这里要说的是如何mixup,如何选择mixup的对象。

在工业类缺陷检测或者违禁物品检测中,常常会给出一些不含有待检测目标的正常图像,可以将含有目标的图像和随机选取的正常图像进行mixup(随机意味着更多的组合~),这样数据量又上来了。还有一种是徐大哥用的比较骚的操作,就是跟coco的数据集进行mixup,真是服气。。。

2.3 填鸭式

这个肯定别人也这么叫过,但是我们队一开始想到的时候,就这么称呼了。所谓填鸭式,就是将一些目标(也可以是误捡的)扣出来,放到没有目标的图上去,增加图像的鲁棒性。比如我们在钢筋识别的时候,有一些小石子和吊机容易被误判成钢筋,索性就选了一些图,把这些伪目标填充合理的位置上,效果就是没有再误判过了。

在津南2违禁物品的检测中,我们一开始也测试了mixup的策略,但是效果一般,后来改用“填鸭式”处理,效果要好很多,应该说前排基本都用了这个方法吧(猜测)。这里有一个要注意的就是“随机”,要是能够让目标在正常图像中的随机位置填充并且随机旋转和缩放,那就完美了,无奈这个没有实现好,如果有大佬实现了,求指导。。。。

2.4 裁剪

根据具体任务,可以将待检测目标进行裁剪。在 DF新的交通标志物的识别竞赛中,图像尺寸较大,3200x1800,但是目标特别小,最大也在200x200左右,这就导致训练尺寸必须要不低于原始图像尺寸,很依赖算力。后来在跟徐大哥、郑烨讨论后,决定在目标周围裁剪出一个尺寸,然后进行训练,效果不错,且训练周期明显缩小。这个策略前排基本都在用,也没什么可隐藏的了。

想到其他的了,再更新。

3.预训练模型

数据处理完以后,基本上就是要冲击前排了,这里就是要考虑如何选用预训练模型了,一般的检测都是使用ImageNet预训练的backbone,这是基本配置,高级一点的就是针对数据集做一次预训练,比如津南2的违禁物品检测,可以将所有目标裁剪出来,然后训练一个不错的分类模型,这样的初始化相比ImageNet就要好太多了。。。

再一点就是使用coco预训练的完整检测模型权重,这样的效果就是模型收敛速度贼快,而且效果一般都比较好,也是大家最常用的方法,这里给出 mmdetection 修改coco预训练权重类别数的脚本:

# for cascade rcnn
import torch
num_classes = 21
model_coco = torch.load("cascade_rcnn_x101_32x4d_fpn_2x_20181218-28f73c4c.pth")

# weight
model_coco["state_dict"]["bbox_head.0.fc_cls.weight"].resize_(num_classes,1024)
model_coco["state_dict"]["bbox_head.1.fc_cls.weight"].resize_(num_classes,1024)
model_coco["state_dict"]["bbox_head.2.fc_cls.weight"].resize_(num_classes,1024)
# bias
model_coco["state_dict"]["bbox_head.0.fc_cls.bias"].resize_(num_classes)
model_coco["state_dict"]["bbox_head.1.fc_cls.bias"].resize_(num_classes)
model_coco["state_dict"]["bbox_head.2.fc_cls.bias"].resize_(num_classes)
#save new model
torch.save(model_coco,"coco_pretrained_weights_classes_%d.pth"%num_classes)

这里再说一下backbone的选择,因为要考虑到要使用coco预训练权重的原因,暂时采用的都是官方给出的模型以及对应的backbone。我自己也在mmdetection中实现了SeNet系列的backbone,无奈没卡,没法训一个coco出来。。。。

4. 训练技巧

深度炼丹名不虚传。。。。。

4.1 warmup lr

翻译一下就是对学习率进行预热,最开始是在 ResNet 的论文中提到的一种方法,原始是先在前几个epoch或iter或目标达到一个水准之前以小于预设值得lr进行训练,然后再恢复lr到初始值。后来Facebook提出了改良版本,详情请移步论文: Gradual warmup[5],这也是当前检测和分割中必不可少的环节,mmdetection中默认是启用了的:

lr_config = dict(
    policy='step',
    warmup='linear',
    warmup_iters=500,
    warmup_ratio=1.0 / 3,
    step=[8, 11])

4.2 lr 如何计算

学习率的初始化设置一直是一个比较头疼的问题,有的时候需要经常实验才能得到一个比较好的值,我们在检测任务中常用的计算方法是:lr = 0.02 / 8 x num_gpus x img_per_gpu / 2,一般情况都是这么计算后设置。

4.3 多尺度训练

检测的两大任务:分类和定位,定位需要模型能够适应变化频繁的尺度特征,但是这恰恰是卷积神经网络所不具备的,目前在网络模型上通过 FPN 结构可以缓解一部分,另外就是多尺度训练,在不同的iter下选择不同的尺度进行训练,注意尺寸最好时能够被32整除,不过mmdetection会检查这一点,帮你pad一下。

4.4 损失函数

4.4.1 Focal Loss

这是CV中根据实验结果调整损失函数最先考虑的一个,论文: Focal Loss for Dense Object Detection,主要是针对模型拟合困难的样例或者样本不均衡的样例,在图像分类中常用作最终的损失函数,直接进行优化,而在目标检测中却有两个选择,一个是在 RPN 层使用 FocalLoss,这样可以缓解由于目标占比较少导致生成的 anchor 正负样本比例失衡;另一种就是类似图像分类一样,在bbox_head中使用,mmdetection中的相应配置(如果要正确使用,需要做点改动,自行修改源码吧,不难):

#1. rpn 处更改
rpn_head=dict(
    type='RPNHead',
    in_channels=256,
    feat_channels=256,
    anchor_scales=[8],
    anchor_ratios=[0.5, 1.0, 2.0],
    anchor_strides=[4, 8, 16, 32, 64],
    target_means=[.0, .0, .0, .0],
    target_stds=[1.0, 1.0, 1.0, 1.0],
    loss_cls=dict(
        type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),
    loss_bbox=dict(type='SmoothL1Loss', beta=1.0 / 9.0, loss_weight=1.0))

#2. bbox_head 处更改
bbox_head=dict(
    type='SharedFCBBoxHead',
    num_fcs=2,
    in_channels=256,
    fc_out_channels=1024,
    roi_feat_size=7,
    num_classes=81,
    target_means=[0., 0., 0., 0.],
    target_stds=[0.1, 0.1, 0.2, 0.2],
    reg_class_agnostic=False,
    loss_cls=dict(
        type='CrossEntropyLoss', #在此处替换
        use_sigmoid=False,
        loss_weight=1.0),
    loss_bbox=dict(
        type='SmoothL1Loss', beta=1.0, loss_weight=1.0)))

4.4.2 GIou Loss

Generalized Intersection over Union: A Metric and A Loss for Bounding Box Regression,之前旷世提出了 Iou Loss 收敛性较差,GIOU的详细介绍,我之前一篇博客介绍了,这里不再赘述,详情移步:Generalized Intersection over Union,这里只提一下,这种损失函数对增加框的回归效果比较有效,如果你的任务要求 IOU > 0.8或者更高,这个可以优先考虑。

4.4.3 其他损失函数

针对分类的损失函数可以试试如 GHM-C Loss,针对回归的损失函数可以试试如 GHM-R Loss。

4.5 OHEM

OHEM(online hard example mining),翻译过来就是在线难例挖掘,就是对所有的 ROI 的损失进行评估,选择损失较大的来优化网络,详情移步:OHEM论文解读

4.6 待更。。。

想到其他的了,再更新。。。

5. infer技巧

5.1 TTA

检测任务中使用比较多的就是多尺度预测,这个时间开销有点高,但是效果也是不错的。另外一个就是CV中最常用的TTA了(Test Time Augmentation),如果在训练的过程中加入了旋转和翻转,那么前向过程中也需要加上,即使训练没用的话,加上翻转也会有提升。

5.2 Soft-NMS

Soft-NMS 改进了之前比较暴力的 NMS,当IOU超过某个阈值后,不再直接删除该框,而是降低它的置信度(得分),如果得分低到一个阈值,就会被排除;但是如果降低后仍然较高,就会被保留。实现细节移步:NMS与soft NMS

在 mmdetection 中的设置如下:

test_cfg = dict(
    rpn=dict(
        nms_across_levels=False,
        nms_pre=1000,
        nms_post=1000,
        max_num=1000,
        nms_thr=0.7,
        min_bbox_size=0),
    rcnn=dict(
        score_thr=0.05, nms=dict(type='soft_nms', iou_thr=0.5), max_per_img=100),
    keep_all_stages=False)

TODO

  • [ ] 其他未知

参考文献

[1] : mmdetection
[2] : Cascade R-CNN: Delving into High Quality Object Detection
[3] : Deformable ConvNets v2: More Deformable, Better Results
[4] : Bag of Freebies for Training Object Detection Neural Networks
[5] : Gradual warmup

Last Modified: August 12, 2019
Archives Tip
QR Code for this page
Tipping QR Code
Leave a Comment

28 Comments
  1. aaa aaa

    感谢您的分享,请问一下有没有mmdetection实现的mixup代码参考一下呢

    1. @aaa在 mmdet/datasets/extra_aug.py中自己添加吧

    2. kevin kevin

      @spytensor博主,您好!请问,您那份包含GIOU损失函数的mmdetection 代码,什么时候公布出来? 求一份

  2. xlt xlt

    后排的小尾巴来汲取经验,受益匪浅。

  3. blateyang blateyang

    请问博主没卡是怎么使用mmdetection训练模型做比赛的?

    1. @blateyangemmm...一般没卡就是说卡太少,跑步起来

    2. @blateyang跑不起来

  4. ex ex

    请问该公式lr = 0.02 / 8 x num_gpus x img_per_gpu / 2的计算顺序是什么?

    1. @ex正常的乘除运算,从左往右

  5. 初学者 初学者

    想问下您,在mmdetection中,怎么正确修改cascade rcnn dcn 101中的focal loss,我们修改了rcnn三层的box head,但是训练结果并不对,不知道原因,您能贴下您的修改么,可以的话贴下修改后的源码,十分感谢。

  6. Alex Alex

    请问lr = 0.02 / 8 x num_gpus x img_per_gpu / 2 学习率这样计算的根据是什么呢,有什么资料吗?

    1. jiang.g.f jiang.g.f

      @Alex这是根据detectron的设置来的,你看看detectron的配置文件。

  7. 膜拜大佬 膜拜大佬

    请问一下你们这个题目的代码开源了么?主要想看一下你们是怎么基于mmdetection改代码的

    1. @膜拜大佬队伍内部使用,暂时没有开源的计划

  8. 膜拜大佬 膜拜大佬

    这里有两个问题
    1.对于预训练模型修改类别的时候,你贴出来的代码和我在配置文件里面修改num_classes有什么区别
    2.如果使用focol loss的话,需要将rpn-head和bbox-head的cls_loss改为focal loss这么改对吧?

  9. kevin kevin

    博主,您好!请问,您那份包含GIOU损失函数的mmdetection 代码,什么时候公布出来? 求一份

    1. @kevin参考这里吧:https://github.com/yhcao6/mmdetection/blob/69ce11fac827391bfcc7f8209a8b9ad309a8c478/mmdet/core/loss/losses.py#L166

    2. kevin kevin

      @spytensor谢谢博主!

  10. 膜拜大佬 膜拜大佬

    请问一下博主,有没有基于mmdetection的目标检测任务做数据增强的可以参考的代码呢?

    1. @膜拜大佬你说的是在线增强吧,可以参考mmdetection中SSD中的增强方式。

  11. pampas pampas

    为什么不用one stage的方法呢?比如SSD?

    1. @pampas单阶段的优势是速度,精度和两阶段相比还是差很多,论文中给出的超过两阶段的mAP,看看就行了。

  12. 我爱学习 我爱学习

    博主,在mmdetection中添加giou loss 如何操作啊

    1. @我爱学习mmd已经更新了,参考相关代码就可以

  13. 谢谢博主的工作,请问图片过大,”目标周围裁剪出一个尺寸“,这个是怎样实现的呢?

    1. @aphlafa在训练过程中有包含目标的标签,根据标签进行裁剪。

    2. @spytensor谢谢博主的回答,但还是有几个小疑问:1、如果是对原图裁剪的,那样是要重新修改原bbox值,但对于多标签的图片怎样处理?
      2、是在训练过程中裁剪,博主这个意思不太理解?

  14. xuy xuy

    你好,请问一下,为什么在预训练模型这一部分,不使用pretrained加载coco训练好的模型+改类别个数,而是改类别并且保存模型呢?