一、代码分析:
参考pytorch代码对论文《Pruning Convolutional Neural Networks for Resource Efficient Inference》使用mxnet框架进行了复现,代码来自于一个GitHub的项目,本文主要是在其代码的基础之上,移植到mxnet的框架之上,移植的过程中发现了很多的问题算是好好学习了一下mxnet吧。
首先剪枝过程主要是通过PrunningFineTuner_CNN类中的prune()函数完成的,基本的流程为:
①计算剪枝迭代次数->②计算rank值(论文中泰勒展开式的值,这一步骤中还涉及到了归一化等操作)->③获得被剪枝的层和卷积核index(实际上第二步在这个函数中的)->④剪枝->⑤fine-tuning
->重复②->⑥过程知道达到迭代次数
1、获得剪枝layer and filter index函数 get_prunning_plan()包含在PrunningFineTuner_CNN中
def get_prunning_plan(self, num_filters_to_prune):
filters_to_prune = self.lowest_ranking_filters(num_filters_to_prune)
filters_to_prune_per_layer = {}
for (l, f, _) in filters_to_prune:
# l:layer index
# f:filter index
if l not in filters_to_prune_per_layer:
filters_to_prune_per_layer[l] = []
filters_to_prune_per_layer[l].append(f)
for l in filters_to_prune_per_layer:
# 同一层有可能需要裁剪掉好多个滤波器
filters_to_prune_per_layer[l] = sorted(filters_to_prune_per_layer[l])
for i in range(len(filters_to_prune_per_layer[l])):
filters_to_prune_per_layer[l][i] = filters_to_prune_per_layer[l][i] - i
filters_to_prune = []
for l in filters_to_prune_per_layer:
for i in filters_to_prune_per_layer[l]:
filters_to_prune.append((l, i))
return filters_to_prune
函数lowest_ranking_filters(num_filters_to_prune)的作用是通过计算的rank值得到最小的num_filters_to_prune个卷积核的index,其返回值为类型为list内部参数格式为:(layerindex,filterindex,rank_value),即return [(layerindex,filterindex,rank_value)]
之前一直对
filters_to_prune_per_layer[l][i] = filters_to_prune_per_layer[l][i] - i
有疑问,不知道为什么会要减去i ,它的目的是为了更新剪枝后的待剪枝的filter index,加入我们lowest_ranking_filters函数返回的数据如下:
[(1,1,1),(1,3,1),(2,1,4),(2,2,6),(2,10,6),(3,2,6),(4,3,10)]
(2,1,4)表示的是第三个卷积层的第二个滤波器的rank值为4,经过get_prunning_plan函数处理后的数据为:
[(1, 1), (1, 2), (2, 1), (2, 1), (2, 8), (3, 2), (4, 3)]
我们看到这里有重复的(2,1)然后本来(2,10,6)也变成了(2,8)这是因为程序中,是一个一个进行减值的,每次只能减去一个卷积核,当我们减去第三层第二个卷积核后,该层的卷积核就少了一个,原来第三个就变成了现在的第二个,第十一个在剪裁掉前两个后变成了第九个(计数从0开始)
二、mxnet复现过程
1、argparse模块的使用
通过载入模块import argparse
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument("--train", dest="train", action="store_true")
parser.add_argument("--prune", dest="prune", action="store_true")
parser.add_argument("--train_path", type = str, default = "train")
parser.add_argument("--test_path", type = str, default = "test")
parser.set_defaults(train=False)
parser.set_defaults(prune=False)
args = parser.parse_args()
return args
add_argument()第一个参数为命令行参数名称,dest为存储为的名称,默认为第一个参数后面的字符串,如上的代码我们就可以使用args,train来访问--train存储的参数,action参数默认为‘store’默认存储,而如果使用‘store_true’就是存储为布尔真值,如果命令行出现了--train那么args.train==True,default为默认值,如上所示前两个参数同样可以使用default=False的方法设定默认值
2、获得卷积核的个数
首先需要说明的是该卷积核是三维卷积核比如说一个卷积操作输入输出卷积核分别为in_channels和out_channels,那么我们可以说这次操作有out_channels个卷积核,每个卷积核是in_channels维度的。
代码如下:
def total_num_filters(self):
filter = 0
for name, block in model.feature._children.items():
if isinstance(block, nn.Conv2D):
filter += block._channels
return filter
class block._children是一个字典类型,其中存放的是所有通过add()函数添加的block,他们均通过register_child()函数注册到_children中去。
add分别存在于
def add(self, *blocks):
"""Adds block on top of the stack."""
for block in blocks:
self.register_child(block)
def register_child(self, block, name=None):
"""Registers block as a child of self. :py:class:`Block` s assigned to self as
attributes will be registered automatically."""
if name is None:
name = str(len(self._children))
self._children[name] = block
2、卷积层数:
nn.Conv2D(in_channels=3, channels=64, kernel_size=3, strides=1, padding=1),
nn.BatchNorm(),
nn.Activation('relu'),
nn.Conv2D(in_channels=64, channels=64, kernel_size=3, strides=1, padding=1),
nn.BatchNorm(),
nn.Activation('relu'),
nn.MaxPool2D(pool_size=2, strides=2),
nn.Conv2D(in_channels=64, channels=128, kernel_size=3, strides=1, padding=1),
nn.BatchNorm(),
nn.Activation('relu'),
nn.Conv2D(in_channels=128, channels=128, kernel_size=3, strides=1, padding=1),
nn.BatchNorm(),
nn.Activation('relu'),
nn.AvgPool2D(pool_size=2,strides=2),
nn.Conv2D(in_channels=128, channels=256, kernel_size=3, strides=1, padding=1),
nn.BatchNorm(),
nn.Activation('relu'),
nn.Conv2D(in_channels=256, channels=256, kernel_size=3, strides=1, padding=1),
nn.BatchNorm(),
nn.Activation('relu'),
nn.AvgPool2D(pool_size=8,strides=8)
程序统计的时候每一个block也就是说batchnorm、conv2d、activation都是一个layer