4、模型轻量化技术(上):模型剪枝原理、结构化剪枝实战、Fine-tune恢复精度
模型剪枝,说白了就是给神经网络“瘦身”。
你想想看,一个训练好的YOLO模型,里面其实有很多参数是冗余的。有些神经元几乎不干活,有些权重小到可以忽略。把它们去掉,模型照样能跑,而且跑得更快。
我在项目中遇到过最夸张的一次——剪掉60%的参数,mAP只掉了0.3个点。嗯,这就是剪枝的魅力。
4.1 剪枝的核心原理
剪枝的本质,是在精度和速度之间找平衡。我们去掉不重要的连接或通道,让模型变小,然后通过微调把精度拉回来。
核心思想:参数越接近0,它对最终结果的影响就越小。把这些“接近0”的参数干掉,模型几乎感觉不到变化。
剪枝分两种:非结构化剪枝和结构化剪枝。我重点讲后者,因为它在边缘设备上真的能提速。
| 类型 | 粒度 | 硬件加速 | 部署难度 | 我的建议 |
|---|---|---|---|---|
| 非结构化剪枝 | 单个权重 | ❌ 几乎不支持 | 高(需要稀疏库) | 学术研究用,生产别碰 |
| 结构化剪枝 | 通道/层/卷积核 | ✅ 直接加速 | 低(直接改网络结构) | 边缘部署首选 |
为什么非结构化剪枝不实用?我曾经在Jetson Nano上试过,剪完后的稀疏模型,推理速度反而慢了。因为硬件没法跳过那些零值,计算量没变。结构化剪枝直接砍掉整个通道,计算量实打实地降下来了。
4.2 结构化剪枝实战:以YOLOv5s为例
我们拿YOLOv5s开刀。目标:把C3模块里的Bottleneck通道数砍掉一半。
步骤其实不复杂,就三步:
- 分析权重——找出每个通道的L1范数,范数小的就是不重要的。
- 生成剪枝掩码——设定一个阈值,低于阈值的通道直接标记为“删除”。
- 重建网络——根据掩码,重新生成一个更窄的网络结构。
我的经验:别一次性剪太狠。我习惯每次剪20%,然后评估一下精度。如果掉点超过1%,就回退。稳扎稳打比一刀切靠谱得多。
下面是一个简化的剪枝流程示意图:
代码实现上,我习惯用 torch.nn.utils.prune 配合自定义的通道选择逻辑。下面是一个核心片段:
import torch
import torch.nn.utils.prune as prune
def structured_prune_channel(module, name, amount):
"""
对指定模块的指定参数进行结构化剪枝
amount: 剪枝比例,0.2表示剪掉20%的通道
"""
# 使用L1范数作为重要性指标
prune.ln_structured(
module,
name=name,
amount=amount,
n=1, # L1范数
dim=0 # 对输出通道维度剪枝
)
# 移除剪枝掩码,使剪枝永久生效
prune.remove(module, name)
# 示例:对YOLOv5s的C3模块第2层卷积进行剪枝
# model.model[4].conv 假设是C3中的某个卷积层
structured_prune_channel(model.model[4].conv, 'weight', amount=0.2)
注意:剪枝后一定要调用 prune.remove(),否则模型里会残留掩码参数,导出时容易出问题。我曾经因为这个坑,在TensorRT转换时卡了一整天。
4.3 Fine-tune恢复精度
剪完枝,模型精度肯定会掉。这时候就需要微调(Fine-tune)来救场。
微调不是从头训练。它的策略完全不同:
- 学习率要小——我一般用原训练学习率的1/10,比如原先是0.01,微调就用0.001。
- 训练轮次要少——50到100个epoch足够了,太多反而过拟合。
- 冻结部分层——靠近输入层的特征提取层尽量不动,只微调后面的检测头。
我的习惯:微调时先用更小的学习率跑10个epoch做预热,让模型先适应新的结构。然后再恢复正常微调。这个小技巧帮我省了不少调参时间。
微调的效果有多好?我拿一个实际项目的数据说话:
| 阶段 | mAP@0.5 | 模型大小 | 推理速度(Jetson Nano) |
|---|---|---|---|
| 原始YOLOv5s | 0.742 | 14.4 MB | 45 ms |
| 剪枝50%后(未微调) | 0.651 | 7.2 MB | 28 ms |
| 剪枝50%后(微调50 epoch) | 0.731 | 7.2 MB | 28 ms |
看到了吗?微调把精度从0.651拉回到了0.731,只比原始低了0.011。但模型大小减了一半,速度快了将近40%。这笔账怎么算都划算。
4.4 避坑指南
做剪枝这几年,我踩过的坑不少。挑几个典型的说说:
- 别剪BN层——BatchNorm的参数虽然小,但它的作用大。剪掉BN层,模型训练直接崩。我吃过这个亏。
- 注意shortcut连接——YOLOv5里有残差连接,剪枝时要保证shortcut两边的通道数一致。否则维度对不上,程序直接报错。
- 剪枝后先跑一次前向推理——别急着微调。先拿一张图跑一遍,确保网络结构没断。这一步能帮你省下至少半天调试时间。
总结一下:结构化剪枝 + 微调,是边缘部署性价比最高的轻量化手段。剪掉不重要的通道,微调拉回精度,模型变小、速度变快、精度几乎不掉。这套组合拳,我在多个项目里屡试不爽。