2. 基于L1范数的非结构化剪枝
任务目标
对预训练模型执行基于L1范数的非结构化剪枝:按权重值大小排序,移除最小权重的连接,使权重矩阵变为稀疏。通过比较不同剪枝率下的实际稀疏度与模型精度,验证剪枝率对模型性能的影响。
环境依赖
Python 3.8+,PyTorch 2.0+,torchvision,NumPy。数据集使用CIFAR-10,模型使用预训练的ResNet-18(torchvision.models.resnet18(pretrained=True))。
核心实现
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
# ---------- 1. 数据加载 ----------
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = DataLoader(testset, batch_size=256, shuffle=False, num_workers=2)
# ---------- 2. 模型与评估 ----------
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = torchvision.models.resnet18(pretrained=True).to(device)
model.eval()
def evaluate(model, loader):
correct = 0
total = 0
with torch.no_grad():
for images, labels in loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
return 100.0 * correct / total
# ---------- 3. L1范数非结构化剪枝 ----------
def prune_l1(model, prune_ratio):
"""按L1范数(值)剪枝:将值最小的prune_ratio比例权重置零"""
for name, param in model.named_parameters():
if 'weight' not in name:
continue
weight = param.data.view(-1)
k = int(weight.numel() * prune_ratio)
if k == 0:
continue
# 找到值最小的k个权重的索引
_, indices = torch.topk(weight.abs(), k, largest=False)
# 将对应权重置零
mask = torch.ones_like(weight, dtype=torch.bool)
mask[indices] = False
param.data.mul_(mask.view(param.data.shape))
def compute_sparsity(model):
"""计算模型整体稀疏度(零权重占比)"""
total_zeros = 0
total_params = 0
for name, param in model.named_parameters():
if 'weight' in name:
total_zeros += (param.data == 0).sum().item()
total_params += param.data.numel()
return 100.0 * total_zeros / total_params
# ---------- 4. 不同剪枝率对比 ----------
prune_ratios = [0.0, 0.3, 0.5, 0.7, 0.9]
print(f"{'目标剪枝率':<10} {'实际稀疏度':<12} {'精度(%)':<10}")
for ratio in prune_ratios:
# 每次从原始预训练模型重新开始,避免累积剪枝
model = torchvision.models.resnet18(pretrained=True).to(device)
model.eval()
if ratio > 0:
prune_l1(model, ratio)
sparsity = compute_sparsity(model)
acc = evaluate(model, testloader)
print(f"{ratio:<10.2f} {sparsity:<12.2f} {acc:<10.2f}")
运行方式
- 安装依赖:
pip install torch torchvision numpy - 将上述代码保存为
l1_pruning.py,直接运行:python l1_pruning.py - 首次运行会自动下载CIFAR-10测试集(约163MB)和ResNet-18预训练权重。
验证方法与判定标准
程序输出5行结果,每行包含目标剪枝率、实际稀疏度和精度。判定通过的准则:
- 稀疏度匹配:实际稀疏度与目标剪枝率基本一致(误差<1%),证明剪枝逻辑正确。
- 精度趋势:剪枝率0%时精度约为85%(ResNet-18在CIFAR-10上的正常水平);剪枝率30%时精度下降不超过2%;剪枝率50%时精度下降不超过5%;剪枝率90%时精度显著下降(通常低于50%)。若精度变化符合此趋势,说明L1剪枝有效且评估流程正确。
- 稀疏度单调性:剪枝率从0%到90%,实际稀疏度严格递增。
关键说明
- 剪枝对象仅限卷积层和全连接层的
weight参数,不处理bias和BN层参数,这是非结构化剪枝的标准做法。 - 每次剪枝都从原始预训练模型重新加载,避免不同剪枝率之间的累积干扰。
- 本案例只验证剪枝后的精度,不包含微调(fine-tuning)步骤。实际工程中剪枝后通常需要微调恢复精度,但本案例目标仅是比较剪枝率的影响,因此不做微调。