1 前言

目前深度学习模型压缩方法的研究主要可以分为以下几个方向:

  • 更精细模型的设计

    目前的很多网络都具有模块化的设计,在深度和宽度上都很大,这也造成了参数的冗余很多,因此有很多关于模型设计的研究,如 SqueezeNetMobileNet 等,使用更加细致、高效的模型设计,能够很大程度的减少模型尺寸,并且也具有不错的性能。

  • 参数修剪和共享(parameter pruning and sharing)

    结构复杂的网络具有非常好的性能,其参数也存在冗余,因此对于已训练好的模型网络,可以寻找一种有效的评判手段,将不重要的 connection 或者 filter 进行裁剪来去除冗余和不重要的项。

  • 核的稀疏化

    在训练过程中,对权重的更新进行诱导,使其更加稀疏,对于稀疏矩阵,可以使用更加紧致的存储方式,如 CSC,但是使用稀疏矩阵操作在硬件平台上运算效率不高,容易受到带宽的影响,因此加速并不明显。

稀疏矩阵是指矩阵中的元素大部分是0的矩阵,事实上,实际问题中大规模矩阵基本上都是稀疏矩阵,很多稀疏度在90%甚至99%以上。因此我们需要有高效的稀疏矩阵存储格式。

  • 权值共享

    就是让一些边共用一个权值,达到缩减参数个数的目的。假设相邻两层之间是全连接,每层有 \(1000\) 个节点,那么这两层之间就有 \(1000×1000=100万\) 个权重参数。可以将这一百万个权值做聚类,利用每一类的均值代替这一类中的每个权值大小,这样同属于一类的很多边共享相同的权值,假设把一百万个权值聚成一千类,则可以把参数个数从一百万降到一千个。
    可以直接使用简单的 K-means,对每一层都做一个weight的聚类,属于同一个 cluster 的就共享同一个权值大小。 注意的一点:跨层的weight不进行共享权值;

  • 量化

    一般而言,神经网络模型的参数都是用的32bit长度的浮点型数表示,实际上不需要保留那么高的精度,可以通过量化,比如用0~255表示原来32个bit所表示的精度,通过牺牲精度来降低每一个权值所需要占用的空间。
    如果我们存储的参数为256个,那么只需要8-bit整数就可以索引,相比于所有位置都存32bit的浮点数,模型的存储量可以下降到原来的1/4。

除此之外,Low-rank分解迁移学习等方法也有很多研究,并在模型压缩中起到了非常好的效果。

2 网络剪枝

方式 一

论文地址:Sparsifying Neural Network Connections for Face Recognition

算法流程

从网络的最后一层开始,根据一定规则对该层进行剪枝,然后retrain网络,循环上述过程。

剪枝的实现方法

就是为权重施加一个相同大小的Mask, Mask中只有激活的地方才是1,其余全0.

![](/img/2018-06-26-ModelCompression-1.jpg)
算法流程图

全连接层剪枝

对于如全连接和局部连接这些没有权值共享的层,我们可以很简单的计算神经元之间的相关性:

假设 \(a_i\) 是当前层的一个神经元, 上一层有 \(K\) 个神经元,则此时 \(a_i\) 与上一层之间应该有 \(K\) 个连接,即 \(K\) 个权重参数: \(b_{i1},b_{i2} … b_{iK}\) 。 于是我们可以用下式计算 \(a_i\) 与每一个 \(b_{ik}\) 的相关系数 :

$$r_{ik} = \frac{E[a_i-\mu_{ai}][_i-\mu_{bik}]}{\sigma_{ai}\sigma_{bik}}$$

其中 \(μ\) 和 \(\sigma\) 分别是在验证集上计算得到的均值与方差。

正相关和负相关同样重要,而且实验发现保留一些相关性较小的权重也会提高实验效果。

