定义:剪枝方法探索模型权重中的冗余, 并尝试删除/修剪冗余和非关键的权重。
目的:减小模型大小并加速模型训练/推断,同时不会显着降低模型性能。
分类
   根据剪枝位置:非结构化剪枝和结构化剪枝
      (1)非结构化剪枝:分别修剪每个单独的权重:
      (2)结构化剪枝:修剪是修剪一组规则的权重,如filter剪枝、通道剪枝
   根据数据依赖关系分类:串行、并行
      (1)串行:
      

模型剪枝源码实现 python 模型剪枝 综述_cnn

 
如图,conv1和conv2之间没有别的支路,确定conv1剪枝掉哪些卷积核,便能确定conv2所有卷积核应该剪枝掉哪些层。
      (2)并行:

 

模型剪枝源码实现 python 模型剪枝 综述_cnn_02

 

模型剪枝源码实现 python 模型剪枝 综述_模型剪枝源码实现 python_03

                                  一对多                                               多对一

一对多:确定剪枝掉conv1的哪些卷积核,便能确定conv2和conv3剪枝掉呢些通道,且conv2和conv3卷积核通道的简直剪枝是一样的。
多对一:conv1和conv2的输出进行add操作,要求两者的输出尺寸是一样。这要求conv1和conv2剪枝的卷积核的个数、位置都是一样的。如conv1要剪枝2、3、4通道,conv2要剪枝2、3、5通道,取两者的交集,最后剪枝掉2、3层通道。

例外:resnet的残差块,有提出:conv bn relu 改成 bn conv relu ,通道选择层放在第一个bn层后面,第一个bn层前面的conv不裁剪,紧 裁剪后面bn层前面的conv层。

剪枝方法

如何确定哪些权重、通道filter应该被减掉呢?

有很多剪枝方法:思路都是 在指定范围内用某种方式表示权重(filter、通道),并将其进行从小到大的排序,将小于某一滤值的元素置为0,然后将是0的剪枝掉。

 

模型剪枝源码实现 python 模型剪枝 综述_cnn_04


如:Slim pruner:《Learning Efficient Networks through Network Slimming》

用L1正则化约束BN层的约束因子,时期接近于0。

 

模型剪枝源码实现 python 模型剪枝 综述_cnn_05

 代码:

上面的代码实现比较复杂,但是现在有已经集合好的工具NNI,微软的NNI。
NNI(神经网络智能)是一个轻量级但功能强大的工具包,包含了很多剪枝方法,能够实现剪枝。
修剪 DNN 模型有三种常见做法:
(1)练模型 -> 修剪模型 -> 微调修剪后的模型
(2)在训练期间修剪模型(即修剪感知训练)-> 微调修剪后的模型
(3)修剪模型 -> 从头开始训练修剪后的模型

NNI的剪枝流程
(1)准备好模型model、剪枝要求config_list
    model=resnet18()
    config_list=[{'sparsity':0.8,'op_types:'['default']}]
(2)确定剪枝方法,如LEVELPruner
  pruner = LEVELPruner(model, config_list)
  _, masks = pruner.compress()#生成权重mask掩码,权重元素>要求的取1,不然取0。
(3)模型加速,加速后模型才会变小
ModelSpeedup(model, torch.rand(3, 1, 28, 28).to(device), masks).speedup_model()

各步骤实现结果如下图所示:

模型剪枝源码实现 python 模型剪枝 综述_模型剪枝源码实现 python_06

值得注意的是,NNI的pruner或quantizer并不能改变网络结构,只能模拟压缩的效果。 真正能够压缩模型、改变网络结构、降低推理延迟的是NNI的加速工具-ModelSpeedup,目前只支持PyTorch版本。

在NNI中,各剪枝方法的实现

One-Shot Pruner
⦁    Level Pruner
可设置目标稀疏度(以分数表示,0.6 表示会剪除 60%)。首先按照绝对值对指定层的权重排序。 然后按照所需的稀疏度,将值最小的权重屏蔽为 0。


from nni.algorithms.compression.pytorch.pruning import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
#default,需要修改的层
pruner = LevelPruner(model, config_list)
pruner.compress()


2.Slim Pruner
它在训练过程中对 batch normalization(BN)层的比例因子进行稀疏正则化,以识别不重要的通道。 比例因子值较小的通道将被修剪。


level: LevelPruner
slim: SlimPruner
l1: L1FilterPruner
l2: L2FilterPruner
fpgm: FPGMPruner
taylorfo: TaylorFOWeightFilterPruner
apoz: ActivationAPoZRankFilterPruner
mean_activation: ActivationMeanRankFilterPruner


