(论文精读)PRUNING FILTER IN FILTER《滤波器中的剪枝滤波器》

论文地址:原文
代码实现
中文翻译

一、精读论文

论文题目

PRUNING FILTER IN FILTER

论文作者

Fanxu Meng 孟繁续

刊物名称

NeurIPS 2020

出版日期

2020

摘要

剪枝已成为现代神经网络压缩和加速的一种非常有效的技术。现有的剪枝方法可分为两大类:滤波器剪枝(FP)和权重剪枝(WP)。与WP相比,FP在硬件兼容性方面胜出,但在压缩比方面失败。为了收敛两种方法的强度,我们提出在滤波器中对滤波器进行剪枝。具体来说,我们将滤波器F∈RC×K×K视为K个×K条,即1 × 1个滤波器∈RC,然后通过修剪条纹而不是整个滤波器,我们可以在硬件友好的同时实现比传统FP更细的粒度。我们称我们的方法为SWP (Stripe-Wise Pruning)。SWP的实现是通过引入一个新的可学习的矩阵,称为滤波器骨架,其值反映了每个滤波器的形状。正如一些最近的工作表明,修剪的结构比继承的重要权值更重要,我们认为单个过滤器的结构,即形状,也很重要。通过大量的实验,我们证明了SWP比之前基于fp的方法更有效,并在cifa10和ImageNet数据集上实现了最先进的剪枝率,而精度没有明显下降。有关代码载于[this url].

关键词

剪枝、SWP

总结

这篇文章的工作总结为:

认为隐含在滤波器参数中的形状属性是很重要的。
用滤波器骨架(Filter Skeleton)学习滤波器形状,将形状和参数分离。
以滤波器的任意一条(Stripe)为单位,将滤波器裁剪为任意形状。
通过卷积计算方式变换,结构化实现逐条剪枝(Stripe-Wise Pruning)。

二、研读总结(分三段总结,500字左右)

1、 针对问题与解决方法

这篇文章主要考虑的是神经网络的结构属性。他这里的做法很有启发性,结合了两种结构化和非结构化剪枝中的典型方法。就是对weights剪枝和对filters剪枝,因为这两种剪枝方法各有优劣。非结构化的剪枝在硬件方面需要有专用的库支持,但是它的压缩率较高,对filters剪枝在硬件方面更兼容,但在压缩率方面不如前者。所以作者提出了一个方法,叫做在filters中剪枝filter。
(论文精读)PRUNING FILTER IN FILTER《滤波器中的剪枝滤波器》_第1张图片
(论文精读)PRUNING FILTER IN FILTER《滤波器中的剪枝滤波器》_第2张图片

那这是怎么做的呢?如上图中一个kernel,它的长和宽是相等的,是k×k×c,那我们就可以按照他的size把它剪成k×k个条,比如一个3×3的卷积,我们就可以把它剪成9 个条纹,然后通过修剪整个条而不是剪掉整个filter,显然就可以实现比传统的filters pruning更精细的一个粒度。这个方法作者叫做SWP。

(论文精读)PRUNING FILTER IN FILTER《滤波器中的剪枝滤波器》_第3张图片
对作者定义的FilterStripe层取代卷积层的剪枝方法也是理解代码的关键。按原论文说的,为了进行有效的修剪,我们设置了一个阈值δ,FS中对应值小于δ的条带在训练期间将不会更新,并且可以在之后进行修剪。值得注意的是,当对修剪后的网络进行推理时,由于过滤器被破坏,我们不能直接使用过滤器作为一个整体对输入特征图进行卷积。相反,我们需要独立地使用每个条带来执行卷积,并对每个条带产生的特征图求和,如图5所示。因为要对每个条带进行单独的卷积,所以就把每个条带进行了展平处理,把有用的条带进行卷积后的通带在前向传播的时候进行叠加,这样在BN操作的时候就和对正常卷积进行剪枝之后的操作相同了。

下面是剪枝之后打印出来的网络,可以看到FilterStripe的第一个参数就是FS骨架的叠加,对其相加求和正好就是后面第三个参数 输出通道数。BN层的通道数是卷积之后同一个FS骨架的通道数叠加,也就是上面说的正常卷积之后的操作了。

