ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

检测模型剪枝与量化实战:从原理到部署全流程复盘

检测模型剪枝与量化实战:从原理到部署全流程复盘 检测模型做压缩剪枝是条性价比极高的路子。别的不说光是不用换推理框架、不用动硬件就能白拿一波推理速度这点就足够让人优先考虑它了。我当时接手的一个目标检测项目骨干网络用的就是ResNet34视频流任务对延迟卡得很死GPU上跑到40 FPS没用换成边缘设备立刻跌到15 FPS根本没法上线。折腾一圈之后是靠剪枝算法把模型瘦下来的之后又叠了一层量化最终体积砍掉一半多帧率翻了两倍多精度只掉了一个点左右。这篇就把整个思路、原理、实操流程和踩过坑的细节都复盘一遍适合正在做检测模型部署、被推理性能卡住、又不想换网络架构的人参考。1. 先想明白一件事检测模型到底是被什么拖慢的1.1 部署场景里的三重约束训练阶段跑模型我们关心的是精度和收敛速度但部署阶段完全是另一套逻辑。端侧设备、边缘盒子、车载平台这些地方要同时面对三重约束第一是算力。GPU上的CUDA核心和边缘芯片的NPU完全是两种东西模型里的卷积操作在GPU上可能被高效并行到了端侧就是老老实实逐层算浮点运算量大一点都看得见。第二是显存带宽。检测模型不仅仅是参数量大中间特征图的尺寸也很大一张1080P的图走到特征金字塔每层特征图都要占内存带宽不够的时候数据搬运时间比计算时间还长。第三是延迟上限。视频流每秒25帧一帧的推理预算只有40毫秒去掉前后处理真正给到模型的可能就20毫秒稍微慢一点就直接掉帧。剪枝解决的正是这三重问题。通道剪枝直接让卷积层的输入输出通道变少FLOPs降下来特征图通道数也降下来内存占用跟着降延迟自然就下来了。它不改变数值精度也不依赖特殊硬件在常规推理框架里就能看到加速效果这也是剪枝在模型压缩技术里特别实用的原因。1.2 模型压缩技术全家桶里剪枝处在什么位置很多文章喜欢把压缩技术分成量化、剪枝、知识蒸馏、低秩分解四类但实际项目里它们不是竞争关系而是互补关系。量化的思路是降低数值精度FP32变FP16或者INT8优点是直接白嫖加速缺点是敏感层经常掉点需要校准甚至重训练。知识蒸馏是让一个大的teacher模型把知识教给小的student模型精度上限高但训练复杂度大得先有个好teacher。低秩分解是把权重矩阵分解成两个小矩阵理论压缩率高但卷积实现和硬件优化都不好做实际收益常常打折扣。这里反而就属剪枝直接、可控性强。剪枝是先把结构变稀疏和量化完全不冲突两个叠加起来就是常见的“剪枝量化”组合拳。提示剪枝和量化经常一起做因为它们分别从“结构”和“精度”两个维度瘦身互不干扰。除非设备对INT8支持太差否则推荐两个都上。2. 剪枝原理网络减掉一部分参数凭什么不影响精度2.1 模型里的冗余是常态不是巧合神经网络训练完之后参数并不是全都“有用”的。你用L1范数或者L2范数把所有卷积核过一遍会发现大量卷积核的权重值趋近于0它们对输出的贡献非常小。原因也好理解训练过程中网络为了拟合数据会把特征表达分散到很多神经元上部分神经元承担主路径部分神经元只是在某个样本上有微弱激活统计意义上看就是冗余的。学术上有过一个常见结论训练好的网络里可能有50%甚至更多的权重可以被置零而不产生精度损失。这个数字听起来夸张但做剪枝实验时你会真实感受到。我用ResNet34的backbone做敏感性分析时把backbone最后一个stage里30%的通道直接删掉mAP只掉了0.3%那个瞬间你就明白之前的算力确实是浪费了不少。2.2 剪枝算法三步走评估、删除、微调剪枝不是一个动作是一个流程核心是三步第一步是评估重要性。给每个可剪单元通道、滤波器或单个权重算一个重要程度。常见指标有权重绝对值范数、BN层缩放因子γ、基于梯度的泰勒展开值。以通道剪枝为例如果通道对应的BN层γ值很小说明这个通道的输出会被缩放得很小删掉它对后续层的影响就有限。这个思想来自Network Slimming那篇工作实操里特别好用。第二步是删除。有两种做法一种是直接物理删除通道网络结构变了输出通道数变了另一种是先用mask把不重要的通道置零网络结构不变但计算图中产生了很多零值。前者是真正的结构化剪枝后者是伪稀疏后面要配合真正裁掉才能提速。第三步是微调。删除参数之后网络精度一定是掉的需要重新训练几步甚至几十步让剩下的参数重新适应。微调质量直接决定最终精度很多时候不是剪枝本身掉点而是微调没做好。2.3 结构化剪枝 vs 非结构化剪枝现实和论文要分开看这里专门说说“非结构化剪枝”。它在学术论文里很常见方法是把不重要的单个权重置零不要求整个通道或者整个滤波器被删掉。好处是细粒度精度损失小高稀疏度下依然能保持很好的效果。但问题也很现实大多数推理框架和硬件对稀疏矩阵的支持并不好你剪完之后如果不配套稀疏卷积库模型文件虽然变小了推理延迟却几乎没变化。结构化剪枝就不一样了它按通道或滤波器整体删网络变成一个小一点的密集网络任何框架都能跑速度提升明显。缺点是因为粒度粗同参数压缩率下精度下降会更多一些。我自己的选择很明确部署项目优先结构化剪枝。对ResNet34这种标准结构通道剪枝够用了没必要为了多保零点几个点的精度去折腾稀疏推理支持。对比项非结构化剪枝结构化剪枝剪枝粒度单个权重整个通道/滤波器精度保持更好略差需更精细的策略实际加速依赖稀疏库直接加速部署通用性差好典型场景论文研究、专用硬件工程落地、边缘部署3. 检测模型剪枝跟分类模型完全不是一回事3.1 backbone、neck、head三部分要区别对待很多人拿分类模型那套剪枝流程直接套到检测模型上结果一剪就崩。分类模型整体都用于提取语义特征剪哪里相对平等检测模型结构上分为backbone、neck、head三个部分敏感度差太多了。backbone承担特征提取冗余度最高也最能剪。我当时对ResNet34的四个stage都做了剪枝测试stage3和stage4可以剪到40%stage1只能剪20%以内越低层越敏感。原因是浅层特征偏向边缘、纹理这些通用特征删多了底层信息就断了深层通道更偏语义冗余更多。neck部分我做的是FPN特征融合结构这些层相当敏感。每一层都在做不同尺度特征的上采样和融合通道删多了跨层信息就对不齐了。neck部分我的建议是保守最多不要超过20%。head就是检测头包括分类分支和回归分支这部分我几乎不动。检测头的参数量虽然不大但直接决定输出质量尤其回归分支涉及坐标的精细调整剪坏一个通道就可能导致大量框的位置偏移。3.2 检测任务里的专有坑小目标、正负样本不均、多尺度融合检测模型剪枝还有一个分类模型从来没有的问题目标有尺度差异。COCO数据集里AP_S、AP_M、AP_L是要分别看的。我在实验里就遇到过整体mAP看起来只掉了0.8%拆开一看AP_S掉了2.1%小目标本来特征就弱剪枝后特征图分辨率又降了小目标直接就被放过去了。正负样本不均也是一个问题。检测任务里绝大多数anchor区域是背景如果剪枝让某些负责背景抑制的特征通道失效网络就会输出大量假阳性框导致precision下降比recall明显这个在剪枝后很容易被忽略。所以评估检测模型剪枝效果时不仅要看mAP最好拆开看AP50、AP75、AP_S、AP_M、AP_L再配合PR曲线看整体形状有没有明显劣化。不看分项指标很容易被一个总分糊弄过去。3.3 剪枝之后评价体系要跟着换分类模型剪枝后用Top-1 Accuracy一个指标就够了检测模型可不行。除了上面说的尺度分项还需要关注推理延迟是不是真的降了。理想情况下FLOPs和延迟应该同步下降但实际里如果剪枝层正好不是计算瓶颈延迟可能没变化。具体到ResNet34这种结构计算密集部分是stage3和stage4通道剪这里FLOPs减少非常明显延迟也能体现。如果只减stage1或者只减head的通道FLOPs降了但延迟几乎不动因为浅层卷积在GPU上并行度高减几个通道根本看不出来。所以检测模型里的剪枝本质上是“把钱花在刀刃上”选择哪些层的通道进行剪枝比设定多少剪枝率更重要。4. 实操记录ResNet34检测模型从剪枝到量化全流程4.1 第一步基线先打牢敏感性分析比拍脑袋靠谱切不可上来就定剪枝率。建议先跑一遍完整基线记录原始模型的参数量、FLOPs、内存占用、单帧延迟以及mAP、AP50、AP_S、AP_M、AP_L这些指标。有了基线后面每一步改动都有参照。接着做敏感性分析。我当时的方法很简单把backbone按stage分成几个区域每个区域单独以10%、20%、30%的比例通道剪枝不做微调直接评估精度掉落情况。这个实验结果能给出一个大概的“安全边界”。比如我看到ResNet34的stage3剪20%掉0.2%剪30%掉0.8%那就说明20%安全30%勉强而stage1剪20%就掉1.5%这条信息直接指导后续全局剪枝率怎么安排。这个步骤看起来很笨但非常值得做。它避免了“全局统一剪枝率”这种省事但危险的做法。我见过太多人直接全局剪50%结果neck或者浅层被剪崩了然后再反过来找人帮忙调费劲程度远大于先花半天做敏感性实验。4.2 第二步设计剪枝方案这里有几个关键参数我的方案是以结构化通道剪枝为主重要性指标用BN层γ值。具体流程是先做稀疏化训练。给BN层的γ添加L1正则让γ值变得稀疏也就是一部分γ趋近于0这部分通道就是候选删除对象。正则系数一般取1e-4到1e-3太大精度崩太小稀疏化效果不明显。我这边取的是5e-4训练80个epoch前面50个epoch正常训练后面30个epoch打开正则让γ慢慢稀疏下来。然后按通道重要性做全局排序。全局排序比逐层排序效果好因为它自动把“不重要”的通道分配到各个层而不是每层都硬剪到同样的比例。设置目标剪枝率。这里要分模块定不能全局一个数。我的实际设置是backbone整体剪35%其中stage1只剪15%stage2剪30%stage3剪40%stage4剪40%neck剪20%head完全不剪。全部流程里最重要的是不要边训练边物理删通道。先训练出稀疏化的γ再生成mask然后用mask把不重要的通道置零再做短窗微调最后才是真正导出小模型。直接删通道再从头训练训练不稳定精度也很难回来。4.3 第三步微调策略决定最终精度剪枝后的微调是最容易被低估的一步。常见的错误是微调只跑几个epoch、学习率开得还很大结果精度一直回不来。我在实操中总结出几个有效做法一是学习率要小。原始训练用0.01的SGD微调时reducelr到0.001甚至0.0005因为剩下参数基本已经具备特征提取能力用大学习率会冲乱现有特征空间。二是加长微调时间。不要少于40个epoch我这边跑了60个epoch才让mAP稳定回来。三是配合知识蒸馏。用未剪枝模型当teacher对剪枝后的student做蒸馏这个操作对检测模型尤其有效每次能多拉回0.5到1个点的mAP。蒸馏损失权重可以放在0.1到0.3之间取0.2时效果最好。还一个细节是BN层统计量的重置。剪枝后BN层对应的通道变了最好在微调开始时用一小批数据重新估计一下running mean和running variance不然前几个epoch会因为统计量错乱出现精度跳水。4.4 第四步量化衔接实操模型结构瘦下来之后接着做量化。ReLU后的激活值分布、conv的权重分布都在FP32范围里用INT8表示就得做min-max映射这一步处理不好剪枝省下来的精度空间可能会全亏进去。我的做法是先做PTQ也就是训练后量化。用500到1000张验证集图片做校准统计每个激活层的min和max然后映射到INT8。PTQ做完通常会有0.3到0.5个点左右的精度下降可以接受。如果某些层掉点严重就把敏感层保留FP16也就是混合精度量化而不是强行全部INT8。要警惕的是直接在剪枝前做量化。剪枝改变的是网络结构原本量化的统计量全都不准了。所以顺序一定是先剪枝微调再量化校准。另外如果PTQ掉点超过1个百分点那就要考虑QAT量化感知训练在训练时就模拟INT8的量化误差。我这边最终是用PTQ的因为硬件支持比较好但如果换更激进的INT8推理芯片QAT会更稳。最终结果可以给大家一个参考原始模型参数约28MB剪枝后约17MB再量化后约4.5MB压缩比接近6倍GPU上单帧延迟从35ms降到18msmAP从36.4降到35.2掉了1.2个点但AP_S只掉了0.8这个结果我认为是可接受的因为换来的是部署端帧率从15 FPS升到32 FPS。5. 预剪枝与后剪枝两个经典概念怎么迁移到深度网络5.1 预剪枝和后剪枝原本解决的是什么问题预剪枝和后剪枝这两个名字最早出现在决策树算法里。很多机器学习的教材里都会提到构建决策树的时候如果边建树边判断当前节点分裂是否能带来泛化增益不能就停止分裂这叫预剪枝另一种是把树完整建立起来再自底向上把贡献不大的子树替换成叶子节点这叫后剪枝。在决策树场景里预剪枝训练时间短、不容易过拟合但可能欠拟合因为“当前增益不大”不代表“后续子树增益不大”后剪枝效果通常更好因为它是基于完整结构做的评估但训练成本更高。这个权衡逻辑放到深度网络里其实也是成立的。5.2 深度网络里两种思想的对应关系深度网络的剪枝世界里预剪枝思想体现在训练过程中。比如NAS或One-shot模型搜索一开始就设计一个小网络或者规定哪些层不参与训练直接用搜索或训练策略决定最终结构这种“边训练边定结构”的思路就是预剪枝。再比如前面提到的稀疏化训练让γ趋近于0的过程其实也在“训练过程中”悄悄进行结构选择只是没有真正删除物理结构。后剪枝就是最常见的流程训练一个完整大模型评估参数重要性把不重要的删掉再微调恢复精度。我们做检测模型压缩用的就是后剪枝。这个思路的好处是模型先用完整能力学到足够的特征表达剪枝时知道哪些东西是冗余的决策依据更可靠缺点是前期的训练成本省不下来。5.3 实际项目到底选预剪枝还是后剪枝从我的实践看常规部署项目基本选后剪枝。原因是深度学习模型的表达能力复杂很难在训练一开始就准确判断哪些通道最终有用预剪枝的“边训练边剪”策略在工程上也不容易实现训练不稳定还要额外调试很多超参。后剪枝反而稳定模型已经训练好做敏感性分析、做稀疏化微调每一步都能看到指标变化排查问题也方便。如果你是为了探索极限压缩比、有充足训练资源可以考虑预剪枝思路比如更极端的稀疏正则或者渐进式剪枝但需要做好掉点修复的成本准备。6. 踩坑实录与常见问题速查6.1 剪枝之后精度骤降先检查BN层这个坑我印象太深了。第一次剪完ResNet34的stage3mAP直接掉了3个点比敏感性分析时预估的掉点严重得多。排查半天最后发现问题出在BN层稀疏化训练时BN层的running mean和running variance还是原始模型的通道删掉之后剩余通道的统计量对应的分布变了前几层输出的尺度被错误放大。解决办法就是在微调前用一批真实数据reset统计量非常有效。6.2 剪完掉的主要是AP_S注意浅层特征保留搞检测的应该有同感普通分类任务剪枝后最多掉1个点但检测模型一剪小目标先崩。AP_S掉的比AP_M和AP_L多是因为小目标本身在特征图上占据的像素少能用的有效信息本来就集中在浅层高分辨率特征图上。所以剪枝策略里必须对浅层和下采样少的层更保守。我们后来在stage1只剪10%FPN的P2层完全不动AP_S的掉点就控制住了。6.3 FLOPs降了很多但帧率没提升FLOPs和延迟不是一回事。剪枝如果只剪了参数量大但计算不密集的层比如1x1卷积的前后过渡层FLOPs看起来降了但GPU实际耗时没变化。ResNet34里最耗时的其实是stage3和stage4的3x3卷积剪这里才有效果。建议剪枝前用profiler看每层实际耗时找到计算热点然后针对热点层做剪枝。6.4 先量化后剪枝是个大坑我一个同事踩过这个坑他先做了INT8量化然后才剪枝结果精度崩得没法看。原因很简单量化是在固定网络结构上做统计映射剪枝之后结构变了通道少了统计量全失效之前校准结果全作废。正确顺序就是剪枝-微调-量化-校准这个顺序别乱改。6.5 常见问题速查表问题现象可能原因解决方案mAP整体掉点超过预期BN统计量未重置或微调学习率过大重置BN统计量降低学习率到0.001以下延长微调AP_S单独掉点严重浅层特征通道被剪过多降低浅层剪枝比例保留高分辨率特征图通道FLOPs降了延迟没降剪枝层不是计算热点用profiler定位热点层优先剪stage3/stage4量化后精度崩量化顺序不对或敏感层被强行INT8先剪枝再量化对敏感层做混合精度剪枝效果很好但模型文件体积变化不大没有物理删除通道只是mask置零导出模型时做真实结构重写删掉零通道微调过程模型发散学习率过大或剪枝率过高检查剪枝率是否超出敏感性安全边界降低lr最后再分享一个我自己的体会剪枝不是单纯的模型瘦身而是一个系统工程需要数据、训练、部署三端配合。检测模型比分类模型复杂敏感性分析、分模块剪枝率、微调策略这些环节都不能省。如果你刚好在项目里被模型体积和延迟卡住了按照这个流程先跑一遍大概率不会走歪路。
返回列表