1. 前言
    本项目只介绍比赛中运用到的各种技巧,并不涉及模型训练流程。如果想看完整的数据准备、模型训练及转换和预测步骤,请移步至我的另一个项目:【改进版】第17届百度创意组线上赛baseline

如果你觉得本次比赛该用的都用了,实在没有什么头绪让成绩继续提升,那么看完本项目或许你会有所收获!

其实我也是深度学习的萌新,去年刚接触PaddlePaddle(PaddleDetection是今年才开始接触的),啥也不会,也走了很多弯路,有幸的是,承蒙各大佬倾囊相助,让我收获了很多有用的知识。所以借此机会,我将把我所学到的东西尽可能详细地表达出来,与君共勉!

拜托,如果你能认真看完,我真的会很开心!在这里插入图片描述
Model Strategy F1-Score FPS
PP-YOLOE-m baseline 0.62121 21.50538
PP-YOLOE-m 8线程图片预处理 0.62121 38.46154
PP-YOLOE-m 4进程图片预测每个进程内单线程图片预处理 0.62121 45.45455
PP-YOLOE-m 4进程图片预测每个进程内8线程图片预处理 0.62121 38.83495
PP-YOLOE-m 3进程图片预测每个进程内8线程图片预处理 0.62121 46.51163
PP-YOLOE-m 2进程图片预测每个进程内8线程图片预处理 0.62121 48.19277
Model Strategy F1-Score FPS
A L模型+合并数据集+DIouLoss+大尺寸测试等 0.67135 28.77698
A1 A 改阈值[0.4975,0.472,0.415,0.42,0.5,0.475,0.41] 0.68791 28.77698
B A+Mosaic数据增强 0.66006 26.66667
C A+AdamW(lr0.0002)+更大尺寸测试 0.69141 23.12139
C1 C 改阈值[0.4,0.472,0.4,0.47,0.42,0.435,0.4] 0.69514 23.80952
C2 C1 nms_threshold改为0.55 0.69536 23.9521
C3 C2 改阈值 [0.4975,0.472,0.4,0.47,0.54,0.435,0.4] 0.70101 23.9521
C4 C3 改阈值[0.45,0.49,0.44,0.46,0.53,0.47,0.398] 0.70964 24.53988
D C3+lr改为0.00002 0.68799 23.9521
E C3+换成GIouLoss 0.68969 23.9521
F E+梯度裁剪参数从0.1改为1.0 0.70237 24.53988
2. 比赛介绍
2.1 比赛背景
车辆的普及和人们对出行体验的重视,对地图导航提出了更高的要求。基于车载影像的AR导航系统能够精准快速的识别道路情况,不仅能够辅助驾驶系统进行高效的决策,同时结合终端渲染与语音技术,也能够为用户带来更为智能精准的导航体验。

作为『新一代人工智能地图』的百度地图,秉承着『用科技让出行更简单』的使命,借助于图像识别、语音识别、大数据处理等人工智能技术,大幅提升地图数据采集和处理的自动化程度,实现道路覆盖超过1000万公里,已成为业内AI化水平最高、搭载的AI技术最强最丰富的地图数据厂商。本次比赛数据由百度地图提供,要求在统一的计算资源下,能够对车载影像中道路障碍物及红绿灯状态进行快速精准的识别。

本次竞赛的题目和数据由百度地图数据引擎部提供,模型和基线相关技术支持由深度学习技术平台部提供,一站式AI开发平台AI Studio由百度AI技术生态部提供。期待参赛者们能够以此为契机,共同推进智能交通领域的发展。

比赛链接

2.2 比赛任务
要求参赛者利用提供的训练数据,在统一的计算资源下,实现一个能够识别道路障碍物与红路灯状态及其具体位置和类别的深度学习模型,不限制深度学习任务。本次比赛要求选手使用飞桨PaddlePaddle2.2及以上版本生成端到端深度学习模型。 选手需上传训练模型、预测代码及环境库(可选)的zip形式压缩包到AI Studio平台进行自动评测。其中,预测代码需命名为predict.py,model目录不超过200M(不压缩),整体压缩包不超过1G。

2.3 数据集介绍
本次大赛提供7类共计16000张图像数据,采用矩形检测框标注方法,具体分类标准如下:在这里插入图片描述
2.4 评分规则
选手需上传训练模型、预测代码及环境库(可选)的zip形式压缩包到AI Studio平台进行自动评测。其中,预测代码需命名为predict.py,model目录不超过200M(不压缩),整体压缩包不超过1G。要求模型预测速度在v100显卡需达到20 FPS及以上(预测时间包括前处理和后处理用时),视为有效,在满足速度基础上,按照精度高低进行排名。

F1-score:我们采用平均F1-score来评估结果,针对每一个类别F1-score 计算公式如下:

在这里插入图片描述
2.5 COCO目标检测比赛中的模型评价指标介绍
TP: IoU>0.5的检测框数量(同一Ground Truth只计算一次)(IOU指的是ground truth box和prediction box的交并比)
FP: IoU<=0.5的检测框,或者是检测到同一个GT的多余检测框的数量(即IOU虽然大于0.5但置信度不是最高的检测框)
FN: 没有检测到的GT的数量在这里插入图片描述
3. PaddleDetection简介
PaddleDetection为基于飞桨PaddlePaddle的端到端目标检测套件,内置30+模型算法及250+预训练模型,覆盖目标检测、实例分割、跟踪、关键点检测等方向,其中包括服务器端和移动端高精度、轻量级产业级SOTA模型、冠军方案和学术前沿算法,并提供配置化的网络模块组件、十余种数据增强策略和损失函数等高阶优化支持和多种部署方案,在打通数据处理、模型开发、训练、压缩、部署全流程的基础上,提供丰富的案例及教程,加速算法产业落地应用。

