第3章:剪枝原理——权重重要性评估方法

剪枝说白了就是「砍掉不重要的连接」。但问题来了——你怎么知道哪个连接不重要?

我刚开始做模型压缩时,天真地以为直接砍掉值小的权重就行。结果模型直接崩了。后来才明白,权重重要性评估才是剪枝的核心。这一章,我们就来聊聊三种主流方法:L1/L2范数、BN层gamma值、梯度敏感度。

核心观点: 剪枝不是随机删除,而是基于某种重要性指标对权重排序,保留重要的,砍掉不重要的。

3.1 基于L1/L2范数的重要性评估

这是最直观的方法。L1范数是权重值之和,L2范数是权重平方和的平方根。数值越大,说明这个权重对输出的贡献越大。

我个人习惯用L1范数做初步筛选。为什么?因为它计算简单,而且对稀疏性更敏感。你想想看,如果一个卷积核的L1范数接近0,那它基本就是个「废核」。

方法 计算公式 特点 适用场景
L1范数 ||W||₁ = Σ|wᵢ| 对稀疏权重敏感,计算快 结构化剪枝(通道级)
L2范数 ||W||₂ = √(Σwᵢ²) 对大权重更敏感,考虑整体分布 非结构化剪枝(权重级)
我的经验: 在YOLOv11中,我通常先用L1范数对卷积核排序,砍掉底部20%的核,然后微调。这样基本不会掉点。

代码实现也很简单:

import torch
import torch.nn as nn

def l1_importance(conv_layer):
    """
    计算卷积层的L1范数重要性
    conv_layer: nn.Conv2d
    return: 每个输出通道的重要性分数
    """
    weight = conv_layer.weight.data  # shape: [out_channels, in_channels, k, k]
    importance = weight.abs().sum(dim=(1, 2, 3))  # 对每个输出通道求和
    return importance

# 使用示例
conv = nn.Conv2d(64, 128, 3)
scores = l1_importance(conv)
print(f"Top-5 重要通道: {scores.topk(5).indices}")
注意: L1/L2范数只考虑权重本身,不考虑输入数据的分布。如果某个通道的权重很大,但输入特征图全是0,那它其实也没用。这就是为什么我们需要更高级的方法。

3.2 基于BN层gamma值的重要性评估

YOLOv11里几乎每个卷积后面都跟着BN层。BN层的gamma参数(缩放因子)其实是个天然的重要性指标。

为什么?因为BN层的计算公式是:

y = gamma * (x - mean) / sqrt(var + eps) + beta

gamma值越小,说明这个通道的输出被压缩得越厉害。当gamma趋近于0时,这个通道的输出基本就是常数beta,对后续层几乎没有影响。

我在项目中遇到过一个问题:直接用L1范数剪枝,剪完后模型精度掉了3个点。后来换成BN gamma值排序,同样的剪枝率只掉了0.5个点。原因就是BN gamma值天然考虑了训练过程中的数据分布。

def bn_gamma_importance(bn_layer):
    """
    提取BN层的gamma值作为重要性指标
    bn_layer: nn.BatchNorm2d
    return: 每个通道的重要性分数
    """
    gamma = bn_layer.weight.data.abs()  # gamma的值
    return gamma

# 在YOLOv11中遍历所有BN层
def collect_gamma_scores(model):
    scores = {}
    for name, module in model.named_modules():
        if isinstance(module, nn.BatchNorm2d):
            scores[name] = bn_gamma_importance(module)
    return scores
关键点: BN gamma值剪枝是目前YOLO系列最主流的剪枝方法。它不需要额外计算,直接利用训练好的参数,而且效果稳定。

3.3 基于梯度敏感度的重要性评估

这个方法更「高级」一些。它考虑的是:如果我把某个权重置为0,损失函数会变化多少?

数学上,梯度敏感度定义为:

S(w) = |w * ∂L/∂w|

说白了,就是权重值乘以它的梯度。如果权重很大但梯度很小,说明这个权重已经收敛了,砍掉它影响不大。反过来,如果权重很小但梯度很大,说明它还在「挣扎」,砍掉它可能会让模型学不到东西。

我曾经在YOLOv11的检测头上试过梯度敏感度剪枝。效果确实比BN gamma好一点,但计算量大了不少——需要跑一次反向传播才能拿到梯度。

实用建议: 对于YOLOv11这种大模型,我建议在backbone用BN gamma剪枝,在head用梯度敏感度剪枝。这样既保证了效率,又兼顾了精度。
def gradient_sensitivity(model, dataloader, criterion):
    """
    计算每个权重的梯度敏感度
    需要一次前向+反向传播
    """
    model.train()
    sensitivity = {}
    
    # 注册hook来捕获梯度
    hooks = []
    for name, param in model.named_parameters():
        if param.requires_grad:
            sensitivity[name] = torch.zeros_like(param)
            def hook_fn(grad, n=name):
                sensitivity[n] = grad.abs() * param.data.abs()
            hooks.append(param.register_hook(hook_fn))
    
    # 跑一个batch
    images, targets = next(iter(dataloader))
    outputs = model(images)
    loss = criterion(outputs, targets)
    loss.backward()
    
    # 清理hook
    for h in hooks:
        h.remove()
    
    return sensitivity

3.4 三种方法的对比与选择

我把这三种方法放在一起对比过,结果如下:

方法 计算开销 精度保持 实现难度 推荐场景
L1/L2范数 低(无需额外计算) 中等 低 快速原型验证
BN gamma值 低(直接提取参数) 高 低 YOLO系列主流方案
梯度敏感度 高(需反向传播) 最高 中 关键层精细剪枝
避坑指南: 我曾经在剪枝时只用了L1范数,结果剪完发现某些通道虽然权重小,但却是残差连接的关键路径。砍掉后梯度传播断了,模型直接不收敛。所以,剪枝前一定要看清楚网络结构,特别是残差连接和跳连。

3.5 本章知识体系

下面这张图总结了三种重要性评估方法的核心逻辑:

权重重要性评估方法体系 权重重要性评估方法体系 权重重要性评估 L1/L2范数 BN层gamma值 梯度敏感度 特点 计算简单,直接基于权重 不考虑输入数据分布 适合快速原型验证 特点 利用训练好的缩放因子 考虑数据分布 YOLO系列主流方案 特点 考虑梯度信息 精度最高但计算量大 适合关键层精细剪枝 选择建议:BN gamma值 > 梯度敏感度 > L1/L2范数