深度神经网络的优化、量化与压缩

1. 简介

深度学习作为机器学习的一个分支,近年来受到了广泛的追捧和应用,但其庞大的运算量一直是制约其发展的瓶颈,因此近年来也涌现出了一系列的神经网络优化方法,本文结合自身的一些工作心得,简单介绍一些深度神经网络的优化方法。

目前,主流的深度神经网络结构有MLP, CNN, RNN等,但针对它们的优化方法都是类似的,因此本文主要介绍FeedForward 网络的优化。

1.1 FeedForward

Fig 1. Feed Forward 网络结构

前向网络的推理计算大体可以分为两步:

  • 对输入做仿射变换:
  • 激活函数做非线性变换(常用的激活函数有 Sigmoid, RELU,分类任务中,输出层的激活函数则常采用Softmax):

其中\(h_i\) 是第\(i\)个隐藏层的输出,\(W_i\)是对应的权值矩阵,\(b_i\) 是对应的偏置向量。

本文的内容也将围绕上述两步展开。

2. 计算优化

2.1 归一化处理

通常,机器学习的算法都要求对输入的特征向量做归一化处理,即

其中\(\mu\)为均值,\(\sigma\)为标准差。

为了减少计算量,可以将上述过程与第一层的仿射变换(\(W_1 \bar{x} + b_1\))合并,

令\(W_1 = \frac{W_1}{\sigma}, b_1 = b_1 - \frac{W_1}{\sigma} \mu\),则可以保证在数学运算结果一致的情况下,节省了输入归一化带来的运算量

2.2 优化仿射变换

在给定 DNN 模型的情况下,计算的核心其实就是矩阵运算,根据输入的不同,可能会有矩阵矩阵,向量矩阵,向量*向量三种运算,其大部分的运算量都消耗在仿射变换上,因此对仿射变换进行优化至关重要。

2.2.1 FanIn & FanOut

Fig 2. FanIn & FanOut

FanIn 和 FanOut只是矩阵乘法之间的运算顺序不一样,它们的使用主要取决于权值矩阵在内存中的存放顺序(数据的稀疏性也有一定影响,详见下一小节),以向量*矩阵运算为例,

其中\(\mathbf{y}\in \mathbf{R}^{1\times m}\), \(\mathbf{x}\in \mathbf{R}^{1\times n}\), \(\mathbf{W}\in \mathbf{R}^{n\times m}\).

  • FanIn
1
2
3
4
5
6
7
8
9
for(int i = 0; i < m; i++)
{
    float accVal = 0.0;
    for(int j = 0; j < n; j++)
    {
        accVal += x(j) * W(j, i);
    }
    y(i) = accVal;
}
  • FanOut
1
2
3
4
5
6
7
for(int j = 0; j < n; j++)
{
    for(int i = 0; i < m; i++)
    {
        y(i) += x(j) * W(j, i);
    }
}

如果矩阵是按行存储(矩阵同一行中相邻元素存储在相邻的内存空间内),则应采用FanOut的计算方式,若按列存储(矩阵同一列中,相邻元素存储在相邻的内存空间内),则应采用FanIn的计算方式。
FanIn 和 FanOut加速的本质在于:增加Cache的命中率,减少数据存取的时间开销

2.2.2 稀疏性处理

数据的稀疏性优化主要针对输入数据,依然以上面向量*矩阵的运算为例,假如数据数据是稀疏的,那么下面的代码显然具有更高的计算效率:

1
2
3
4
5
6
7
8
9
10
for(int j = 0; j < n; j++)
{
    if(x(j) != 0.0) //如果等于0,则无需后续计算
    {
        for(int i = 0; i < m; i++)
        {
            y(i) += x(j) * W(j, i);
        }
    }
}

针对上述向量*矩阵的运算,对于数据稀疏的情况,显然FanOut具有更高的运算效率。

另外,如果DNN中选用RELU作为激活函数,那么可以使得激活函数的输出向量/矩阵具有更高的稀疏性(约50%),从而大幅减少仿射变换的计算量,因此在性能符合要求的情况下,应尽量选择RELU作为激活函数。

2.3 优化激活函数

激活函数的优化主要是针对 Softmax:

显然,如果

那么

并且由于

因此对于

每次计算是否减去常数项\(z_{max} + \log (\sum e^{z_j - z_{max}})\) 并不会改变最终的竞争条件,即相互之间的大小关系,因此模型在推理计算时,如果不需要概率值,可以移除 Softmax 的计算,直接采用仿射变换的结果即可。

3. 量化与压缩

在数学上对DNN的推理过程进行优化之后,后续的优化则主要围绕工程实现进行。

3.1 定点化

目前,对于大部分的嵌入式设备而言,定点的运算速度要快于浮点的运算,因此,对DNN的输入和权值进行定点化,可以大幅的提升计算效率同时降低模型大小。
以8bit定点化为例,需将浮点的权值映射到\([-128, 127)\)这个区间上,因此,权值的缩放倍数为

其中\(S\)为权值映射的缩放倍数,\(W_f\)为浮点权值矩阵。

定点化后,下面等式成立:

其中\(W_f, B_f\)为浮点的权值矩阵和偏置向量,\(W_i, B_i\)为定点的权值矩阵和偏置向量。

3.2 优化缩放倍数

完成上述定点化后,DNN前向传播时,每往后传播一层,依然要做浮点的除法(可转换为浮点乘法),依然会有浮点运算,如何将这部分浮点计算也优化掉呢?
在程序实现中,我们常将除以\(2^n\)优化为向右移位\(n\)位,因此,我们可以对缩放倍数\(S\)进行优化,

一般取\(Q\)为\(2^n\),从而将除法转换为移位计算,同时保证比较高的定点化精度。

3.3 稀疏性处理

统计DNN的权值矩阵,我们可以发现,每一层的权值都大量分布在\(0\)周围的某个小区间内,如下图: Fig 2. DNN权值分布图

因此,为了保证尽可能高的精度,同时不增加太多的存储空间和计算量,我们允许有一定量的权值(比如%2)在定点化后位宽大于8 。在具体的实现中,采用两个权值矩阵\(W_L\)和\(W_H\)分别表示定点化权值的低8位和高8位,此时有

其中\(W_H\)是一个极度稀疏的矩阵,因此并不会增加太多的计算量和存储空间,但却能大幅提高模型的精度。

4. 其他优化方法

  • SIMD 优化
  • Lazy Evaluation(在语音识别领域,跳帧处理,既可以降低计算量,同时保持性能基本不变。)
  • 模型剪枝
  • 知识蒸馏
  • SVD 分解

5. 总结

采用上述优化后,可以实现:

  • 大幅提高计算效率
  • 大幅压缩模型大小
  • 模型精度只有轻微的损失

上述内容主要基于近期做的一些神经网络的优化工作,使得神经网络可以在低功耗的嵌入式设备上运行,因此特将整个过程中的一些心得体会记录下来,若有不当之处,欢迎交流指正。

Reference

  1. Vanhoucke V, Senior A, Mao M Z. Improving the speed of neural networks on CPUs[C]//Proc. Deep Learning and Unsupervised Feature Learning NIPS Workshop. 2011, 1: 4.
  2. https://zhuanlan.zhihu.com/p/22323068
  3. 超全总结:神经网络加速之量化模型