2. 剪枝原理
剪枝,说白了就是给模型「瘦身」。但怎么瘦,瘦哪里,这里头门道不少。我刚开始接触剪枝时,也踩过不少坑。今天咱们就把两种主流剪枝方式掰开揉碎讲清楚,再聊聊YOLOv11到底该用哪种。
一、非结构化剪枝:细粒度,但硬件不买账
非结构化剪枝,也叫细粒度剪枝。它直接干掉单个权重——那些值很小的权重,我们认为它对输出贡献不大,直接置零。
核心思想:权重值越小 → 对最终结果影响越小 → 可以干掉
举个例子,一个卷积核是3×3=9个权重,非结构化剪枝可能只保留其中4个,剩下5个变成0。这9个权重里,哪些保留哪些干掉?完全看数值大小。
我在项目中试过这种方案。精度确实能保住,但问题来了——
- 稀疏矩阵存储:权重矩阵变成稀疏的,需要特殊格式存储(如CSR、CSC),推理时也得用稀疏算子
- 硬件不友好:GPU、NPU对稀疏矩阵的支持很差,实际加速比远低于理论值
- 内存不连续:权重在内存里东一块西一块,访存效率低
我曾经踩过的坑:在YOLOv5上试过非结构化剪枝,剪掉70%的权重,精度只掉了0.3%。但部署到TensorRT上,推理速度几乎没变。为什么?因为硬件不支持稀疏计算,那些零权重还是占着位置,白剪了。
二、结构化剪枝:粗粒度,但立竿见影
结构化剪枝,也叫粗粒度剪枝。它不剪单个权重,而是剪整个「结构单元」——比如一个通道、一个卷积核、甚至一个层。
你想想看,一个卷积层有64个输出通道,结构化剪枝可能直接砍掉其中16个通道。剩下的48个通道继续工作,整个网络结构变得「窄」了。
| 对比维度 | 非结构化剪枝 | 结构化剪枝 |
|---|---|---|
| 剪枝粒度 | 单个权重 | 通道/卷积核/层 |
| 模型结构 | 不变(权重稀疏) | 变窄/变浅 |
| 存储方式 | 稀疏矩阵 | 普通稠密矩阵 |
| 硬件加速 | 依赖稀疏算子,支持差 | 原生支持,加速明显 |
| 精度恢复难度 | 相对容易(细调即可) | 较难(结构变化大) |
| 实际加速效果 | 理论高,实际低 | 理论实际一致 |
结构化剪枝的好处很明显:剪完后的模型还是「正常」的卷积层,不需要特殊算子,直接跑在现有硬件上就能加速。
我的建议:做工业级部署,优先选结构化剪枝。虽然精度恢复难度大一点,但加速效果是实打实的。非结构化剪枝更适合学术研究或硬件支持稀疏计算的场景。
三、YOLOv11适用的剪枝策略
YOLOv11的结构比前几代更复杂,有C2f模块、SPPF、Detect头等。剪枝策略不能一刀切,得对症下药。
3.1 通道剪枝(Channel Pruning)
这是YOLOv11最推荐的剪枝方式。具体做法是:
- 对每个卷积层的输出通道计算重要性(比如用BN层的gamma值)
- 按重要性排序,砍掉排名靠后的通道
- 连带砍掉下一层对应的输入通道
为什么用BN的gamma值?因为BN层的gamma值越大,说明这个通道对后续层的影响越大,越重要。gamma值接近0的通道,基本是「废通道」,砍掉不影响精度。
关键点:通道剪枝必须「层间联动」。你砍掉这一层的第i个输出通道,下一层的第i个输入通道也得砍掉,否则维度对不上。
3.2 层剪枝(Layer Pruning)
层剪枝更激进,直接干掉整个层或模块。YOLOv11的Backbone里有很多重复的C2f模块,有些层对最终精度的贡献很小。
我个人的习惯是:先做层剪枝,再做通道剪枝。层剪枝砍掉冗余模块,通道剪枝再精细化调整。
3.3 混合剪枝策略
针对YOLOv11的不同部位,我建议用不同的剪枝力度:
| 网络部位 | 推荐剪枝方式 | 剪枝比例建议 | 注意事项 |
|---|---|---|---|
| Backbone(C2f) | 通道剪枝 | 30%~50% | 保留浅层特征,深层可多剪 |
| Neck(FPN/PAN) | 通道剪枝 + 层剪枝 | 20%~40% | 特征融合层要谨慎,剪多了掉召回 |
| Detect Head | 不剪或微剪 | 0%~10% | 检测头对精度最敏感,尽量不动 |
| SPPF | 不剪 | 0% | SPPF负责多尺度特征,剪了影响大 |
注意:Detect Head的卷积层千万别乱剪。我试过把Head的通道剪掉30%,结果mAP直接掉了8个点,怎么调都回不来。Head是模型的「最后一道防线」,尽量保持原样。
四、剪枝流程总览
下面这张图是我在实际项目中总结的YOLOv11剪枝流程,你可以照着这个步骤来:
五、剪枝后的模型结构变化
剪枝完成后,YOLOv11的模型文件会发生变化。你可以用 torchinfo 或 netron 查看结构:
# 剪枝前
Backbone: C2f × 3 (64→128→256)
Neck: C2f × 2 (256→512)
Detect: 3 heads (256, 512, 1024)
# 剪枝后(通道剪枝50%)
Backbone: C2f × 3 (32→64→128)
Neck: C2f × 2 (128→256)
Detect: 3 heads (128, 256, 512)
看到没?通道数直接减半,但层数没变。这就是结构化剪枝的特点——模型变「瘦」了,没变「矮」。
小技巧:剪枝后记得用 torch.save(model.state_dict(), 'pruned.pth') 保存权重。下次加载时,要先定义剪枝后的模型结构,再加载权重。结构对不上会报错。
六、总结一下
非结构化剪枝精度好,但硬件不买账。结构化剪枝加速明显,但精度恢复需要技巧。YOLOv11推荐用通道剪枝为主,层剪枝为辅,Detect Head尽量不动。