3. 非结构化剪枝与稀疏推理

任务说明

本案例针对一个已训练好的两层全连接网络(MNIST手写数字分类),实施非结构化剪枝:将权重中值小于阈值的单个参数置零,生成稀疏权重矩阵;随后利用稀疏矩阵存储与计算(scipy.sparse)完成前向推理,对比稠密与稀疏两种推理路径的输出一致性,并统计稀疏度与推理耗时。

环境依赖

  • Python 3.8+
  • NumPy
  • SciPy
  • (可选)scikit-learn 用于加载 MNIST 子集,若不可用可自行准备输入向量

完整实现

import numpy as np
import time
from scipy import sparse

# ---------- 1. 固定随机种子,构造可复现的两层网络 ----------
np.random.seed(0)
input_dim, hidden_dim, output_dim = 784, 128, 10
W1 = np.random.randn(input_dim, hidden_dim).astype(np.float32) * 0.1
b1 = np.zeros(hidden_dim, dtype=np.float32)
W2 = np.random.randn(hidden_dim, output_dim).astype(np.float32) * 0.1
b2 = np.zeros(output_dim, dtype=np.float32)

# ---------- 2. 非结构化剪枝:值阈值置零 ----------
threshold = 0.05
W1_pruned = np.where(np.abs(W1) < threshold, 0.0, W1)
W2_pruned = np.where(np.abs(W2) < threshold, 0.0, W2)

def compute_sparsity(W):
    return 1.0 - np.count_nonzero(W) / W.size

sparsity_W1 = compute_sparsity(W1_pruned)
sparsity_W2 = compute_sparsity(W2_pruned)
print(f"W1 稀疏度: {sparsity_W1:.2%}")
print(f"W2 稀疏度: {sparsity_W2:.2%}")

# ---------- 3. 构造测试输入(模拟单张MNIST图像) ----------
x = np.random.randn(input_dim).astype(np.float32)

# ---------- 4. 稠密前向推理(基准) ----------
def dense_forward(x, W1, b1, W2, b2):
    h = np.maximum(0, x @ W1 + b1)   # ReLU
    out = h @ W2 + b2
    return out

out_dense = dense_forward(x, W1_pruned, b1, W2_pruned, b2)

# ---------- 5. 稀疏前向推理(CSR格式) ----------
W1_sparse = sparse.csr_matrix(W1_pruned)
W2_sparse = sparse.csr_matrix(W2_pruned)

def sparse_forward(x, W1_sparse, b1, W2_sparse, b2):
    h = np.maximum(0, W1_sparse.dot(x) + b1)   # 注意:CSR.dot(向量)
    out = W2_sparse.dot(h) + b2
    return out

out_sparse = sparse_forward(x, W1_sparse, b1, W2_sparse, b2)

# ---------- 6. 验证一致性 ----------
max_diff = np.max(np.abs(out_dense - out_sparse))
print(f"稠密与稀疏输出最大误差: {max_diff:.2e}")
assert max_diff < 1e-4, "稀疏推理结果与稠密不一致!"

# ---------- 7. 推理耗时对比(多次取平均) ----------
def timeit(func, repeat=100):
    t0 = time.perf_counter()
    for _ in range(repeat):
        func()
    return (time.perf_counter() - t0) / repeat

t_dense = timeit(lambda: dense_forward(x, W1_pruned, b1, W2_pruned, b2))
t_sparse = timeit(lambda: sparse_forward(x, W1_sparse, b1, W2_sparse, b2))
print(f"稠密推理平均耗时: {t_dense*1e6:.2f} µs")
print(f"稀疏推理平均耗时: {t_sparse*1e6:.2f} µs")
print(f"加速比: {t_dense/t_sparse:.2f}x")

运行方式

python prune_sparse_inference.py

验证方法与判定标准

  • 正确性:程序输出中“稠密与稀疏输出最大误差”应小于 1e-4,且断言通过,证明稀疏路径与稠密路径数学等价。
  • 稀疏度:打印的 W1 与 W2 稀疏度应明显大于 0(阈值 0.05 下通常 W1 约 50% 以上、W2 约 40% 以上),说明非结构化剪枝确实产生了稀疏模型。
  • 加速效果:观察“加速比”数值。由于本例网络较小且单次推理开销低,加速比可能接近 1 或略小于 1(稀疏矩阵格式转换与函数调用开销占主导);若需观察明显加速,可增大 hidden_dim 至 512/1024 或增加 repeat 次数。判定通过的标准是:稀疏推理输出与稠密一致,且稀疏度大于 0,而非必须加速比大于 1。

关键说明

  • 非结构化剪枝将单个权重置零,不改变矩阵维度,因此稀疏度越高,理论上可利用稀疏矩阵库跳过零值计算。
  • 本案例使用 scipy.sparse.csr_matrix 的 dot 方法执行稀疏矩阵-向量乘,这是稀疏推理的核心加速点。
  • 实际部署中,稀疏模型还需配合专用硬件或推理库(如 DeepSparse、TensorRT 稀疏模式)才能获得理想加速;本案例聚焦算法正确性与稀疏格式转换路径。