第2章:模型剪枝基础:结构化剪枝与非结构化剪枝的原理,L1与L2范数剪枝策略
大家好,欢迎来到第二章。今天咱们聊聊模型剪枝。
说实话,我刚入行那会儿,对剪枝这事儿挺不屑的。总觉得模型都训练好了,你咔嚓剪掉一堆参数,这不是瞎折腾吗?直到有一次,我在一个智能门锁项目上,芯片只有256KB的Flash,模型死活塞不进去。嗯,从那以后,我再也不敢小看剪枝了。
模型剪枝,说白了就是给神经网络“瘦身”。你想想看,一个训练好的大模型,里面其实有很多参数是冗余的。就像一棵大树,有些枝叶虽然茂盛,但真正支撑树干的就那么几根。剪掉多余的,树反而长得更好。
2.1 非结构化剪枝:精细到每个参数
非结构化剪枝,是我个人觉得最“暴力”的一种方式。它直接对单个权重下手——把那些不重要的权重直接置零。
怎么判断一个权重重不重要?最直观的办法就是看它的大小。如果一个权重值特别小,比如0.0001,它对最终结果的影响微乎其微。剪掉它,模型精度几乎不受影响。
我习惯用L1范数来做这个判断。L1范数就是所有权重绝对值之和。为什么用绝对值?因为正负号只是方向,大小才是关键。
核心思想:权重绝对值越小,对模型贡献越小,越应该被剪掉。
举个例子,假设我们有一个卷积层,里面有1000个权重。我们设定一个阈值,比如0.01。所有绝对值小于0.01的权重,统统置零。这样一来,这层就变成了一个稀疏矩阵。
# 非结构化剪枝的伪代码
def unstructured_pruning(weights, threshold=0.01):
mask = np.abs(weights) > threshold
pruned_weights = weights * mask
return pruned_weights, mask
这里有个坑,我曾经踩过——剪枝比例太高,模型直接崩了。比如你一口气剪掉90%的参数,精度可能从95%掉到60%。所以,我建议你从小比例开始试,比如10%、20%,慢慢往上加。
避坑指南:非结构化剪枝后,模型权重变成稀疏矩阵。但大多数硬件对稀疏矩阵的支持并不好。你剪完了,推理速度可能一点没变快,甚至更慢。我曾经在一个Cortex-M4芯片上试过,稀疏矩阵的推理效率反而下降了30%。
2.2 结构化剪枝:整块整块地剪
非结构化剪枝的问题在于,它破坏了矩阵的规则结构。硬件喜欢规整的数据,你给它一堆零散的稀疏矩阵,它处理起来很痛苦。
结构化剪枝就不一样了。它不剪单个权重,而是剪整个通道、整个滤波器、甚至整个层。说白了,就是整块整块地砍。
举个例子,一个卷积层有64个滤波器。结构化剪枝会评估每个滤波器的重要性,然后直接删掉最不重要的那几个。这样一来,输出通道数就从64变成了48,矩阵结构依然规整。
怎么评估一个滤波器的重要性?我常用L2范数。L2范数是所有权重平方和的平方根。它比L1更“敏感”——大的权重会被放大,小的权重会被压缩。
| 范数类型 | 计算公式 | 特点 |
|---|---|---|
| L1范数 | Σ|w_i| | 对大小均匀的权重敏感,鼓励稀疏 |
| L2范数 | √(Σw_i²) | 对异常大权重敏感,鼓励权重分布均匀 |
我个人习惯在结构化剪枝中用L2范数。为什么?因为一个滤波器里,如果大部分权重都挺大,只有几个特别小,L1可能会误判它不重要。但L2会抓住那几个大权重,给出一个合理的评估。
# 结构化剪枝的伪代码(按滤波器评估)
def structured_pruning(filters, prune_ratio=0.3):
# 计算每个滤波器的L2范数
l2_norms = [np.sqrt(np.sum(f**2)) for f in filters]
# 按重要性排序
sorted_idx = np.argsort(l2_norms)
# 剪掉最不重要的30%
keep_idx = sorted_idx[int(len(filters)*prune_ratio):]
return filters[keep_idx]
小技巧:结构化剪枝后,模型结构变了。你需要重新训练一下,让剩下的滤波器适应新的任务。我一般会做3-5个epoch的微调,效果就很好了。
2.3 L1与L2范数剪枝策略对比
咱们来对比一下这两种策略。说实话,没有绝对的好坏,关键看场景。
- L1范数剪枝:适合非结构化剪枝。它倾向于把不重要的权重直接清零,产生高度稀疏的模型。但稀疏矩阵的硬件加速是个问题。
- L2范数剪枝:适合结构化剪枝。它更关注权重的整体分布,适合评估滤波器或通道的重要性。剪完后模型结构规整,硬件友好。
我在一个语音唤醒项目上做过对比。用L1非结构化剪枝,剪掉50%的参数,精度掉了2%。但推理速度没变,因为硬件不支持稀疏加速。换成L2结构化剪枝,剪掉30%的滤波器,精度只掉了0.5%,推理速度反而快了40%。
你想想看,同样的剪枝比例,效果天差地别。所以,选对策略很重要。
2.4 实际项目中的选择建议
如果你问我,在实际项目中该怎么选?我的建议是这样的:
- 先看硬件:如果你的芯片支持稀疏矩阵加速(比如某些NPU),非结构化剪枝是首选。否则,老老实实用结构化剪枝。
- 再看精度要求:如果精度要求极高(比如医疗影像),剪枝比例控制在10%以内,用L2结构化剪枝更稳妥。
- 最后看开发周期:非结构化剪枝实现简单,但后续优化麻烦。结构化剪枝实现复杂一点,但一劳永逸。
我的经验:在嵌入式设备上,结构化剪枝+L2范数是最稳妥的组合。它不会破坏模型结构,硬件加速效果好,精度损失也小。我最近三个项目都是这么干的,效果都不错。
好了,这一章就到这里。下一章咱们聊聊量化——把32位浮点数变成8位整数,模型体积直接缩到四分之一。嗯,那才是真正的“瘦身”大法。