模型剪枝经典论文精读:Learning Efficient Convolutional Networks through Network Slimming 一、论文基本信息论文题目Learning Efficient Convolutional Networks through Network Slimming作者Zhuang Liu、Jianguo Li、Zhiqiang Shen、Gao Huang、Shoumeng Yan、Changshui Zhang发表信息ICCV 2017DOI10.1109/ICCV.2017.298论文链接arXivLearning Efficient Convolutional Networks through Network SlimmingCVF Open AccessICCV 2017 论文页面PyTorch 实现https://github.com/Eric-mingjie/network-slimming这篇论文发表于 ICCV 2017DBLP 记录显示其页码为2755–2763DOI 为10.1109/ICCV.2017.298。官方 GitHub 仓库说明该工作对应论文Learning Efficient Convolutional Networks through Network Slimming并且原始代码基于 Torch / fb.resnet.torch另一个 PyTorch 实现支持 VGG、ResNet 和 DenseNet。这篇论文的核心思想非常清晰利用 Batch Normalization 中的缩放因子 (\gamma) 表示每个通道的重要性在训练时对 (\gamma) 加 L1 稀疏正则让不重要通道的 (\gamma) 自动趋近于 0训练后再根据 (\gamma) 大小进行通道剪枝。论文明确提出在 BN scaling factors 上施加 L1 regularization从而识别不重要通道并在剪枝后 fine-tune 得到紧凑网络。二、论文要解决的问题在前面几篇论文中我们已经看到几类典型通道剪枝思路。Pruning Filters for Efficient ConvNets直接用 filter 的 L1 范数判断通道重要性。它简单有效但属于事后启发式评分。ThiNet和Channel Pruning for Accelerating Very Deep Neural Networks则从特征重构角度出发判断删除某些通道后下一层或当前层输出能否被较好重构。它们比 L1 filter pruning 更精细但需要额外采样特征、构建优化问题并且剪枝流程相对复杂。Network Slimming 想解决的是另一个问题能不能在训练阶段就让网络自己学出哪些通道重要、哪些通道不重要而不是训练后再额外设计复杂的通道评分或重构算法论文的回答是可以。具体做法是利用 BN 层里的缩放因子 (\gamma)。在带 BN 的 CNN 中每个通道通常都有一个对应的缩放参数 (\gamma)。如果某个通道的 (\gamma) 很小那么该通道经过 BN 后的输出幅度也会被压得很小。因此(\gamma) 可以自然地作为通道重要性的代理指标。Network Slimming 的关键不是简单地“观察 (\gamma)”而是在训练时主动让一部分 (\gamma) 变小正常训练损失 BN gamma 的 L1 稀疏正则这样网络在学习分类任务的同时也会学习一个更稀疏的通道结构。三、核心思想Network Slimming 的核心思想可以概括为一句话在训练过程中对 BN 层的缩放因子 (\gamma) 加 L1 正则使部分通道的 (\gamma) 自动趋近于 0然后根据 (\gamma) 的大小删除对应通道。普通 BN 的形式是其中通道缩放因子通道平移因子归一化后的特征yBN 输出。对于一个卷积通道来说如果它对应的 (\gamma) 很小那么这个通道的输出会被整体压低。此时即使该通道仍然存在它对后续网络的影响也可能很弱。因此论文将当作通道选择因子gamma 大通道重要保留 gamma 小通道不重要删除这和前几篇论文的区别很明显L1 Filter Pruning: 看 filter 权重大小 ThiNet: 看下一层输出重构误差 Channel Pruning: 用 LASSO 选通道再做最小二乘重建 Network Slimming: 训练时直接让 BN gamma 稀疏化用 gamma 选通道论文 Figure 1 中也明确说明每个卷积通道对应一个 scaling factor训练时对这些 scaling factors 加稀疏正则小 scaling factor 对应的通道会被剪掉剪枝后得到 compact network并通过 fine-tuning 恢复精度。四、方法细节4.1 为什么选择 BN gammaNetwork Slimming 最巧妙的地方在于它没有额外引入新的通道选择参数而是直接复用 BN 层已有的 (\gamma)。对于带 BN 的卷积网络来说卷积层后通常接 BN 层。BN 层中每个通道都有一个可学习的缩放因子 (\gamma)。这个参数天然是一维的且和通道一一对应非常适合作为通道重要性指标。论文也指出直接利用 BN 中的 (\gamma) 有一个重要优势不需要改变现有 CNN 架构也不会引入额外推理开销。相比单独插入 scaling layer复用 BN 参数更自然因为 BN 本来就包含 channel-wise scaling/shifting。4.2 训练目标任务损失 gamma 稀疏正则Network Slimming 的训练目标写作论文实验中选择也就是对所有 BN 的加 L1 正则。L1 正则会鼓励一部分变小甚至接近 0从而形成通道级稀疏。论文中给出的公式和说明明确指出训练目标由正常 CNN loss 和 BN scaling factors 的 sparsity-induced penalty 组成并在实验中采用 L1 penalty。4.3 稀疏训练阶段训练阶段和普通训练非常接近只是损失函数多了一项loss classification_loss lambda * sum(|BN_gamma|)也就是说训练时不仅希望分类准确还希望网络尽量使用更少的有效通道。这一步非常重要。Network Slimming 不是训练好模型后再临时找通道而是在训练过程中让网络自动形成通道稀疏性。直观上可以理解为如果某个通道确实重要 分类损失会阻止它的 gamma 被压到 0。 如果某个通道不重要或可被其他通道替代 L1 正则会把它的 gamma 推向 0。因此训练结束后很多 BN (\gamma) 会呈现明显的大小差异这就给后续剪枝提供了依据。4.4 剪枝阶段根据 gamma 全局阈值删通道训练完成后Network Slimming 根据 (\gamma) 的大小剪通道。论文采用的是全局阈值策略而不是每层单独设置剪枝率。具体来说把所有 BN scaling factors 放在一起排序然后根据目标剪枝比例选择一个 percentile threshold。例如如果希望剪掉 70% channels就选择所有 (\gamma) 中第 70 百分位作为阈值低于该阈值的通道被删除。论文明确说明使用 global pruning threshold并以 percentile 方式确定阈值例如剪掉 40% 或 60% channels。剪枝时会删除1. 当前 BN 中对应的 gamma / beta / running mean / running var 2. 当前卷积层对应的输出 filter 3. 下一层卷积中对应的输入 channel 4. 如果是全连接层也要同步删除对应输入维度。剪枝后得到的不是稀疏 mask 网络而是一个真正更窄的 dense network。论文也强调通道剪枝后可以直接得到 narrow network不需要特殊稀疏计算库。4.5 Fine-tuning 阶段剪枝会导致网络性能暂时下降尤其是剪枝比例较高时。因此论文在剪枝后会对紧凑模型进行 fine-tuning。整体流程是训练带 gamma 稀疏正则的大网络 ↓ 统计所有 BN gamma ↓ 根据全局阈值删除小 gamma 通道 ↓ 构建更窄的 compact network ↓ fine-tune 紧凑模型论文 Figure 2 给出了这一流程先 train with channel sparsity regularization然后 prune channels with small scaling factors最后 fine-tune pruned network。4.6 Multi-pass Network SlimmingNetwork Slimming 还提出了 multi-pass 版本。单次 slimming 流程是稀疏训练 → 剪枝 → fine-tuningmulti-pass 则是把这个流程重复多次稀疏训练 → 剪枝 → fine-tuning ↓ 继续对剪枝后的网络稀疏训练 ↓ 再次剪枝 ↓ 再次 fine-tuning论文指出剪枝得到的窄网络可以再次应用整个 slimming 训练流程从而进一步得到更紧凑的模型。实验中multi-pass scheme 可以带来更高压缩率。4.7 ResNet / DenseNet 中的特殊处理对于 VGG 这类串行网络剪通道相对简单conv - BN - ReLU - conv - BN - ReLU只要删除当前层输出通道并同步删除下一层输入通道即可。但 ResNet 和 DenseNet 有跨层连接。某一层输出可能会被多个后续层使用。如果直接物理删除通道可能破坏残差相加或 dense concatenation 的维度一致性。因此论文指出对于 ResNet、DenseNet 这类带 cross-layer connections 和 pre-activation 结构的网络需要额外适配。它们往往在卷积前放 BN此时稀疏性体现在某层输入端即该层选择性使用输入通道。为了在测试阶段获得参数和计算节省需要放置 channel selection layer 来 mask 掉不重要通道。这也是 Network Slimming 在工程实现中比论文公式更复杂的地方。VGG 很容易物理删通道但 ResNet / DenseNet 需要依赖结构约束或 channel selection layer。 Network Slimming toy demo: 5-layer CNN with BatchNorm gamma sparsity channel pruning. 对应论文 Learning Efficient Convolutional Networks through Network Slimming Zhuang Liu et al., ICCV 2017 这份代码是“理解性代码”不是完整论文工程复现。 它演示 Network Slimming 的核心流程 1. 构造一个带 BN 的 5 层 CNN。 2. 随机生成一组图片和标签。 3. 训练时给所有 BN gamma 加 L1 正则 loss cross_entropy lambda * sum(|gamma|) 4. 根据所有 BN gamma 的绝对值做全局排序。 5. 删除 gamma 较小的通道。 6. 物理构建一个更窄的新模型。 7. 检查剪枝前后输出 shape 和参数量。 核心思想 BN 层输出为 y gamma * x_hat beta 如果某个通道的 gamma 很小 说明这个通道的输出被整体压得很弱 因此可以把 gamma 当作通道重要性指标。 运行 python network_slimming_toy_5conv_bn.py from __future__ import annotations from dataclasses import dataclass from typing import Dict, Tuple import torch import torch.nn as nn import torch.nn.functional as F # # 1. 定义一个带 BN 的 5 层 CNN # class Slimming5ConvNet(nn.Module): 一个用于理解 Network Slimming 的 5 层 CNN。 默认结构 conv1: 3 - 16, bn1 conv2: 16 - 24, bn2 conv3: 24 - 32, bn3 conv4: 32 - 32, bn4 conv5: 32 - 16, bn5 global average pooling fc: 16 - num_classes 每个 BN 层都有一个 gamma也就是 PyTorch 中的 bn.weight。 Network Slimming 正是对这些 gamma 加 L1 正则。 def __init__( self, channels: Tuple[int, int, int, int, int] (16, 24, 32, 32, 16), num_classes: int 10, ) - None: super().__init__() c1, c2, c3, c4, c5 channels # Conv 后面接 BN因此 Conv 不使用 bias。 self.conv1 nn.Conv2d(3, c1, kernel_size3, padding1, biasFalse) self.bn1 nn.BatchNorm2d(c1) self.conv2 nn.Conv2d(c1, c2, kernel_size3, padding1, biasFalse) self.bn2 nn.BatchNorm2d(c2) self.conv3 nn.Conv2d(c2, c3, kernel_size3, padding1, biasFalse) self.bn3 nn.BatchNorm2d(c3) self.conv4 nn.Conv2d(c3, c4, kernel_size3, padding1, biasFalse) self.bn4 nn.BatchNorm2d(c4) self.conv5 nn.Conv2d(c4, c5, kernel_size3, padding1, biasFalse) self.bn5 nn.BatchNorm2d(c5) self.pool nn.AdaptiveAvgPool2d(1) self.fc nn.Linear(c5, num_classes) def forward(self, x: torch.Tensor) - torch.Tensor: x F.relu(self.bn1(self.conv1(x))) x F.relu(self.bn2(self.conv2(x))) x F.relu(self.bn3(self.conv3(x))) x F.relu(self.bn4(self.conv4(x))) x F.relu(self.bn5(self.conv5(x))) # [B, C, H, W] - [B, C, 1, 1] - [B, C] x self.pool(x).flatten(1) x self.fc(x) return x # # 2. 工具函数参数量、结构、BN gamma # def count_params(model: nn.Module) - int: 统计模型总参数量。 return sum(p.numel() for p in model.parameters()) def get_conv_bn_pairs(model: Slimming5ConvNet): 按顺序返回 5 组 Conv-BN。 return [ (layer1, model.conv1, model.bn1), (layer2, model.conv2, model.bn2), (layer3, model.conv3, model.bn3), (layer4, model.conv4, model.bn4), (layer5, model.conv5, model.bn5), ] def print_model_structure(model: Slimming5ConvNet, title: str) - None: 打印模型每层通道数。 print(f\n[{title}]) for name, conv, bn in get_conv_bn_pairs(model): print( f{name}: conv {conv.in_channels:2d} - {conv.out_channels:2d}, fBN features {bn.num_features:2d} ) print(ffc: {model.fc.in_features:2d} - {model.fc.out_features:2d}) print(fparams: {count_params(model):,}) def bn_gamma_l1(model: Slimming5ConvNet) - torch.Tensor: 计算所有 BN gamma 的 L1 和。 在 PyTorch 中 BatchNorm2d.weight 就是 gamma BatchNorm2d.bias 就是 beta loss 0.0 for _, _, bn in get_conv_bn_pairs(model): loss loss bn.weight.abs().sum() return loss torch.no_grad() def collect_all_bn_gamma_abs(model: Slimming5ConvNet) - torch.Tensor: 收集所有 BN gamma 的绝对值用于全局排序。 gammas [] for _, _, bn in get_conv_bn_pairs(model): gammas.append(bn.weight.detach().abs().cpu()) return torch.cat(gammas, dim0) torch.no_grad() def print_gamma_statistics(model: Slimming5ConvNet, title: str) - None: 打印每个 BN 层 gamma 的统计信息。 print(f\n[{title}]) for name, _, bn in get_conv_bn_pairs(model): g bn.weight.detach().abs().cpu() print( f{name}: fmin{g.min().item():.6f}, fmean{g.mean().item():.6f}, fmax{g.max().item():.6f}, fnum{g.numel()} ) all_g collect_all_bn_gamma_abs(model) print( fall gamma: min{all_g.min().item():.6f}, fmean{all_g.mean().item():.6f}, fmax{all_g.max().item():.6f}, fnum{all_g.numel()} ) # # 3. 随机数据训练task loss gamma L1 # def train_with_gamma_l1( model: Slimming5ConvNet, images: torch.Tensor, labels: torch.Tensor, steps: int 40, lr: float 0.03, gamma_l1_lambda: float 1e-3, ) - None: 在随机数据上做一个小训练演示 gamma L1 正则。 注意 随机数据和随机标签没有真实精度意义。 这里只是演示 Network Slimming 的训练机制。 loss: cross_entropy gamma_l1_lambda * sum(abs(BN gamma)) model.train() optimizer torch.optim.SGD( model.parameters(), lrlr, momentum0.9, weight_decay5e-4, ) for step in range(1, steps 1): logits model(images) task_loss F.cross_entropy(logits, labels) sparse_loss bn_gamma_l1(model) loss task_loss gamma_l1_lambda * sparse_loss optimizer.zero_grad() loss.backward() optimizer.step() if step 1 or step % 10 0 or step steps: all_g collect_all_bn_gamma_abs(model) print( fstep {step:03d} | ftask_loss{task_loss.item():.4f} | fgamma_l1{sparse_loss.item():.4f} | fmean|gamma|{all_g.mean().item():.6f} | fmin|gamma|{all_g.min().item():.6f} ) # # 4. 根据 gamma 全局排序生成每层保留通道 # dataclass class PrunePlan: 剪枝计划。 keep_indices: 每层保留下来的输出通道编号。 threshold: 全局 gamma 阈值。 prune_ratio: 目标剪枝比例。 keep_indices: Dict[str, torch.Tensor] threshold: float prune_ratio: float torch.no_grad() def make_prune_plan_by_global_gamma( model: Slimming5ConvNet, prune_ratio: float 0.5, min_channels_per_layer: int 2, ) - PrunePlan: 根据所有 BN gamma 的绝对值做全局剪枝计划。 论文中的思想 把所有 BN gamma 放在一起排序 选择一个全局阈值 小于阈值的通道删除。 参数 prune_ratio: 希望删除多少比例的通道。 min_channels_per_layer: 每层至少保留几个通道。 这是工程保护避免某一层被全部剪空。 all_g collect_all_bn_gamma_abs(model) total_channels all_g.numel() prune_count int(total_channels * prune_ratio) prune_count max(0, min(prune_count, total_channels - 1)) sorted_g, _ torch.sort(all_g) threshold sorted_g[prune_count].item() keep_indices: Dict[str, torch.Tensor] {} print(\n[Global gamma threshold]) print(ftotal channels: {total_channels}) print(ftarget prune ratio: {prune_ratio:.2f}) print(fthreshold: {threshold:.6f}) for name, _, bn in get_conv_bn_pairs(model): g bn.weight.detach().abs().cpu() keep torch.nonzero(g threshold, as_tupleFalse).flatten() # 防止某层被剪空。 if keep.numel() min_channels_per_layer: keep torch.topk(g, kmin_channels_per_layer, largestTrue).indices keep torch.sort(keep).values.long() keep_indices[name] keep print( f{name}: keep {keep.numel():2d} / {g.numel():2d}, findices {keep.tolist()} ) return PrunePlan( keep_indiceskeep_indices, thresholdthreshold, prune_ratioprune_ratio, ) # # 5. 根据保留通道物理构建更窄模型 # torch.no_grad() def copy_pruned_conv( old_conv: nn.Conv2d, new_conv: nn.Conv2d, keep_in: torch.Tensor, keep_out: torch.Tensor, ) - None: 复制剪枝后的卷积权重。 old_conv.weight shape: [old_out_channels, old_in_channels, k, k] 剪枝后要保留 输出通道 keep_out 输入通道 keep_in new_conv.weight shape: [len(keep_out), len(keep_in), k, k] w old_conv.weight.detach().cpu() w w[keep_out][:, keep_in].clone() new_conv.weight.copy_(w) torch.no_grad() def copy_pruned_bn( old_bn: nn.BatchNorm2d, new_bn: nn.BatchNorm2d, keep: torch.Tensor, ) - None: 复制剪枝后的 BN 参数。 BN 中与通道相关的量包括 gamma: old_bn.weight beta: old_bn.bias running_mean running_var keep keep.cpu().long() new_bn.weight.copy_(old_bn.weight.detach().cpu()[keep]) new_bn.bias.copy_(old_bn.bias.detach().cpu()[keep]) new_bn.running_mean.copy_(old_bn.running_mean.detach().cpu()[keep]) new_bn.running_var.copy_(old_bn.running_var.detach().cpu()[keep]) new_bn.num_batches_tracked.copy_(old_bn.num_batches_tracked.detach().cpu()) torch.no_grad() def build_pruned_model( old_model: Slimming5ConvNet, plan: PrunePlan, ) - Slimming5ConvNet: 根据剪枝计划构建一个真正变窄的新模型。 原始结构 conv1: 3 - 16 conv2: 16 - 24 conv3: 24 - 32 conv4: 32 - 32 conv5: 32 - 16 fc: 16 - num_classes 假设每层保留通道数为 k1, k2, k3, k4, k5 剪枝后结构 conv1: 3 - k1 conv2: k1 - k2 conv3: k2 - k3 conv4: k3 - k4 conv5: k4 - k5 fc: k5 - num_classes keep1 plan.keep_indices[layer1] keep2 plan.keep_indices[layer2] keep3 plan.keep_indices[layer3] keep4 plan.keep_indices[layer4] keep5 plan.keep_indices[layer5] new_channels ( keep1.numel(), keep2.numel(), keep3.numel(), keep4.numel(), keep5.numel(), ) new_model Slimming5ConvNet( channelsnew_channels, num_classesold_model.fc.out_features, ) rgb_keep torch.tensor([0, 1, 2], dtypetorch.long) copy_pruned_conv(old_model.conv1, new_model.conv1, rgb_keep, keep1) copy_pruned_bn(old_model.bn1, new_model.bn1, keep1) copy_pruned_conv(old_model.conv2, new_model.conv2, keep1, keep2) copy_pruned_bn(old_model.bn2, new_model.bn2, keep2) copy_pruned_conv(old_model.conv3, new_model.conv3, keep2, keep3) copy_pruned_bn(old_model.bn3, new_model.bn3, keep3) copy_pruned_conv(old_model.conv4, new_model.conv4, keep3, keep4) copy_pruned_bn(old_model.bn4, new_model.bn4, keep4) copy_pruned_conv(old_model.conv5, new_model.conv5, keep4, keep5) copy_pruned_bn(old_model.bn5, new_model.bn5, keep5) # fc 输入维度等于 conv5 输出通道数所以也要同步裁剪。 new_model.fc.weight.copy_(old_model.fc.weight.detach().cpu()[:, keep5]) new_model.fc.bias.copy_(old_model.fc.bias.detach().cpu()) return new_model # # 6. 主函数完整跑一遍 # def main() - None: torch.manual_seed(42) # 随机图片和随机标签。 # 这里只是演示流程不看真实精度。 batch_size 16 num_classes 10 images torch.randn(batch_size, 3, 32, 32) labels torch.randint(0, num_classes, size(batch_size,)) model Slimming5ConvNet(num_classesnum_classes) print_model_structure(model, Original model) print_gamma_statistics(model, BN gamma before sparse training) print(\n[Sparse training with BN gamma L1]) train_with_gamma_l1( modelmodel, imagesimages, labelslabels, steps40, lr0.03, gamma_l1_lambda1e-3, ) print_gamma_statistics(model, BN gamma after sparse training) plan make_prune_plan_by_global_gamma( modelmodel, prune_ratio0.5, min_channels_per_layer2, ) pruned_model build_pruned_model(model, plan) print_model_structure(pruned_model, Pruned compact model) model.eval() pruned_model.eval() with torch.no_grad(): old_out model(images) new_out pruned_model(images) print(\n[Forward check]) print(foriginal output shape: {tuple(old_out.shape)}) print(fpruned output shape: {tuple(new_out.shape)}) print(foutput MSE before fine-tuning: {F.mse_loss(new_out, old_out).item():.8f}) print(\n[Summary]) print(This demo shows the Network Slimming pipeline:) print( 1. Train with BN gamma L1 sparsity.) print( 2. Sort all |gamma| values globally.) print( 3. Prune channels with small gamma.) print( 4. Build a physically narrower CNN.) print( 5. In real experiments, fine-tune the pruned model next.) if __name__ __main__: main()五、关键公式5.1 BN 层缩放其中是每个通道对应的缩放因子。5.2 Network Slimming 训练目标目标可以写成这就是整篇论文最核心的公式。六、实验设置6.1 数据集论文在多个数据集上验证 Network Slimming包括CIFAR-10 CIFAR-100 SVHN ImageNet MNIST论文描述中CIFAR-10 / CIFAR-100 都是 (32 \times 32) 自然图像数据集训练集 50,000 张、测试集 10,000 张ImageNet 包含 1.2M 训练图像、50,000 验证图像和 1000 个类别MNIST 用于测试该方法在全连接网络上的效果。6.2 网络结构论文使用了多种网络VGGNet ResNet-164 DenseNet-40 VGG-A on ImageNet 3-layer fully-connected network on MNIST其中CIFAR 和 SVHN 上使用 VGGNet、ResNet-164 和 DenseNet-40ImageNet 上使用带 BN 的 11 层 VGG-AMNIST 上使用三层全连接网络并把每个 neuron 视为 (1 \times 1) 空间尺寸的 channel。6.3 训练、剪枝和微调论文使用 SGD 训练。CIFAR 和 SVHN 上 batch size 为 64CIFAR 训练 160 epochsSVHN 训练 20 epochsImageNet 和 MNIST 使用 batch size 256分别训练 60 和 30 epochs。训练中使用 (10^{-4}) weight decay 和 0.9 Nesterov momentum。稀疏正则强度 (\lambda) 通过网格搜索确定。论文在 CIFAR 和 SVHN 上从 (10^{-3}, 10^{-4}, 10^{-5}) 中搜索VGGNet 选择 (\lambda10^{-4})ResNet 和 DenseNet 选择 (\lambda10^{-5})ImageNet 上的 VGG-A 也设置为 (\lambda10^{-5})。剪枝时论文使用所有 scaling factors 的全局 percentile threshold剪枝后构建新的窄网络并复制保留通道对应权重。ImageNet 上由于时间限制论文只对剪枝后的 VGG-A 以 (10^{-3}) 学习率 fine-tune 5 个 epochs。七、实验结果解读7.1 CIFAR-10 上的结果论文在 CIFAR-10 上报告了 VGGNet、DenseNet-40 和 ResNet-164 的结果模型Test Error参数量参数剪枝FLOPsFLOPs 剪枝VGGNet Baseline6.34%20.04M-(7.97 \times 10^8)-VGGNet 70% Pruned6.20%2.30M88.5%(3.91 \times 10^8)51.0%DenseNet-40 Baseline6.11%1.02M-(5.33 \times 10^8)-DenseNet-40 40% Pruned5.19%0.66M35.7%(3.81 \times 10^8)28.4%DenseNet-40 70% Pruned5.65%0.35M65.2%(2.40 \times 10^8)55.0%ResNet-164 Baseline5.42%1.70M-(4.99 \times 10^8)-ResNet-164 60% Pruned5.27%1.10M35.2%(2.75 \times 10^8)44.9%这个结果非常关键VGGNet 剪掉 70% channels 后测试错误率从 6.34% 降到 6.20%参数减少 88.5%FLOPs 减少 51.0%。DenseNet-40 和 ResNet-164 在适度剪枝时也出现了错误率下降说明 BN gamma 稀疏正则本身具有一定正则化效果。7.2 CIFAR-100 与 SVHN 上的结果在 CIFAR-100 上VGGNet 50% Pruned 的测试错误率从 baseline 的 26.74% 变为 26.52%参数减少 75.1%FLOPs 减少 37.1%ResNet-164 40% Pruned 的测试错误率从 23.37% 降到 22.87%同时 FLOPs 减少 33.3%。在 SVHN 上VGGNet 60% Pruned 的错误率从 2.17% 降到 2.06%参数减少 84.8%FLOPs 减少 50.1%。这些结果说明Network Slimming 不只是适用于某一个数据集或某一个模型。在多个数据集上它都能在保持甚至提升精度的情况下减少参数和计算量。7.3 为什么有些剪枝后精度更高论文中一个有意思的现象是剪枝后模型有时比 baseline 更准确。原因可以从两个角度理解。第一训练时对 BN (\gamma) 加 L1 正则相当于增加了一种结构化正则化。它会抑制冗余通道减少过拟合。第二剪枝后 fine-tuning 进一步让紧凑模型适应新的结构。对于本来过宽、冗余较多的模型剪掉冗余通道反而可能改善泛化。论文也提到适度剪枝有时会降低 test error而剪枝比例达到 60% 或更高时通常仍能保持相近精度。7.4 Multi-pass 结果multi-pass 是这篇论文很重要的扩展。论文在 CIFAR 上使用 VGGNet 测试多轮 slimming。CIFAR-10 上第 3 轮时 fine-tuned error 为 6.10%参数已剪掉 91.4%FLOPs 剪掉 63.1%第 5 轮时参数剪掉 98.3%FLOPs 剪掉 88.7%但错误率上升到 7.73%。这说明 multi-pass 可以进一步提升压缩率但也存在明显边界前几轮压缩率提高精度基本可控 过多轮通道过少表达能力不足精度明显下降因此multi-pass 不是越多越好而是需要根据目标压缩率和精度损失进行折中。八、方法优点8.1 实现非常简单Network Slimming 的最大优点是简单。它不需要复杂的二阶信息不需要特征重构不需要 LASSO也不需要额外训练一个评分网络。只需要在训练 loss 中加入这使得它非常容易加入现有 CNN 训练代码。8.2 通道重要性是训练出来的L1 filter pruning 是事后根据权重大小判断重要性ThiNet 和 Channel Pruning 是事后根据特征重构判断重要性。Network Slimming 则是在训练阶段就让网络自己决定哪些通道重要。这意味着通道选择和任务训练是耦合的而不是训练完成后再额外打分。8.3 剪枝后是规则稠密网络Network Slimming 删除的是整个通道因此得到的是更窄的 dense network。这和非结构化 weight pruning 不同。后者虽然参数中有很多 0但不一定能在普通硬件上直接加速。Network Slimming 得到的模型不需要特殊稀疏存储或计算操作可以直接使用常规深度学习库推理。论文也明确指出该方法能在 conventional hardware 和 deep learning packages 上实现压缩和加速因为剪枝后的窄模型不依赖 sparse storing format 或 sparse computation。8.4 全局阈值简单有效相比每层手动设置剪枝率Network Slimming 使用全局 percentile threshold。这有一个好处网络自己决定哪些层该多剪哪些层该少剪。如果某一层大量通道的 (\gamma) 都很小就会被剪得更多如果某一层大多数通道 (\gamma) 较大就会保留更多。这种全局排序思想后来在很多剪枝方法中也被广泛使用。8.5 可以扩展到多种结构论文不仅测试 VGGNet还测试了 ResNet-164 和 DenseNet-40并讨论了 cross-layer connections 和 pre-activation 结构下如何通过 channel selection layer 处理剪枝。这说明 Network Slimming 不只是 VGG 上的简单技巧而是可以推广到更复杂 CNN 结构的通道稀疏训练框架。九、方法局限9.1 依赖重新训练Network Slimming 不是纯 post-training pruning。它需要在训练过程中加入 BN (\gamma) 的 L1 正则。如果只有一个已经训练好的普通模型而没有重新训练资源那么 Network Slimming 不能像 L1 filter pruning 那样直接剪。9.2 对 BN 结构依赖较强这篇论文的核心依赖 BN 的 (\gamma)。如果网络没有 BN或者 BN 的位置和结构不适合通道选择那么方法需要额外改造。论文中也讨论了为什么复用 BN gamma 比额外插入 scaling layer 更合适如果 scaling layer 放在 BN 前其缩放效果会被 BN 归一化抵消如果放在 BN 后又会出现连续两个缩放因子。9.3 ResNet / DenseNet 需要特殊结构处理对于 VGG 这种串行结构剪通道很直接。但在 ResNet、DenseNet 中一个通道可能参与残差相加、跨层连接或拼接。如果不处理这些依赖关系直接剪通道可能造成维度不匹配。因此Network Slimming 在复杂结构上的工程实现并不是简单地“删掉所有小 gamma 通道”还需要考虑结构依赖、channel selection layer 或一致性约束。9.4 剪枝比例需要调节(\lambda) 控制稀疏强度剪枝 percentile 控制通道删除比例。二者都需要实验调节。如果 (\lambda) 太小(\gamma) 不够稀疏剪枝效果有限如果 (\lambda) 太大重要通道也可能被压小训练精度会受影响。如果剪枝比例太高fine-tuning 也难以完全恢复性能。9.5 不直接适用于无 BN 的 Transformer / LLMNetwork Slimming 是 CNN BN 时代非常自然的方法。但在 Transformer、ViT、LLM 中常见归一化层是 LayerNorm而不是 BatchNormLayerNorm 的缩放参数与通道维度、token 维度、隐藏维度的关系也不同。因此Network Slimming 的思想可以迁移例如对 LayerNorm scale、MLP hidden dimension 或 attention head 引入稀疏门控但原方法不能直接照搬到所有现代大模型上。十、后续影响Network Slimming 对后续结构化剪枝有很大影响。第一它把通道剪枝从“训练后评分”推进到了“训练中学习”。通道重要性不再完全依赖人工设计的指标而是通过稀疏正则在训练中形成。第二它强化了 BN scaling factor 作为通道重要性指标的思路。后续大量 CNN 剪枝方法都会利用 BN (\gamma)、scale parameter 或类似 gating variable 来判断通道重要性。第三它推动了“稀疏训练 结构化剪枝 fine-tuning”的经典流程训练时加结构稀疏约束 ↓ 根据稀疏参数剪通道 ↓ 构建紧凑模型 ↓ fine-tuning 恢复精度第四它对后续可学习剪枝、可微门控、自动通道选择和 slimmable network 都有影响。官方代码仓库也明确给出了训练带稀疏正则、prune、convert compact network、fine-tune 的完整流程。从专栏脉络上看这篇论文可以放在这里Pruning Filters for Efficient ConvNets ↓ ThiNet ↓ Channel Pruning for Accelerating Very Deep Neural Networks ↓ Network Slimming ↓ FPGM / HRank / EagleEye ↓ AutoSlim / Slimmable Networks / Hardware-aware Pruning ↓ ViT / LLM / VLM structured pruning如果说前几篇论文主要回答的是训练后怎么判断哪些通道可以剪那么 Network Slimming 回答的是能不能在训练时就让通道重要性自己显现出来它的答案就是给 BN gamma 加 L1 稀疏正则。十一、一句话总结《Learning Efficient Convolutional Networks through Network Slimming》通过对 BN 缩放因子 (\gamma) 加 L1 稀疏正则让网络在训练过程中自动学习通道重要性再根据小 (\gamma) 删除冗余通道是“稀疏训练驱动结构化剪枝”的经典代表工作。