如果大家对PaddleDetection不熟悉的话,墙裂建议看这三个: PaddleDetection进阶教程、 PaddleDetection Gitee网址、 PaddleDetection GitHub网址

这是我第一次使用PaddleDetection套件,深刻地感受到了它的便捷性!最爱的还是PaddlePaddle框架的模块化设计(使得调参只需要在yml文件下进行):在这里插入图片描述
也希望大家多多支持PaddlePaddle,让咱们国产的深度学习框架越做越大,越做越强。只有用的人多了,才会涌入更多的人才,才能打造更好的生态,从而让更多的百度人工智能产品走进我们的生活,造福国人!

  1. 数据分析与处理
    4.1 GT框分析
    首先,对本次比赛的数据集进行分析 待检测的目标一共有七个类别:

{‘id’: 1, ‘name’: ‘Motor Vehicle’}, 机动车
{‘id’: 2, ‘name’: ‘Non_motorized Vehicle’},非机动车
{‘id’: 3, ‘name’: ‘Pedestrian’}, 行人
{‘id’: 4, ‘name’: ‘Traffic Light-Red Light’}, 红灯
{‘id’: 5, ‘name’: ‘Traffic Light-Yellow Light’}, 黄灯
{‘id’: 6, ‘name’: ‘Traffic Light-Green Light’}, 绿灯
{‘id’: 7, ‘name’: ‘Traffic Light-Off’} 未亮灯
图片数量 Box数量 单张图Box数统计[max,mean,min] 最小的[width,height]
训练集 14000 113951 [44, 8.14, 1] [2, 0]
验证集 2000 16123 [35, 8.06, 1] [1, 0]
其中训练集出现height为0的图片为06918.jpg,验证集出现height为0的图片为06048.jpg和014216.jpg。所以官方数据集中是含有脏数据的,大家可以选择将该数据去除或者在json文件中把height改为1。

至于统计这些数值有什么意义呢?例如,由上表我们可以知道单张图的Box数最大为44,那进行NMS操作时就可以据此调整变量keep_top_k的值了。再者,统计GT框的长宽比等信息可以在anchor-based模型下设置Anchor的大小,数量,长宽比这些超参数。当然还可以统计GT框占原图面积的百分比,这有助于分析该检测任务是以大中小哪种目标为主。总之,对数据集进行分析是很有必要的!

4.2 类别实例数量分析
官方数据集各类别实例数量统计如下图:在这里插入图片描述
可以看到各类别严重不均衡,红绿灯数量很少,黄灯数量更是少得可怜。而且经过对图片进行分析后发现目标大小的差距很大,所以这些就是我们需要重点下功夫的地方。 2022.3.17直播提供了处理这种情况的思路(直播的回放和PPT在这:课节9:基于车载影像的驾驶环境感知)

4.3 Copy-Paste数据增强
4.3.1 算法介绍
数据增强的作用想必大家早已心知肚明,而且PaddleDetection也提供了很便捷的Data Augmentation设置方式,这些都算是在线数据增强,其中很多tircks也值得大家去探索和尝试。而接下来我要介绍的是,我在本次比赛中对数据集进行离线数据增强的思路与方法:

Copy-Paste(也叫作填鸭式数据增强)在本次比赛中的运用:(参考论文:Simple Copy-Paste is a Strong Data Augmentation Method for Instance Segmentation) 思路如下:(具体代码实现将在后文展示)

根据COCO数据集的json文件获得所有的图片ID,依次读取每一张图片作为main_img
获得特定类别(category_id)的源图像ID,从中随机选择一张图片作为src_img
根据设定的概率,让图片main_img进行copy-paste
将src_img上的特定目标按原bbox的坐标位置粘贴到main_img上
src_img保持不变 ; main_img新目标物的标注信息写入json,并让增强后的新图像覆盖原图像
另外,可以设置“是否允许copy后的目标框与原先存在的目标框重叠”以及“是否将src_img上的所有特定目标都copy”
简而言之,就是读取数据集图像->对图像做数据增强->调整图像中的目标框(在此过程不增加图片数量,只是增加每张图片中annotations的数量)。

这里解释一下这么做的意义: 经过前面的数据分析可知:红绿灯目标的实例少,且标注面积占比小,而当前检测器大部分的anchor匹配策略是以anchor和groud truth的IOU来划分正负样本,例如将 anchor 和 GT 匹配后,匹配 IoU≥0.5 的 anchor 会作为正样本参与训练。然而这种匹配方式更倾向于大目标,会造成了小目标匹配少、大目标匹配多的不平衡性。也就是训练数据中小目标的再现性较差,所以通过Copy-Pasting(复制粘贴)就可以提供足够的小目标来和 anchor 进行匹配,以此实现提高小目标检测的性能。

4.3.2 讨论与思考
或许你心里会想:这么做好像挺不错,但会不会有点简单粗暴以致不合理:

目标边缘是不是要处理一下?(毕竟直接放进去违和感十足)
粘贴的位置是不是有悖常理?(红绿灯出现在地板上)
在看过上文提到的参考论文后你就能释然了。该文主要使用了训练集中实例分割对象复制粘贴实现训练阶段的数据增广,其增广方法三个字概括为“无限制”,其过程可以总结为 5 个随机:

随机选择两幅训练图像
随机尺度抖动缩放
随机水平翻转
随机选择一幅图像中的目标子集
粘贴在另一幅图像中随机的位置
谷歌学者在论文中一再说明:位置不需要在意,乱放其实挺好! 谷歌学者还发现粘贴后的目标边缘其实没必要处理,反正他们相信一些论文提到的:处理了也没发现有提升。

总结起来就是:数据增强的结果看起来不自然,没关系!又不是给人看的。

