1. 网络剪枝 Network Pruning
网络剪枝(Network Pruning)就是删除一个较大网络中的一些weight或neuron得到一个更小的网络。
我们相信,通常情况下我们训练出的神经网络是over-parameterized,即其中存在很多weight或neuron是没有用的(比如有些neuron的输出总是0、有些weight非常接近0) ,因此我们可以把这些没有用的weight或neuron剪掉。
在90年代,Yann Le Cun就提出了“网络剪枝”,paper名称为Optimal Brain Damage。
有个问题是:为什么不直接使用较小Network而是对较大Network进行剪枝?常见的解释是:较小的Network训练出来的结果一般都不好,而较大的Network更容易optimize(李老师这个视频有讲解为什么:https://www.youtube.com/watch?v=_VuWvQUMQVk)。在训练神经网络时可能会遇到local minima和saddle point的问题,但如果Network够大这种问题就会不那么严重,现在有很多文献甚至可以证明只要Network够大就可以用梯度下降找到global optimal。
How to Prune a Network
- 训练出一个较大的Network
- 评估该Network中每个weight和neuron的重要性,这一步有很多种做法
- weight的重要性
比如:如果其值接近0,则说明该weight不重要,因此可以计算weight的L1或L2判断weight的重要性。 - neuron的重要性
比如:给定dataset,如果某个neural的输出都是0那么该neural是不那么重要的
根据重要性将weight和neuron排序并删除那些不那么重要的weight和neuron
删除一些weight和neuron后,Network会变小但精度一般也会变低,因此还需要进行fine-tune
一次最好不要删除太多neuron或weight,否则Network的精度会无法通过fine-tune恢复,最好是每次只删除一小部分然后进行fine-tune并重复该过程fine-tune
训练剪枝得到的较小的网络,直到模型收敛
熟悉YOLO的读者,可以根据这个仓库(https://github.com/tanluren/yolov3-channel-and-layer-pruning)感受一下剪枝和知识蒸馏。
Lottery Ticket Hypothesis
论文链接:https://arxiv.org/abs/1803.03635,这是ICLR2019的一篇论文
如下图所示,现有一个较大网络A,随机初始化其参数并记该参数为W,训练该较大网络A并进行剪枝得到较小网络B。有个现象是:如果我们随机初始化较小网络B的参数并进行训练,得到的结果就不行;但如果使用参数W中的对应参数初始化,得到的结果就可以。
Rethinking the Value of Network Pruning
论文链接:https://arxiv.org/abs/1810.05270,这是ICLR2019的一篇论文
这篇论文的结论和Lottery Ticket Hypothesis一文相反:现有剪枝后的网络,将其参数随机初始化是可以训练出好的结果的。
ICLR2019的review是开放的,网上可以搜到两篇文章作者的讨论,知乎上也有关于这两篇论文的讨论,后续也有人做了相关研究。
Some Issue in Weight Pruning
如果是weight pruning,那剪枝后的Network会变得不规则(比如有些neuron有2个weight而有些neuron有4个weight)。这样的不规则的Network是不好用keras等代码框架实现的,并且GPU只能对矩阵运算进行加速而无法加速这样的Network。比较常见的方法是将需要剪掉的weight设成0,因此仍然可以用GPU加速,但这样其实并没有使网络变小。

实际上做weight pruning是很麻烦的,通常都进行neuron pruning,因为比较容易用代码实现、也容易达到加速的目的。
2.知识蒸馏 Knowledge Distillation
Knowledge Distillation:https://arxiv.org/abs/1503.02531
Do Deep Nets Really Need to be Deep?:https://arxiv.org/abs/1312.6184
熟悉YOLO的读者,可以根据这个仓库感受一下剪枝和知识蒸馏:https://github.com/tanluren/yolov3-channel-and-layer-pruning
Student and Teacher
什么是Knowledge Distillation?
我们可以让一个较小的Student Net向较大的Teacher Net学习,使得Student Net的输出尽可能接近Teacher Net的输出。
普通的训练方式为仅在数据集上训练Student Net,而Knowledge Distillation的思路是:即使Teacher Net的输出并不一定是正确的,但Teacher Net可以提供一些数据集无法提供的信息,比如手写数字图片分类模型Teacher Net的输出为“1:0.7,7:0.2,9:0.1”,这不仅说明这张图片像1,还可以说明1和7、9很相似。
或者可以这么理解:学生自己直接做题目太难了,让学生学习下老师是怎么想的可能会更好。
Ensemble
Knowledge Distillation有什么用呢?
打Kaggle比赛时很多人的做法是ensemble(将多个model的结果进行平均)。Ensemble通常可以得到更好的精度,但现实中设备上不可能放这么多个model,这时就可以利用Knowledge Distillation让Student Net向Teacher Net学习,最终设备上只运行Student Net就可以。
Temperature
在分类任务中,网络的最后一般有个softmax函数:,其中
是输入属于类别
的置信度。
在Knowledge Distillation中,我们需要对softmax函数进行调整:,其中
为Temperature,一般是一个大于1的数,它的作用是使得Teacher Net输出的属于各个类别的置信度更加接近(因为如果模型很强大,预测其他类别都接近或等于0,则达不到效果),如下图所示。
3.参数量化 Parameter Quantization
用更少的bit表示一个value
比如说本来用32位表示一个weight,现在用16位表示一个weight,这样就缩小了一半。Weight Clustering
根据weight的值对weight进行聚类,每个类中的weight都用同一个value(比如该类中所有weight的平均值)表示。每个类有个id,2个bit就可以表示4个类的id(再进一步还可以使用哈夫曼编码),在存储时只需要存储每个weight所属的类的id以及每个类对应的value即可。
因为每个类中的weight都用了同一个value表示,所以模型会有一些精度损失。

- Binary Weights
weight的值只有±1。
有不少研究者提出直接训练一个Binary Network,最早的是Binary Connect,其它的还有Binary Network,XNOR-Net。
Binary Connect在训练中有2个分别使用real value和binary value的model,暂称为R和B。首先初始化R的参数,然后找到和R最接近的B,再使用B的梯度更新R的参数,然后再找到和R最接近的B,循环该过程直到停止,最后就使用最终的B。
Binary Connect其实像是一种Regularization,它约束weight的值必须是±1。
4.架构设计Architecture Design
调整Network的架构设计(Architecture Design),让它变得只需要比较少的参数,这是在实际操作中最有效的做法。
Low Rank Approximation
如果是Fully Connected Network,前一层和后一层分别有N、M个neuron则需要个参数,我们可以在这两层中间加一个有K个neuron的层(不要激活函数)就需要
个neuron,这样就可以减少参数量,但是根据线性代数的知识可知这3层的性能不一定比之前的2层好。
Depthwise & Pointwise Convolution
在普通卷积中,每个filter(卷积核)要处理输入的所有channel。假设输入有个channel,有
个尺寸为
的filter,则需要
个参数、输出
个channel。

深度可分离卷积(Depthwise Separable Convolution)又称为Depthwise&Pointwise Convolution,分为以下2步,共需要个参数、输出
个channel。
- Depthwise Convolution
在这一步中,filter的数量等于输入channel的数量,即每个filter只处理一个channel,这步的作用就是修改输入的尺寸。
假设输入有个channel,因此就有
个filter;假设每个filter的尺寸为
,则需要
个参数、输出
个channel。
- Pointwise Convolution
在这一步中,以上一步(Depthwise Convolution)的输出作为输入,每个filter的尺寸必须为,和普通卷积核一样要处理输入的所有channel,这步的作用就是修改输入的通道数。
假设有个filter,则需要
个参数、输出
个channel。
Group Convolution
Group Convolution就是把输入的多个channel分成多个group,对每个group分别进行一次或多次普通卷积。Group Convolution算是普通卷积和Depthwise Convolution的折衷,当group数量和输入的通道数相同时它就相当于Depthwise Convolution,当group数量为1时它就相当于普通卷积。
不同卷积的PyTorch实现
# 普通卷积, weight数量 = in_chs * out_chs * kernel_size^2nn.Conv2d(in_chs, out_chs, kernel_size, stride, padding)# Group Convolution, Group数量可以自行控制,表示要分成几个group,其中in_chs和out_chs必须可以被groups整除nn.Conv2d(in_chs, out_chs, kernel_size, stride, padding, groups=groups)# Depthwise Convolution, 输入通道数=输出通道数=group数量, weight数量 = in_chs * kernel_size^2nn.Conv2d(in_chs, out_chs=in_chs, kernel_size, stride, padding, groups=in_chs)# Pointwise Convolution, 也就是1×1卷积, weight数量 = in_chs * out_chsnn.Conv2d(in_chs, out_chs, 1)
Application
Depthwise Separable Convolution被广泛地用在各种小型网络中:SqueezeNet、MobileNet、ShuffleNet、Xception,其中最知名的为MobileNet。
5.动态计算 Dynamic Computation
动态计算(Dynamic Computation)就是资源充足时就做到最好,资源不足时就减少运算量、先求有再求好(但也不要太差)。
一种方法是训练多个从小到大的model,然后选择合适的模型,这样的问题是需要存储很多个model。
另外一种方法是,训练一个在中间层就可以得到最终结果的model。因为网络浅层和深层提取到的特征一般分别是低级特征和高级特征,所以在网络浅层得到的结果一般要比在网络深层得到的结果差一些。在网络浅层就计算最终结果可能会迫使网络浅层学习一些高级特征,这会破坏网络从浅层到深层逐步提取低/高级特征的架构。那如何处理这些问题呢?可以看一看Multi-Scale Dense Convolutional Networks:https://arxiv.org/abs/1703.09844
