深入浅出的模型压缩:你一定从未见过如此通俗易懂的Slimming操作

作者丨科技猛兽
编辑丨极市平台

极市导读

本文首先介绍了模型压缩领域的指标含义,并通过梳理文献,介绍了模型压缩领域常用的方法。随后对Slimming这一模型压缩方法进行了详细介绍,并讲解了Slimming操作压缩GAN模型的细节。 >>加入极市CV技术交流群,走在计算机视觉的最前沿

目录

  • 如何衡量模型的复杂度?

论述参数量,计算量,运行占用内存,单位换算关系。

  • Literature Review for Model Compression

列举剪枝,量化,蒸馏方法list。

  • Slimming

讲解如何进行Slimming操作?

  • Slimming操作压缩GAN模型

讲解在GAN模型中如何去结合多种压缩方法。

  • 参考文献

如何衡量模型的复杂度?

在学习slimming操作之前的很重要一步是搞懂模型压缩领域的指标的含义。很多优秀的CNN模型在部署到端侧设备时会遇到困难,主要难在下面这3个方面。它们也是模型压缩领域关注的3个参数:

  • model size

  • Runtime Memory

  • Number of computing operations

  • model size

就是模型的大小,我们一般使用参数量parameter来衡量,注意,它的单位是。但是由于很多模型参数量太大,所以一般取一个更方便的单位:兆(M) 来衡量。比如ResNet-152的参数量可以达到60 million = 0.0006M。

有些时候,model size在实际计算时除了包含参数量以外,还包括网络架构信息和优化器信息等。比如存储一个一般的CNN模型(ImageNet训练)需要大于300MB。

这里你可能会有疑问:刚才的单位是M,怎么这里出来了个MB?是不是写错了?

肯定没有,我们需要注意这里的M和MB的换算关系:

比如说我有一个模型参数量是1M,在一般的深度学习框架中(比如说PyTorch),一般是32位存储。32位存储的意思就是1个参数用32个bit来存储。那么这个拥有1M参数量的模型所需要的存储空间的大小即为:1M * 32 bit = 32Mb = 4MB。因为1 Byte = 8 bit。

所以读到这里你应该明白说一个模型的model size,用M和MB其实是一样的意思。

那你可能还会有疑问:是不是一定要用32位存储?

这个问题很好,现在的quantization技术就是减少参数量所占的位数:比如我用8位存储,那么:

所需要的存储空间的大小即为:1M * 8 bit = 8Mb = 1MB。

更有甚者使用二值神经网络进一步减小参数量所占的位数(权值被限制为{-1, 1}或{-1, 0, 1}),后文有论文的链接,有空再专门介绍这个方法吧。下面简单介绍下参数量的计算方法:

卷积层参数量的计算方法:

如图中第2行所示为卷积核:这些卷积核时权重共享的,所以参数量为:

全连接层参数量的计算方法:

  • Run time Memory

就是模型实际运行时所占的内存。注意这个指标与只存储模型参数所占的存储空间的大小是不一样的,这个指标更大。这对于GPU来讲不算是问题,但是对于硬件能力极为有限的端侧设备来说就显得无法承受了。它的单位是兆字节 (MB)

  • Number of computing operations

就是模型的计算量,有FLOPs和MACs两种衡量的方式、简而言之,前者指的是乘加的数量,而后者指运算量。比如ResNet-152在前向传播一张256 * 256的图片的运算量可以达到20 GFLOPs。下面简单介绍下模型计算量的计算方法:

第1种:FLOPs:

卷积层FLOPs的计算方法:

只需在parameters的基础上再乘以feature map的大小即可,即对于某个卷积层,它的FLOPs数量为:

如果计算 ,则 为乘法的运算量。

为加法的运算量。

为 的运算量。

全连接层FLOPs的计算方法:

对于全连接层,由于不存在权值共享,它的FLOPs数目即是该层参数数目:

为乘法的运算量, 为加法的运算量。

第2种:MACs:

MACs与FLOPs的关系:

设有全连接层为:

y = w[0]*x[0] + w[1]*x[1] + w[2]*x[2] + ... + w[n8]*x[8]