特别值得一提的是,作者除了重点强调复制粘贴要简单粗暴外,还在论文实验中发现,对于尺度抖动也不要太温柔。

一种标准的尺度抖动,把图像缩放到原来的0.8-1.25,这个标准看起来对原数据的改变不是太大,应该是大部分人的选择。但谷歌学者发现把图像缩放到0.1到2.0之间其实更好。

4.3.3 结果展示
本次Copy-Paste是在基于车载影像的ar导航项目下的数据集基础上进行的,其对官方数据集进行了增强(包括JpegCompression(降画质)、MotionaBlur(运动模糊)、RandomBrightnessContrast(亮度对比度)、马赛克等数据增强的方法)。

最后的结果如下图所示:(对训练集的红绿黄灯及未亮灯都进行了增强)在这里插入图片描述
训练集:
原来的:{‘1’: 82329, ‘2’: 12074, ‘3’: 11658, ‘4’: 1616, ‘5’: 139, ‘6’: 2213, ‘7’: 3922}
增强后:{‘1’: 92547, ‘2’: 13842, ‘3’: 13382, ‘4’: 14285, ‘5’: 13929, ‘6’: 14232, ‘7’: 17751}
验证集:
原来的:{‘1’: 11632, ‘2’: 1767, ‘3’: 1676, ‘4’: 205, ‘5’: 27, ‘6’: 307, ‘7’: 509}
增强后:{‘1’: 11560, ‘2’: 1709, ‘3’: 1687, ‘4’: 239, ‘5’: 26, ‘6’: 329, ‘7’: 537}
对图片017006.jpg进行Copy-Paste后的结果如下图所示:在这里插入图片描述
经测试后,发现copy-paste虽然能增大小目标的AP(Average Precision)和AR(Average Recall),但是大目标的这两个指标又降低了,所以这个增强手段的具体应用还是得多尝试,或许不应该增加那么多小目标。最终我使用的数据集红绿灯的实例数量为1W左右。

4.4 重新划分数据集
另外,本项目还提供了数据集重新划分的代码,(官方数据集的训练集:验证集=7:1),另外有一点要注意的是:由于基于车载影像的ar导航项目下的数据集中的训练集是经过数据增强的(有部分图片来源于同一张图片,两两之间除了数据增强方式不同其他都一样),所以为了确保重新划分数据集后验证集的图片从未在训练集中出现。我们应该这样操作:

对验证集的json文件进行重新划分(运行work/json_split.py)
将划分得到的其中一个json文件与训练集的json文件合并(运行work/json_merge.py)
从而得到新的训练集json文件和验证集json文件
5.模型选择
这次多亏了喜乐多多多大佬的引荐,我们才能知道PP-YOLOE的强大,也为我们节省了很多本该花在模型选择上的时间。但是,如果必须靠自己呢,该如何是好?在这里插入图片描述
由上图,可以先在模型库PaddleDetection/docs/MODEL_ZOO_cn.md(GitHub传送门)中根据对应的模型评估指标,结合比赛的要求(比如本次比赛对FPS和模型大小有限制),选择较好的模型进行训练,多尝试总会有收获!下图为不同模型在COCO 2017测试集上的实验结果对比,我们可以清楚地看到PP-YOLOE的优势所在:在这里插入图片描述
我最后使用的是PP-YOLOE的L模型

这里有同学可能会有疑问:跑这个模型最后导出的model文件夹不是大于200MB吗?是的,的确如此。但还是有办法的,模型压缩是一种,可自行探索,这里我们看看导出的静态图模型文件都有哪些:

├── infer_cfg.yml # 模型配置文件信息
├── model.pdiparams # 静态图模型参数
├── model.pdiparams.info # 参数额外信息,一般无需关注
└── model.pdmodel # 静态图模型文件在这里插入图片描述
以上截图是就是L模型的model文件夹,确实超过了200MB,可是也就超过了7KB(按照我的超参数设置训练出来的模型大小几乎都是这种情况),所以我将model.pdiparams.info文件删除后就能正常提交结果了。经测试,在200MB的限制条件下,L模型的图片输入尺寸最大为1088(此时model.pdmodel为3882KB左右),如果model.pdmodel为3750KB左右,则最大尺寸为1120。(这个尺寸增大会导致model.pdiparams占用内存变大)

6.模型训练
6.1 调参之路
调参一般调的是啥?主要是更改learning_rate(学习率)、batch_size(单张卡上,每步迭代训练时的数据量)、epoch(训练总轮数)等等。其实所有配置文件中涉及到的参数都可以尝试去修改。在这里插入图片描述
batch_size:batch_size可以说是所有超参数里最好调的一个,应该是最早确定下来的超参数。所以,先选好batch_size,再调其他的超参数。那到底设置为多少合适?在查完资料后你会发现他们都在说batch_size不能太大也不能太小,这……这不是在讲废话吗?(当然高质量博客还是有的)我的做法简单概括下就是:把显存拉满(终端输入watch -n 1 nvidia-smi可查看cpu和gpu占用情况),也就是batch_size能取多大就多大。因为增大batch_size,其确定的下降方向越准,引起训练震荡越小。让GPU满载运行,能提高训练速度,但同时达到相同精度需要的epoch数量变多(因为在同样的epochs下参数更新变少了,因此需要更长的迭代次数)。在微调的时候,大的batch size可能会取得更好的结果。(另外,batch_size的取值可以任意,6、10甚至奇数都可以,但是有些帖子中提到:在使用GPU时,通常使用2的幂数作为batch_size可以获得更少的运行时间)

学习率:因PaddleDetection配置文件中原始学习率是适配多GPU训练(大多数是8x GPU,PicoDet是4x GPU),若使用单GPU训练,须对应调整学习率(除以8),且batch_size应该同倍率增大或减小,所以可以得到以下公式:

