Distiller tutorial: Pruning Filters & Channels

本文为对distiller教程 Pruning Filters & Channels 的翻译。
原文地址:Pruning Filters & Channels

Introduction

channel和filter的剪枝是结构化剪枝的示例,这些剪枝方法不需要特殊硬件即可完成剪枝并压缩模型,这也使得这种剪枝方法非常有趣并广泛应用。在具有串行数据依赖性的网络中,很容易理解和定义如何修建channel和filter。然而在更复杂的有着并行数据依赖性的模型中(比如ResNet的skip connection,GoogleNet的Inception层),这件事就变得很复杂,需要对模型中的数据流有更深入的了解,以便定义剪枝schedule。
  
这篇文章解释了channel和filter的剪枝,其中的一些challenge,以及如何为这些结构化剪枝定义一个distiller pruning schedule。

名词解释:

feature map:(FM for short),每个卷积层的input plane。

input channel:就像RGB channel一样。

activation:每个卷积层的输出。

convolution output shape(N,Cout,Hout,Wout),其中N是batch size,Cout表示输出channel,Hout是输出channel的height,Wout是width。我们不会对batch size大小有很多关注,因为它对我们的讨论不重要,因此不失一般性我们将N设为1.卷积weight为:(F,C,K,K),F为filter的数量,C是channel的数量,K是kernel大小。每个kernel与一个2Dinput channel(也就是feature map)进行卷积,所以如果输入有Cin个channel,一个filter中就有Cin个kernel。每个filter与整个input进行卷积产生一个output channel(feature map),如果有Cout个output channel,就有Cout个filter。

Filter Pruning

filter剪枝和channel剪枝非常相似。
  
  在filter剪枝中,我们使用一些标准来确定哪些filter很重要,哪些filter不重要。研究人员提出了各种剪枝标准,比如filter的L1-norm值;激活层的熵;分类精度降低等。不管如何选择要修剪的filter,假设在下图中我们选择修剪绿色和黄色的filter。
  
  由于我们在输入端运算中少了两个filter,我们必须减少两个output feature map。所以当我们修剪filter时,除了更改权重tensor的大小外,我们还需要重新配置当前卷积层(更改其out_channels)和后续卷积层(更改其in_channels)。最后,由于下一层的输入现在较小(具有较少的channel),我们还应该通过删除与修剪过的filter相对应的channel来缩小下一层的权重张量。我们称两个卷积层之间具有数据依赖性(data-dependency)。我没有提到通常在卷积后使用的激活函数,因为这些函数没有参数,并且对输入形状不敏感。Distiller还解决了其他一些依赖关系(例如紧密耦合到权重的optimizer参数),这里不再讨论。
  
Distiller tutorial: Pruning Filters & Channels_第1张图片
此示例的schedule YAML语法在下方。

pruners:
  example_pruner:
    class: L1RankedStructureParameterPruner_AGP
    initial_sparsity : 0.10
    final_sparsity: 0.50
    group_type: Filters
    weights: [module.conv1.weight]

考虑在两个卷积层之间添加一个batch-norm层:

Distiller tutorial: Pruning Filters & Channels_第2张图片
  Batch-Normalization层由几个tensor进行参数化,这些tensor包含每个input channel的信息(即scale和shift)。因为我们的卷积产生的输出FM较少,而这些FM是BN层的输入,所以我们还需要重新配置BN层。而且,我们还需要缩小BN层的scale和shift tensor,它们是BN输入变换中的系数。此外,我们从tensor中删除的scale和shift系数必须与我们从卷积权重tensor中删除的filter(或输出feature map channel)相对应。这个细节会让人很痛苦,但我们将在后面的示例中解决。上面示例中BN层的存在对我们没有影响,实际上,YAMLschedule不变。Distiller会检测到BN层的存在并自动调整其参数。

考虑另一个与非串行数据相关的示例。在此,conv1的输出是conv2conv3的输入。这是并行数据依赖的示例。
Distiller tutorial: Pruning Filters & Channels_第3张图片
  由于我们仍然仅显式修剪conv1的weight filter,因此YAML schedule与前两个示例相同。conv2conv3的weight channel在distiller的一个名为“Thinning”的process中被隐式修剪。
  
  另一个涉及到三个卷积的例子,这次我们想剪掉两个卷积层的filter,其输出逐元素相加并喂入第三个卷积。在这个例子中conv3conv1conv2都有依赖性,对这种依赖性有两种含义。第一个且是最明显的含义是,我们需要从conv1conv2中修剪相同数量的filter。因为我们对conv1conv2的输出进行了逐元素相加,它们必须有同一shape,且如果conv1conv2修剪掉同样数量的filter他们也只能有同一形状。这种三角数据依赖性的第二层含义是,conv1conv2必须剪掉同样的filter。如果如下图中忽略掉第二个约束,就会出现两难境地:不能修剪conv3的权重channel。
  Distiller tutorial: Pruning Filters & Channels_第4张图片
  我们必须应用第二个约束,这也意味着我们现在需要积极主动:我们需要通过conv1conv2的filter剪枝选择来决定是否修剪conv1conv2.下图解释了在决定根据conv1的剪枝choice后进行剪枝。
Distiller tutorial: Pruning Filters & Channels_第5张图片
  YAML schedule语法需要能够表达上面讨论的两个约束。首先,我们需要告诉filter pruner我们存在一个leader类型的依赖项。这意味着该weights中列出的所有tensor都将在每次迭代中以相同程度进行修剪,并且为了修剪filter,我们将使用列出的第一个tensor的修剪决策。在下面的示例中,通过module.conv1.weight 的剪枝选择来共同修剪module.conv1.weight和module.conv2.weight.

pruners:
  example_pruner:
    class: L1RankedStructureParameterPruner_AGP
    initial_sparsity : 0.10
    final_sparsity: 0.50
    group_type: Filters
    group_dependency: Leader
    weights: [module.conv1.weight, module.conv2.weight]

当我们对ResNet应用filter-pruning时,由于skip connection,我们会看到一些很长的依赖链。如果不注意,会很容易的未指定(或错误指定)依赖链并且distiller会退出并报错。该异常不能解释规范错误,因此需要改进。

channel pruning

channel pruning与filter pruning非常相似,但依赖性会反转。
Distiller tutorial: Pruning Filters & Channels_第6张图片
如图所示,conv1现在依赖于conv2,并且conv1的weight filter会依据从conv2的权重中删除的channel进行隐式修剪。

你可能感兴趣的:(distiller,pytorch,distiller,神经网络压缩)