文章目录
本文主要参考李宏毅机器学习笔记—— 15. Network Compression( 网络压缩)
摘要: 本章主要是讲解了为什么要去做网络压缩,以及怎样去做压缩的五种方式。分别是网络剪枝,知识蒸馏,参数量化,架构设计和动态计算。(本章仅从软件方面去考虑)
- 网络剪枝的基本思想是先评估weight和neuron的重要性,然后把不重要的删除掉。
- 知识蒸馏的基本思想是训练一个大网络,用小网络(Student Net)学习大网络(teacher net)。并计算两者之间的cross-entropy,使其最小化,从而可以使两者的输出分布相近。
- 参数量化是从参数的存储空间角度考虑,以及权重聚类,哈夫曼编码,二元权重角度,去尽可能让参数占用更少的空间。
- 架构设计是用低秩近似的方法去减少参数量,但会存在缺陷;接着讲解了效果更好的深度可分离卷积。
- 动态计算是对CNN中间的每一层的输出进行分类,并作为结果预测,这样做也会存在很多缺陷。
Network Pruning(网络剪枝)
Network Compression,之所以提出模型压缩是因为我们有把Deep Model放在移动端设备(智能手表,无人机,智能机,机器人)的需求,但是这些设备上的资源是非常有限的(空间或者计算能力不足),因此要压缩空间大小、加快计算速度等。
Network can be Pruned
神经网络的参数有很多,但其中有些参数对最终的输出结果贡献并不大,相反就显得冗余,将这些冗余的参数剪掉的技术称为剪枝。剪枝可以减小模型大小、提升运行速度,同时还可以防止过拟合。
神经网络中的一些权重和神经元是可以被剪枝的,这是因为这些权重可能为零或者神经元的输出大多数时候为零,表明这些权重或神经元是冗余的。
网络剪枝的过程主要分以下几步:
①训练网络;
②评估权重和神经元的重要性:可以用L1、L2来评估权重的重要性,用不是0的次数来衡量神经元的重要性;
③对权重或者神经元的重要性进行排序然后移除不重要的权重或神经元;
④移除部分权重或者神经元后网络的准确率会受到一些损伤,因此我们要进行微调,也就是使用原来的训练数据更新一下参数,往往就可以复原回来;
⑤为了不会使剪枝造成模型效果的过大损伤,我们每次都不会一次性剪掉太多的权重或神经元,因此这个过程需要迭代,也就是说剪枝且微调一次后如果剪枝后的模型大小还不令人满意就回到步骤后迭代上述过程直到满意为止。
以参数为单位进行修剪,修剪后的network形状是不规则的,最大的问题是在pytorch等不好实做,而且不容易用GPU来加速矩阵运算,所以实际上是补0,然而这样以来结果是并没有真的吧net变小
以神经元为单位裁剪结构比较规则
剪枝技术按照细粒度的不同可分为结构性剪枝以及非结构性剪枝:
- 结构性剪枝剪除的基本单元为神经元(卷积中为filter),由于是对神经元直接进行剪枝,结构性剪枝后的模型能够在现有硬件条件下实现明显的推理加速以及存储优势。但其缺点是剪枝的颗粒度较大,往往会对压缩后模型的精度产生较大的影响。
- 非结构剪枝剪除的基本单元为单个权重,其经过剪枝后的模型精度损失更小,但最终会产生稀疏的权重矩阵,需要下层硬件以及计算库有良好的支持才能实现推理加速与存储优势。
为什么大的网络容易train?
在实践过程中我们可以感受到大的网络比小的网络更容易训练,而且也有越来越多的实验证明大的网络比小的网络更容易收敛到全局最优点而不会遇到局部最优点和鞍点的问题。 解释这一想象的一个假设是大乐透假设(Lottery Ticket Hypothesis)。
假设:
大网络 -> loss分布在小值 -> 特征值λ分布在正值的几率大 -> 不容易遇到saddle point -> 容易找到local minima
(参数多的话,如果loss比较大的话是鞍点的可能大,还有下降的余地)
在dl领域,有推论:几乎所有的local minimum他的loss跟global optimum是差不多的
Yann Lecun在07 06年的一个演讲(17年有paper证明)
train的时候停在一个驻点:极值点或者鞍点,究竟是哪一个?要分析hessian矩阵:假设有N个参数,H 是一个N*N的矩阵,有N个特征向量v1,v2,…,vN,每个特征向量对应一个特征值,如果所有特征值都是+,那这个驻点就是局部极值点,如果有正有负,则是鞍点
net的参数越多,当碰到一个驻点的时候就越不可能是极值点,越有可能是一个鞍点
上面假设特征值是+或-的概率各1/2,但其实他是和你的loss有关系的,假设p是负的特征值出现的概率,loss的值越大,p的值越大(负的特征值代表说你有某一条路可以继续往下走),其实这个假设挺合理的,当刚开始训练的时候loss比较大的时候有很多挑路可以往loss更低的地方走,当loss很低的时候相反----》极小值点容易出现在loss低的地方,鞍点容易出现在loss大的地方
上面是一个假设,但是benjo的实验已经佐证了
绿色和蓝色表示特征值有正有负,是鞍点,红色的loss很低,他的特征值几乎都是+的,说明他是一个局部极小值
一个驻点越像是一个局部极小值,他的error越低
大乐透假说:
在下图中,首先我们使用一个大的网络然后随机初始化一组参数,这组参数用红色表示,然后训练后得到紫色的参数,接着进行网络剪枝。我们再尝试使用剪枝的网络结构随机初始化一组参数然后训练发现这种方式没能取得剪枝得到的效果,而如果用大的网络中对应的初始化参数来初始化这个剪枝的网络结构然后再进行训练,就发现可以取得较好的效果:
大乐透假设可以用来解释这个现象,在买大乐透时买得越多就越容易中奖,同样的这里我们假设一个大的网络中包含很多小的网络,这些小的网络结构有的可以训练成功而有的不可以训练成功,只要有一个训练成功,整个大的网络结构就可以训练成功,因此我们可以把多余的网络结构剪枝掉。
知识蒸馏
知识蒸馏的方式就是将Teacher Network输出的soft label作为标签来训练Student Network。比如在上图中我们训练Student Network来使其与Teacher Network有同样的输出。
这样的好处是Teacher Network的输出提供了比独热编码标签更多的信息,比如对于输入的数字1,Teacher Network的输出表明这个数字是1,同时也表明了这个数字也有一点像7,也有一点像9。另外训练Student Network时通常使用交叉熵作为损失函数,这是因为训练过程相当于要拟合两个概率分布。
知识蒸馏训练出的Student Network有一点神奇的地方就是这个Network有可能辨识从来没有见过的输入,不如把Student Network的训练资料中的数字7移除后可能训练完成后也会认识数字7,这是因为Teacher Network输出的soft label提供了额外的信息。
知识蒸馏的一个用处是用来拟合集成模型,有时候我们会集成(Ensemble)很多个模型来获取其输出的均值从而提高总体的效果,我们可以使用知识蒸馏的方式来使得Student Network学习集成模型的输出,从而达到将集成模型的效果复制到一个模型上的目的:
那Student Net到底如何学习呢?首先回顾一下在多类别分类任务中,我们用到的是softmax来计算最终的概率,即
但是这样有一个缺点,因为使用了指数函数,如果在使用softmax之前的预测值是x1=100,x2=10,x3=1,那么使用softmax之后三者对应的概率接近于y1=1,y2=0,y3=0,那这和常规的label无异了,所以为了解决这个问题就引入了一个新的参数T,称之为Temperature即:
此时,如果我们令T=100,那么最后的预测概率是y1=0.56,y2=0.23,y3=0.21。即概率相对大小没变,分类结果没变
参数量化(Parameter Quantization)
-
使用更少的bits来表示一个参数,比如将64位浮点数换成32位浮点数。
-
权重聚类(Weight Clustering)
下图方格代表权重,我们可以使用聚类算法(如K-Means)来将权重进行聚类,然后每个权重就只需要存储对应的类别,比如下图中聚成了四类则每个权重只需要2个bit就可以存储,另外还需要存储四类的值,每个值都是该类中所有参数的平均值:
-
用更少的bit表示频繁出现的类别,用较多的bit来表示出现较少的类别。例如使用哈弗曼编码(Huffman encoding)。
-
使用二进制参数(Binary Weights),使用Binary Connect的方式训练神经网络,下图代表参数空间,灰色点代表二进制的一组参数,更新梯度时计算离当前参数最近的二进制参数的梯度然后进行梯度下降,最终的结果也是取距离最近的一组二进制参数:
下图中展示了使用Binary Connect的一组实验结果,可以看到使用Binary Connect的方式可以取得比无正则化更好的效果,这是因为使用Binary Connect相当于做了正则化操作:
结构设计(Architecture Design)
Low rank approximation(低秩近似)
下图是低秩近似的简单示意图,左边是一个普通的全连接层,可以看到权重矩阵大小为M×NM×N,而低秩近似的原理就是在两个全连接层之间再插入一层K。是不是很反直观?插入一层后,参数还能变少?
我们可以用发现这个新插入一层后的参数数量为: N×K+K×M=K×(M+N),因为K<M,K<N,并且原来的参数量是M×N,所以参数减少了。
那么这样会带来什么影响呢?那就是原先全连接层能表示更大的空间,而现在只能表示小一些的空间,会限制原来的NN能做的事情。
Depthwise Separable Convolution(深度可分离卷积)
复习一下标准的CNN怎么做的?
从标准卷积CNN中看所需要的参数量。如上图示,输入数据由两个6 ∗ 6 666∗6的feature map组成,之后用4个大小为3 ∗ 3 333∗3的卷积核做卷积,最后的输出特征图大小为4个4 ∗ 4 444∗4的矩阵。每个卷积核参数数量为233=18,所以总共用到的参数数量为418=72个。
Depthwise Separable卷积分成了两步:
- Depthwise Convolution(DW卷积)
将立体的,深度为2的filter分成两个平面的,不考虑之间的联系(每个卷积核只考虑一个通道),然后分别对一个feature map进行卷积,得到下图结果:
由于第一步得到的输出特征图是用不同卷积核计算得到的,所以不同通道之间是独立的,因此我们还需要对不同通道之间进行关联。
- Pointwise Convolution(PW卷积)
为了实现关联,在第二步中使用了1 ∗ 1 1∗11∗1大小的卷积核,并且通道数量等于输入数据的通道数量,然后按照正常的CNN做法,得到结果4个4 ∗ 4 4*44∗4的矩阵,如图:
并且做深度可分离卷积的过程参数数量比标准的CNN更少:
18+8=26(共用了26个参数)
Dynamic Computation(动态计算)
该方法的主要思路是如果目前的资源充足(比如你的手机电量充足),那么算法就尽量做到最好,比如训练更久,或者训练更多模型等;反之,如果当前资源不够(如电量只剩10%),那么就先算出一个过得去的结果。
那么如何实现呢?
- Train multiple classifiers(训练大量的分类器)
需要我们提前训练多种网络(从小到大),比如小网络,中等网络,大网络,那么我们就可以根据资源情况来选择不同的网络。但是这样的缺点是我们需要保存多个模型,这在移动设备上的可操作性不高。
- Classifiers at the intermedia layer(用中间层输出)
这样的思路其实也挺直观的,就是比如说我们做分类任务,当资源有限时,我们可能只是基于前面几层提取到的特征做分类预测,但是一般而言这样得到的结果会打折扣,因为前面提取到的特征是比较浅的,可能只是一些纹理,而不是比较高层次抽象的特征。
两个缺陷:
- 前面的layer抽取的feature对于做分类效果不好(CNN的前面的隐藏层抽取的特征比较小,不适合分类)。也就是说,越靠近输入,预测结果越差。
- 每一层加上Classifier,会影响最终的分类结果,因为在训练的时候中间层就想要做分类,所以模型会促使参数在第一层就抽取大特征,破坏了整个CNN的布局,这样会使得后面的结果变差。