3. FPGM Pruner
用最小的几何中值修剪卷积滤波器。 FPGM 选择最可替换的滤波器。

from nni.algorithms.compression.pytorch.pruning import FPGMPruner
config_list = [{ 'sparsity': 0.5,'op_types': ['Conv2d'] }]#修改所有的卷积层
pruner = FPGMPruner(model, config_list)
pruner.compress()


4.L1Filter Pruner
  它修剪卷积层中的滤波器。L1正则化剪枝。


from nni.algorithms.compression.pytorch.pruning import L1FilterPruner
config_list = [{'sparsity_per_layer': 0.5, 'op_types': ['Linear', 'Conv2d']},
            {'exclude': True, 'op_names': ['fc3']}]
pruner = L1FilterPruner(model, config_list)
pruner.compress()


表示将修剪类型为Linear或Conv2d的所有层,除了名为fc3的层,因为fc3是exclude。每层的最终稀疏率是 50%。名为fc3的层将不会被修剪。

5. L2Filter Pruner
  这是一种结构化剪枝算法,用于修剪权重的最小 L2 规范卷积滤波器,算是一种一次性修剪器。


from nni.algorithms.compression.pytorch.pruning import L1FilterPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['Conv2d'] }]
pruner = L1FilterPruner(model, config_list)
pruner.compress()


在训练中剪枝的方法

6. AGP Pruner
一种自动逐步剪枝算法,在 n 个剪枝步骤中,稀疏度从初始的稀疏度(通常为 0)增加到最终的稀疏度。


from nni.algorithms.compression.pytorch.pruning import AGPPruner
config_list = [{
    'initial_sparsity': 0,
    'final_sparsity': 0.8,
    'start_epoch': 0,
    'end_epoch': 10,
    'frequency': 1,
    'op_types': ['default']
}]

# 加载模型和权重。
model = MyModel()
model.load_state_dict(torch.load('mycheckpoint.pth'))
# AGP Pruner 会在 optimizer. step() 上回调,在微调模型时剪枝,
# 因此,必须要有 optimizer 才能完成模型剪枝。
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=1e-4)

pruner = AGPPruner(model, config_list, optimizer, pruning_algorithm='level')
pruner.compress()


AGP Pruner 默认使用 LevelPruner 算法来修建权重,还可以设置 pruning_algorithm 参数来使用其它剪枝算法

7. NetAdapt Pruner
NetAdapt 在算力足够的情况下,自动简化预训练的网络。 给定整体稀疏度,NetAdapt 可通过迭代剪枝自动为不同层生成不同的稀疏分布。


from nni.algorithms.compression.pytorch.pruning import NetAdaptPruner
config_list = [{'sparsity': 0.5,'op_types': ['Conv2d']}]
pruner=NetAdaptPruner(model,config_list,
short_term_fine_tuner=short_term_fine_tuner, evaluator=evaluator,base_algo='l1', experiment_data_dir='./')
pruner.compress()



Evaluator函数,返回测试的平均损失、识别正确的个数,

8. SimulatedAnnealing Pruner
模拟退火剪枝,此 Pruner 基于先验经验,实现了引导式的启发搜索方法,模拟退火(SA)算法。 增强的模拟退火算法基于以下理论:具有更多权重的深度神经网络层通常具有较高的可压缩度,对整体精度的影响更小。


from nni.algorithms.compression.pytorch.pruning import SimulatedAnnealingPruner
config_list = [{'sparsity': 0.5,'op_types': ['Conv2d']}]
pruner = SimulatedAnnealingPruner(model, config_list, evaluator=evaluator, base_algo='l1', cool_down_rate=0.9, experiment_data_dir='./')
pruner.compress()

9. AutoCompress Pruner


from nni.algorithms.compression.pytorch.pruning import AutoCompressPruner
config_list = [{'sparsity': 0.5, 'op_types': ['Conv2d'] }]
pruner = AutoCompressPruner(
            model, config_list, trainer=trainer, evaluator=evaluator,
            dummy_input=dummy_input,num_iterations=3, optimize_mode='maximize', base_algo='l1',
            cool_down_rate=0.9,admm_num_iterations=30, admm_training_epochs=5, experiment_data_dir='./')
pruner.compress()



10. AMC Pruner
AMC Pruner 利用强化学习来提供模型压缩策略。 这种基于学习的压缩策略比传统的基于规则的压缩策略有更高的压缩比, 更好地保存了精度,节省了人力。


from nni.algorithms.compression.pytorch.pruning import AMCPruner
config_list = [{'op_types': ['Conv2d', 'Linear'] }]
pruner = AMCPruner(model, config_list, evaluator, val_loader, flops_ratio=0.5)
pruner.compress()