目前主流的深度网络都非常大,如Imagenet上效果较好的Alexnet、VGGnet、Resnet和检测任务上好用的yolo骨干网络darknet53,模型都达百兆。同时这种大模型下推理一张图片的计算量高达数十亿,因而很难在资源有限的嵌入式端上实时地运行,这时候就需要模型压缩。要达到这个目的,我们可以分成几种研究领域,比如量化、低秩分解、知识蒸馏、剪枝等。其中最具代表性的就是剪枝。
简单说,模型剪枝就是将不重要的权重值删除掉,保留有用的部分。
Pruning最根本上主要分为两个策略:
filters/channel-based 策略
基于filter/channel的重要性度量
∎ weights/Activations (以weights或者Activation的值为度量做pruning)
基于Archetecture based 策略
∎ 找最优的子Architecture的搜索
2015年song han开启了现代深度网络剪枝的先河:
在工业界,主要考虑能带来速度提升的结构剪枝
前期剪枝主要探索的问题:
(1)何时剪枝
(2)通道排序
(3)正则化
Archetecture based 策略:
AMC(RL)/ABCPruner/Greedy/Soft Channel
Pruning/AutoCompress/DMCP(CVPR2020)/MetaPruning(EA) (ICCV2019)
2018年AutoML和NAS的兴起,同时Rethinking the value of Neural Network Pruning横空出世,认为网络剪枝本质是一种NAS,大家把重点放在了剪枝子网络的结构探索上
环境:
Python 3.6 以上
Pytorch 1.0以上,
CUDA 9.0 on Ubuntu 16.04及以上版本。
数据集:
准备好的数据集放到文件夹data中,并在下文介绍的六个文件中的–dataset 参数中加入该数据集名称以供选择。
预训练模型:
准备好的模型放到models文件夹中。
训练文件:
准备好的训练文件放到放到pretrained_model文件夹中,如densenet_cifar.py。
根目录中主要会用到以下六个文件:
第一步(准备工作)—— 将预训练模型放到pretrained_model文件夹中,模型训练.py文件放在models文件夹.\VGG16prune\5-HRank-master\pretrained_model中
第二步——运行rank_generation.py,SVD求解每层卷积层的秩并保存到根目录下的rank_conv文件夹
注意:在rank_generation.py中修改np.save位置,这样保留生成的每层卷积层特征图对应的秩,默认是
np.save(‘rank_conv/’ + args.arch+‘_limit%d’%(args.limit) + ‘/rank_conv%d’ % (1) + ‘.npy’, feature_result.numpy())
第三步—Model Training,运行main.py
例如:python main.py
–job_dir /Lun2/great99/HRankmaster/rank_conv/resnet_56_limit128
–resume /Lun2/great99/HRank-master/pretrained_model/resnet_56.pt.pt --dataset cifar10
–arch resnet_56 --compress_rate [0.1]+[0.60]*35+[0.0]*2+[0.6]*17
第四步:测试剪枝后模型的精度——运行evaluate.py
例如:python evaluate.py
–dataset cifar10 --data_dir /Lun2/great99/HRank-master/data
–test_model_dir /Lun2/great99/HRank-master/rank_conv/resnet_56_limit128
–arch resnet_56
第五步:测试剪枝后的参数量和计算量——运行cal_flops_params.py
所以该测试程序可以针对第二步以后的模型进行快速的参数量和计算量的计算。
示例:拿vgg16_bn为例,整个剪枝过程为:
对vgg16_bn先生成秩,再main.py训练,再测参数量,再测精度
1.python rank_generation.py --resume /Lun2/great99/HRank-master/pretrained_model/vgg_16_bn.pt --arch vgg_16_bn --limit 128 (秩只要生成一次!!!) |
2.python main.py --job_dir /Lun2/great99/HRank-master/rank_conv/vgg_16_bn --resume /Lun2/great99/HRank-master/pretrained_model/vgg_16_bn.pt --dataset cifar10 --arch vgg_16_bn -- compress_rate [0.95]+[0.5]*6+[0.95]*6 |
3.python cal_flops_params.py --arch vgg_16_bn --compress_rate [0.95]+[0.5]*6+[0.95]*6 |
4.python evaluate.py --dataset cifar10 --test_model_dir /Lun2/great99/HRank-master/rank_conv/vgg_16_bn --arch vgg_16_bn |
解释一下,
实例:对vgg16剪枝的完整步骤:
∎ Rank Generation
∎ Refine the accuracy by finetuning
建议剪枝率和对应模型性能、大小:(压缩比最佳参数,精度略低)
(若对精度下降要求较高,可以自行选择如下参数设置)
由于resnet参数量较少,一般resnet_56训8个半小时就行
(i) 我的数据集是Imagenet,代码中没有,怎么做?
answer:把预训练过程中的数据集预处理过程加入rank_generation和main.py中,再把你的数据集解压到data文件夹即可。
(ii) 我的网络并非已经有的模板里面,怎么做?
answer:把网络及相关训练代码加入(根目录下的)models文件夹中,(如densenet_cifar.py ),在rank_generation和main.py中def中加入网络的参数设定即可。
(iii) main.py有什么作用?
answer:这个里面是把生成秩按照降序的顺序排序,然后for cov_id in range(args.start_cov, len(convcfg)):,从第一层开始按照1,2,3… len(convcfg)设定的剪枝率把想剪的filters剪掉,每层设置几个epoch进行微调。
(iv) main.py中m.grad_mask(cov_id+1)将该层部分权重变成0,那剪枝的过程中会将这部分参数去掉吗?
answer:不会,对一个卷积层cov_id剪枝,就是将该层部分权重变成0,就保存该层模型。
(v) Main.py中有没有考虑偏置?
answer:对每一层权重和偏置都参与计算, for index, item in enumerate(params): 就会打印
1 bn1.weight
2 bn1.bias
3 layer1.0.conv1.weight等。
(vi) 如下:
这里的convcfg里面的参数是什么?
convcfg里面对应的数字对应的是索引,是name_parammeters(),对应的索引,用来访问卷积的参数索引,如果相应的卷积层后接了Relu,就指的是Relu层对应的参数。
(vii) 若卷积层后面有Pooling和reLu层,则减去的对象是reLu层,若卷积层后面只有Pooling层,则减去的对象是Pooling层。
(viii) reLu最好不要选preLu,EreLu,RreLu等变形,因为可能会造成满秩,可能会造成无法剪枝。最好就选ReLu。
根据 filter 产生的特征图的秩的大小衡量 filter 的重要性,从而决定裁剪掉哪些 filter,其流程如下图所示:
本文收到的启发是来自于一些经验,作者观察到网络特征图的秩并不会根据训练 batch 的变化而变化(也就是filter对输入数据分布并不敏感),见下图:
可见特征图的秩基本是没有变化的,与输入图片有几批无关。所以可以用很少的图片得到特征图的秩,理论上这个方法既准确又不需要多少训练时间,但是实际上实验的时候测试生成的秩也需要一个白天的时间,尤其对于vgg这种参数量大的网络。
论文地址:Hrank论文
转载请标明出处,或与作者唐三/SQM联系。