4. 分层阈值剪枝

任务目标

实现分层阈值剪枝:为模型每一层独立计算剪枝阈值并移除低于该阈值的权重,与全局统一阈值剪枝进行稀疏度分布与精度对比。

核心实现

import torch
import torch.nn as nn
import torch.nn.utils.prune as prune
from torchvision.models import resnet18

def layerwise_prune(model, sparsity):
    """按层独立阈值剪枝:每层保留sparsity比例的最大权重"""
    for name, module in model.named_modules():
        if isinstance(module, nn.Conv2d) or isinstance(module, nn.Linear):
            # 计算该层独立阈值(按分位数)
            tensor = module.weight.data
            threshold = torch.quantile(tensor.abs().flatten(), sparsity)
            # 生成掩码:值大于阈值的保留
            mask = tensor.abs() > threshold
            prune.custom_from_mask(module, name='weight', mask=mask)
    return model

def global_threshold_prune(model, sparsity):
    """全局统一阈值:所有层共享一个阈值"""
    all_weights = []
    for module in model.modules():
        if isinstance(module, (nn.Conv2d, nn.Linear)):
            all_weights.append(module.weight.data.abs().flatten())
    all_weights = torch.cat(all_weights)
    threshold = torch.quantile(all_weights, sparsity)
    
    for module in model.modules():
        if isinstance(module, (nn.Conv2d, nn.Linear)):
            mask = module.weight.data.abs() > threshold
            prune.custom_from_mask(module, name='weight', mask=mask)
    return model

def measure_sparsity(model):
    """统计每层实际稀疏度"""
    stats = {}
    for name, module in model.named_modules():
        if isinstance(module, (nn.Conv2d, nn.Linear)):
            mask = module.weight_mask
            sparsity = 1 - mask.float().mean().item()
            stats[name] = sparsity
    return stats

# 使用示例
model = resnet18(pretrained=False)
# 分层剪枝:每层剪掉50%最小权重
layerwise_prune(model, sparsity=0.5)
layer_stats = measure_sparsity(model)

# 重置模型做全局剪枝对比
model2 = resnet18(pretrained=False)
global_threshold_prune(model2, sparsity=0.5)
global_stats = measure_sparsity(model2)

# 输出对比(示例输出,非实测)
print("分层剪枝各层稀疏度范围: {:.2f} ~ {:.2f}".format(
    min(layer_stats.values()), max(layer_stats.values())))
print("全局剪枝各层稀疏度范围: {:.2f} ~ {:.2f}".format(
    min(global_stats.values()), max(global_stats.values())))

运行与验证

  1. 依赖:PyTorch ≥1.9,torchvision(仅用于获取ResNet-18结构)。
  2. 执行:直接运行上述脚本,观察两种策略下各层稀疏度分布。
  3. 通过条件:
    - 分层剪枝后,各层稀疏度应接近设定的50%(因权重分布不同略有波动);
    - 全局剪枝后,权重分布较宽的层稀疏度偏高,较窄的层稀疏度偏低,层间差异明显大于分层剪枝;
    - 若需精度验证,可在剪枝后调用torch.optim微调模型,对比两种方法在验证集上的Top-1准确率。

关键说明

分层阈值剪枝的核心在于每层独立计算阈值,避免全局阈值对权重分布不均匀层造成过度剪枝或欠剪枝。实际应用中,通常对敏感层设置较低稀疏度、对冗余层设置较高稀疏度,而非统一50%。上述代码使用torch.nn.utils.prune实现掩码剪枝,剪枝后模型仍保留原始结构,可通过model.weight_mask查看掩码。

分层阈值 vs 全局阈值剪枝效果示意 分层阈值剪枝 层1: 48% 层2: 51% 层3: 47% 层4: 53% 层5: 49% 层6: 50% 全局阈值剪枝 层1: 22% 层2: 38% 层3: 71% 层4: 82% 层5: 30% 层6: 58% 分层剪枝各层稀疏度均匀;全局剪枝使权重分布窄的层稀疏度低、宽的层稀疏度高 分层均匀 全局不均