lr0bs0∗n0=lrbs∗n\frac{{{lr}{0}}}{{{bs}{0}}{{n}_{0}}}=\frac{{lr}}{{bs}{n}}
bs
0

∗n
0

lr
0

bs∗n
lr

其中lr0表示原配置文件的学习率,bs0表示原配置文件的batch_size,n0表示原配置文件的GPU卡数

当然这个不是绝对的,还需要在自己的数据集上进行finetune(微调)。

若发现验证集遭遇瓶颈,不妨将LR除以2(或5),然后继续。最终,LR将会变得非常小,这也到了停止训练的时候了。这样做可以确保在验证性能受到损害的时候,你不会拟合(或过 度拟合)训练数据。降低LR是很重要的,通过验证集来控制LR是个正确的做法!

至于学习率降低到什么程序可以停止训练,理论上训练loss和验证loss都达到最小的时候就可以了。

学习率衰减策略:采用余弦模拟退火(包含WarmUp策略),T_max(配置文件中是max_epochs)为总训练次数;(注:当epoch达到max_epochs时学习率正好为0,这样就可以通过设置这个参数让最后学习率下降至接近于0)

优化器的选择:我尝试了Adam,AdamW效果都不怎么样,然后再去查资料后发现(这里直接搬结论):“从实验可以看出monument非常非常重要,让有点垃圾的SGD一下子就上天了(怪不得论文里都喜欢用这对couple),这时候再加上一些学习率调整策略(退火/ReduceLROnPlateau)就可以直接上天了,不说了,以后我就先首发这对组合。Adam相对于SGD来讲,的确好上不少,但是相比较于最优组合(SGD+momentum+学习率调整策略),就弱了一点。”当然,也可能是我使用AdamW的时候学习率设得太大了(当时设为0.005),最终我选择的是SGD+Momentum+学习率余弦退火.这里补充一点:在听完喜乐多大佬的直播过后我又重新使用AdamW了。而且有一个trick大家也可以尝试:前期用AdamW加快收敛,后期换成SGD!

epoch:说实话这个超参数我也没啥经验,只能说都试一试吧,100e、200e、300e……其实有很长一段时间我就因为这个超参数导致我错过了最优的成绩(一般我到180e就停止了),可是经过和其他参赛队伍交流后我才知道:排名靠前的队伍都是300e左右的。但是我最后的成绩,换了AdamW优化器后最优epoch是150e。

预训练模型:如果网络基于预训练权重做的finetune,由于模型在原数据集上以及收敛,有一个较好的起点,可以将初始学习率设置的小一些进行微调,比如0.0001

模型的调参训练技巧其实说白了就是怎么让模型得到的是数据全集的稀疏分布,且和别的类别有比较好的区分,也就是类内差小类间差则尽量大。以这个为核心,告诉模型应该关注什么,少关注什么,既然是数据的科学,那就要多关注数据的分布和呈现的状态,这和前面数据集的分析密切相关!

6.2 Multi-scale Training
输入图片的尺寸对检测模型的性能影响相当明显,事实上,多尺度是提升精度最明显的技巧之一。在基础网络部分常常会生成比原图小数十倍的特征图,导致小物体的特征描述不容易被检测网络捕捉。通过输入更大、更多尺寸的图片进行训练,能够在一定程度上提高检测模型对物体大小的鲁棒性。

配置文件中默认的BatchRandomResize取的是[320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768],而大Iuput size有助于对小物体的识别,所以我也把这个列表进行扩大,最后取的是[320, 384, 448, 512, 576, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992, 1024, 1056, 1088] 虽然前面提到“对于尺度抖动也不要太温柔”,但是考虑到显存占用问题(bs不能太小)所以多尺度训练并没有把尺寸拉的很大。(这种情况下L模型的训练bs只能取8左右)

6.3 数据增强方案
PaddleDetection为我们提供了很多数据增强的API,使用起来极其方便。大家只需要到PaddleDetection/ppdet/data/transform/operators.py下查看各个函数的参数设置即可,以下数据增强是我在本次比赛中使用过的:

名称 作用
Decode 从图像文件或内存buffer中加载图像,格式为RGB格式
GridMask GridMask数据增广
Mosaic Mosaic数据增广
Mixup Mixup数据增强,按比例叠加两张图像
RandomDistort 随机扰动图片亮度、对比度、饱和度和色相
RandomFlip 随机水平翻转图像
RandomExpand 将原始图片放入用像素均值填充的扩张图中,对此图进行裁剪、缩放和翻转
RandomCrop 原理同CropImage,以随机比例与IoU阈值进行处理
BatchRandomResize 对一个batch的图片进行resize,使得batch中的图片随机缩放到相同的尺寸
NormalizeImage 对图像像素值进行归一化,如果设置is_scale=True,则先将像素值除以255.0, 再进行归一化。
Permute 假如输入是HWC顺序变成CHW
其中,可能是由于我的参数设置不当,在使用了Mosaic和GridMask数据增强后效果反而变差了,所以后期也就把这两个给去掉了。

Mosaic数据增强:这是release/2.4版本的PaddleDetection新增的数据增强方案。在 YOLO-V4 论文中提到:Mosaic 数据增强是一种新的混合4幅图像的数据增强方法,是CutMix数据增强方法的改进版。这种数据增强方式简单来说就是把4张图片,通过随机缩放、随机裁减、随机排布的方式进行拼接。Mosaic有如下优点:(1)大大丰富了检测数据集,特别是随机缩放增加了很多小目标,让网络的鲁棒性更好;(2)减少GPU显存:直接计算4张图片的数据,使得Mini-batch大小并不需要很大就可以达到比较好的效果(间接增大了 batch_size)
GridMask数据增强:GridMask方法属于信息删除的方法,这种方法的实现方式是随机在图像上丢弃一块区域,作用相当于是在网络上增加一个正则项,避免网络过拟合,相比较改变网络结构来说,这种方法只需要在数据输入的时候进行增广,简单便捷。
另外,PaddleDetection还提供自动数据增强(AutoAugment)等众多实用的数据增强方法,大家可以多加尝试!
6.4 混合精度训练——FP32和FP16(Half-precision floating-point)
在训练命令行加上–amp,表示Enable auto mixed precision training(开启自动混合精度训练) 官方文档介绍