于是,作者首先将所有正相关的 \(r_{ik}\) 降序排列,然后均分为两部分,在前一部分随机采样 \(λSK+\) 个,在后面一部分随机采样 \((1−λ)SK+\) 个, 其中 \(S\) 为事先确定的稀疏度, \(λ\) 文中设定为 \(0.75\) 。对负相关采取同样操作。据此,我们可以创建出表示剪枝的掩膜矩阵。

卷积层剪枝

卷积层剪枝稍微复杂一点,因为存在权值共享。

设 \(a_{im}\) 是当前层第 \(i\) 个 feature map 中的第 \(m\) 神经元,该 feature map 中的共有 \(M\) 个神经元( \(m=1,2,…,M\) )。显然,根据卷积规则,这 \(M\) 个神经元都只与一个卷积核有关,即 \(K\) 个权值有关 ( \(K\) 为 filter size,例如 \(3 × 3\),再乘以输入 channel 的数量)。\(b_{mk}\) 应该是上一层 feature map 中卷积的部分,该部分的位置与 \(m\) 有关,且包含 \(K\) 个元素( \(k=1,2,…,K\) )。

最后,相关系数通过平均的方式计算:

$$r_{ik}=\sum_{m=1}^M \left\vert \frac{E[a_{im}−μ_{aim}][b_{mk}−μ-{bmk}]}{σ_{aim}σ_{bmk}} \right\vert$$

实验结果

下面是在LFW人脸验证的实验(整个实验都没有去碰卷积层,因为对于作者所用的VGG来说,全连接占据了90%的参数量):

![](/img/2018-06-26-ModelCompression-2.jpg)
![](/img/2018-06-26-ModelCompression-3.jpg)
  • \(1/256\) 表示稀疏度
  • 默认为主要选择相关性高的,小部分为相关性小的
  • \(r\) 表示全部随机选择
  • \(h\) 表示只选择相关性高的

可以看出:权重的幅值并不能很好地指示权重的重要性。

方式 二

论文地址:Pruning Filters for Efficient ConvNets

这篇论文中,作者提出对卷积层进行完全的剪枝。对第 \(k\) 个卷积输出层进行剪枝,不仅影响当前的卷积层输出,也会影响接下来的网络层,也就是对于之后的网络层没有了原始输入中的第 \(k\) 个。

![](/img/2018-06-26-ModelCompression-4.jpg)

这篇论文对卷积窗口的贡献度排序的方法很简单,所采用的的排序指标为卷积窗口经 L1 正则化的权重参数

![](/img/2018-06-26-ModelCompression-5.jpg)

对卷积窗口剪枝的迭代过程中,每一轮迭代会将全部的卷积窗口进行排序(排序指标为卷积核中 L1 正则化的权重参数),舍弃排序后指标最低的 m 个卷积窗口以达到剪枝的目的,然后用剪枝后的卷积窗口进行模型训练,再不断地重复这个过程。

剪枝的敏感度(Sensitivity)

敏感度指每一卷积层进行单独剪枝,查看在 validation set 上准确度的变化

对于 VGG-16, 一些卷积层的 filter 数量是一样的,所以对于差不多 Sensitivity 的卷积层,使用相同的比例进行剪枝,而对于 Sensitivity 比较大的,选择最小的比例进行剪枝或者不进行剪枝

![](/img/2018-06-26-ModelCompression-6.jpg)

多层剪枝的策略

之前的一些剪枝策略是逐层剪枝,然后进行retraining,但是这样是非常耗时的

两种策略:

  • 独立剪枝:就是每一层是独立的,然后进行剪枝
  • 贪心剪枝:就是考虑到上一层被剪掉的情况

如下图,第一种方法就是不考虑已经前面已经移除的filters(蓝色的),黄色的kernel仍然参与计算;而对于贪心剪枝就不用计算黄色的kernel。

![](/img/2018-06-26-ModelCompression-7.jpg)

方式 三

对接近0的权重进行剪枝

  1. 首先对正常模型进行训练
  2. 然后对接近 0 的权值设置 mask,使其相当于0
  3. 最后进行小型的 retrain

