模型剪枝基础:剪枝原理与稀疏性概念
剪枝,说白了就是给模型「瘦身」。你想想看,一个训练好的 YOLO 模型里,有大量权重其实接近于零,或者对最终输出贡献很小。把这些冗余参数去掉,模型照样能跑,而且跑得更快。
我刚开始接触剪枝时,总觉得这玩意儿很玄乎。后来亲手试了一次,把 YOLOv5 的参数量砍掉 40%,mAP 只掉了 0.5 个点,我才真正信了。嗯,这里面的门道,咱们今天好好捋一捋。
剪枝的核心原理
剪枝的本质,就是根据某种重要性指标,把不重要的参数「干掉」。这个重要性怎么衡量?常见的有三种思路:
- 基于权重大小:值越小的权重,对输出的影响越小,优先剪掉。
- 基于梯度信息:梯度小的参数,说明它对损失函数的贡献不大,可以剪。
- 基于激活值:某个通道的输出激活值普遍很低,说明这个通道是「闲人」,可以裁掉。
我在项目中用得最多的是权重大小法,简单直接,效果也够用。不过要注意,剪枝不是一次性剪到位,而是「剪一点→微调→再剪一点」的迭代过程。
核心公式:剪枝后的模型精度 ≈ 原始精度 - 剪枝率 × 冗余度系数
冗余度系数取决于模型本身的设计冗余,一般 YOLO 模型在 30%~50% 剪枝率下,精度损失可控。
结构化剪枝 vs 非结构化剪枝
这是剪枝领域最基础的一个二分法。我当年在这上面踩过坑,所以特意拿出来细讲。
| 对比维度 | 非结构化剪枝 | 结构化剪枝 |
|---|---|---|
| 剪枝粒度 | 单个权重 | 通道、卷积核、层 |
| 稀疏模式 | 不规则稀疏 | 规则稀疏 |
| 硬件加速 | 需要专用硬件/库 | 通用硬件直接加速 |
| 精度保留 | 通常更好 | 可能略差 |
| 部署难度 | 高(需稀疏矩阵库) | 低(直接删通道) |
非结构化剪枝
非结构化剪枝,就是把权重矩阵里那些值小的元素直接置零。结果就是得到一个「千疮百孔」的稀疏矩阵。
# 非结构化剪枝示例
import torch
import torch.nn.utils.prune as prune
# 对卷积层进行非结构化剪枝,剪掉 50% 的权重
prune.l1_unstructured(module.conv1, name='weight', amount=0.5)
# 剪枝后权重变成稀疏的,但形状不变
print(module.conv1.weight) # 很多元素为 0
这种方法的优点是精度损失小,缺点嘛——我当年在 Jetson 上部署时才发现,普通硬件根本加速不了这种不规则稀疏。你得用 NVIDIA 的 cuSPARSE 库或者专门的稀疏推理引擎,否则剪了等于白剪。
我曾经踩过的坑:在 PC 上做非结构化剪枝,推理速度反而变慢了。原因很简单——稀疏矩阵的存储和计算开销,比直接做密集矩阵乘法还大。所以非结构化剪枝更适合那些有专用稀疏加速硬件的场景。
结构化剪枝
结构化剪枝就实在多了。它直接干掉整个通道、整个卷积核,或者整层。剪完之后,模型的宽度或深度直接变小,任何硬件都能享受加速红利。
# 结构化剪枝(通道级)伪代码
def prune_channel(model, prune_ratio):
for layer in model.conv_layers:
# 计算每个通道的重要性(比如 BN 层的 gamma 值)
importance = layer.bn.weight.abs()
# 按重要性排序,剪掉最不重要的通道
threshold = torch.quantile(importance, prune_ratio)
mask = importance > threshold
# 保留重要通道,删除不重要通道
layer.apply_mask(mask)
结构化剪枝是我在实际项目中的首选。虽然精度损失比非结构化稍大,但胜在部署友好——剪完直接导出 ONNX,在 TensorRT 上跑,速度提升立竿见影。
稀疏性概念
稀疏性,就是模型里零元素的比例。比如一个权重矩阵有 80% 的元素是零,那它的稀疏度就是 80%。
稀疏性分两种:
- 权重稀疏:单个权重值为零的比例。非结构化剪枝追求的就是这个。
- 结构稀疏:整个通道或层被剪掉的比例。结构化剪枝的结果。
你可能会问:「稀疏度越高越好吗?」不一定。我见过有人把稀疏度推到 95%,结果模型直接崩了,mAP 掉到个位数。稀疏度有个临界点,过了这个点,精度会断崖式下跌。
我的经验值:对于 YOLOv5/v8 这类模型,权重稀疏度控制在 60%~80% 比较安全;结构稀疏度(通道剪枝)控制在 30%~50%。超过这个范围,就需要更精细的微调策略来补救。
剪枝粒度:权重级、通道级、层级
剪枝粒度决定了你「剪得多细」。粒度越细,剪枝的灵活性越高,但管理成本也越大。
权重级剪枝
就是前面说的非结构化剪枝。每个权重独立判断是否保留。灵活性最高,但稀疏模式不规则。
- 优点:精度保留好,可以精细控制每个参数
- 缺点:硬件加速困难,存储格式复杂
- 适用场景:学术研究、有专用硬件的部署环境
通道级剪枝
这是结构化剪枝中最常用的粒度。直接剪掉整个通道(即特征图的一个切片)。
- 优点:硬件友好,直接减少计算量和显存
- 缺点:灵活性不如权重级,可能误伤重要通道
- 适用场景:工业部署、边缘设备、移动端
通道级剪枝的关键在于如何评估通道的重要性。我常用的方法有:
- BN 层 gamma 值:BN 层的缩放系数 gamma 越小,说明这个通道越不重要,优先剪。
- 激活值统计:在验证集上跑一遍,统计每个通道的平均激活值,低的剪掉。
- 梯度敏感度:计算每个通道对损失的梯度,梯度小的通道贡献小。
层级剪枝
层级剪枝最粗暴——直接删掉整个卷积层或残差块。这种粒度适合那些冗余度特别高的深层网络。
- 优点:压缩效果最明显,推理加速最大
- 缺点:精度损失大,需要仔细选择要删的层
- 适用场景:超大模型、对速度要求极高的场景
层级剪枝我一般不太敢用,除非模型真的太大。YOLO 系列本身设计就比较紧凑,层级剪枝容易伤筋动骨。不过对于像 YOLOv8x 这种大模型,适当删掉最后几个高分辨率层,有时反而能提升小目标检测的召回率——这个现象挺有意思的。
三种剪枝粒度的对比
| 粒度 | 压缩率 | 精度保留 | 加速效果 | 部署难度 | 推荐场景 |
|---|---|---|---|---|---|
| 权重级 | 高 | 高 | 低(无专用硬件) | 高 | 研究、专用硬件 |
| 通道级 | 中 | 中 | 高 | 低 | 工业部署、边缘端 |
| 层级 | 极高 | 低 | 极高 | 低 | 超大模型、极限加速 |
剪枝知识体系总览
下面这张图把本章的核心概念串起来了。你可以把它当作剪枝的「地图」,后续章节都会围绕这些节点展开。
这张图把剪枝原理、稀疏性、剪枝粒度三个核心概念串在了一起。你可以看到,剪枝原理是「为什么剪」,稀疏性是「剪了多少」,剪枝粒度是「怎么剪」。三者缺一不可。
我的建议:初学者先从通道级结构化剪枝入手。它最容易上手,效果也最直观。等你把通道级剪枝玩熟了,再去研究权重级和层级剪枝,会轻松很多。