当前位置: 首页 > 科技观察

AI算法工程师的一些泪目经历

时间:2023-03-14 10:52:51 科技观察

前段时间一直在优化部署模型。这几天需求终于来了,又到了开始训练一些新模型的时候了。借此机会总结一下之前训练模型的一些笔记,可能比较复杂,抛砖引玉吧!当然,这是一个不完整的统计经验。除了训练部分,还有很多部署坑没写。训练模型阶段1.算法工程师50%的时间都在和数据打交道,有时还会复制数据(从多个文件夹复制到某个文件夹);有时筛选数据(过滤掉一些质量较差的数据);有时更改数据的名称,添加前缀(为了在后续训练时区分数据的特征,例如多尺度,多种图像增强策略)等。这些任务可能一个月重复n次,所以最好总结一下;可以使用Python或者shell脚本来处理,也可以使用jupyternotebook保存自己常用的文件处理代码。2、如果不知道数据的来源和可靠性,可以先用find./-size-1k-execrm{}\等命令过滤。刚才的命令扫描1k以下的数据(或其他值)损坏图像并删除,当然你也可以设置其他参数。在很多情况下,给您的图片不一定是正常图片。最好提前筛选一下,不然后续处理起来会很麻烦。3.并非所有数据都有标注信息。如果采集了一批数据需要标注,公司也有人力进行标注。你可以尝试用一个预先标记的框来标记这批数据,让他们调整或补充框。这样效率更高。至于如何做预标记框,可以让模型先训练一小批数据,训练一个召回率高的小模型,然后预测框框。也可以用一些旧款来装框;人员标注时,对于有预标注框的数据,标注质量已经变差。虽然速度提高了,但这是因为大多数标注者不想调整。这时候就需要细心监督了,不然后面的模型准确率上不去,大概率是数据有问题。4.有时模型的指标不仅准确。模型对外提供服务时,取决于PM如何看待模型输出结果在实际场景中的使用效果;对于检测模型最终输出的分数,最终给用户的框一般是根据你得到的分数阈值来设置的。设得低,箱子多(召回率高),设得高,箱子少(准确率高);不同的方法在不同的场景下设置不同的阈值会有不同的效果。说白了,模型的效果很大一部分取决于场景;这种情况在实际项目中其实是比较常见的。说白了,无论是loss还是accuracy,都是非常片面和脆弱的评价指标。这与评估的模型结构和数据分布有很大关系。如何选择模型应该与应用场景强相关。5.当模型遇到badcase时,简单的增加模型的容量可能效果不佳;因为badcase极有可能是和场景强相关的,这种情况下最好收集badcase,可能会有人用你的模型给你提供badcase,但是这个效率比较低,取决于提供者的情绪或紧迫性;可以直接收集大量模型使用场景的查询,使用当前模型进行检测,收集对应类别中置信度相对较低的案例。然后选择它;6.测试集很重要。测试集一般不是从训练集中切出来的,而是验证集是从训练集中切出来的;验证集一般用来判断模型是否过拟合。训练很疯狂。如果要用验证集来判断模型好坏,往往不能代表模型的实际水平;最好有测试集,测试集和模型集合batch不同。在训练模型时,它更接近现实。评价标准的水平;如果没有测试集,也可以查看训练集的loss来大致判断。一般来说,只要不是demo级别的场景,模型是不会轻易过拟合的。我们的训练集通常有大量的图像增强策略,每个epoch可能会有不同的imagedistribution,这时候其实可以选择模型model_last。7.同样,loss和accuracy的关系不是完全成正比的。亏损波动是正常的。低损失并不一定意味着模型的mAP高;反之,如果loss变高了,不一定是模型的准确率差,可能是loss设置的不够好,以至于一部分上升占主导地位,掩盖另一部分正常是正常的衰退;相关讨论:https://github.com/thegregyang/LossUpAccUp和https://www.zhihu.com/question/3183994188。计算检测模型的mAP,在实际计算中不考虑目标帧得分的阈值,也就是说我们会把所有得分大于0的检测帧都送来计算mAP;但这里需要注意的是,mAP的计算中有max_num是最大检测目标数。根据任务要求,可能是100、500、5000等,当有这个限制时,此时需要根据分数对箱子进行排序,取前100、前500或前5000帧计算;最后,如果我们需要在图上对可视化结果进行分框,此时可以卡住阈值,比如score阈值大于0.2,否则最终的图会出现很多断框;最后,别忘了NMS!9.测试转换后的模型是否正确,必须保证输入图片一致;这里的一致性是指输入图像的值必须完全相同,dif为0;一般来说,我们输入模型的图像范围是0-1,通道数一般是颜色,也就是RGB,但是需要注意颜色是否为假色(有时为了节省传输中的资源,实际推理时会传输一张灰度图,然后转成彩色图。颜色和真彩色的精度相差不大),输入大小也要一致,是否需要padding(padding为0或127或255,这些填充方式对结果影响很大),需要填充成32的倍数,或者需要限制最大边和最小边,一定要保持一致;对于类别,测试模型可以保证准确性。10.对于模型,如果以后考虑上线。上线方式有很多种:可以直接用pytorch+flask上线,也可以尝试用libtorch上线,也可以用TensorRT上线,当然也可以通过自研框架上线。..ETC。如果这个模型追求精度,离线运行一定时间,不是实时的,可以试试flask+docker服务;如果这个模型实时性高,那么在设计模型的时候就必须考虑后续的上线。您需要考虑模型优化和相应的服务器推理框架。你可以试试TensorRT+tritonserver;部署1.再次强调训练集、验证集和测试集在训练模型中的实际作用:训练集相当于老师布置的作业,验证集相当于模拟试卷,测试集相当于试卷。做完作业直接拿试卷,估计大概率考不好,然后再调参数什么的,最后真正考的时候就可以通过了;训练集的一部分可以拆分成验证集,但是测试集一定不能再从训练集中取出来,因为我们要保证测试集的“未知”性;虽然验证集会不直接参与训练,我们还是会根据验证集的表现调整模型的一些超参数,其实这也可以看作是“学习”了验证集的知识;和验证集一样,它参与在“各种形式”的训练中,否则就是信息泄漏。我们使用测试集作为泛化误差的近似,所以直到最后我们都不能泄漏关于测试集的信息。2.数据的质量直接影响到模型质量;在数据量大的初始阶段,可以通过改变一开始的模型结构,增加一些attention,增加一些DCN,增强backbone,或者使用其他巧妙的struc来提高模型的准确率tures可以提高最终的精度。但是后期如果想提高模型的泛化能力,就需要增加训练数据。为什么?因为此时你的大部分badcase都不在训练集中,模型肯定是看不到badcase就学不会的。这时候就需要有针对性地补充badcase;如果badcase不容易补充怎么办?这时候图像的生成就很重要了。如何生成badcase场景的训练集图,生成数据的好坏直接影响模型的最终效果;此外,图像增强也非常非常重要。我们要做的就是让数据在图像增强后的分布接近测试集的分布。说白了,就是通过图像生成和图像增强两种技术来模拟实际场景。3、当有两个数据集A和B时,A有类别a和b,但只有a的GTbox;B也有类别a和b,但是只有b的GTbox,显然这个数据集不能直接使用(没有GTbox的A和b在训练时会被当作背景),你的模型需要训练来检测a和b框同时出现,怎么办?四种方法:1.分别训练检测a和b的模型,然后在另一个数据集上做预测,帮助标注,控制分数阈值,做完新的数据集后训练模型;2.使用distillation的方法,同样训练分别检测a和检测b的模型,然后使用这两个模型的soft-label训练新模型;3.修改损失。一般来说,我们的损失函数也会对负样本(也就是背景)进行反向传播,这也是有损失的反向传播。这里我们修改为,如果当前图片没有a类的GT帧,我们直接将a的loss设置为0,让这个类别通道不进行反向传播,这样就可以训练没有a的图片了frame,而模型不会把a当作背景,因为模型“连a都不看,也不知道a是什么”。你可以想一想模型经过最后的训练会是什么样子呢?4.模型的最后一部分,头部分离,一个负责检测a,另一个负责检测b。这时,模型的骨干就变成了特征提取器。4.在工作中,有很多场景需要使用旧模型为需要训练的新模型过滤数据。例如,使用训练好的检测模型A,选择类别为a的图片,供新模型训练。这时候就需要构建一个小服务来实现这个过程;当然,你也可以打开你旧模型的python库代码,然后回想一下找到之前的demo.py和一些对应的参数;显然这样比较麻烦,最好总结一下之前的模型,随时搭建一个小服务供内部使用,因为别人也可能需要用你的模型来挑数据,怎么搭建一个小服务呢?直接用flask+Pytorch就可以了,但是这个qps请求在请求大的时候会卡顿,但是毕竟只是过滤数据,可以适当降低一些qps,离线请求一夜之间就可以完成。5、目前比较好用的目标检测框架无外乎那些经典的,用的人多,资源多,部署方便。毕竟我们训练模型的最终目标是上线;单阶段系列包括SSD、yolov2-v5系列、FCOS、CenterNet系列、Cornernet等单阶段系列,其中二阶段的faster-rcnn实现了很多次,mask-rcnn也实现了许多人;而最新的DETR使用了transformer结构的检测框架,都可以使用TensorRT进行部署;其实实用的无非就是看速度和准确度如何,是否支持动态维度;但是跑最好的分数不一定对你的数据好。你必须根据数据集的特点来选择模型。对于您自己的数据集,效果可能会有所不同。需要自己拉下来运行;相关模型TensorRT部署资源:https://github.com/grimoire/mmdetection-to-tensorrt和https://github.com/wang-xinyu/tensorrtx6。换句话说,其实很多模型最终都会落地。第一个难点在于模型是否用过我做过;如果有人已经做了并且开源了,那就直接复制粘贴修改即可。有陷阱,是别人帮你踩的;如果没有开源代码可以借鉴,那就需要自己来了!首先检查这个模型的backbone是否有特殊的op(比如dcn,比如senet,当然这两个已经支持了),结构是否特殊(不仅仅是普通的卷积组合,还有各种reshape,roll,window-shift)等特殊操作),后期处理复杂吗?我已经转换了最复杂的模型。backbone有自定义op,需要自己实现。另外,这个模型有很多的后处理,有一部分后处理会参与训练,也就是有学习到的参数,但是这个后处理的一些操作不能转成trt或者其他框架(有些不支持操作),所以这个模型只能拆成两部分,一部分用TensorRT实现,一部分用libtorc实现;其实大部分模型都可以部署,只是难度不同,只要你肯多想,总会有办法的。7、转换后的模型,无论是从Pytorch->onnx还是onnx->TensorRT还是tensorflow->TFLITE,转换前的模型和转换后的模型,虽然参数相同,结构相同,但是同样的输入,输出不可能是完全一样的。当然,如果你的输出精度卡在4位小数,应该是一样的,但是5、6、7位小数不能完全一样,转换本身也不能无损;比如一个检测模型A,用Pytorch训练,然后有一个模型A`转为TensorRT。这两个是同一个模型,转换后的TensorRT也是FP32精度。可以输入一个随机数,求出两个模型的输出对比,绝对误差和相对误差在1e-4的基准下都是0,但是当你用这两个模型测试的时候,保持一切一致(input,post-processing等),最终的检测框与高分基本一致。分数低的(比如小于0.1或者0.2)会有一些差异,边缘的hardcase也会不一致;当然,这种情况一般影响不大,但需要注意。8.模型的理论flops与实际模型执行速度关系不大。这取决于具体的执行平台。不要盲目认为flops低的模型就快。很多开源检测库都是直接在Pytorch上运行的,用于对比。虽然都是GPU,但这其实并没有优化,因为Pytorch是动态图;一般的模型部署都会涉及大大小小的优化,比如计算子融合和计算图优化最简单的例子就是CONV+BN的优化。许多基于Pytorch的模型速度比较忽略了这一点。当我们比较两种模型的速度时,最好使用实际要部署的框架和平台。比较;但是如果这个模型中有很多参数,模型很可能会很慢。原因很简单。大多数参数一般是卷积核参数和全连接参数。这些参数自然意味着有很多op操作。自然会慢。9、同一个TensorRT模型(或者Pytorch,或者其他使用GPU运行的模型)运行在同一个模型卡上,可能是因为cuda、cudnn、驱动等版本不同,或者硬件功耗墙设置显卡(P0、P1、P2)不同,或者系统版本/内核版本不同,导致速度有差异。这个区别有大有小。我见过最大速度相差70%,不知道为什么模型速度不一致。在这种情况下,您不妨考虑这些原因。10.转换待部署模型后,一般需要测试模型的速度和吞吐量;速度可以直接跑循环推理取平均值,但实际吞吐量需要模拟数据传输、模型执行和排队的时间;一般来说,模型的吞吐量可以简单地用1000/xx来计算,其中xx是模型执行的毫秒数。但是,如果某些任务的输入图像特别大,那么这是不行的。我们需要考虑实际的图片传输时间,是否是本地的,是否跨网段等。