VGG(
  (features): Sequential(
    (0): FilterStripe(
      tensor([[ 6, 15,  4],
              [ 8, 13, 10],
              [ 7, 13,  8]], device='cuda:0'),3, 84, kernel_size=(1, 1), stride=(1, 1)
    )
    (1): BatchNorm(41, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (2): ReLU(inplace=True)
    (3): FilterStripe(
      tensor([[40, 45, 33],
              [46, 46, 33],
              [34, 39, 31]], device='cuda:0'),41, 347, kernel_size=(1, 1), stride=(1, 1)
    )
    (4): BatchNorm(63, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (5): ReLU(inplace=True)
    (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (7): FilterStripe(
      tensor([[ 51,  74,  56],
              [ 77, 107,  77],
              [ 61,  70,  58]], device='cuda:0'),63, 631, kernel_size=(1, 1), stride=(1, 1)
    )
    (8): BatchNorm(127, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (9): ReLU(inplace=True)
    (10): FilterStripe(
      tensor([[ 98, 105,  98],
              [106, 123,  98],
              [ 93, 106, 100]], device='cuda:0'),127, 927, kernel_size=(1, 1), stride=(1, 1)
    )
    (11): BatchNorm(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (12): ReLU(inplace=True)
    (13): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (14): FilterStripe(
      tensor([[187, 189, 188],
              [191, 213, 195],
              [194, 182, 181]], device='cuda:0'),128, 1720, kernel_size=(1, 1), stride=(1, 1)
    )
    (15): BatchNorm(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (16): ReLU(inplace=True)
    (17): FilterStripe(
      tensor([[201, 189, 206],
              [174, 151, 176],
              [195, 180, 200]], device='cuda:0'),256, 1672, kernel_size=(1, 1), stride=(1, 1)
    )
    (18): BatchNorm(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (19): ReLU(inplace=True)
    (20): FilterStripe(
      tensor([[113, 118, 100],
              [104, 127, 102],
              [102, 115, 116]], device='cuda:0'),256, 997, kernel_size=(1, 1), stride=(1, 1)
    )
    (21): BatchNorm(244, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (22): ReLU(inplace=True)
    (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (24): FilterStripe(
      tensor([[101, 123, 121],
              [ 97, 154, 102],
              [ 73, 110,  81]], device='cuda:0'),244, 962, kernel_size=(1, 1), stride=(1, 1)
    )
    (25): BatchNorm(417, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (26): ReLU(inplace=True)
    (27): FilterStripe(
      tensor([[ 74,  71,  53],
              [ 27, 129,  28],
              [ 42,  79,  55]], device='cuda:0'),417, 558, kernel_size=(1, 1), stride=(1, 1)
    )
    (28): BatchNorm(398, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (29): ReLU(inplace=True)
    (30): FilterStripe(
      tensor([[15, 21, 20],
              [49, 76, 36],
              [23, 16, 40]], device='cuda:0'),398, 296, kernel_size=(1, 1), stride=(1, 1)
    )
    (31): BatchNorm(272, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (32): ReLU(inplace=True)
    (33): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (34): FilterStripe(
      tensor([[  0,   2,   0],
              [  0, 220,   2],
              [  0,   1,   0]], device='cuda:0'),272, 225, kernel_size=(1, 1), stride=(1, 1)
    )
    (35): BatchNorm(225, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (36): ReLU(inplace=True)
    (37): FilterStripe(
      tensor([[  1,  10,   0],
              [ 24, 245,  21],
              [  3,  10,   0]], device='cuda:0'),225, 314, kernel_size=(1, 1), stride=(1, 1)
    )
    (38): BatchNorm(314, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (39): ReLU(inplace=True)
    (40): FilterStripe(
      tensor([[ 11,  70,   3],
              [ 18, 153,  19],
              [  8,  84,  14]], device='cuda:0'),314, 380, kernel_size=(1, 1), stride=(1, 1)
    )
    (41): BatchNorm(378, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (42): ReLU(inplace=True)
    (43): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (44): AvgPool2d(kernel_size=1, stride=1, padding=0)
  )
  (classifier): Linear(in_features=378, out_features=10, bias=True)
)

(论文精读)PRUNING FILTER IN FILTER《滤波器中的剪枝滤波器》_第4张图片
以上图举例:
FS的sum(dim(1,2))四个值分别为 1、9、3、3
FS的sum(dim(0))是一个9*9的矩阵也就是FilterStripe的第一个参数应该是
tensor
[[ 2, 2, 1],
[ 3, 2, 2],
[ 1, 1, 2]]
其实就是FS的四个块对应位置求和。
那么FilterStripe的第三个参数展平之后的输出通道就是上面的矩阵相加也就是16.
因为FS没有全0的块,所以BN的处理仍然是展平之前的4.

这篇论文的作者跟rethinking the value of network pruning思路是一样的。 都是认为网络的体系结构很重要。而且本文中作者认为,Filter本身的结构也很重要。而且他的观点是,内核越大的filters性能越好。就要提出一个形状的概念。这个形状是什么意思呢?比如这个图
(论文精读)PRUNING FILTER IN FILTER《滤波器中的剪枝滤波器》_第5张图片
这是通道的L1范数值的示意图。从这个图可以看出,filters中并非所有的条纹贡献都相等,对应L1范数非常低的条带就可以删除。那删除以后保留最少条数的,同时保持filters功能的形状就叫做最佳的Filter形状。所以将要解决的一个问题就是我们怎么找到最佳的形状,还提出了一种filters框架来学习这个最佳形状。

2、 数据实验与结论分析

将每个条纹分为多个组,并修剪每个组中的权重。然而,这些非结构化剪枝方法的一个缺点是所得到的权重矩阵是稀疏的,这在没有专用硬件/库的情况下不能实现压缩和加速。所以虽然这个方法很新颖,但是还是只能在GPU上加速,至于在IC或者ASIC上就很难支持了。
(论文精读)PRUNING FILTER IN FILTER《滤波器中的剪枝滤波器》_第6张图片

3、 科研启发与积累工作

实现了对VGG网络模型的复现工作。

你可能感兴趣的:(轻量化网络,剪枝,算法,机器学习)