模型剪枝:给神经网络“瘦身”的艺术
模型剪枝,说白了就是给神经网络做“减法”。
你想想看,一个训练好的大模型,里面其实有很多冗余的权重和神经元。就像一棵大树,有些枝丫是多余的,剪掉反而能让主干长得更好。我在做边缘端部署时,经常遇到模型太大、跑不动的情况。这时候,剪枝就是我第一个想到的办法。
非结构化剪枝 vs 结构化剪枝
先说说两种最基础的剪枝方式。
非结构化剪枝,也叫细粒度剪枝。它直接干掉单个的权重参数。比如某个权重值接近0,我就把它置为0。这种方式压缩率很高,但有个致命问题——剪完后的权重矩阵变得稀疏,普通硬件根本加速不了。我曾在某款AI芯片上试过,稀疏矩阵推理速度反而更慢,因为硬件不支持稀疏计算。
结构化剪枝就不一样了。它剪掉的是整个滤波器(Filter)或整个通道(Channel)。剪完之后,网络结构还是规整的,硬件友好度极高。我个人的习惯是:能上结构化剪枝,就绝不碰非结构化的。
核心区别一句话总结:
- 非结构化剪枝:剪权重,精度损失小,但硬件加速难
- 结构化剪枝:剪通道,精度损失稍大,但推理速度快
细粒度剪枝与通道剪枝
细粒度剪枝就是上面说的非结构化剪枝。它逐个权重地判断要不要保留。阈值怎么定?我一般用全局阈值法:把所有权重的绝对值排个序,设定一个剪枝比例,比如剪掉50%最小的权重。
通道剪枝则是结构化剪枝的代表。它评估每个通道的重要性,然后直接砍掉整个通道。评估方法有很多,比如看通道权重的L1范数、看通道对输出的贡献度等等。
我曾经在项目中做过对比:
| 剪枝方式 | 压缩比 | 精度损失 | 推理加速 |
|---|---|---|---|
| 细粒度剪枝(50%) | 2x | 0.3% | 几乎无加速 |
| 通道剪枝(50%) | 2x | 1.2% | 1.8x |
你看,通道剪枝虽然精度损失大一点,但推理速度实打实地提升了。对于边缘端来说,这个交换是值得的。
剪枝后的微调策略
剪完枝,模型精度肯定会掉。这时候就需要微调(Fine-tuning)。
我踩过最大的坑是什么?一次性剪太多。我曾经一上来就剪掉70%的通道,结果模型直接崩了,怎么微调都回不来。后来学乖了,采用迭代剪枝策略:
- 训练一个基线模型
- 剪掉10%-20%的通道
- 微调几个epoch,让模型恢复
- 重复步骤2-3,直到达到目标压缩率
微调时,学习率要调小。我一般用初始学习率的1/10,比如原来用0.01,微调时就用0.001。另外,只微调几个epoch就够了,没必要从头训起。
我的小技巧:微调时,把剪枝掉的通道对应的梯度直接mask掉,不让它们更新。这样可以防止剪掉的权重又“复活”。
我在项目中把模型缩小了4倍的经验
去年有个项目,要把一个ResNet-50部署到树莓派上。原始模型200多MB,树莓派根本跑不动。我用了通道剪枝,目标是把模型缩小到原来的1/4。
具体做法是这样的:
- 先分析每一层通道的重要性。我用的是BN层gamma值作为重要性指标。gamma值越小,说明这个通道越不重要。
- 设定一个全局剪枝率,比如50%。但不同层的重要性不一样,我让剪枝率逐层自适应:重要层少剪,不重要层多剪。
- 剪完之后,模型从200MB降到了50MB左右。精度从原来的76.3%降到了74.8%,损失了1.5%。
- 然后微调了10个epoch,精度恢复到了75.9%。
嗯,这里要注意:剪枝不是万能的。如果模型本身已经很小了,比如MobileNet,再剪枝效果就不明显。我试过把MobileNet剪掉30%,精度直接掉了5个点,得不偿失。
避坑指南:我曾经在剪枝后直接部署,结果发现推理速度没提升多少。后来排查发现,是因为剪枝后的网络虽然通道数少了,但内存访问模式没变,导致缓存命中率低。解决办法是:剪枝后做一次网络重参数化,把剪掉的通道彻底从计算图中移除。
知识体系图:剪枝的核心逻辑
这张图把剪枝的核心逻辑串起来了。从根节点出发,左边是非结构化(细粒度),右边是结构化(通道),底部是微调策略。我个人建议:优先走右边这条路,除非你的硬件支持稀疏计算。
最后说一句:剪枝不是终点,而是起点。剪完枝之后,通常还要配合量化、蒸馏等技术,才能把模型压到极致。但那是后面章节的内容了。
公众号:蓝海资料掘金营,微信deep3321