2、模型剪枝基础:剪枝的定义、分类与核心思想

好,咱们正式开始聊剪枝。说实话,我第一次接触模型剪枝是在一个边缘端的人脸识别项目上。模型跑在树莓派上,帧率只有可怜的3帧,CPU风扇呼呼转,跟开拖拉机似的。当时我就在想,这模型里到底有多少参数是真正有用的?

后来我明白了,剪枝就是干这个的——把模型里那些「摸鱼」的参数给裁掉,让模型轻装上阵。

2.1 剪枝的定义:到底在剪什么?

模型剪枝,说白了就是从训练好的大模型中,移除那些对最终结果影响不大的参数或结构。你想想看,一个上亿参数的模型,里面很多权重值其实非常小,接近零。这些参数对推理结果的贡献微乎其微,但计算它们却要消耗实实在在的算力。

我个人的理解是:剪枝就像给一棵大树修剪枝叶。主干和粗枝保留,那些细枝末节砍掉,树照样活得好好的,甚至长得更壮实。

核心定义:模型剪枝 = 在保持模型精度可接受的前提下,通过移除冗余参数来减小模型体积、降低计算量。

这里有个关键点——「保持精度可接受」。不是剪完精度一点不掉,而是掉在可容忍范围内。我在项目中一般设定精度下降不超过1%就算成功。

2.2 剪枝的分类:结构化 vs 非结构化

剪枝的分类,我习惯从剪枝粒度来划分。这是最实用的分法,直接决定了你后续能不能拿到硬件加速收益。

2.2.1 非结构化剪枝

非结构化剪枝,也叫细粒度剪枝。它剪的是单个权重。比如一个卷积核有9个权重,我把其中3个接近零的权重直接置为零。

优点很明显:
- 剪枝率可以很高,甚至能剪掉90%的参数
- 精度损失相对较小

缺点也很致命:
- 权重矩阵变得稀疏,零值分布不规则
- 普通硬件(CPU/GPU)很难加速这种稀疏计算
- 需要专门的稀疏计算库(如NVIDIA的cuSPARSE)

我的经验:非结构化剪枝更适合学术研究。我在实际部署中很少用,因为边缘端的芯片(比如瑞芯微、地平线)基本不支持这种不规则稀疏。你剪完了,推理速度反而可能变慢,因为多了很多判断零值的开销。

2.2.2 结构化剪枝

结构化剪枝,剪的是整个结构单元。比如:
- 剪掉整个卷积核(channel pruning)
- 剪掉整层(layer pruning)
- 剪掉整个注意力头(head pruning)

这才是工业界的主流做法。为什么?因为剪完后模型结构还是规整的,可以直接跑在现有的推理框架上,不需要任何特殊支持。

对比维度 非结构化剪枝 结构化剪枝
剪枝粒度 单个权重 卷积核/通道/层
压缩率 高(可达90%+) 中等(通常30%-50%)
硬件加速 困难,需专用库 直接支持,无需改动
精度损失 较小 相对较大
部署难度

注意:结构化剪枝虽然部署友好,但剪枝率过高时精度会断崖式下跌。我曾经在一个分类模型上尝试剪掉60%的通道,结果准确率直接从95%掉到了82%,完全不可用。后来调整到40%,精度只掉了0.3%。所以,剪枝率不是越高越好,得找到那个平衡点。

2.3 剪枝的核心思想:重要性评估

剪枝的核心思想其实就一句话:找到不重要的参数,然后干掉它们

但问题来了——怎么判断一个参数「不重要」?

我总结了几种主流方法:

2.3.1 基于权重大小

这是最直观的方法。权重绝对值越小,说明它对输出的影响越小。直接按权重绝对值排序,剪掉最小的那批。

# 伪代码示例
import numpy as np

def magnitude_prune(weights, prune_ratio):
    # 计算权重的绝对值
    abs_weights = np.abs(weights)
    # 找到剪枝阈值
    threshold = np.percentile(abs_weights, prune_ratio * 100)
    # 生成掩码
    mask = abs_weights > threshold
    return weights * mask

这个方法简单粗暴,但有个坑:不同层的权重分布差异很大。第一层的权重可能普遍偏大,最后一层普遍偏小。如果全局统一剪枝,最后一层可能被剪光。我一般会分层剪枝,每层单独设定阈值。

2.3.2 基于梯度

权重小不代表不重要。有些权重虽然小,但梯度很大,说明它对损失函数很敏感。这时候应该保留它。

常用的指标是:权重 × 梯度(一阶泰勒展开近似)。这个值越大,说明参数越重要。

避坑指南:我曾经在一个Transformer模型上只用权重大小剪枝,结果剪完后模型直接崩了。后来改用梯度加权的方法,同样的剪枝率,精度只掉了0.5%。所以,对于注意力层这种梯度变化剧烈的结构,建议用梯度信息辅助判断。

2.3.3 基于结构化重要性

对于结构化剪枝,我们需要评估整个通道或卷积核的重要性。常用方法:

  • BN层缩放因子:BatchNorm层的γ值越小,说明这个通道越不重要。直接按γ值排序剪枝。
  • 通道激活值:统计每个通道在验证集上的平均激活值,激活值低的通道可以剪掉。
  • 通道相关性:计算通道之间的相关性,冗余度高的通道可以合并或剪掉。

我个人最常用的是BN层缩放因子法。原因很简单——不需要额外计算,BN层的γ值本来就是训练好的,直接拿来用就行。

2.4 剪枝的完整流程

一个标准的剪枝流程通常包含三步:

  1. 预训练:先训练一个完整的大模型,作为剪枝的起点
  2. 剪枝:根据重要性评估,移除不重要的参数
  3. 微调:对剪枝后的模型进行少量训练,恢复精度

这里有个常见的误区——很多人觉得剪完枝直接就能用。其实不行。剪枝后的模型精度一定会掉,必须微调才能恢复。微调一般只需要原始训练量的10%-20%的迭代次数就够了。

关键结论:剪枝不是一锤子买卖。我建议采用「迭代剪枝」策略——每次剪掉一小部分(比如10%),然后微调,再剪,再微调。这样比一次性剪掉50%再微调的效果好得多。

2.5 小结

嗯,这一章的内容差不多就这些。总结一下:

  • 剪枝就是去掉模型里不重要的参数
  • 非结构化剪枝精度高但部署难,结构化剪枝部署友好但精度损失大
  • 核心思想是重要性评估,方法有基于权重、基于梯度、基于结构化指标
  • 剪枝后一定要微调,推荐迭代剪枝策略

下一章我会带大家手写一个简单的剪枝工具,从零开始剪一个CNN模型。到时候咱们直接上代码,看看剪枝到底是怎么操作的。