0%

Network Slimming

由于深度学习模型大小以及计算资源的限制,导致将深度学习模型部署到如移动端的时候会受到一定的限制,为了解决这个问题,就形成了一个新的领域:模型压缩,即减少模型的参数以及计算量,并且依然保证模型的精度。常见的方法比如:量化、剪枝、蒸馏等,本文便是“剪枝”当中一篇非常经典的文章,也是比较有效果的一篇文章。本文方法其实比较简单:首先,使用L1 regularization对BN层的scaling factor进行稀疏化训练,然后将scaling factor较小的参数相连接的filter减掉,达到减少模型size的目标。最后再重新finetune剪枝后的网络,以达到更好的效果。


论文名称:Learning Efficient Convolutional Networks through Network Slimming

作者: Zhuang Liu & Jianguo Li 等

论文链接: https://arxiv.org/abs/1708.06519

github:https://github.com/liuzhuang13/slimming


模型压缩加速方法介绍

  • Low-rank Decomposition

采用奇异值分解等方法对权重矩阵进行分解,以达到减少矩阵大小的作用,这种方法适用于全连接层,大概可以减少3倍的模型大小,但是对于卷积层,作用不大。

  • Weight Quantization

参数量化,比如将参数量化的int8,或者量化到-1,0,1,可以较大的提升模型的速度,但是这种方法往往较难保证精度,精度一般都会下降。

  • Weight Pruning / Sparsifying

参数剪枝与稀疏化,将不重要的较小的权重置为0。这就需要矩阵较为稀疏才能起作用。

  • Structured Pruning / Sparsifying

结构化剪枝就是不单单是对每个参数,而是对网络结构进行剪枝,比如:channel,neurons,layers等

本文方法:Network Slimming

Advantage of Channel-level Sparsity

首先需要指出的是,稀疏化可以应用在各个level: weight-level, kernel-level, channel-level, layer-level. 其中weight-level的稀疏化程度最高,也可以达到最大的压缩率,但是在weight-level进行加速,往往需要特殊的软硬件加速器,来对稀疏的模型进行加速。

与其相反的layer-level,相对比较简单,不需要特殊的硬件,但是对layer比较多的时候才具有一定的效果,比如50个layer以上,想想可以理解,把50个layer减到40个layer可能还能保留一定的精度。但是10个layer减少到5个layer,差别就比较大了。

相比之下,channel-level就是一种很好的平衡,即不需要特殊的硬件同时还保留了一定的复杂度。

但是channel-level依然具有一定得挑战,由于channel-level的剪枝会影响到输入和输出,这就使得直接在预训练模型上面将权重减掉是不行的,因为一般不会存在输入和输出都接近0的情况。据统计,一般情况,也就减掉约10%是对精度影响不大的。

如何解决这个问题呢?

本文的方法是引入scale参数$\gamma$,将其作用于每一个channel,与输出weight相乘,然后在训练的时候,一起训练这个$\gamma$参数,并对其增加 正则,使其稀疏化,稀疏化后,将$\gamma$参数较小的值减掉就可以了。

Loss公式表示就是如下:

$$L = \sum_{x,y}l(f(x,W),y)+\lambda\sum (g(\gamma))$$

为了达到稀疏化的效果,这里g()采用的是L1范数。

整个过程如下图所示:

0.png

引入的scale参数放在哪里呢?

我们知道BN层在深度网络中起到了很大的作用,使得网络可以快速的收敛,而且BN层往往是被插入到conv层的后面的,并且BN也是在channel-level进行计算的,基于此,我们可以直接利用BN层的$\gamma$来当做我们上面设计的参数,对channel进行筛选。

由于对$\gamma$参数加入了L1 正则化,所以训练过后,该参数会较为稀疏,所以在训练后,可以根据阈值,对网络进行裁剪,将$\gamma$小于阈值的对应channel减掉,就得到了新的网络。但是这样得到的网络大小减少了,自然精度会稍有下降,所以我们需要再对其进行fine-tune操作,恢复期精度,整个流程可以迭代进行如下图所示:

1.png

对BN层增加L1 正则化参数的方法也很简单,如下代码可以实现,就是在网络反向传播后,对BN层的参数加上其值的sign()就可以了,为什么可以这样做呢?其实按照正常公式推理,需要将增加的BN的loss对参数求导,而由于loss是L1正则,也就是绝对值,所以求导后也就变成了1,-1, 0三个值,所以也就是如下代码的解释了。

1
2
3
4
def updateBN(model):
for n, m in model.named_modules():
if isinstance(m, nn.BatchNorm2d):
m.weight.grad.data.add_(args.s*torch.sign(m.weight.data))

实验结果

show几张图吧

3.png

2.png

4.png

分析

如下图是剪枝比例与精度的关系,可以发现,当减值比例超过40%的时候,精度下降非常明显。但是经过finetune,基本还是可以恢复的,但是到70%以上,恢复的就比较难了。

5.png

对于正则化的效果,作者也进行了一定的分析,作者分别对正则化系数进行了分析,分别实验了0,10e-4,10e-5的结果,结果如下图所示,可见,随着系数的增加,BN层的scale参数也越来越接近0,说明稀疏化的作用还是比较明显的。

6.png