当前流行的目标检测算法主要分为三种,一阶段算法:SSD,FCOS,Scaled,YOLO系列等;二阶段算法:Faster-RCNN,Mask R-CNN,Cascade R-CNN等;Transformer系列算法:DETR,Deformable-DETR,DINO,Swin Transformer等等。其中优秀的算法性能都比较相近,Transformer系列的算法在渐渐崛起的时候已经超越了传统的卷积神经网络,代价是需要更好的预训练和更大的模型和更长训练推理时间,二阶段的算法精度也相对较高,但是最近一阶段的算法,尤其是YOLO系列,有很多优秀的工作也在COCO上达到了很强的性能,比如2022年提出的YOLOv7,YOLOv6,RTMDet,DAMO-YOLO等以及2023年提出的YOLOv8算法。YOLO系列一直是实时目标检测的代名词,他们在保持精度的同时可以达到一个很快的速度,基于决赛的分数评判标准,我们选择了性能和泛化性都相对较好的YOLOv5算法作为我们的baseline算法展开我们的后续研究。
我们参考了TPH-YOLOv5的思想,他们的算法在VisDrone Challenge 2021竞赛中获奖,数据集为针对无人机捕获场景,包含很多尺度变化大的物体和小物体,最简单的结论是适当提高训练的图像分辨率可以达到很好的性能提升。我们观察了人车目标检测数据集,发现小目标确实很多,所以一开始基于速度性能综合考虑,我们初步选择了YOLOv5M-P6模型,并使用1280尺寸训练和推理。同时预想到了以下两点:第一,1280训练推理无疑会带来更大的推理延时,可能造成不利影响,后续可以考虑减少尺度,使用960训练和推理。第二,优先使用coco预训练模型可以显著提升性能,但是如果对模型进行改进,就无法完整加载预训练权重,导致性能损失。我们决定对模型做出改进后使用额外的人车数据集进行预训练,然后使用比赛数据集迁移学习。
Fig.1. 不同基线模型的性能延时比较
如图一所示,我们针对初步思路进行了实验,以便于重新确认baseline模型,发现了训练尺度和模型大小对MAP和延时的关系。
第一步使用YOLOv5M-P6,1280训练和推理,我们得到了最高的80%MAP,但是延时达到了236ms,导致最终成绩只有156.4。
第二步,为了提高推理速度我们试图将传统YOLOv5中的Silu激活函数替换为Relu激活函数,发现MAP掉了2个点,但是速度提升了20%多,最终成绩反而更高。
第三步,我们尝试了使用参数量和GFLOPs更小的YOLOv5s-P6模型进行实验,还是采用1280的训练推理尺度,MAP只掉了2个点,但是延时继续提高了20%左右,达到了更高的成绩。
第四步,我们采用960训练和推理尺度,MAP只下降1个点,速度缩减到了80ms。
最终我们采用了YOLOv5s模型,960训练推理,并将所有激活函数替换为Relu,达到了最高成绩。通过初步的实验,我们得到了以下结论:
综合考虑性能和速度,小模型相比大模型,精度略有下降,但是速度提升很多,最终的分数可能更高,使用小模型是更优选择。
最终成绩的计算公式为score=mAP*100+(1000-i_time)*0.1,实验中大模型和小模型MAP仅相差2个点,性能带来的score差距为2,但是延时相差了100ms,延时带来的score差距为10,所以得出结论:提升速度带来的收益在一定程度上大于MAP提升的收益。
使用960训练推理为同时提升性能和速度的折衷方案。
使用 relu激活函数替换silu激活函数可以加快部署的推理速度。
DAMO-YOLO是2022年阿里达摩院提出的YOLO系列的算法改进工作,其中比较有意思的一点就是他们针对于YOLO的neck改进的RepGFPN。最初的想法是GFPN,作者认为GFPN有效的主要原因之一是因为它可以充分交换高级语义信息和低级空间信息。在GFPN中,多尺度特征在前一层和当前层的层次特征中都被融合。更重要的是,log2(n)跳过层连接提供了更有效的信息传输,可以扩展到更深层次的网络。他们发现在现代yolo系列模型上用GFPN直接替换原先的Neck结构后,可以获得了更高的精度。问题在于基于GFPN的模型的延迟远远高于基于改进的panet的模型,所以精度的提升或许有些得不偿失。然后提出一种新颖的高效—RepGFPN来满足实时目标检测的设计。
如图二所示,RepGFPN允许检测网络在网络早期阶段就以相同优先级处理高层语义信息和低层空间信息,使其在检测任务上更加有效。其中Fusion融合模块的输入通常是两到三个语义信息,融合模块参考了经典的CSP思想,YOLOv6中的RepCov重参数化卷积思想以及YOLOv7中ELAN特征聚合网络的思想,它由两个1x1卷积和N个RepConv和普通3x3卷积组成。达到了比传统YOLO中的PANnet更优秀的性能。
Fig.3. DAMO-YOLO融合模块的消融实验
我们在比赛中对RepGFPN做出了改进,首先我们发现代码中的RepGFPN结构与论文中的图片有所出入,论文中有6个融合模块,但是代码中只有5个。询问作者后得知,因为右下角的模块只有一个输入,所以是否用融合模块差距不大,所以他们去除了这个模块,直接将上层的信息做输出。为了改善这一问题,如图4所示,我们扩展了RepGFPN 模块,并增加了两个额外连接。经在coco数据集实验,该操作可以有效提升0.8MAP。同时考虑到RepConv重参数化不利于部署,可能对延时产生不利影响,我们去掉了结构中的RepConv,替换为普通的3X3卷积,最终组成GFPN+结构加入到yolov5s中。最终的算法结构图如图5所示。
Fig.4. 改进的GFPN+结构
Fig.5. YOLOv5s_GFPN+模型结构图
我们替换了YOLOv5s的neck结构,因此只有主干部分可以加载coco的预训练权重。为了解决这个问题,我们找到了额外数据集BDD100k,由加州大学伯克利分校AI实验室(BAIR)公开,包含十个类别。
Fig.6. BDD100k类别
为了和比赛数据集对应,我们先将数据集标注进行转化,将吧bdd100k中的bus,truck,car类别合并为car类别,将person和rider合为person类别,将bike,motor合为bike类别。在使用额外数据集的时候我们观察到了一个问题,比赛数据集的标注和bdd100k存在出入,如图7所示。比赛数据集,一个骑车的人由三个框组成,包含一个bike框,一个person框,整体为一个bike框,bdd100k中一个骑车的人由两个框组成,包含一个bike框,一个person框,我们尝试自己融合标签,使用了人和自行车的最大外接矩形合成一个大的框来解决,最终由于时间关系,重叠问题,数据集比例不均衡。融合算法还有问题没有采用,所以我们只采用bdd100k进行预训练,没有直接加入训练集中一起训练。
Fig.7. 左图为比赛数据集标注,右图为bdd100k标注
我们选择模型为YOLOv5s_GFPN+_relu激活函数,训练推理尺寸为960。第一阶段:先使用4669张bdd100k转化图像进行训练,593张作为验证集,使用仅加载主干网络的coco预训练权重,训练50轮。第二阶段:使用2592张比赛数据集训练,293张作为验证集使用bdd100k的预训练权重,训练100轮,用最终的best权重作为最终的推理权重。最终模型的参数量,GFLOPs,MAP,Latency,score如下图所示。
如下图所示,报错提示输出Tuple问题,经过修改源码中模型 Detect 层输出后仍然无效。解决方案:降级 PyTorch 版本到 1.8.1 后导出 TorchScript 正常。
Fig.9. PyTorch 1.13 转换的 TorchScript 无法通过编译
如下图所示,依据报错提示无法定位异常位置,无法解决问题。
Fig.10. 使用 `torch.utils.mobile_optimizer.optimize_for_mobile` API 优化的 TorchScript 模型无法通过编译
再次转换时报错 torch.permute 接口不存在,是由于 PyTorch 1.8.1 没有这个接口,修改下图部分代码可以解决。
Fig.11. 使用 `PNNX` 重新编译优化 TorchScript 然后再导出优化后的 TorchScript
1. 将后处理从模型移除,单独实现后处理代码。
·后处理时提前通过置信度筛选候选框,然后再对候选框解码,避免多余的计算。
·实现了较简单的类间 NMS,通过类别将不同类框隔离到不同空间然后进行整体运算。
·应用了 OpenCV 最新的 API cv2.dnn.NMSBoxesBatched 加速类间 NMS 计算。
·使用 PyBind11 调用 C++ 进行后处理计算 (加速效果不理想)。
·尝试将 letterbox 预处理的缩放和填充的还原操作固定在模型最后,减少 CPU 后处理运算。
2. 前处理融合到模型中。
·尝试将 Resize 算子集成到模型中,由于数据类型限制,实验没有完成。
·将 Normalize 集成到模型中,通过 除法(÷255) 和乘法(÷255)分别实现和测试,影响速度较小。
·将通道转换集成到模型中,BGR->RGB 通过 Gather 算子实现重排通道。
·通过修改第一层卷积的权重实现 Normalize 和 通道转换,会影响 INT8 量化精度。
·尝试使用 BMCV 实现前处理的 Resize 和 Padding,测试环境运行失败。
3. 模型部分算子调整。
4. 自定义模块中 Conv + BN 没有完全融合,在 PyTorch 层面单独融合这些层,然后导出。
5. 后处理中的 Sigmoid 算子考虑还原到模型中运算,CPU 版本的 Sigmoid 耗时比较大。
6. 尝试将 NMS 算子注册到 ONNX 然后转换 bmodel,犹豫时间原因仅做了 ONNX 转换。
7. 使用 bmnetp 转换模型,ufw.cali.cali_model 量化模型。
8. bmnetp 转换模型时 --dyn=False 关闭动态性状优化,--opt=2 开启最高优化等级。
9. ufw.cali.cali_model 调整前处理中 scales 和 bgr2rgb,这部分已经集成到模型。
10. 模型推理预热,防止模型前几次推理速度较慢,避免模型激活值内存开辟等模型运行开销。在正式推理前,提前推理10次随机噪声,让模型推理速度稳定。
经过本次的竞赛,我们在算法中找到了兼顾性能和速度的优化技巧,比如使用960尺度进行训练和推理,使用relu激活函数加快部署的推理速度,去除RepConv重参数化卷积方便量化,小模型综合成绩更优,最终成功在YOLOv5中融入RepGFPN的思想,在提升精度的同时没有产生很大的延时。在算能的TPU平台完成部署和量化,也熟悉了SDK端口的使用,取得了很大进步,还发现很多优化速度的trick,自我也得到了很大的提升。
我们同样存在不足之处:比如没有完美优化bdd100k数据集,否则可以直接加入训练集中一起训练。因为时间原因,没有对新模型进行深入的优化和实验,MAP还有很大的提升空间。我们还在YOLOv6上进行实验,发现收敛速度很快且验证集精度很高,但是在实际测试集上效果一般。在部署的时候我们也尝试了int8量化,多次提交结果之后,发现精度损失比较严重,速度提升在10%左右,基于score的权衡我们还是采用了fp32的推理结果。第二点是我们在TPU推理的时候采用了batch1推理,如果采用大batch推理,可能可以获得更快的速度。