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

  1. 训练出一个较大的Network
  2. 评估该Network中每个weight和neuron的重要性,这一步有很多种做法
  • weight的重要性
    比如:如果其值接近0,则说明该weight不重要,因此可以计算weight的L1或L2判断weight的重要性。
  • neuron的重要性
    比如:给定dataset,如果某个neural的输出都是0那么该neural是不那么重要的
  1. 根据重要性将weight和neuron排序并删除那些不那么重要的weight和neuron
    删除一些weight和neuron后,Network会变小但精度一般也会变低,因此还需要进行fine-tune
    一次最好不要删除太多neuron或weight,否则Network的精度会无法通过fine-tune恢复,最好是每次只删除一小部分然后进行fine-tune并重复该过程

  2. 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中的对应参数初始化,得到的结果就可以。
image.png

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加速,但这样其实并没有使网络变小。
图片.png
图片.png
实际上做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很相似
或者可以这么理解:学生自己直接做题目太难了,让学生学习下老师是怎么想的可能会更好。
图片.png

Ensemble

Knowledge Distillation有什么用呢?
打Kaggle比赛时很多人的做法是ensemble(将多个model的结果进行平均)。Ensemble通常可以得到更好的精度,但现实中设备上不可能放这么多个model,这时就可以利用Knowledge Distillation让Student Net向Teacher Net学习,最终设备上只运行Student Net就可以。
图片.png

Temperature

在分类任务中,网络的最后一般有个softmax函数:模型压缩 - 图6,其中模型压缩 - 图7是输入属于类别模型压缩 - 图8的置信度。
在Knowledge Distillation中,我们需要对softmax函数进行调整:模型压缩 - 图9,其中模型压缩 - 图10为Temperature,一般是一个大于1的数,它的作用是使得Teacher Net输出的属于各个类别的置信度更加接近(因为如果模型很强大,预测其他类别都接近或等于0,则达不到效果),如下图所示。
image.png

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表示,所以模型会有一些精度损失。

图片.png

  • Binary Weights
    weight的值只有±1。
    有不少研究者提出直接训练一个Binary Network,最早的是Binary Connect,其它的还有Binary NetworkXNOR-Net
    Binary Connect在训练中有2个分别使用real value和binary value的model,暂称为R和B。首先初始化R的参数,然后找到和R最接近的B,再使用B的梯度更新R的参数,然后再找到和R最接近的B,循环该过程直到停止,最后就使用最终的B。
    Binary Connect其实像是一种Regularization,它约束weight的值必须是±1。

图片.png

4.架构设计Architecture Design

调整Network的架构设计(Architecture Design),让它变得只需要比较少的参数,这是在实际操作中最有效的做法。

Low Rank Approximation

如果是Fully Connected Network,前一层和后一层分别有N、M个neuron则需要模型压缩 - 图14个参数,我们可以在这两层中间加一个有K个neuron的层(不要激活函数)就需要模型压缩 - 图15个neuron,这样就可以减少参数量,但是根据线性代数的知识可知这3层的性能不一定比之前的2层好。
图片.png

Depthwise & Pointwise Convolution

在普通卷积中,每个filter(卷积核)要处理输入的所有channel。假设输入有模型压缩 - 图17个channel,有模型压缩 - 图18个尺寸为模型压缩 - 图19的filter,则需要模型压缩 - 图20个参数、输出模型压缩 - 图21个channel。
图片.png
深度可分离卷积(Depthwise Separable Convolution)又称为Depthwise&Pointwise Convolution,分为以下2步,共需要模型压缩 - 图23个参数、输出模型压缩 - 图24个channel。

  1. Depthwise Convolution
    在这一步中,filter的数量等于输入channel的数量,即每个filter只处理一个channel,这步的作用就是修改输入的尺寸
    假设输入有模型压缩 - 图25个channel,因此就有模型压缩 - 图26个filter;假设每个filter的尺寸为模型压缩 - 图27,则需要模型压缩 - 图28个参数、输出模型压缩 - 图29个channel。
  2. Pointwise Convolution
    在这一步中,以上一步(Depthwise Convolution)的输出作为输入,每个filter的尺寸必须为模型压缩 - 图30,和普通卷积核一样要处理输入的所有channel,这步的作用就是修改输入的通道数
    假设有模型压缩 - 图31个filter,则需要模型压缩 - 图32个参数、输出模型压缩 - 图33个channel。

图片.png

Group Convolution

Group Convolution就是把输入的多个channel分成多个group,对每个group分别进行一次或多次普通卷积。Group Convolution算是普通卷积和Depthwise Convolution的折衷,当group数量和输入的通道数相同时它就相当于Depthwise Convolution,当group数量为1时它就相当于普通卷积。

不同卷积的PyTorch实现

  1. # 普通卷积, weight数量 = in_chs * out_chs * kernel_size^2
  2. nn.Conv2d(in_chs, out_chs, kernel_size, stride, padding)
  3. # Group Convolution, Group数量可以自行控制,表示要分成几个group,其中in_chs和out_chs必须可以被groups整除
  4. nn.Conv2d(in_chs, out_chs, kernel_size, stride, padding, groups=groups)
  5. # Depthwise Convolution, 输入通道数=输出通道数=group数量, weight数量 = in_chs * kernel_size^2
  6. nn.Conv2d(in_chs, out_chs=in_chs, kernel_size, stride, padding, groups=in_chs)
  7. # Pointwise Convolution, 也就是1×1卷积, weight数量 = in_chs * out_chs
  8. nn.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

李宏毅2020机器学习课程ppt