问题来了:为什么要用混合精度训练呢?

因为正常训练我们使用的是FP32,而FP16除了能节约内存,还能同时节省模型的训练时间,其爽感类似于两倍速看肥皂剧!
但是FP16会带来舍入误差(因为FP16的可表示范围小),所以要引入混合精度。混合精度训练的精髓在于“在内存中用FP16做储存和乘法从而加速计算,用FP32做累加避免舍入误差”
使用混合精度还有一个好处:解决了单纯使用FP16导致的数据溢出-梯度消失问题
除此之外,因为fp16混合精度加速,导致显存占用减少,因此可以启用更大的batch_size!!
经测试,在本次比赛中用这个会导致F1-score下降两个百分点,所以在比赛中还是不适合用amp的。
6.5 使用DIouLoss
在PaddleDetection/ppdet/modeling/heads/ppyoloe_head.py中将GIoULoss换成DIouLoss即可。

由于IOU 和 GIOU 损失函数无法衡量真实框和预测框相对位置的问题(当目标框完全包裹预测框的时候,IoU和GIoU的值都一样,此时GIoU退化为IoU, 无法区分其相对位置关系),因此提出了DIouLoss,其计算公式如下:

其中,b,bgt分别代表了预测框和真实框的中心点,p2代表计算两个中心点间的欧式距离。 c2代表的是能够同时包含预测框和真实框的最小闭包区域的对角线距离。DIoU loss可以直接最小化两个目标框的距离,因此比GIoU loss收敛快得多。

  1. 后处理优化
    7.1 NMS
    nms 类型参数,可以设置为[MultiClassNMS, MatrixNMS, MaskMatrixNMS], 默认使用 MultiClassNMS

nms可设置的参数介绍(以下部分参数是某一类型没有的,比如MatrixNMS就无需设置nms_threshold):

NMS步骤后每个图像要保留的总bbox数。 -1表示在NMS步骤之后保留所有bbox。

keep_top_k: 100

过滤掉低置信度分数的边界框的阈值。

score_threshold: 0.01

经过NMS衰减后,过滤掉低置信度分数的边界框的阈值。

post_threshold: 0.01

在NMS中用于剔除检测框IOU的阈值,默认值:0.3 。

nms_threshold: 0.45

基于 score_threshold 的过滤检测后,根据置信度保留的最大检测次数。

nms_top_k: -1

是否归一化,默认值:True 。

normalized: false

background_label,背景标签(类别)的索引,如果设置为 0 ,则忽略背景标签(类别)。如果设置为 -1 ,则考虑所有类别。默认值:0

background_label: -1
由于数据集中存在很多类似下图(014568.jpg)的情况,GTbox(真实框)重叠严重,使用NMS的时候很容易将其中一个抑制了,从而导致漏检,召回率(Recall)降低。所以对nms策略的选择也可以进行考虑,另外如果选择MultiClassNMS的话,nms_threshold调大一点就能提高召回率,但是太高又可能让NMS的效果失效,从而降低精确率(Precision)。在这里插入图片描述
经测试,MatrixNMS的效果比MultiClassNMS差了一点点:在这里插入图片描述
对nms_threshold进行修改(只需要在导出模型的时候更改就行!)在这里插入图片描述
可以看出nms的操作对成绩的影响还是挺大的,为此我还尝试了在predict.py中对预测结果的框进行SoftNMS、WBF等操作。这些方法在密集目标下召回是有提升的!

7.2 predict.py优化
改变写入result.json的x,y,w,h的小数位数:可一定程度提高FPS(大概能提升1~2)
因为在模型推理后得到的这些值保留了好几位小数,但实际上我们并不需要这么多,如下图所示在这里插入图片描述
可以通过在predict.py中修改,通过round()来决定保留几位小数(经测试保留两位小数即可让精度无损)
c_results[“result”].append({“image_id”: image_id,
“type”: int(id_results[idx]) + 1,
“x”: round(float(bbox_results[idx][0]),2),
“y”: round(float(bbox_results[idx][1]),2),
“width”: round(width,2),
“height”: round(height,2),
“segmentation”: []})
置信度阈值的选择:
不同目标设定不同输出阈值(每个类别的score阈值也就是置信度阈值):例如multiclass_thres = [0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4] 可根据预测结果可视化大致选定每一类的置信度阈值(Confidence threshold),这个阈值的设定将直接决定是否将预测结果的以下信息写入json文件。

所以这一步是决定F1-score高低的最后一道关卡,其值的设定变得尤为重要。这里可以跟大家分享一个经验:阈值的设定对成绩影响最大的就是红绿灯,尤其是黄灯!(如果运气好,仅将该类别改为最优阈值可能会有0.01的提升<defult阈值是0.4的情况下>)

虽然这七个阈值的最优解对于每个模型是不太一样的,但是总体趋势一致。由于我们无法知道测评系统的图片是怎么样的,所以只能利用每天的六次机会去实验。至于怎么样搜索才能比较快地找到最优值,就得靠大家去设计了(比如根据经验每一类的最优阈值在0.40.6之间,大部分是0.40.5,那可以采用二分法查找最优阈值)。阈值的提分还是很明显的,这里我也做了一个实验:

