4. 剪枝理论基础
剪枝,说白了就是给模型「瘦身」。
YOLOv11 跑在边缘设备上,动不动就几十兆甚至上百兆的参数量,推理延迟根本扛不住。我最早接触剪枝是在一个无人机巡检项目里,模型在 Jetson Nano 上跑不到 10 FPS,客户要求至少 25 FPS。那时候我才意识到,光靠量化远远不够,必须动刀剪枝。
但剪枝不是乱剪。你得先搞清楚:剪什么?怎么剪?剪完会怎样?
4.1 结构化剪枝 vs 非结构化剪枝
这两种剪枝方式,本质区别在于「剪完后的模型结构是否规整」。
| 对比维度 | 非结构化剪枝 | 结构化剪枝 |
|---|---|---|
| 剪枝粒度 | 单个权重/神经元 | 通道/卷积核/层 |
| 稀疏模式 | 不规则稀疏 | 规则稀疏 |
| 硬件加速 | 需要专用硬件/库 | 通用硬件直接加速 |
| 模型体积压缩 | 高(理论极限高) | 中高 |
| 推理加速效果 | 依赖稀疏计算库 | 立竿见影 |
| 部署难度 | 高(需稀疏推理引擎) | 低(直接导出即可) |
非结构化剪枝:把权重矩阵里接近 0 的小值直接置 0。模型变成稀疏矩阵,但形状没变。问题在于——大多数推理框架对不规则稀疏的支持很差。我曾经试过用 PyTorch 的 mask 做非结构化剪枝,剪掉 70% 的权重,结果在 ONNX Runtime 上跑,速度几乎没变。为什么?因为稀疏矩阵的存储和计算需要特殊优化,通用引擎不买账。
结构化剪枝:直接砍掉整个通道或卷积核。模型变「窄」了,但结构还是规整的。你想想看,一个 Conv2d 层原来有 64 个输出通道,剪掉 32 个,下一层的输入通道也跟着变。这种剪枝方式,任何推理框架都能直接加速。
核心结论:对于 YOLOv11 这种需要部署到边缘设备的模型,结构化剪枝是更务实的选择。非结构化剪枝更适合学术研究或拥有自研稀疏推理引擎的团队。
4.2 通道剪枝原理
通道剪枝是结构化剪枝里最常用的一种。它的核心思路很简单:找出不重要的通道,连根拔掉。
具体来说,YOLOv11 的每个卷积层都有多个输出通道。每个通道对应一个卷积核,卷积核的权重决定了这个通道对最终输出的贡献。如果某个通道的权重整体都很小,说明它对特征提取没什么用,砍掉它不会对精度造成太大影响。
我习惯把通道剪枝分为三步:
- 评估通道重要性:给每个通道打分,分数低的优先剪掉。
- 确定剪枝率:决定每层砍掉多少比例的通道。
- 重训恢复精度:剪完后微调模型,把精度拉回来。
这里面最关键的是第一步——怎么打分?
4.3 L1 范数与 BN 层权重
打分方法有很多,但最经典、最实用的两个指标是:卷积核的 L1 范数和BN 层的缩放因子 γ。
4.3.1 卷积核 L1 范数
一个卷积核的 L1 范数,就是它所有权重值之和。公式很简单:
L1 = sum(|w_i|) for i in 1..N
为什么 L1 范数能衡量重要性?
你想想看,如果一个卷积核的权重都很小,那它提取的特征响应肯定也弱。L1 范数大的卷积核,通常对应更强烈的特征模式。我在项目中做过对比实验:按 L1 范数排序剪枝,比随机剪枝的精度损失小 3-5 个百分点。
我的经验:L1 范数适合剪枝浅层卷积层。浅层特征比较通用,冗余度高,L1 范数能有效区分重要和不重要的通道。但到了深层,特征变得抽象,L1 范数的区分度会下降。
4.3.2 BN 层缩放因子 γ
YOLOv11 每个卷积层后面都跟着 BN 层。BN 层的计算公式是:
y = γ * (x - μ) / √(σ² + ε) + β
这里的 γ 就是缩放因子。它控制着这个通道的输出幅度。γ 接近 0 的通道,输出被压缩到很小,说明这个通道对后续层的影响微乎其微。
用 γ 做剪枝指标有个天然优势:训练过程中 γ 会自动学习。你只需要在训练时给 γ 加一个 L1 正则化项,模型自己就会把不重要的通道的 γ 推向 0。
注意:用 γ 剪枝时,一定要在训练时加入稀疏正则化。否则 γ 的分布会很均匀,剪枝效果大打折扣。我曾经踩过这个坑——直接拿预训练模型的 γ 剪枝,剪掉 30% 的通道,mAP 直接掉了 12 个点。
4.4 剪枝率与性能权衡
剪枝率越高,模型越小越快,但精度损失也越大。这个权衡是剪枝的核心矛盾。
我整理了一份 YOLOv11 在 COCO 数据集上的剪枝实验数据,供你参考:
| 剪枝率 | mAP@0.5 下降 | 推理加速比 | 模型体积缩减 | 推荐场景 |
|---|---|---|---|---|
| 0%(基线) | 0% | 1.0x | 0% | 精度优先 |
| 30% | 0.5% - 1.0% | 1.3x - 1.5x | 25% - 30% | 通用部署 |
| 50% | 1.5% - 3.0% | 1.8x - 2.2x | 45% - 50% | 边缘设备 |
| 70% | 4.0% - 8.0% | 2.5x - 3.0x | 65% - 70% | 极端加速 |
几点经验:
- 剪枝率 30% 以内:基本无损,微调 10-20 个 epoch 就能恢复。我一般把这个作为默认剪枝率。
- 剪枝率 30%-50%:精度会有轻微下降,但通过知识蒸馏或更长时间微调可以弥补。
- 剪枝率超过 50%:精度损失明显,需要精细调整每层的剪枝比例,不能一刀切。
我的建议:不要追求极限剪枝率。在实际项目中,30%-40% 的剪枝率 + 量化,往往能获得最佳的精度-速度平衡。剪太多,微调成本高,得不偿失。
4.5 本章知识结构图
下面这张图总结了通道剪枝的核心流程和关键决策点:
4.6 避坑指南
最后分享几个我踩过的坑:
- 不要对 shortcut 层随意剪枝:YOLOv11 有残差连接,剪掉 shortcut 的某个通道会导致维度不匹配。我一般对 shortcut 层设置剪枝率为 0,或者保持输入输出通道数一致。
- 剪枝后一定要重训 BN 层:剪完枝,模型结构变了,BN 层的统计量(μ 和 σ)需要重新计算。我习惯先 freeze 卷积层,只训 BN 层 5-10 个 epoch,再全量微调。
- 逐层剪枝比全局剪枝更稳:全局剪枝(所有层用同一个剪枝率)容易导致某些层被过度剪枝。我推荐逐层设定剪枝率,敏感层少剪,冗余层多剪。
小技巧:用 torch.nn.utils.prune 做快速原型验证,但生产环境建议自己写剪枝逻辑。框架自带的剪枝工具灵活性不够,尤其是处理 YOLOv11 这种多分支结构时。