对于上式而言共有9次乘加,即9MACs(实际上,9次相乘、9-1次相加,但为了方便统计,将计算量近似记为9MACs。所以近似来看 。(需要指出的是,现有很多硬件都将乘加运算作为一个单独的指令)。

全连接层MACs的计算:

激活层MACs的计算:

激活层不计算MAC,计算FLOPs。假设激活函数为:

则计算量为 FLOPs。

假设激活函数为:

则计算量为 FLOPs(乘法,指数,加法,除法)。

在计算FLOPS时,我们通常将加,减,乘,除,求幂,平方根等计为单个FLOP。

但是,实际上,我们通常不计这些操作,因为它们只占总时间的一小部分。通常只计算矩阵乘法和点积(dot product),忽略激活函数的计算量

卷积层MACC的计算:

关于这些指标,更详细的解读以及对应的代码实现可以参考:

科技猛兽:PyTorch 63.Coding for FLOPs, Params and Latency
https://zhuanlan.zhihu.com/p/268816646


Literature Review for Model Compression:

学会计算模型的复杂度之后,下面的任务就是压缩这个模型。在模型压缩领域,有一些常用的方法,比如:剪枝,量化,蒸馏,轻量化模块设计,低秩分解,加法网络等等。因为方法类别太丰富,这里就简单做个文献综述:

1 剪枝就是通过去除网络中冗余的channels,filters, neurons, or layers以得到一个更轻量级的网络,同时不影响性能。 代表性的工作有:

奇异值分解SVD(NIPS 2014): Exploiting linear structure within convolutional networks for efficient evaluation
韩松(ICLR 2016): Deep compression: Compressing deep neural networks with pruning, trained quantization and huffman coding
(NIPS 2015): Learning both weights and connections for efficient neural network
频域压缩(NIPS 2016): Packing convolutional neural networks in the frequency domain
剪Filter Reconstruction Error(ICCV 2017): Thinet: A filter level pruning method for deep neural network compression
LASSO regression(ICCV 2017): Channel pruning for accelerating very deep neural networks
Discriminative channels(NIPS 2018): Discrimination-aware channel pruning for deep neural networks
剪枝(ICCV 2017): Channel pruning for accelerating very deep neural networks
neuron level sparsity(ECCV 2017): Less is more: Towards compact cnns
Structured Sparsity Learning(NIPS 2016): Learning structured sparsity in deep neural networks

2 轻量化模块设计就是设计一些计算效率高,适合在端侧设备上部署的模块。 代表性的工作有:

Bottleneck(ICLR 2017): Squeezenet: Alexnet-level accuracy with 50x fewer parameters and 0.5 mb model size
MobileNet(CVPR 2017): Mobilenets: Efficient convolutional neural networks for mobile vision applications
ShuffleNet(CVPR 2018): Shufflenet: An extremely efficient convolutional neural network for mobile devices
SE模块(CVPR 2018): Squeeze-and-excitation networks
无参数的Shift操作(CVPR 2018): Shift: A zero flop, zero parameter alternative to spatial convolutions
Shift操作填坑(Arxiv): Shift-based primitives for efficientconvolutional neural networks
多用卷积核(NIPS 2018): Learning versatile filters for efficient convolutional neural networks
GhostNet(CVPR 2020): GhostNet: More features from cheap operations

3 蒸馏就是过模仿教师网络生成的软标签将知识从大的,预训练过的教师模型转移到轻量级的学生模型。 代表性的工作有:

Hinton(NIPS 2015): Distilling the knowledge in a neural network
中间层的特征作为提示(ICLR 2015): Fitnets: Hints for thin deep nets
多个Teacher(SIGKDD 2017): Learning from multiple teacher networks
两个特征得新知识再transfer(CVPR 2017): A gift from knowledge distillation: Fast optimization, network minimization and transfer learning

4 量化就是减少权重等的表示的位数,比如原来网络权值用32 bit存储,现在我只用8 bit来存储,以减少模型的Memory为原来的 ;更有甚者使用二值神经网络。 代表性的工作有:

量化:
(ICML 2015): Compressing neural networks with the hashing trick
(NIPS 2015): Learning both weights and connections for efficient neural network
(CVPR 2018): Quantization and training of neural networks for efficient integer-arithmetic-only inference
(CVPR 2016): Quantized convolutional neural networks for mobile devices
(ICML 2018): Deep k-means: Re-training and parameter sharing with harder cluster assignments for compressing deep convolutions
(CVPR 2019): Learning to quantize deep networks by optimizing quantization intervals with task loss
(CVPR 2019): HAQ: Hardware-Aware automated quantization with mixed precision.

二值神经网络:
Binarized weights(NIPS 2015): BinaryConnect: Training deep neural networks with binary weights during propagations
Binarized activations(NIPS 2016): Binarized neural networks
XNOR(ECCV 2016): Xnor-net: Imagenet classification using binary convolutional neural networks
more weight and activation(NIPS 2017): Towards accurate binary convolutional neural network
(ECCV 2020): Learning Architectures for Binary Networks
(ECCV 2020): BATS: Binary ArchitecTure Search

5 低秩分解就是将原来大的权重矩阵分解成多个小的矩阵,而小矩阵的计算量都比原来大矩阵的计算量要小。 代表性的工作有:

低秩分解(ICCV 2017): On compressing deep models by low rank and sparse decomposition
乐高网络(ICML 2019): Legonet: Efficient convolutional neural networks with lego filters
奇异值分解(NIPS 2014): Exploiting Linear Structure Within Convolutional Networks for Efficient Evaluation

6 加法网络就是:利用卷积所计算的互相关性其实就是一种“相似性的度量方法”,所以在神经网络中用加法代替乘法,在减少运算量的同时获得相同的性能。 代表性的工作有:

(CVPR 20): AdderNet: Do We Really Need Multiplications in Deep Learning?
(NIPS 20): Kernel Based Progressive Distillation for Adder Neural Networks
(Arxiv): AdderSR: Towards Energy Efficient Image Super-Resolution

7 Slimming:

(ICCV 2017):清华张长水,黄高团队: Learning Efficient Convolutional Networks through Network Slimming
(ECCV 2020):得克萨斯大学奥斯汀分校团队: GAN Slimming: All-in-One GAN Compression by A Unified Optimization Framework


Slimming:

而本文要讲的方法名字叫做slimming,它相比于上面这些方法,优点是什么?

答: 上面这些方法(1-6)有2个缺点:

  1. 只能解决上面讲的3个指标(model size, runtime memory, number of computation numbers)中的1-2个,即比如剪枝减少了模型参数量,可是运行时占得内存空间仍旧很大因为许多Memory花在了activation map上面,而不是weights上面,另外有的pruning方法需要专门的硬件电路;或是比如量化可以减少model size,可是运行时占得内存空间仍旧很大,Latency也没有下降,因为你得把权重还原回去。这是第一个缺点。
  2. 许多技术依赖于specially designed software/hardware accelerators,即如果我想用这个方法,丫还得专门去买你们设计的硬件电路芯片或者对应的软件[○・`Д´・ ○]。有一个组的很多文章都是这样。换句话说,以上这些压缩方法不够general-purpose

而Slimming这个模型压缩方法,就是为了解决这两个问题而生的。

具体是咋做的呢?

总体思路:实现一种channel-level的sparsity,就是在通道数上面稀疏化。本质是识别并剪掉那些不重要的通道(identify and prune unimportant channels)。

模型压缩可以在weight-level, kernel-level, channel-level 或者 layer-level上进行,为什么要在channel-level上面着手?

weight-level往往需要特殊的硬件加速器或软件来实现fast inference。

layer-level只适用于一些特别深的模型,比如超过50层的模型,但是不是很灵活。

相比之下,channel-level 取得了灵活性和实现方便程度的折中。

具体的做法可以用下面这幅图来表示:

slimming的具体做法

首先我们可以给每个channel定义一个scaling factor ,这个值在前向传播时要和这个channel的输出值乘在一起。在训练网络时,这个scaling factor与其他的权重一起训练,目标函数如下式:

只是为这个scaling factor 加上了sparsity regularization的罚项。其中 代表正常的训练数据, 代表权重。在实验中取 ,即使用 范数。

训练完后。把scaling factor比较小的channel视为是unimportant channel,剪掉。

剩下的部分网络(上图右)就是压缩后的网络,即compact model。

所以,这个scaling factor 的作用相当于是 an agent for channel selection。重要的是因为它们与网络联合优化,使得模型可以自动识别unimportant channel,可以安全地剪掉,而不影响性能。

所以,有了这个scaling factor,就可以实现模型channel的压缩。

现在的问题是:scaling factor如何得到?

我们将它与Batch Normalization联系起来,在BN中:

这里的 和 是trainable affine transformation parameters (scale and shift)。

所以,可以直接使用BN中的 参数作为我们要找的scaling factor 。这样,网络就不用做出任何的修改而直接训练。

而且,使用这种方式得到scaling factor 是最便捷的方式了,相比下面这几种实现方式:

  1. 如果我们放弃BN而给模型每一层加上一个scaling层:那么这个scaling层的参数将会是无意义的。因为convolution层和scaling层都是线性变换(Linear Transformation),你用这个scaling层,和把卷积层的weights调大一点结果是一样的。
  2. 如果我们在BN层之前加上一个scaling层:那么 带来的scaling的效应将会被BN层的normalization的作用抹去。
  3. 如果我们在BN层之后加上一个scaling层:那么每个channel将会有2个scaling factor,没有必要。

以这样的方式进行训练,有的channel的scaling factor就会是接近0的,那么就剪掉这些channel。那么scaling factor的阈值如何设计呢?比如我想剪掉70%的channel,那么阈值就设定在scaling factor排名70%左右的位置。

还有一个要解决的问题就是accuracy loss的问题,当剪枝率较大的时候,可能会带来精度的下降,此时我们通过fine-tune来补偿。

更有趣的是,这一过程还可以重复进行,如下图所示:

Slimming操作流程图,Slimming一时爽,一直Slimming一直爽。

就是sliming一次,得到小模型之后再次slimming,得到更小的模型。slimming一时爽,一直slimming一直爽。

最后一个要解决的问题就是存在跨层连接的问题:有的网络(比如VGG, AlexNet)不存在跨层连接,但是像ResNet和DenseNet这种存在跨层连接的,就需要做一些调整。

作者在下面的数据集上展示了这种方法的效果:

Slimming操作的实验结果

表中60% pruned的含义是剪掉了60%的channel数。

Baseline是指不使用sparsity regularization进行训练。

压缩后的评价指标参数量计算量分别在Parameter和FLOPs这2列展示。

对比结果可得:Slimming操作使参数量计算量都有下降,且模型的精度提升了。

消融实验1:

接下来我们评估下剪枝率对模型的影响的大小:

如果剪枝率设的比较低,则对于压缩模型意义不大;可如果设置的太高,又会很影响模型的精度。所以通过在CIFAR-10上面训练DenseNet-40,取超参数 ,得到下面的结果:

剪枝率的影响

可以得到下面的结论:

  • 当剪枝率超过一定的阈值时,精度会大大降低。
  • Finetuning过程可以补偿精度的下降。
  • 当使用sparsity罚项时,即使不fine-tuning,性能也会优于baseline。

消融实验2:

接下来我们评估关于sparsity罚项的超参数 对结果的影响:

Distributions of scaling factors in a trained VGGNet under various degree of sparsity regularization

超参数 的作用是驱使scaling factor 的距离拉大,以使得剪枝对模型的影响减少。

当超参数 越来越大时,scaling factor 之间的距离将越拉越大。有的channel将会变得越来越不重要,以极小化剪掉它们给模型带来的影响。

Slimming操作压缩GAN模型

问:蒸馏,剪枝,量化 都是模型压缩的技巧,它们可以结合在一起使用吗?

答:可以。 在普通的深度学习分类模型上,已有很多成功的把三者结合起来的例子,比如:

ICLR 2016 Best Paper: 韩松老师的Deep compression: Compressing deep neural networks with pruning, trained quantization and huffman coding

问: 既然如此,相同的做法可以复制到GAN模型上吗?

答: 并不容易。因为GAN模型存在众所周知的training instability,即训练不稳定的问题。

问: 那这个问题可以克服吗?

答:GAN Slimming 就是用来解决这个问题,它的特点有:

  • end-to-end:端到端
  • unified:统一的框架
  • combining multiple compression means:包含多种压缩方法

具体是怎么做的?

答:channel pruning + quantization

这步channel pruning的过程就相当于是上一节介绍的slimming操作

我们从GAN的训练过程说起:

首先GAN模型的训练是一个minmax optimization的问题:

因为实际部署时只需要Generator而不需要Discriminator,所以我们只需要压缩G即可。

要对Generator进行压缩,我们定义小的Generator为 ,预训练一个原模型 。通过蒸馏操作,即下面的目标函数来减少二者的距离:

下面的问题是:如何定义 的结构?

按照蒸馏distillation的办法,直接减少的channel数即可。可是实验发现这样做对性能影响较大。

按照我们的slimming的操作,我们为trainable scaling parameter 增加一个罚项 ,这个scale factor就来自于 normalization layers。

定义生成器 的参数为 ,我们把目标函数重写为:

式中 是超参数。

量化方面,我们针对activations和weights进行量化,使用2个量化器 和 (这个量化器是什么先别着急,下面会详细讲解~)。量化后的weights可以表示为 ,我们使用 代表经过量化器 和 量化之后的小生成器 。最终包含蒸馏,剪枝,量化的目标函数为:

式中代表小生成器 的参数, 代表判别器 的参数。

蓝色代表蒸馏部分。

绿色代表剪枝部分。

红色代表量化部分。

训练方式依旧是minmax方法:

这样的一套流程可以视作是一种特殊的NAS过程,其中,student generator由teacher generator经剪枝,量化而来。它的特点是:

  1. 每一步操作可解释。
  2. 轻量化,稳定。
  3. 不需要NAS方法耗费大量的计算资源与精心设计搜索空间。

端到端优化策略

要优化19式,我们需要解决3个问题:

  • minmax优化本身就是不稳定的。
  • 更新 的权重会遇到不可微分的量化参数,如何应用梯度下降?
  • 更新罚项参数 ,罚项是 范数,也是不可微的,如何应用梯度下降?

首先看更新 的问题:

19式中包含 的只有前两项,因此这个问题可以被等效为:

要使用梯度下降更新 ,就必须先求出这个东西: 。

但由于和 的不可微分性,无法直接求梯度。

现在开始着手解决这个问题:

首先观察到和 属于元素级操作,专业术语叫做element-wise operation。

所以,我们只需要解决标量scalar的不可求导问题,那么向量,矩阵的问题也就解决了。

无法对梯度求导的根本原因是和 在经过量化以后,变成了不可微分的值。

我们定义 和 分别为activation和weight中的一个标量。

要解决量化的不可导问题,那就首先要了解量化的过程是个啥。

量化 (Quantization) 一词,如果你学过数字电路或者控制理论的话应该并不陌生,看一副图片吧~

采样-保持电路及其输出

数字电路的A/D转换器通常要经过4个步骤:采样,保持,量化和编码。

采样就是: 对连续变化的模拟信号定时地测量,抽取其样值,采样结束后,再将此取样信号保持一段时间,使A/D 转换器有充足的时间进行A/D转换。采样-保持电路就是完成该任务的。其中,采样频率越高,采样越密,采样值就越多,采样-保持电路的输出就越接近于输入信号。因此对采样频率提出了一定的要求,必须满足采样定理。

量化就是: 如果要把变化范围在 的模拟电压转换为3位二进制代码的数字信号,由于3位二进制代码只有 即8个数值,因此就必须将模拟电压平分为8个等级。每个等级规定一个基准值,例如 为一个等级,基准值为0V,二进制代码为000; 为一个等级,基准值为7V,二进制代码为111。凡属于某一等级范围内的模拟电压值,就用该级电压的基准值去表示。例如 ,它在 之间,就用该级的基准值3V来表示,代码是011。显然,相邻两级间的差值是 。

所谓量化,在数电里面,就是把采样电压转换为某个最小电压整数倍的过程,编码就是用二进制代码来表示量化后的量化电平。

有点扯远了,说这么一段的目的就是为了使你看懂:量化在深度学习里面,就是把权重weights看作是模拟电压,把它转换为一个编码,这个编码可以是二进制的。你比如说我想量化为3位,3 bit,那么这个二进制代码就是000-111,如下图所示。你再比如说我想量化为1位,1 bit,那么这个二进制代码就是{-1, 1}。

量化为3位

还回到我们的GAN Slimming里面,现在的任务是量化和 :

量化 :把 clamp到一个范围里面: ,假设量化到 位。那么一共就有 种编码。每个编码占的长度就是 。所以量化器 可以写作是:

你细品一下,这个公式是不是和上面模拟电压 的量化过程一模一样?

量化 : 这个参数有正有负,所以我们先取绝对值,每个编码占的长度就是 ,假设量化到 位( ),那么一共就有 种编码。量化器 可以写作是:

接下来就是最棘手的不可微分问题(non-differentiable),使用pseudo gradient代指梯度,对于activation quantization,我们有:

对于weight quantization,我们有:

就是量化之后,对 或者 求导都不改变梯度,即量化后的值对量化前的值得导数认为是1。

这样我们就能计算 了,也就可以使用梯度下降更新 了。

接下来看更新 的问题:

19式中包含 的项保留,因此这个问题可以被等效为:

因为 范数这一项无法直接求导,所以我们采用近似梯度下降更新:

这一步其实也很好理解:因为 由标量组成,我们考虑它的一个分量(标量),所以 范数无非也就2种情况:当 时为 ;当 时为 。所以当 时梯度应当多减掉一个 ;当 时梯度应当多加上一个 。上式的意思就是这样。

要细究深层次的详细原因,就不得不从近端梯度下降开始说起,由于这部分内容很长,所以不在本文中详细展开,具体可以阅读下文:

科技猛兽:机器学习中的数学理论2:近端梯度下降
https://zhuanlan.zhihu.com/p/277041051

最后看更新 的问题:

19式中包含 的项保留,因此这个问题可以被等效为:

注意这里更新遵循的是gradient ascend的方法。

接下来就是按照GAN的训练方式去迭代更新 和 了,在更新 时,更新里面的权重 和 ,更新 时,更新里面的权重 即可。

最后一个问题是蒸馏的距离度量函数 该如何选择?

传统的蒸馏办法使用一些soft label + KL divergence的方法。

这里我们使用perceptual loss。所以最终的算法流程为:

GAN Slimming算法流程

其中,4,5两步更新 的参数;6这一步更新 的参数,交替进行。

实验部分:

我们先把这种方法应用在CycleGAN上面,得到如下结果:

CycleGAN原模型

CycleGAN压缩模型

表中 代表的含义是:

它们分别代表model size的压缩比,计算量的压缩比,结果FID的优化度。

GS-32就是不进行量化的32位存储。GS-8就是Slimming操作完成的8位存储的结果。

我们发现:GS-8与GS-32相比在结果差不多的前提下,多压缩了4倍。

在winter-to-summer这个数据集上,GS-8可以压缩31倍之多。下图展示了一些可视化实验结果:

GAN Slimming可视化实验结果

消融实验:

作者又做了其他一些方法1的实验作为对比:

D:Distillation:只使用蒸馏技术,来自1的方法。

CEC:来自2的方法。

D+CP:Distillation与channel pruning一起用。

CP+D:channel pruning与Distillation一起用。

GS-8 (MSE):GS-8 换成MSE Loss。

PostQ:GS-32+Quantization:先使用GS-32,再使用8 bit量化和fin-tune。

不同方法消融实验对比结果

参考文献:

[1] Distilling portable Generative Adversarial Networks for Image Translation
[2] Co-Evolutionary Compression for Unpaired Image Translation
[3] Convex Optimization, Lecture 7, CMU
[4] Learning Efficient Convolutional Networks through Network Slimming
[5] GAN Slimming: All-in-One GAN Compression by A Unified Optimization Framework
[6] Proximal Gradient Descent (by Ryan Tibshirani from CMU):http://stat.cmu.edu/~ryantibs/

◎作者档案
作者:科技猛兽
(0)

相关推荐