3、模型剪枝技术:结构化剪枝与非结构化剪枝、剪枝策略(L1/L2范数、基于梯度)、剪枝后的微调与恢复
模型剪枝,说白了就是给神经网络“减肥”。
我刚开始接触剪枝时,总觉得这玩意儿挺玄乎——把模型里的参数砍掉一部分,精度居然还能保住?后来踩过几次坑才明白,剪枝的本质是去掉那些“不重要”的连接或通道。就像你整理书架,把不常看的书收进储藏室,常用的书留在手边,阅读效率反而更高。
3.1 非结构化剪枝:细粒度,但硬件不友好
非结构化剪枝,也叫细粒度剪枝。它直接干掉单个权重参数,把那些接近零的数值直接置零。
举个例子,一个卷积核是 3x3 的矩阵:
原始权重:
[[0.1, 0.8, -0.02],
[0.0, 0.5, 0.01],
[0.9, 0.3, 0.001]]
剪枝后(阈值 0.05):
[[0.1, 0.8, 0.0],
[0.0, 0.5, 0.0],
[0.9, 0.3, 0.0]]
你看,绝对值小于 0.05 的权重都被干掉了。这样做的好处是压缩率很高,模型体积能大幅缩小。但问题也很明显——权重矩阵变得稀疏且不规则,普通硬件(比如 GPU)没法高效计算。你想想看,GPU 擅长的是密集矩阵乘法,突然冒出一堆零,计算单元就闲置了。
3.2 结构化剪枝:粗粒度,硬件友好
结构化剪枝就聪明多了。它不剪单个权重,而是直接干掉整个通道、整个卷积核,甚至整个层。
我个人习惯用通道剪枝。比如一个卷积层有 64 个输出通道,剪掉 16 个不重要的,剩下 48 个。这样一来,网络结构变窄了,但形状还是规整的,GPU 照样跑得飞起。
# 伪代码示例:按通道 L2 范数剪枝
def channel_prune(conv_weight, prune_ratio):
# conv_weight shape: [out_channels, in_channels, k_h, k_w]
l2_norms = torch.norm(conv_weight.view(conv_weight.size(0), -1), dim=1)
threshold = torch.quantile(l2_norms, prune_ratio)
mask = l2_norms > threshold
return conv_weight[mask] # 保留重要通道
嗯,这里要注意:结构化剪枝虽然硬件友好,但压缩率通常不如非结构化剪枝。你想想看,一刀切掉整个通道,损失的信息量肯定比只切几个权重多。所以实际项目中,我一般先用结构化剪枝砍掉 20%-30% 的通道,再用非结构化剪枝做精细调整。
3.3 剪枝策略:怎么判断哪些参数该砍?
剪枝的核心问题就一个:怎么定义“不重要”?
我常用的策略有三种,各有各的脾气。
3.3.1 L1/L2 范数剪枝
这是最直观的方法。计算每个权重或每个通道的 L1 或 L2 范数,范数越小,说明这个参数对输出的贡献越小,优先剪掉。
| 策略 | 计算公式 | 特点 |
|---|---|---|
| L1 范数 | ||w||₁ = Σ|wᵢ| | 倾向于产生稀疏解,对异常值不敏感 |
| L2 范数 | ||w||₂ = √(Σwᵢ²) | 对大权重更敏感,保留能量大的通道 |
我在项目中遇到过这种情况:用 L1 范数剪枝,剪完后模型精度掉得比预期快。后来发现是因为 L1 范数对所有权重一视同仁,而有些通道虽然范数小,但负责的是关键特征(比如边缘检测)。所以我现在更倾向于用 L2 范数,或者两者结合。
3.3.2 基于梯度的剪枝
范数只看权重本身的大小,但梯度能告诉我们权重对 loss 的影响程度。
具体做法是:在训练过程中,记录每个权重的梯度值。如果一个权重的值很小,而且梯度也很小,说明它既不重要,也不在优化方向上,可以放心剪掉。
# 基于梯度与权重的混合重要性评分
importance = abs(weight) * abs(gradient)
这个公式看着简单,但效果出奇的好。我习惯在训练的最后几个 epoch 开始收集梯度信息,然后做一次性剪枝。为什么?因为训练初期梯度波动大,容易误判。
3.4 剪枝后的微调与恢复
剪枝不是终点,微调才是关键。你想想看,模型被砍掉一部分参数后,剩下的参数需要重新适应新的网络结构。
我见过不少新手,剪完枝直接部署,结果精度暴跌 10 个点。然后跑来问我:“是不是剪枝算法有问题?”其实不是算法的问题,是没做微调。
3.4.1 标准微调流程
- 剪枝后重置学习率:我一般把学习率设为初始训练时的 1/10。比如原来用 0.01,微调时用 0.001。
- 冻结未剪枝层:如果只剪了部分层,其他层可以先冻结,只训练被剪的层。等被剪层恢复精度后,再解冻所有层做联合微调。
- 迭代剪枝:不要一次剪太多。我习惯每次剪 10%-15%,然后微调 5-10 个 epoch,再继续剪。这叫“渐进式剪枝”。
3.4.2 精度恢复的“黄金法则”
根据我的经验,剪枝后微调能否恢复精度,取决于三个因素:
- 剪枝率:低于 30% 的剪枝,微调后基本能恢复原精度。超过 50%,精度损失就很难补回来了。
- 模型冗余度:大模型(比如 ResNet-50)比小模型(比如 MobileNet)更能扛剪枝。因为大模型本身就有冗余。
- 微调时长:我建议至少微调原训练周期的 20%。比如原模型训练了 100 个 epoch,微调至少 20 个 epoch。
🔑 核心要点总结:
- 非结构化剪枝:压缩率高,但硬件不友好,适合存储优化
- 结构化剪枝:硬件友好,适合推理加速,但压缩率有限
- 剪枝策略:L2 范数 + 梯度混合策略最稳健
- 微调是必须的,渐进式剪枝比一次性剪枝效果好得多
3.5 本章知识体系图
下面这张图帮你理清剪枝技术的整体脉络:
这张图把剪枝的完整流程串起来了。从选择剪枝类型,到确定剪枝策略,再到最后的微调恢复,每一步都有讲究。我个人觉得,最关键的还是微调这一步——剪枝只是手段,恢复精度才是目的。
好了,这一章的内容就到这里。剪枝技术说难不难,说简单也不简单,关键是多动手试。你可以在自己的模型上跑一遍,看看不同剪枝策略的效果差异,感受会更直观。