多阈值 F1-score FPS
[0.4,0.4,0.4,0.4,0.4,0.4,0.4] 0.62121 37.38318
[0.4975,0.472,0.415,0.42,0.5,0.475,0.41] 0.63211 38.09524
使用多线程对图片进行预处理:大幅提高FPS。这里指的多线程是用在图片预处理的时候,而在模型预测时候仍然使用单线程。(m模型最大尺寸可达1408左右,此时FPS接近20)
不知道大家有没有遇到和我一样的问题,如果要使用pool.map()的话就只能对preprocess函数传入一个变量,一开始写这个代码的时候我就卡在了这里。尝试过用functools模块中的偏函数partial来实现传两个参数但还是会报错。既然改传参行不通,那就只能改送入线程池的函数了:

def my_preprocess(para):
im_path, preprocess_ops = para
im, im_info = preprocess(im_path, preprocess_ops)
return im, im_info

para = [[i,detector.preprocess_ops] for i in im_paths]
imandinfos = pool.map(my_preprocess, para)
使用多进程对图片进行预测:大大幅提高FPS。(m模型最大尺寸可达1632左右,此时FPS接近20)
这里的基本思路就是将测试集的图片平均分成work_num(进程数)份,(如果不能整除就将多余的图片送入最后一个进程),然后对每个进程分别预测图片(当然,可以在每个进程里开多线程对图片进行预处理)。这里就会涉及到多进程之间通信的问题,我尝试过将每个进程的预测结果put到队列中,并且一定要在join之前及时get队列中的信息,否则会使进程一直阻塞,但是就算这样效果还是不行。就在我一筹莫展的时候,七七神话大佬的精妙绝伦的设计让我茅塞顿开!!他的解决方案是将每个进程的预测结果各自写入一个json文件中,并在最后合并这些json文件,从而解决了多进程之间通信的问题!!!本来各进程之间就是解耦的,所以压根就不需要进程通信共享变量什么的,佩服佩服!!

  1. some tricks(奇淫技巧)
    8.1 改变resize图片的插值方法(cv2.resize的参数interpolation):
    interpolation 选项 插值方式
    cv2.INTER_NEAREST 最近邻插值
    cv2.INTER_LINEAR 双线性插值(默认)
    cv2.INTER_AREA 使用像素区域关系进行重采样
    cv2.INTER_CUBIC 4x4像素邻域的双3次插值
    cv2.INTER_LANCZOS4 8x8像素邻域的Lanczos插值
    对同一模型的实验结果:在这里插入图片描述
    8.2 Large scale testing.
    为了提高小目标的检测性能,可以在测试时候使用大尺度测试——在基础网络部分常常会生成比原图小数十倍的特征图,导致小物体的特征描述不容易被检测网络捕捉,在测试阶段引入大尺度,使小目标的特征更明显,从而更好地检测出小目标。当然这并不意味着图片分辨率越大越好,当分辨率增大到一定程度时,大目标和中等目标检测准确率是会下降的。

8.3 合并数据集
深度学习,数据为王,更多的数据往往会为网络优化带来新的方向。当其他参数都已固定时,联合训练集和验证集进一步训练,让网络看见更多数据,进一步加强训练。

合并数据集的代码在work/json_merge.py,合并方法很简单:分别读取train.json和eval.json文件,对其中相同的字段进行合并,这里唯一要注意的就是’images’和’annotations’下的id不能有重复!而图片直接从eval文件夹copy到train文件夹下即可。注意:我只是将验证集复制到训练集中,也就是训练的时候还是可以评估的,但是这个评估指标就没什么参考价值了(因此,为了加快训练速度,我把–eval去掉了),最终的epoch是根据合并数据集前的经验事先确定的。而合并数据集带来的效果我在另外一个项目已经实验过了:第十五届中国计算机设计大赛智慧导盲组-第5名方案分享

8.4 随机权重平均-SWA(Stochastic Weights Averaging) Object Detection论文地址
SWA简单来说就是对训练过程中的多个checkpoints进行平均,以提升模型的泛化性能。那么问题来了,训练多少个epoch再平均?如何调整学习率?一般情况下,我们会选择训练过程中最后一个epoch的模型或者在验证集上效果最好的一个模型,并在此模型基础上再恢复训练——采用合适的固定学习速率或者周期式学习率额外训练一段时间,取多个权重模型的平均值作为最终模型。论文中提到:checkpoints平均后获得了比之前训练路径上所有模型都更好的结果,循环余弦退火学习率调整获得的结果更好,bbox AP 和 mask AP都可以获得超过 1 个AP的提升!而且相比于6个、24个、48个checkpoints的平均,12是一个足够好的数字。

那么SWA为什么有效呢,论文也给了简单的解释,由于模型的参数属于高维空间,SGD训练的模型往往收敛到最优解的边界区域,如下图a所示,模型W1、W2和W3都落在了边缘位置,但是平均它们可以接近最优解。SWA后面采用固定学习率或者周期式学习率来寻找更多的次优解,最后平均接近最优解。图b和c说的是,训练误差和测试误差往往不对齐,也就是所说的模型泛化能力,那么平均的话是可以提升模型泛化性的。

在这里插入图片描述
在本次比赛中我只是简单地对最好模型进行恢复训练12e,并没有使用论文中提到的循环余弦退火学习率调整,最终的效果也不是很理想:

在这里插入图片描述
8.5 目标框加权融合-WBF(Weighted Boxes Fusion)论文地址
WBF算法的工作方式与NMS不同。它有点长,而且涉及到很多数学方程,这里只是简要介绍一下它的算法。(如果你想深入研究数学和低层次的细节,建议查看论文)