项目地址:

  1. pruning_with_tensorflow
  2. impl-pruning-TF

## 3 模型蒸馏

论文地址:Distilling the Knowledge in a Neural Network

模型蒸馏直接设计了一个简单结构的小网络,那小网络的准确率怎么和大网络比呢?

模型蒸的主要思想是用预训练好的网络(通常结构较复杂,准确率较高),来指导小网络的训练,并使小网络达到与复杂网络相近的准确率。

大网络类比于老师,小网络类比于学生,老师经过漫长时间的“训练”摸索出一套适用于某个任务的方法,于是将方法提炼成“知识”传授给学生,帮助学生更快地学会处理相似的任务。

整个思想中最大的难题在于如何有效地表达“知识”,并有效地指导小网络的训练。其整体结构如下图所示:

![](/img/2018-06-26-ModelCompression-8.jpg)
模型蒸馏结构

整个网络的损失函数包括原本任务的损失函数,和大网络对小网络的指导损失函数,其中指导损失函数为每个网络块输出特征图的均方误差,如下式所示:

$$L_{TS}={\frac{1}{2}}\Vert{u_{Teacher}-r_{student}}\Vert ^2$$

  • \(L_{TS}(Block)\) 表示指导损失函数
  • \(u_{Teacher}\) 表示大网络输出特征图
  • \(r_{student}\) 表示小网络的输出特征图

整体网络的损失函数如下式所示:

$$L_{total} =\lambda L_{orig}+(1-\lambda) L_{TS}$$

  • \(L_{orig}\) 为直接训练网络的损失函数
  • \(\lambda\) 为提前设定的超参数,表示大网络对小网络指导损失函数的重要性

对于 \(\lambda\) 的取值:

  • 当 \(\lambda\) 过小时,总损失函数与原损失函数几乎相同
  • 当 \(\lambda\) 过大时,总损失函数与指导损失函数几乎相同,每次迭代的参数更新值几乎全部取决于指导损失函数,这种训练将完全陷入模仿训练误区。此时,小网络学习重点偏向于模仿大网络而忽略了任务本身,导致实际训练效果下降甚至发生错误。
  • 推荐 \(0.1至0.5\)

如果先单独对指导损失函数进行训练,然后再加入任务损失函数联合训练,得到的模型效果可能将会比直接联合训练得到的模型好很多。

![](/img/2018-06-26-ModelCompression-9.jpg)

细节部分, \(softmax\) 层的公式如下:

$$q_{i}=\frac{exp(\frac {z_i}{T})}{\sum_j exp(\frac{z_j}{T})}$$

  • \(z_i : \mathrm {the\ logit,\ i.e.\ the\ input\ to\ the\ softmax\ layer}\)
  • \(q_i : \mathrm {the\ class\ probability\ computed\ by\ the\ softmax}\)
  • \(T : \mathrm {a\ temperture\ that\ is\ normally\ set\ to\ 1}\)

\(T\) 就是调节参数,一般设为 1。 \(T\) 越大,分类的概率分布越“软”

“蒸馏”最简单的形式就是:以从复杂模型得到的“软目标”为目标(这时T比较大),用“转化”训练集训练小模型。训练小模型时T不变仍然较大,训练完之后T改为1。

代码实现

1
2
3
4
5
6
7
8
9
teacher=nin()
student=lenet()

one_hot = tf.one_hot(y, n_classes,1.0,0.0)
teacher_tau = tf.scalar_mul(1.0/args.tau, teacher)
student_tau = tf.scalar_mul(1.0/args.tau, student)
objective1 = tf.nn.sigmoid_cross_entropy_with_logits(student_tau, one_hot)
objective2 = tf.scalar_mul(0.5, tf.square(student_tau-teacher_tau))
tf_loss = (args.lamda*tf.reduce_sum(objective1) + (1-args.lamda)*tf.reduce_sum(objective2))/batch_size

项目地址:

  1. Distilling-the-knowledge-in-neural-network
  2. model_compression

参考