yolov5剪枝与量化一键运行:模型瘦身加速实战

发布时间:2026/10/9 1:12:55
yolov5剪枝与量化一键运行:模型瘦身加速实战 简介面向YOLOv5模型压缩与加速的实战资源包专为需要在边缘设备或低算力环境部署检测模型的算法工程师设计可解决模型参数量大、推理延迟高、显存占用多等部署痛点。资源将结构剪枝、权重剪枝、量化感知训练与TensorRT部署整合为一套完整流程提供一键运行的Python脚本用户只需修改YAML配置中的剪枝比例与量化参数即可将模型体积压缩超过70%而不显著损失检测精度。资料包共208个文件以Python脚本59个py、YAML配置48个yaml、C源码5个cpp、Markdown文档11个md为主体并预置了PyTorch权重、ONNX模型、Dockerfile等依赖内容压缩包整体仅24.2MB目录划分清晰便于按阶段取用。目前已有1884人学习下载。资源不仅包含从模型剪枝到TensorRT引擎转换的完整代码还附有相应原理说明、依赖配置及推理示例能够帮助开发者绕开环境搭建、参数调优中的常见坑位快速在无人机、安防摄像头等资源受限场景下完成模型压缩与部署验证。1. 剪枝和量化不是玄学yolov5 模型瘦身的真实收益手里有一个在树莓派4B上跑的 yolov5 检测模型帧率只有个位数模型文件又大这时该考虑剪枝和量化了。剪枝把不重要的通道删掉量化把权重从 FP32 压到 INT8两者叠加能省下大半算力。标题里的“yolov5剪枝和量化代码一键运行”说的是把稀疏训练、通道剪枝、微调和 INT8 量化串成一个脚本让模型优化不再是玄学。这套流程解决的是三件事模型体积变小、推理延迟变短、功耗下降。适合的人群也很明确——在树莓派、Jetson、手机端做边缘部署的开发者以及想降低服务器推理成本的后端工程师。一键运行不等于无脑跑通你依然要理解每个环节在干什么否则剪完模型直接翻车的概率极高。下面我按自己实际搭过的流程讲一遍从原理到脚本到坑给你一条能复现的路。2. 剪枝和量化怎么选先看懂剪哪里、量到哪里收益才不是玄学在动任何脚本之前先把两个基础问题想清楚剪枝到底剪掉的是什么东西、量化到底压的是哪一层精度。这也是很多人在搜索引擎里问“yolov5网络结构图”时真正想知道的答案——结构图是定位剪枝和量化操作位置的依据。2.1 非结构化剪枝与结构化剪枝yolov5网络结构图里实际裁剪单位是什么看 yolov5 的结构图卷积层后面基本都跟着 BatchNorm2d。BN 层每个通道有一个可学习的缩放系数 gamma通道剪枝的核心思路是如果某个通道的 gamma 接近 0那这个通道对后续输出几乎没有贡献可以直接整条砍掉。这个操作叫结构化剪枝因为它按通道整块删不破坏稠密矩阵结构部署时能真实省算力。另一种是非结构化剪枝逐个权重设为 0让矩阵变稀疏。yolov5 里有不少 1x1 卷积稀疏化后理论压缩率高但 CPU 和大多数边缘芯片对稀疏矩阵没有加速指令实际推理时间基本不变。所以我做 yolov5 落地公开模型时默认只考虑结构化通道剪枝。判断稀疏训练是否生效不会去看权重而是看 BN gamma 的分布我一般直接用下面这段代码统计import torch def inspect_bn_gamma(weight_path): ckpt torch.load(weight_path, map_locationcpu) model ckpt[model].float().eval() gammas [] for name, module in model.named_modules(): if isinstance(module, torch.nn.BatchNorm2d): gammas.append(module.weight.data.abs().view(-1)) g torch.cat(gammas).numpy() total_channels len(g) near_zero (g 1e-3).mean() * 100 print(fBN 层总数: {len(gammas)}总通道数: {total_channels}) print(fgamma 均值: {g.mean():.4f}接近0比例: {near_zero:.2f}%) return g代码里先 load checkpoint然后遍历所有 BatchNorm2d 的 weight取绝对值后拼接成一个大向量。打印的“接近0比例”是剪枝潜力的第一指标。如果这个比例不到 5%说明稀疏训练强度不够直接剪枝会损伤 mAP。注意 load 后要调成 float().eval()避免 AMP 半精度统计不准确。2.2 稀疏训练是剪枝的前置条件yolov5超参数与 L1 惩罚怎么调剪一个未做稀疏训练的模型是常见误用。直接看 BN gamma分布通常比较均匀找不到明显可以砍的通道。硬剪的话 mAP 可能掉 5 个点以上。所以正确做法是先做稀疏训练也就是在训练时给 BN 的 gamma 加上 L1 正则逼一部分通道趋近于 0。yolov5 官方训练脚本有 --sparse 参数直接把 L1 惩罚系数传进去。我常用这样的命令python train.py \ --data data/coco128.yaml \ --weights yolov5s.pt \ --batch-size 32 \ --epochs 50 \ --sparse 0.001 \ --adam \ --lr0 0.001参数说明--sparse 是 L1 稀疏系数0.001 是比较安全的起点。太大会导致 BN gamma 全部被压掉模型表达能力崩掉太小则稀疏效果不明显剪枝后收益有限。--adam 在稀疏训练里能缓解梯度波动配合较小的 lr0 使用。跑完 30 到 50 轮后再运行刚才的 inspect_bn_gamma 脚本接近 0 比例通常能到 20% 到 40%。如果不想依赖官方内部的实现也可以自己在 loss 上追加正则项效果更可控l1_lambda 0.001 l1_reg 0.0 for module in model.modules(): if isinstance(module, torch.nn.BatchNorm2d): l1_reg module.weight.abs().sum() loss total_loss l1_lambda * l1_reg这段代码的作用是遍历所有 BN 层把 gamma 的绝对值累加后乘一个系数加到总 loss 里。l1_lambda 越小通道越不容易被清空越大则稀疏力度越强。我建议从 0.0005 开始试探观察一个短期训练后 gamma 直方图再决定要不要加大。2.3 剪枝比例和流程先练后剪还是边剪边练剪枝比例不是拍脑袋定的。对 yolov5s 这类模型第一次做先剪 30% 的通道比较稳妥mAP 通常损失在 1 个点以内剪 50% 可以明显减小体积但后面必须留足微调轮数。各类模型对剪枝的耐受度不一样yolov5n 本身已经很小剪到 40% 以上容易掉检测率。常见流程是稀疏训练 → 剪枝 → 微调恢复也就是“先练后剪”。也有人问要不要“边剪边练”即在训练中动态调整剪枝位置这种做法在最近的研究里有但工程上复现容易崩梯度反复横跳训练曲线不稳定。我做生产模型时不会用宁可多花一轮微调时间也不把训练过程变成黑匣子。从流程选型角度看剪枝和量化是两个独立阶段剪枝后的模型要先微调收敛再做量化。如果把两者同时做出现精度问题时很难定位是剪枝引起的还是量化引起的。这里用表格说明几条路径的适用场景路径精度损失部署收益适用场景只做剪枝较小中等CPU 边缘设备INT8 加速不明显只做 INT8 量化较小中等算力受限但通道不冗余先剪枝后量化中等最大树莓派、Jetson 等资源紧张设备只做非结构化稀疏小几乎无存储压缩不追求推理加速所以我的建议很直接如果你的目标是“模型体积和延迟一起降”就按稀疏训练、结构化剪枝、微调、INT8 量化的顺序做。下一章就写怎样把这些环节串成一键运行脚本。3. 一键运行怎么落地把稀疏训练、剪枝、微调与量化串成流水线很多人把“一键运行”理解为点一个按钮。我理解的“一键”是把容易遗忘、容易出错的手工步骤固定成命令比如剪枝后必须重建 yaml、量化前必须生成 onnx。下面给一个可扩展的脚本骨架分为四个阶段每个阶段都能单独调试。3.1 一键脚本骨架Bash 入口调度四个阶段脚本用 Bash 做入口好处是阶段之间依赖清晰某个步骤失败可以立刻停下。我通常写成这样#!/usr/bin/env bash set -e DATASET${1:-data/coco128.yaml} WEIGHTS${2:-yolov5s.pt} PRUNE_RATIO${3:-0.3} QUANT${4:-int8} # 阶段1: 稀疏训练 python train.py \ --data $DATASET \ --weights $WEIGHTS \ --epochs 50 \ --sparse 0.001 \ --batch-size 16 \ --adam # 阶段2: 通道剪枝 python prune.py \ --weights runs/train/exp/weights/best.pt \ --ratio $PRUNE_RATIO \ --save pruned.pt # 阶段3: 微调恢复 python train.py \ --data $DATASET \ --weights pruned.pt \ --epochs 30 \ --sparse 0.0 \ --batch-size 16 # 阶段4: 导出与量化 python export_quant.py \ --weights runs/train/exp2/weights/best.pt \ --quant $QUANT参数说明前三个参数分别控制数据集、初始权重和剪枝比例第四个是量化类型默认 int8。set -e 保证某个 Python 进程返回非 0 时整个流水线终止避免用坏权重继续往下跑。注意阶段 2 的权重路径依赖阶段 1 的 runs/train/exp 输出目录跑之前先确认日志里有真正的新 exp而不是复用旧的。这个脚本虽然叫“一键”但你完全可以在任何阶段打断。我习惯先跑阶段 1 和 2检查剪枝后的模型能正常 load 再跑后面的微调。阶段 4 的 export_quant.py 是我们自己写的下一节讲最核心的剪枝逻辑。3.2 剪枝脚本的核心逻辑同步裁剪 BN 与卷积通道剪枝的核心不是“找哪些通道该删”而是“删完之后让模型还能正常 forward”。yolov5 的卷积层和 BN 层在通道维度上是绑定的某一层 Conv 的 out_channels就是下一层 Conv 的 in_channels。只剪 BN 不剪卷积维度一定崩。一个通用做法是先用阈值筛选每个 BN 层要保留的通道再重建模型结构并复制保留的权重。我给出筛选阈值和掩码的简化代码import torch import torch.nn as nn def build_keep_masks(model, ratio): masks {} for name, module in model.named_modules(): if isinstance(module, nn.BatchNorm2d): gamma module.weight.data.abs() # 每层独立阈值按该层通道数剪掉 ratio 比例 threshold torch.quantile(gamma, ratio) masks[name] gamma threshold return masks这里每个 BN 层独立计算阈值避免全局阈值对大通道数层不公平。ratio0.3 表示删掉该层 30% 的通道。注意 torch.quantile 得到的是第 ratio 分位的 gamma 值严格大于阈值的才保留。如果某一层 gamma 分布很集中这一层可能一个通道都没有被剪掉这在结构化剪枝里是允许的。拿到掩码后真正麻烦的是重建模型。常见做法是直接修改内部模块的通道属性然后新建一个模型实例复制权重而不是原地修改。核心要同步的字段# 伪代码示意实际需要按 yolo 结构遍历 conv1.out_channels keep_count conv2.in_channels keep_count bn1.num_features keep_count原因很直接PyTorch 的 Conv2d 权重 shape 是 (out_channels, in_channels, kh, kw)BN 的 weight 是 (num_features,)。你改 conv1.out_channels 会影响它自己的输出改 conv2.in_channels 会影响下一层的输入两处必须保持一致。初学最容易漏的是 shortcut 分支和 concat 分支yolov5 的 CSP 结构中有多条分支合并合并处通道数必须对齐否则一 forward 就报错。3.3 剪后微调学习率、轮数和冻结策略剪枝后的模型是一个“受伤”的模型必须微调恢复。微调跟普通训练不一样学习率要降下来训练轮数不用太多。我用得比较多的命令是python train.py \ --data data/coco128.yaml \ --weights pruned.pt \ --epochs 30 \ --batch-size 16 \ --sparse 0.0 \ --no-autoanchor \ --lr0 0.0005参数说明--sparse 0.0 表示微调阶段不再加 L1 惩罚让保存下来的通道自由恢复表达力。--no-autoanchor 是为了避免训练脚本重新计算 anchor 尺寸因为剪枝后的特征层结构没变anchor 不需要重算。学习率我习惯设成正常训练的 1/2 到 1/30.0005 是一个经验值如果看到 loss 波动再降一档。微调 30 轮后mAP 一般能回到原来的 97% 左右。如果只微调 10 轮可能恢复不够充分后面量化会把误差进一步放大。这个阶段没耐心的话会直接导致最终模型“虚胖”所以别省。4. 量化到导出INT8 与 FP16 在 ONNX 和 TensorRT 上的差异剪枝完成后进入量化环节。量化是把模型权重或激活从 FP32 用更低比特表示INT8 是最常见的量级因为 CPU 和多数边缘 NPU 对 INT8 都有硬件加速。但量化不是简单地调用一个转换函数校准数据、算子支持和导出格式都会影响最终效果。4.1 PTQ 和 QAT怎么选不翻车量化有两种落地路线一种叫训练后量化PTQ模型已经收敛直接收集一些真实输入做校准统计激活值的 min/max 或直方图算出每个 tensor 的缩放系数另一种叫量化感知训练QAT在微调阶段插入伪量化节点让模型权重自己去适应量化误差。PTQ 的好处是省时间一个 yolov5s 用 500 张校准图做静态量化几分钟就能出 onnx 模型。缺点是激活分布广、特别是小目标检测时直接 PTQ 会导致输出置信度漂移。QAT 精度更高但需要改训练脚本和重新训练工程量翻倍。我一般按部署目标来选如果最终跑 TensorRT且手头有充足的标注数据优先 QAT如果只是快速验证 onnxruntime int8 性能就 PTQ 跑一轮先看体积和延迟收益精度不行再回头做 QAT。下面是 PTQ 和 QAT 的对比对比项PTQQAT校准数据需要几百张真实图片需要训练集参与改造范围只需改动导出脚本需要插入伪量化算子精度恢复依赖校准集代表性训练过程自适应耗时数分钟到几小时多次完整训练周期适应算子常规卷积、BN 融合也适配反卷积、注意力4.2 用 ONNX Runtime 做 ONNX INT8 量化校准数据读取器是关键导出 onnx 后做 INT8 量化我首选 onnxruntime 的 quantize_static。先记住一点动态量化quantize_dynamic对卷积几乎无效它只压缩权重不改变激活精度yolov5 的卷积层大量算子在激活上所以必须用静态量化。静态量化需要准备一个校准数据读取器。常见写法是实现 CalibrationDataReader 接口我这里给出一个简化版本import numpy as np from onnxruntime.quantization import CalibrationMethod, QuantFormat from onnxruntime.quantization import CalibrationDataReader class YoloCalibReader(CalibrationDataReader): def __init__(self, calib_images): self.images calib_images self.idx 0 def get_next(self): if self.idx len(self.images): return None img self.images[self.idx].astype(np.float32) / 255.0 tensor np.expand_dims(img, axis0) data {images: tensor} self.idx 1 return data def rewind(self): self.idx 0说明yolov5 导出的 onnx 输入名通常是 images形状是 NCHW每个通道要归一化到 0 到 1。校准数据不能都用同一批图片至少要覆盖不同光照、不同尺度和类别。rewind 方法让量化器能反复读取校准集不同版本 onnxruntime 要求不一致建议保留。拿到读取器后调用静态量化接口from onnxruntime.quantization import quantize_static, QuantFormat quantize_static( model_inputyolov5s.onnx, model_outputyolov5s.int8.onnx, calibration_data_readerreader, quant_formatQuantFormat.QDQ, per_channelTrue, activation_typeQuantOp, calibrate_methodCalibrationMethod.MinMax, )参数说明per_channelTrue 对卷积权重的每个输出通道独立算缩放系数精度更好但部署时可能增加少量计算。activation_typeQuantOp 表示对算子输出做量化而不是只量化权重。calibrate_method 用 MinMax 最直观如果发现掉点厉害换 Entropy 或 Percentile 再试。4.3 导出后处理yolov5后处理在量化模型里怎么调阈值量化模型输出的是 [1, num_anchors, 85] 的张量前四位是 bbox 的 xywh第五位是 obj 置信度后面是类别概率。yolov5 原仓库的后处理 NMS 一般放在模型外用 numpy 或 PyTorch 在 CPU 上解码。量化后激活值的精度下降小目标的置信度会比原来低一些NMS 阶段如果沿用原来的 conf 阈值容易出现漏检。我跑 INT8 模型时会把 conf 阈值从 0.25 降到 0.2 或 0.15再看 precision 和 recall 的平衡。调整的幅度要靠验证集来定不能盲目降。树莓派上跑 onnxruntime int8 时CPU 线程数也要调推荐python test_ort.py \ --onnx yolov5s.int8.onnx \ --threads 4 \ --conf 0.2 \ --iou 0.45这一段命令中的 conf 和 iou 就是后处理阈值threads 控制在树莓派 CPU 的物理核心数上。这里要特别提醒后处理不要对量化模型的输出做额外的平滑操作那样会掩盖真实误差反而更难定位问题。5. 避坑yolov5剪枝量化最容易翻车的五个环节剪枝量化的坑太多了这里只列我踩过最狠的五条每一条都是“现象 → 原因 → 解决”的完整链。这章不是理论是我多次跑坏模型后总结的血泪经验。5.1 现象剪枝后模型加载报 Conv 的 weight shape mismatch首先说现象你按脚本剪完保存 pruned.pt然后尝试 load 到 yolov5 模型报类似 size mismatch for model.0.conv.weight 的错误shape 对不上。很多人以为是自己保存坏了其实不是。原因是剪枝时只改了 BN 的通道数没有同步修改前后卷积层的输入输出通道。yolov5 的卷积层和 BN 层是固定搭配Conv 后面紧跟 BN下一层 Conv 又把这个 BN 的输出当输入。裁剪通道等于同时动了三处当前 Conv 的 out_channels、当前 BN 的 num_features、下一层 Conv 的 in_channels。漏掉任何一处都会在加载时暴露。解决方法是先重建模型结构再拷贝权重。不要试图原地 load state_dict。我建议在剪枝代码里输出每一层的前后通道变化验证 concat 分支处的通道对齐。yolov5 的网络里有多个 concat 操作不同分支合并时总通道数必须相等。剪枝后如果某个分支的通道没改concat 处就会报张量维度不匹配。这个错误通常不会在加载时出现而在 forward 时报出来更难定位。所以剪完先跑一次空 forward 再保存。5.2 现象稀疏训练后 mAP 掉到 0loss 完全失控这是最吓人的一个坑。正常训练 mAP 慢慢涨稀疏训练一开前几轮 loss 直接涨到 NaN 或者检测全部为空。原因通常是 --sparse 系数太大L1 正则对 BN gamma 的生硬惩罚让梯度爆炸。正常 yolov5 训练对梯度没有裁剪gamma 的 L1 梯度是常数符号函数叠加起来很容易越过合理范围。解决手法是把稀疏系数从 0.001 降到 0.0005并把优化器换成 Adam或者打开 train.py 里的梯度裁剪。另一个坑是 AMP 混合精度和 L1 正则叠加半精度累积误差会被 sparse 放大。遇到 loss 异常就先关掉 AMP 再试。我自己在调参时先用 5 轮短训观察 gamma 的 histogram如果接近 0 比例蹭得太快就立刻调小系数而不是等 50 轮全跑完。这样能节省大量试错时间。5.3 现象INT8 量化后所有小目标都检测不到量化后大目标还好小目标全丢是典型症状。先说原因yolov5 的检测层里有多个尺度小目标对应的特征层分辨率高激活值分布通常比较稀疏静态量化时校准集如果没覆盖足够多小目标实例min/max 范围就会被大面积背景拉宽导致有效信号的量化步长过大小目标特征被压成噪声。解决方法是先检查校准集数据分布确保图片中有足够多的 mid-size 和 small-size 目标。如果数据集本身小目标少就考虑用 entropy 校准方法代替 min/max再不行做 QAT。我做过一个项目PTQ 后小目标 recall 从 0.85 掉到 0.62把校准集从 300 张扩展到 1200 张并加入裁剪的局部放大图后recall 回到 0.78。这说明校准集质量直接影响量化模型效果。5.4 现象ONNX INT8 推理耗时反而比 FP32 还高我有一次在 x86 CPU 上跑 onnxruntime导出的 int8 模型比 fp32 还要慢 20%那叫一个尴尬。原因是 onnxruntime 的 int8 算子在不同 CPU 平台上的支持度差别很大如果没有 AVX512 VNNI 指令集int8 卷积会走 fallback 实现反而不如 fp32 优化得彻底。解决方法是先查询当前 CPU 支持的指令集再用 TensorRT 或 OpenVINO 作为替代推理后端。特别是树莓派这类 ARM 设备并不是所有 onnxruntime 版本都带完整的 int8 kernel有时候用 fp16 反而更快。不要只看模型文件变小就认为一定提速要实际跑时延。我在脚本里都会加一个时延测试步骤直接打印每线程耗时避免被“文件体积变小”迷惑。5.5 现象剪枝和量化叠加后总体精度掉超过 5 个点最后一个常见翻车单独剪枝损失 1 个点单独量化损失 1 个点叠加起来却掉了 5 个点。这不是算术加法是误差放大效应。剪枝后模型的表达能力已经变弱量化又给权重和激活加了一层噪声两个问题纠缠在一起互相放大。解决方法是调整顺序剪枝微调后先导出 fp32 onnx 验证精度确认 mAP 只掉了 1 个点左右再做量化量化发现掉点大就回到微调阶段加少量 QAT 轮次。这里有一个后悔药剪枝微调后的 best.pt 一定要留好量化翻车时可以从这个权重重新导出而不是从原始权重再来一遍。因为重来一遍稀疏训练和剪枝可能要两天而 QAT 微调只要几个小时。6. 验证投入产出mAP、体积和吞吐量与基线对比后再上线剪枝量化做完不能只看能运行就完事先用同一份验证集跑基线、剪枝后、量化后三组指标再决定是否上线。验证指标主要包括 mAP0.5、模型体积和单帧推理耗时。注意耗时要在同一台设备、同一线程数下测否则对比没有意义。我一般会写一行命令统计这三个值python val.py --data data/coco128.yaml --weights best.pt --img 640用 val.py 拿到剪枝后和量化后的 mAP然后用 onnxruntime 写一个简单的耗时测试脚本测 100 次取平均。这里的重点是保持输入尺寸都是 640x640NMS 后处理方式一致。下面是一张我常见的对比表格式也是我判断收益是否值得的参照指标基线 FP32剪枝后INT8 量化后模型体积14 MB9 MB3 MBmAP0.50.610.600.57CPU 单帧耗时45 ms32 ms21 ms是否可接受基线合格合格如果剪枝后 mAP 回到原来 98% 以上量化后 mAP 比剪枝后只低 2 个点以内同时体积和时延都有明显下降这个项目就值得投入。如果 mAP 掉得太多先不要急着调部署回到微调阶段多跑 20 轮。最后一个进阶技巧在 TensorRT 上做 INT8 时别直接信任 onnx 的量化参数用 TensorRT 自带的校准器重新校准效果通常会更好。我做过的几次项目里同样的剪枝模型TensorRT int8 比 onnxruntime int8 快了 20% 以上。如果你需要部署到 Jetson 或独立 GPU建议把这一条纳入流程。我记得第一次跑这套流程时以为一键运行就是跑完命令收工结果剪枝后的模型完全验证不起来后来才发现是 concat 分支维度没同步。从那以后我养成了一个习惯每个中间产物都要跑一次打印网络结构和空 forward 的检查脚本再往下走。这个习惯帮我省了无数个“深夜 debug”的消耗。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询