卷积核层面的模型剪枝
本质方法。就是计算每一个filter上权重绝对值之和,去掉m个权重最小的filters,并同时去掉与其相关的特征图及下一层的所有相关的输入filters;
筛选需要裁减的卷积核步骤为: 1.对每个Filter,使用L1-Norm来计算每一个filter上权重绝对值之和; 2.对所得权重之和进行排序,和大小反映了相关filter的重要性; 3.选择前k个较大的权重之和保留,建立一个mask,大保留的部分为1,小于阈值的部分为0。
cfg_mask = []
layer_id = 0
for m in model.modules():
if isinstance(m, nn.Conv2d):
out_channels = m.weight.data.shape[0]
if out_channels == cfg[layer_id]:
cfg_mask.append(torch.ones(out_channels))
layer_id += 1
continue
weight_copy = m.weight.data.abs().clone()
weight_copy = weight_copy.cpu().numpy()
L1_norm = np.sum(weight_copy, axis=(1, 2, 3))
arg_max = np.argsort(L1_norm)[::-1]
arg_max_rev = arg_max[:cfg[layer_id]]
assert arg_max_rev.size == cfg[layer_id], "size of arg_max_rev not correct"
mask = torch.zeros(out_channels)
mask[arg_max_rev.tolist()] = 1
cfg_mask.append(mask)
layer_id += 1
elif isinstance(m, nn.MaxPool2d):
layer_id += 1
之后需要进行BN2D层的剪枝,即需要丢弃刚才被抛弃的卷积核。
start_mask = torch.ones(3)
layer_id_in_cfg = 0
end_mask = cfg_mask[layer_id_in_cfg]
for [m0, m1] in zip(model.modules(), newmodel.modules()):
if isinstance(m0, nn.BatchNorm2d):
idx1 = np.squeeze(np.argwhere(np.asarray(end_mask.cpu().numpy())))
if idx1.size == 1:
idx1 = np.resize(idx1,(1,))
m1.weight.data = m0.weight.data[idx1.tolist()].clone()
m1.bias.data = m0.bias.data[idx1.tolist()].clone()
m1.running_mean = m0.running_mean[idx1.tolist()].clone()
m1.running_var = m0.running_var[idx1.tolist()].clone()
layer_id_in_cfg += 1
start_mask = end_mask
if layer_id_in_cfg < len(cfg_mask):
end_mask = cfg_mask[layer_id_in_cfg]
最后需要进行卷积层剪枝,根据前后BN层的保留层,可以计算得到卷积层保留的卷积核大小(上层BN层输出,下层BN层输入),保留前后BN的对应保留的元素,其余剪枝。
elif isinstance(m0, nn.Conv2d):
idx0 = np.squeeze(np.argwhere(np.asarray(start_mask.cpu().numpy())))
idx1 = np.squeeze(np.argwhere(np.asarray(end_mask.cpu().numpy())))
print('In shape: {:d}, Out shape {:d}.'.format(idx0.size, idx1.size))
if idx0.size == 1:
idx0 = np.resize(idx0, (1,))
if idx1.size == 1:
idx1 = np.resize(idx1, (1,))
w1 = m0.weight.data[:, idx0.tolist(), :, :].clone()
w1 = w1[idx1.tolist(), :, :, :].clone()
m1.weight.data = w1.clone()
最后对FC层进行剪枝,由于最后一层FC层的输出是固定的(分类类数),因此只对FC层的输入维度进行剪枝,也是根据上一层BN层的输出,对应保留的元素,其余剪枝。
elif isinstance(m0, nn.Linear):
if layer_id_in_cfg == len(cfg_mask):
idx0 = np.squeeze(np.argwhere(np.asarray(cfg_mask[-1].cpu().numpy())))
if idx0.size == 1:
idx0 = np.resize(idx0, (1,))
m1.weight.data = m0.weight.data[:, idx0].clone()
m1.bias.data = m0.bias.data.clone()
layer_id_in_cfg += 1
continue
m1.weight.data = m0.weight.data.clone()
m1.bias.data = m0.bias.data.clone()
对BN1d层不剪枝
elif isinstance(m0, nn.BatchNorm1d):
m1.weight.data = m0.weight.data.clone()
m1.bias.data = m0.bias.data.clone()
m1.running_mean = m0.running_mean.clone()
m1.running_var = m0.running_var.clone()
Thinet核心思路
Filters Pruning在卷积核的层面进行剪枝,上述思路filter的修剪取决于当前层,Thinet则选择使用下一层的输出进行卷积核的修剪依据。思想就是如果某层输入数据中的一部分就可以得到与全部输入非常近似的结果,那么就可以将输入数据中其他部分去掉,同时其对应的前面层的filter也就可以去掉。
|