资讯详情

资讯详情

图像分类项目实战:从模型训练到推理加速的完整流程

图像分类是深度学习里最“老”的任务之一但真把它从论文搬到实际项目里一点不比那些新模型省心。前阵子我完整做了一个图像分类项目自己建数据、选模型结构、调训练参数、走部署流程最后把单类图片的推理时间压到几十毫秒分类准确率也稳定提到了98%上下。整个过程踩坑不少但也把很多原本“只可意会”的东西摸明白了。这篇文章就直接拿这个项目当例子把从零到落地的全流程拆开讲。内容包括方案选型、数据处理、模型训练、性能优化、问题排查这些环节每一步我都会解释当时为什么这么做、实际效果怎么样、坑在哪里。无论你是正准备做自己的第一个图像分类项目还是已经跑通了一个模型但卡在效果和速度上这篇都值得花点时间看完。1. 项目整体设计与方案选型1.1 需求拆解这个图像分类项目到底要解决什么问题项目目标说起来很朴素给一批植物叶部病害图像做自动分类一共有24个类别每个类别大概四五百张样本要求模型在验证集上的准确率不低于95%同时单张图片的推理耗时控制在100毫秒以内。如果只看准确率这个任务在学术界早就“饱和”了但放到实际场景里问题就多了样本量少、类别不均衡、很多病害在视觉上极其相似而且最终要部署到普通服务器上而不是实验室那几块显卡上跑着玩。所以我在技术选型之前先列了几个硬约束数据规模有限不能选那种需要海量数据才能喂饱的超大模型推理端的环境资源固定没有GPU加速卡只能用CPU跑整个项目周期两到三周不能花大量时间在魔改网络结构上分类结果需要能解释否则业务方不敢直接用。这些约束直接决定了后面的每一个选择。说白了这类项目真正难的不是“用深度学习做分类”这个动作而是如何在有限数据和有限算力的双重限制下把模型做到又快又准又可靠。想明白这一点方案就好选多了。1.2 网络结构选型为什么选择了ResNet这条路线网络结构的选择是我最先定下来的。当时考虑了三条路线一是经典的VGG系列二是ResNet系列三是MobileNet、EfficientNet这类轻量化结构。VGG结构简单、实现容易但参数量大得离谱哪怕是VGG16模型文件就接近500MB训练和推理都慢。在CPU推理的约束下基本可以直接排除。MobileNet和EfficientNet虽然推理速度快、参数少但在小数据集上容易欠拟合而且它们的性能上限受限于结构设计后期想进一步调优的空间比较小。最后选了ResNet50。原因有三第一ResNet的残差结构解决了深层网络的退化问题50层这个深度在中等规模数据集上表现出稳定的优势第二它有一整套成熟的预训练权重可以用迁移学习大幅缩短训练时间第三ResNet在工程化方面非常成熟部署链路的坑少后续做剪枝、量化都有大量现成方案可以参考。提醒一句选模型不要只看排行榜上的精度数字还得看你的部署环境。我见过有人用Swin Transformer刷到了99%的准确率结果发现推理环境根本跑不动最后只能换回CNN前面时间全白费了。1.3 训练框架与硬件环境先想清楚全流程再搭台子训练框架我选了当前应用最广的深度学习框架配套的工具链也比较完整。选择标准主要看四块生态是否成熟遇到问题能不能搜到解决方案从训练到导出的链路是否顺畅能否方便地导出为通用推理格式社区教程和预训练模型是否丰富团队里其他人是否熟悉不能我一个人会用就定下来后面交接协作会很难受。硬件环境方面训练用一张普通的中端图形计算卡就够跑了显存占用在混合精度加梯度累积的控制下没有超出太多。推理性能优化部分我在另外一台CPU服务器上完成验证了模型脱离显卡后的实际表现。这个阶段我还做了一件容易被忽略的事把整个项目流程从数据到训练再到推理的各个节点全部列了出来标清楚每个节点的输入输出。后面所有工作都是按这个流程推着走的不会出现做到一半发现少了个关键步骤的情况。2. 数据准备与预处理实战2.1 原始数据清洗别急着喂给模型先做人眼检查很多人拿到数据就开训练结果模型效果差、怎么调都不收敛回头才发现是数据本身就有问题。这个项目在数据清洗上花了整整一天时间现在看来非常值得。清洗主要做了三件事。第一是去重用感知哈希算法找出重复和近似重复的图片避免相同或几乎相同的图片同时出现在训练集和验证集造成虚假的高准确率。第二是纠正标注错误24类数据里有大概3%的错标样本比如两种病害形态相似标注的人搞混了这类样本不处理就是给模型种“毒”。第三是剔除低质量图像包括严重过曝、对焦模糊、遮挡面积过大的图片这些图连人眼都难分辨模型更不可能学到有效特征。清洗完成后我做了一次类别分布统计发现其中两个病害类别的样本数只有其他类别的一半左右。这就是典型的长尾分布如果不处理模型会对样本少的类别严重欠拟合分类结果会明显偏向样本多的类。2.2 数据增强策略有限样本下的容量扩充方案数据增强是这个小数据集项目能不能做成的关键之一。我的增强策略分为两层。第一层是基础几何和颜色增强包括随机水平翻转、随机旋转15度、随机裁剪缩放、颜色抖动、亮度和对比度调整。这些增强方式能让模型对拍摄角度、光照变化不那么敏感。需要注意的是增强强度不能太大比如旋转角度超过30度后很多叶子形态会产生变形反而不利于学习真实特征。第二层是输入混合类增强用了MixUp和CutMix两种方法。MixUp把两张训练图按比例混合标签也按同样比例混合CutMix则是把一张图的某个区域切下来贴到另一张图上标签按面积比例混合。这两种方法的共同作用是让模型学到更平滑的决策边界在小数据集上能有效抑制过拟合。实际跑下来MixUp和CutMix组合使用比单用任何一种效果都更好。数据增强还要注意在验证集上不能使用否则验证结果会失真。我们只在训练阶段做增强验证阶段只做缩放和中心裁剪。2.3 数据加载的效率优化别让GPU等你读图训练过程中如果GPU利用率上不去先别急着怪模型复杂大概率是数据加载拖了后腿。我在项目里做了三个针对性优化。第一使用多进程数据加载把图片读取和增强操作从主进程拆出去默认线程数调成CPU核心数的一半让数据准备的并行度和主训练互补卡顿。第二开启锁页内存加快数据从内存向显存拷贝的速度。第三把增强后的中间数据做了本地缓存一轮迭代之后后续每个epoch就不再重复做同样的增强计算直接读取缓存结果整体训练时间缩短了近30%。这里想多说一句数据管道的优化往往随手就能带来几倍收益这对新手来说比调模型结构更快见效。我见过不少人在网络上反复调参结果连GPU利用率都只有30%花在数据加载上的时间比训练还长。3. 模型训练与核心算法实现3.1 迁移学习与损失函数站在预训练模型肩膀上起步项目最开始我用的是在大型通用数据集上预训练好的ResNet50权重。迁移学习的思路很简单让模型先具备通用的图像特征提取能力再针对我们的病害数据集做微调。具体实现上我把ResNet50的最后一个全连接分类层替换成适配24类的输出前几层卷积的参数先冻结不动只训练后面的层。等训练几个epoch后再解冻全部参数做全量微调。这个策略能避免在数据量不足时把预训练学好的底层特征给破坏掉。损失函数选择上主损失用的交叉熵然后叠加了标签平滑。标签平滑的意思是不再让模型用1和0这种硬标签去拟合而是把正类的目标值从1降为0.9左右剩下0.1分摊到其他类别上。这么做的效果是防止模型在训练集上过度自信对提升泛化能力有明显帮助。在小数据集上我发现加了标签平滑后验证集的准确率普遍提升了1到2个百分点。3.2 优化器与学习率设置那些会被忽略的临界参数优化器我选了带动量的随机梯度下降SGD动量设0.9权重衰减设5e-4。很多人默认用Adam但在这个项目里SGD配合合适的学习率调度效果更好。原因是SGD对学习率的敏感度更高但配合合理的调度策略后泛化性能通常优于自适应优化器。如果一定要用Adam类优化器建议配合权重衰减使用AdamW效果比Adam更稳定。学习率方面使用了warmup加余弦退火的组合。前5个epoch做线性warmup让学习率从很小的值慢慢升到初始值0.01。这么做是因为模型在刚开始训练时参数是乱的一上来就用大学习率容易把预训练权重冲坏等参数稳定了再用大学习率快速收敛。之后学习率按余弦曲线衰减前期快速下降、后期缓慢逼近零这样有利于在训练后期找到更平坦的极小值点。实际观察中学习率设在0.01附近时训练最稳定。设0.1的话训练初期损失会剧烈震荡设0.001的话收敛速度又太慢导致在有限的训练轮数内无法充分收敛。注意如果有多个GPU做分布式训练学习率需要按GPU数量线性放大。比如单卡用0.014卡就可以用0.04。这个细节很多人踩坑导致多卡训练效果反而不如单卡。3.3 训练监控与调优迭代用数据说话别靠感觉训练过程我做了比较完整的监控。除了常规的训练损失和验证准确率还记录了每个类别的准确率、精确率、召回率和F1值。只看总体准确率容易掩盖问题可能整体96%但某两个相似病害类别只有70%这种模型是不能上线用的。训练到一半时我还用了类激活映射方法做了可视化分析把模型分类时聚焦的图像区域展示出来。这个方法在调试时特别好用——如果模型分类某张叶子图片靠的是背景里的花盆而不是叶子本身类激活映射图能一眼看出来。这类问题在数据量小的时候特别常见模型会找数据里的“捷径”而不是学习真正的语义特征。第一个版本训练到第40个epoch左右验证准确率稳定在93%左右距离95%的目标还有差距。这时我没有盲目追求加训练轮数而是回头检查数据、增强策略和模型结构找到问题逐个击破。4. 性能优化实战准确率从93%到98%4.1 找瓶颈先问一句“慢在哪、差在哪”再动手很多人一上来就想着换模型、加数据、改结构结果折腾半天效果还是不行。我的习惯是先做诊断把问题量化出来。当时整体准确率93.2%我专门统计了每一类的准确率发现最差的是两类叶霉病和早疫病准确率一个80%一个82%。这两个病害在视觉上确实高度相似都是叶片上长斑块区别只在于斑块的颜色和分布细节。推理耗时方面也做了详细分析单张图片在CPU上推理耗时约220毫秒其中模型计算占了85%数据预处理和格式转换占了15%。所以优化方向就很清楚了一是提高这两个困难类别上的分类能力二是把模型计算时间砍下来。4.2 准确率优化数据重平衡与损失函数微调针对长尾分布我增加了困难类别在训练中的采样权重让模型在每一个batch里都能看到足够多这类样本。具体做法是使用加权采样样本少的类别被抽中的概率更高。第二个是针对两个易混淆类别我重新检查了它们的标注数据发现又挑出了十来个错标样本清洗后准确率立刻提升了一点。第三个优化是调整损失函数在交叉熵基础上叠加了中心损失。中心损失的核心思路是让同一类样本的特征向量尽可能靠近类中心这样天然就能让相似类别的特征分布拉开距离。加了中心损失之后叶霉病和早疫病的准确率分别涨到了91%和93%整体准确率到了95.4%。这个阶段的教训是当遇到整体准确率上不去时优先看最差的几个类别多数情况下全局问题是由少数困难类别拖累的解决它们往往比整体重新调参更有效。4.3 推理加速剪枝、算子和量化三板斧模型准确率达标后重点转向推理耗时优化。我的优化思路是分三步走。第一步是结构化剪枝。把模型中贡献不大、权重范数较小的卷积通道剪掉减少计算量。剪枝比例从20%开始逐步试探到40%后发现准确率还好但再往上剪性能掉得有点快就保持在35%。剪枝后的模型需要重新微调几个epoch让剩余通道适应新的特征表达能力。第二步是算子融合和格式转换。把模型的BN层和卷积层合并减少计算过程中的中间张量操作。同时把训练格式的模型文件导出为更高效的推理格式这个转换过程本身就能带来30%以上的加速收益因为推理引擎会做大量的计算图优化。第三步是量化。把模型权重从32位浮点数压缩到8位整数推理时的矩阵乘法会大幅提速。量化之后我在验证集上对比了精度差异准确率从95.4%微降到94.9%损失不到一个点。对绝大多数场景来说这点精度损失换来推理耗时减少60%以上实在是太值了。优化完后的推理耗时从220毫秒降到了75毫秒超过预期的100毫秒目标。整套优化流程我整理成了一张对比表模型版本验证准确率单图推理耗时模型大小原始训练模型95.4%220ms98MB剪枝算子融合95.1%130ms64MB剪枝量化后94.9%75ms11MB从这个表能直观看到每一层优化带来的收益不同按顺序组合起来效果远超单步优化。4.4 工程实用建议性能优化阶段有三个容易踩的坑单独说一下。第一不要在项目一开始就做量化。模型还没收敛稳定、验证集还没合格时就量化会把精度损失放大。第二步量化前先小范围试跑确认推理引擎对这个模型的支持程度有些自定义算子可能不支持量化。第三剪枝比例不是越高越好一定要画一条“剪枝比例对准确率”的曲线出来找到拐点再选比例。经验之谈任何优化手段上线前都要先在部署环境用真实数据做验证别只在自己电脑上测。部署环境的数据分布、推理库版本、CPU型号不同结果可能差异很大。5. 常见问题与排查技巧实录5.1 训练不收敛先查这三个地方第一个项目跑了几轮后遇到损失值降不下去的情况。排查下来是数据没有做归一化。图像分类中如果不把像素值缩放到0到1或者按批次均值方差归一化深层网络很容易出现梯度异常尤其是预训练模型输入分布和随机初始化的模型输入分布完全不一样时更明显。第二个问题是某些epoch开始时损失突然跳高。这往往是batch size设置过大导致梯度不稳定或者是学习率调度在warmup阶段设置不合理。我最后把batch size控制在32到64之间同时检查了学习率预热曲线问题就不再出现了。第三个问题是任务复杂但模型过小导致的欠拟合。如果训练损失和验证损失同时高基本可以判断是模型容量不足这时优先考虑加深网络或更换更强的结构。5.2 验证准确率高但实际效果差警惕数据泄露训练时模型的验证准确率达到了99%但是到了业务方测试数据上只有80%多一点差得离谱。排查后发现了典型的数据泄露问题原始数据里有部分图片本身是从同一个视频或者同一批拍摄环境中截出来的这些相似图片被按文件顺序随机划分到训练集和验证集里导致验证集的数据分布和训练集过度重复。正确做法是按拍摄来源分组划分数据集同一来源的所有图片要么全放训练集、要么全放验证集才能真实反映模型面对新数据时的表现。这是我踩过的一个大坑印象特别深刻。5.3 推理速度比预期慢看看这几个被忽视的因素有时候模型已经量化了推理速度还是达不到预期。我遇到过的情况有以下几种大家可以对照排查模型还在CPU上跑而不是在GPU上那就要确认推理引擎是否正确配置了设备输入尺寸没有固定动态分辨率会导致推理引擎反复做形状推理非常影响速度推理时batch size设成了1而又没有开启并行执行模式图片前处理里做了太多耗时的Python层操作比如逐像素循环这类操作在CPU上特别慢建议用矩阵运算替代模型文件中还残留着训练相关的算子比如Dropout、BatchNorm的运行统计导出时没去掉这些都会拖慢计算。5.4 别只盯着准确率评估指标要配齐模型评估阶段我除了看准确率还额外关注了宏平均F1值和混淆矩阵。对于类别不平衡的数据集准确率是很误导人的指标——如果95%的样本都是类别A模型全部猜A也有95%的准确率但显然这不是我们想要的。混淆矩阵能直观看出哪些类别之间互相混淆帮助针对性优化。F1值则能综合反映精确率和召回率的平衡类别少的那几个类F1值比准确率更能说明真实能力。写在最后最后一个个人体会。图像分类这个方向看着成熟但真正把一个模型从训练到部署完整走一遍需要处理的细节比想象中多得多。我的最大感受是先搭好能跑通全流程的基线版本再一步步优化每一步只改一个变量这样所有效果变化都能归因到具体操作上。还有一个体会是项目的关键往往不在最新最炫的模型上而在于把数据质量把关好、把每一步的影响量化清楚这些基本功到位了性能自然就上去了。如果你也在做类似的图像分类项目建议一开始就把目标指标定清楚包括准确率、速度、稳定性然后围绕这些指标做迭代。过程中多做记录、多保存模型版本你会在回看时发现那些看似无用的中间版本其实都是宝贵经验。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →