深入浅出的模型压缩:你一定从未见过如此通俗易懂的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个缺点:
只能解决上面讲的3个指标(model size, runtime memory, number of computation numbers)中的1-2个,即比如剪枝减少了模型参数量,可是运行时占得内存空间仍旧很大因为许多Memory花在了activation map上面,而不是weights上面,另外有的pruning方法需要专门的硬件电路;或是比如量化可以减少model size,可是运行时占得内存空间仍旧很大,Latency也没有下降,因为你得把权重还原回去。这是第一个缺点。 许多技术依赖于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 是最便捷的方式了,相比下面这几种实现方式:
如果我们放弃BN而给模型每一层加上一个scaling层:那么这个scaling层的参数将会是无意义的。因为convolution层和scaling层都是线性变换(Linear Transformation),你用这个scaling层,和把卷积层的weights调大一点结果是一样的。 如果我们在BN层之前加上一个scaling层:那么 带来的scaling的效应将会被BN层的normalization的作用抹去。 如果我们在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经剪枝,量化而来。它的特点是:
每一步操作可解释。 轻量化,稳定。 不需要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/