首先,它将所有边界框按照置信度分数的递减顺序进行排序。然后,它生成另一个可能的框“融合”(组合)列表,并尝试检查这些融合是否与原始框匹配。它通过检查IoU是否大于指定的阈值(iou_thr)来实现这一点。

然后,它使用一个公式来调整坐标和框列表中所有框的置信度分数。新的置信度仅仅是它被融合的所有框的平均置信度。新坐标以类似的方式融合(平均),除了坐标是加权的(意味着不是每个框在最终融合的框中都有相同的贡献)。权重的值由置信度决定,这是有意义的,因为较低的置信度可能表明预测不正确。(WBF效果演示图如下:)在这里插入图片描述

可以说,WBF已经成为优化目标检测的SOTA了(论文中有体现它的实际效果)基于它的特点我们可以使用WBF快速实现目标检测涨点,主要方法有以下几种:(代码实现在work文件夹下)

用WBF替代检测网络中的NMS方法:在这种方法下,导出模型时MultiClassNMS的nms_threshold应该设置为1.0,但是这意味着最优置信度阈值又得重新调……所以我在代码实现中并没有利用融合后的置信度,而是先通过置信度阈值过滤,对剩下的框进行WBF,这样做提升效果甚微。感兴趣的同学可以试试,先WBF再调整置信度阈值!
利用WBF实现测试增强TTA结果的融合:测试时增强(test time augmentation, TTA)可将准确率提高若干个百分点。这里会为原始图像造出多个不同版本,包括不同区域裁剪、更改缩放程度和水平翻转等,并将它们输入到模型中;然后对多个版本进行计算得到平均输出,作为图像的最终输出分数。 (这一点的代码我还没有实现)
利用WBF实现目标检测的多模型集成:多模型集成是指可以使用多种模型进行预测,最后结合多个模型的预测结果,其中我们可以选择多种不同网络的模型进行集成,也可以使用同种网络的不同训练方式的模型进行集成。由于我使用的是L模型,所以就无法使用这个trick了。有兴趣的同学可以用两个m模型或者其他模型尝试一下,需要设置的参数就是两个模型的权重weights和变量iou_thr。
9. 改进方案
添加分类矫正网络,修正分类结果。由于本次比赛采用的是One Stage的模型,需要同时兼顾分类和检测框,可能导致分类训练不充分,或者受其他因素影响,使得分类结果不准确。因此通过重新训练一个分类网络,可以矫正分类效果,提升分类置信度。
在这里插入图片描述
标签平滑:标签平滑其实是一种正则化策略,降低网络对标签置信度的依赖,这对有漏标、错标数据具有很好地适应性。

伪标签技术(Pseudo-Labelling):伪标记是将可靠的预测测试数据添加到训练数据的过程。可以分为以下5个步骤:

使用训练数据建立模型

用训练好的model对一批未标记图像进行预测,用最大置信度作为伪标记

最后将有标签和伪标签的数据一起进行finetune

使用新模型再次预测测试数据

重复以上几步,对模型迭代

值得一提的是:伪标签方法制作对于基础模型精度要求较高,当基础模型精度较低的时候,所标记的标签不准确,训练伪标签数据集效果不好,因此只有当模型检测精度达到一定程度时再加入伪标签技术才能有所提升。

最优置信度阈值自动寻优 :可以考虑写一个脚本——让模型在验证集上进行推理,将所有阈值大于0.35的结果保存,并采用网格搜索的方式在0.35-0.55的范围内以0.01的步长搜索每一类的置信度阈值,计算一次平均F1值结果并保留最佳结果,再通过微调这个阈值测试出最优置信度阈值。

误检问题优化:误检对象与正样本存在相似的特征,如将路灯误检为红绿灯,如下图所示:

在这里插入图片描述
图像中的误检目标实际是一个负样本,如果在训练过程中关于这个误检目标的负样本anchor并没有送到网络中,那网络自然也就学不到这个背景特征,而且负样本的数量往往很多,而送去学习的负样本却很少,所以如果采用随机抽样的方法进行选取,误检目标做为负样本被送进网络学习的概率很低。

因此,可以考虑将相应的负样本加入到训练数据集中——索性就选一些图,把这些伪目标(误识别的目标)抠出来填充到合理的位置上,组成新的训练集,送入模型进行训练。

  1. 工欲善其事,必先利其器
    合理利用一些辅助软件工具,可以让我们事半功倍!

10.1 VSCODE全局查询代码
利用VSCODE打开PaddleDetection文件夹,使用其查找功能全局查询你所需要了解的变量在这里插入图片描述
10.2 快速比较两个文件的不同点
利用软件Beyond Compare对两个文件进行比较,快速查看不同点:

在这里插入图片描述
10.3 共享文档
团队之间可以使用一个共享文档进行调参记录,如腾讯文档、石墨文档等,我们用的是飞书文档。在这里插入图片描述
10.4 VisualDL可视化调参
VisualDL 是一个面向深度学习任务设计的可视化工具。VisualDL 利用了丰富的图表来展示数据,用户可以更直观、清晰地查看数据的特征与变化趋势,有助于分析数据、及时发现错误,进而改进神经网络模型的设计。

使用VisualDL的Scalar功能对比分析本地训练日志:一行命令即可启动:

!visualdl service upload --logdir VisualDL/bs12/ VisualDL/bs28/
使用说明:visualdl service upload --logdir [训练日志目录所在的路径地址] [可设置多个路径实现多组实验对比]
分析经过VDL可视化处理后的训练日志
主要看mAP和loss

下图中,蓝色的是bs为28跑出的结果,绿色的是bs为12跑出的结果,通过对比生成的两条折线可以发现,适当增大batchsize能有效降低loss值。在这里插入图片描述
通过使用VisualDL的Scalar功能可以直观清晰地对比可视化处理后的多组训练日志,进而发现通过适当增大batchsize能有效改善模型训练结果(降低loss值),适当增大batchsize还能让模型训练过程更稳定。根据这一点能让我们接下来的调参更有方向,并不断积淀以形成我们的经验。

