模型剪枝基础:什么是剪枝?结构化剪枝与非结构化剪枝的区别
各位同学好,今天我们来聊聊模型剪枝。说实话,我第一次接触这个概念时,脑子里想的是「修剪树枝」。后来发现,还真有点像——把模型里那些「不干活」的参数去掉,让模型更轻便。
我在做模型部署时,经常遇到这样的困境:模型精度明明很高,但放到手机上就是跑不动。要么太大,要么太慢。这时候,剪枝就成了我的救命稻草。
什么是模型剪枝?
模型剪枝,说白了就是把神经网络中不重要的连接或神经元去掉。你想想看,一个训练好的模型里,很多参数其实贡献很小。去掉它们,模型照样能工作,甚至还能跑得更快。
我习惯把剪枝比作「瘦身」。一个200斤的壮汉和150斤的运动员,谁更灵活?当然是后者。模型也是这个道理。
核心思想:在保持模型精度的前提下,减少参数量和计算量。
剪枝的依据是什么?通常看权重的大小。权重接近0的,说明这个连接不重要,可以砍掉。当然,也有更复杂的判断标准,比如基于梯度、基于激活值等。但最常用的,还是看权重绝对值。
非结构化剪枝:精细但麻烦
非结构化剪枝,是对单个权重进行操作。它会将小于某个阈值的权重直接置为0。
举个例子,一个4x4的权重矩阵:
原始权重:
[[0.8, 0.1, 0.5, 0.02],
[0.3, 0.9, 0.01, 0.6],
[0.05, 0.4, 0.7, 0.03],
[0.2, 0.08, 0.6, 0.9]]
剪枝后(阈值0.1):
[[0.8, 0.0, 0.5, 0.0],
[0.3, 0.9, 0.0, 0.6],
[0.0, 0.4, 0.7, 0.0],
[0.2, 0.0, 0.6, 0.9]]
看到没?那些小于0.1的权重全变成了0。这样做的好处是精度损失小,因为只砍掉了最不重要的连接。
但问题来了——这种稀疏矩阵,在普通硬件上很难加速。我曾经在一个项目里试过,剪枝后模型大小确实小了,但推理速度几乎没变。为什么?因为CPU和GPU对稀疏矩阵的支持很差,你想想看,一堆0夹杂着非0值,计算起来反而更慢。
我的经验:非结构化剪枝适合在支持稀疏计算的专用硬件上使用,比如某些NPU。在普通GPU上,效果往往不如预期。
结构化剪枝:粗暴但有效
结构化剪枝就不一样了。它不针对单个权重,而是直接砍掉整个通道、整个滤波器或整个层。
比如在卷积层中,一个滤波器对应一个输出通道。结构化剪枝会判断哪些滤波器不重要,然后整个移除。
# 伪代码示例
# 假设有64个滤波器,我们想剪掉16个
# 计算每个滤波器的重要性(比如L1范数)
importance = [sum(abs(w)) for w in filters]
# 保留最重要的48个
keep_indices = argsort(importance)[-48:]
pruned_filters = filters[keep_indices]
这样做的好处很明显:剪枝后的模型结构是规整的,可以直接在现有硬件上加速。我做过测试,结构化剪枝后,推理速度能提升30%-50%,而且不需要特殊硬件支持。
但代价是什么?精度损失更大。你想想看,一刀切掉整个通道,肯定比只切几个权重来得猛。所以结构化剪枝通常需要配合微调(fine-tuning)来恢复精度。
两者的核心区别
| 对比维度 | 非结构化剪枝 | 结构化剪枝 |
|---|---|---|
| 剪枝粒度 | 单个权重 | 通道/滤波器/层 |
| 精度损失 | 较小 | 较大 |
| 加速效果 | 依赖硬件支持 | 直接有效 |
| 模型规整性 | 稀疏、不规则 | 规整、密集 |
| 部署难度 | 高(需特殊库支持) | 低(通用框架即可) |
实际项目中怎么选?
我个人习惯这样判断:
- 目标设备是手机或嵌入式设备:选结构化剪枝。因为这些设备上的推理框架(如TFLite、CoreML)对稀疏矩阵支持很差。
- 目标设备是专用AI芯片:可以试试非结构化剪枝。有些芯片专门优化了稀疏计算,能发挥出优势。
- 精度要求极高:先用非结构化剪枝试试,如果精度损失在可接受范围内,再考虑是否转结构化。
避坑指南:我曾经在一个项目中,花了大量时间做非结构化剪枝,剪到了90%的稀疏度。结果部署时发现,推理速度反而慢了20%。原因就是硬件不支持稀疏加速。从那以后,我每次做剪枝前,都会先确认目标平台的硬件特性。
TensorFlow中的剪枝工具
TensorFlow提供了tensorflow_model_optimization库,里面集成了剪枝API。使用起来很简单:
import tensorflow_model_optimization as tfmot
# 定义剪枝参数
pruning_params = {
'pruning_schedule': tfmot.sparsity.keras.ConstantSparsity(
target_sparsity=0.5, # 目标稀疏度50%
begin_step=0,
frequency=100
)
}
# 对模型应用剪枝
pruned_model = tfmot.sparsity.keras.prune_low_magnitude(
model, **pruning_params
)
# 训练时注意要使用回调
callbacks = [
tfmot.sparsity.keras.UpdatePruningStep(),
tfmot.sparsity.keras.PruningSummaries(log_dir='./logs')
]
pruned_model.fit(x_train, y_train, callbacks=callbacks)
这里要注意,prune_low_magnitude默认做的是非结构化剪枝。如果你想做结构化剪枝,需要自己实现或使用第三方库。
小结
嗯,今天的内容就到这里。总结一下:
- 剪枝就是去掉不重要的参数,让模型更轻便
- 非结构化剪枝精细但加速受限,适合专用硬件
- 结构化剪枝粗暴但加速明显,适合通用平台
- 选择哪种方式,取决于你的目标设备和精度要求
下一章,我会带大家动手实操,在TensorFlow中实现一个完整的剪枝流程。到时候你们会发现,其实剪枝没那么神秘,关键是要选对方法。
公众号:蓝海资料掘金营,微信deep3321