本文来自52CV群友Spytensor参加Kaggle目标检测比赛的总结,作者是位数据竞赛爱好者,文章非常具有实战意义。欢迎收藏~

背景

国内的数据竞赛真的缺乏交流,还是喜欢 kaggle 的 kernel 和讨论区,真硬核!这里分享一下我总结的一些目标检测中会用到的 “奇淫技巧”,牵扯到代码的我就直接拿 mmdetection[1] 来举例了,修改起来比较简单。

1. 模型选择

近一年多以来目标检测领域没有太大的动静,即使最近一段时间的 Anchor Free 和神经网络搜索框架比较热,但都没有太大的革新,当前检测竞赛圈的通用配置还是 Cascade-R-CNN + ResNeXt/ResNet 系列 + FPN+DCN 2,毕竟二阶段为王!然后就是一些根据实验结果的调整了,由于商汤没有开源 SeNet 系列的训练模型,所以如果有卡的话可以自己搞。

640.png

2. 数据预处理

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

2.1 数据扩充

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

2.2 mixup


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

640.png

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

2.3 填鸭式

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

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

640.png

3. 预训练模型

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

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
















# for cascade rcnnimport torchnum_classes = 21model_coco = torch.load("cascade_rcnn_x101_32x4d_fpn_2x_20181218-28f73c4c.pth")# weightmodel_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)# biasmodel_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 modeltorch.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 的损失进行评估,选择损失较大的来优化网络。

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)



品略图书馆 http://www.pinlue.com/