这个方法真的很实用,毕竟图比数字来得直观,还方便对比实验,从而实现有目的地调参!

  1. Talk is cheap. Show me the code
    所有代码均放在work文件夹下,使用说明都已放在代码的开头部分

文件/文件夹 功能
json_infoShow.py 显示json文件的信息
json_merge.py 融合两个json文件
json_split.py 将json文件按任意比例重新划分数据集
copy_paste.py Copy-Paste数据增强
create_swa_model.py 随机权重平均SWA
predict多线程.py 多线程图片预处理
predict多进程.py 多进程图片预测
predict多进程+多线程.py 多进程图片预测+多线程图片预处理
predict多进程+单模型WBF.py 单模型WBF
predict多进程+双模型WBF.py 双模型WBF
备份代码 本项目可能用到的代码
下面展示copy-paste数据增强的使用
In [ ]

运行之前需要安装必要的库

!pip install pycocotools
!pip install tqdm --upgrade
In [ ]

训练集和验证集数据解压缩至/home/aistudio/data/

这做的原因是方便后台任务

!unzip -oq /home/aistudio/data/data97737/detection.zip -d /home/aistudio/data/
In [ ]

copy-paste(大约要跑几分钟or十几分钟)

–muti_obj:True表示将src图片上的所有的特定目标都复制粘贴,False表示只随机粘贴一个目标,默认False

–coincide:True表示允许数据增强后的图像目标出现重叠,默认不允许有重叠区域

–category_id:需要进行copypaste的类别号,默认5,也就是黄灯

–copypaste_ratio:需要进行copypaste的概率(对所有图片来说)例如:设为0.45表示,json_path包含的所有图片中有45%会进行数据增强

%cd /home/aistudio/data/data97737/detection
!python /home/aistudio/work/copy_paste.py
–input_dir=/home/aistudio/data/data97737/detection/JPEGImages
–json_path=/home/aistudio/data/data97737/detection/train.json
–output_dir=/home/aistudio/data/data97737/detection/train.json
–muti_obj=True
–coincide=False
–category_id=5
–copypaste_ratio=0.45
/home/aistudio/data/data97737/detection
------------------------------------------------Args------------------------------------------------
input_dir = /home/aistudio/data/data97737/detection/JPEGImages
json_path = /home/aistudio/data/data97737/detection/train.json
output_dir = /home/aistudio/data/data97737/detection/train.json
coincide = False
muti_obj = True
category_id = 5
copypaste_ratio = 0.45
Args_show = True

---------------------------------------------Copy paste---------------------------------------------

loading annotations into memory…
Done (t=0.68s)
creating index…
index created!
100%|█████████████████████████████████████| 15414/15414 [10:53<00:00, 23.59it/s]
Successfully copy-paste!
12. 他山之石
COCO目标检测数据集工具

Copy-Paste 数据增强for 语义分割

多类别非极大值抑制(MultiClassNMS)API文档

数据科学竞赛:你从未见过的究极进化秘笈

参数详解之BATCH-SIZE的使用和取值

深度学习之目标检测

目标检测比赛中的tricks

  1. 总结
    本项目介绍了copy-paste数据增强、SWA、WBF、多进程预测的具体实现,同时也介绍了目标检测任务从数据集分析与处理→模型选择与训练→后处理优化等过程的细节。
    通过VisualDL对模型训练的可视化,有助于训练过程中的调参对比,帮助我们选择更适合的超参数(调参要讲究策略!)。
    关于调参我补充一点:一些超参数是没有必要进行太多调整的,比如激活函数我们现在基本上都是采用Relu,而momentum一般我们会选择0.9-0.95之间,weight decay我们一般会选择0.005, filter的个数为奇数,而dropout(dropblock)现在也是标配的存在。这些都是近年来论文中通用的数值,也是公认出好结果的搭配。
    不足之处:

没有把主要的时间花在数据分析处理上,例如数据清洗等,据说删除小的bounding-box(<10pixel)会有奇效。
由于时间问题并且考虑到各超参数组合起来的效果可能与每个超参数各自使用的效果不一样,所以就没有好好地做消融实验,让人感觉结果的说服力不强(毕竟不知道使用了一个trick后到底提升了多少)
调参的时候没有固定随机数种子,结果具有一定随机性。
当然,优化模型以及炼丹技巧还有很多值得大家去探索,这里我也不能将我的炼丹历程一一呈现出来,比如恢复训练、批量删除文件夹等tips(work_num调大可以加快训练速度)……不过,不管怎样,多尝试总会有意想不到的收获! 前面分享的只是我的一些心得和做法,还有很多很多“高级手段”在等着大家去发现,同时我也希望本项目能给大家带来一些启发,并运用到各自的实践中去。

本次比赛前前后后经历了几个月,这也是我第一次打目标检测的比赛,第一次使用PaddleDetection,花了不少时间尝试各种方法,在不断试错中慢慢改进,所幸最后取得了很好的成绩,证明了自己的努力没有白费。这几个月的各种专注、探索、脑洞,也让我学习到了很多新的东西,这是课堂上老师教不会的,也远比西瓜书上的知识来得直观且有效。所以,很感谢本次比赛,也很感谢所有给我宝贵建议的大佬( 小U、 七七神话、 喜乐多多多、 张牙舞爪 等等),同时也要感谢百度的PaddlePaddle、PaddleDetection等框架,可以让我的想法快速得到实现,也才能有我现在的成绩。

最后的最后,求关注!求Fork!求❤!,同时预祝大家线下赛都能取得满意的成绩🤭

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