【神经网络】神经网络加速之量化模型


1.简介

量化模型(Quantized Model)是一种模型加速(Model Acceleration)的方法的总称,主要包括二值化网络(Binary Network)、三值化网络(Ternary Network)、深度压缩(Deep Compression)。本文通过TensorLayer讲解各类量化模型。

对于TensorLayer是基于TensorFlow的高级开发工具,TensorLayer提供了一套搭建量化网络的API。这套API不能加速,主要是进行产品部署,可以使用TensorLayer进行魔性训练,然后可以使用C++实现二值化的计算。

2.模型介绍

比较常见的量化模型主要有DeepCompression、Binary-Net、Tenary-Net、Dorefa-Net。

2.1 DeepCompression

DeepCompression 主要分为三个部分:剪枝、量化、哈夫曼编码

剪枝:在训练的过程中,当网络收敛到一定的程度的时候,认为网络结构中小于阈值的网络权重对网络的作用很小,因此对这种权重参数进行剪枝,被剪掉的权重不会再接受任何梯度。

具体过程:首先加载网络,然后重新训练到收敛,进行剪枝,重复这个过程。直到网络参数变为一个高度稀疏的矩阵。由于小的参数会不断地进行剪枝,因此为了不断地增大压缩率,就要不断地加大阈值。并针对该问题设计了这一个基于准确率损失和压缩率上升的公式,当最后参数变为一个稀疏矩阵的时候,然后继续编码。不过当压缩率低于一定的值的时候,采用编码解码的方式会增大算法的开销。

量化:将接近的值变成一个数。其实是一种权值共享的策略。量化后的权值张量是一个高度稀疏的有很多共享权值的矩阵,对于非零参数,还可以进行定点压缩,从而获得更高的压缩率。

哈弗曼编码:使用哈弗曼编码方式对权值进行压缩,但是这个编码解码过程是会增加时间开销代价的。

2.2 Binary-Net

通常我们在构建神经网络的时候,使用的精度都是32位单精度浮点数。当网络模型规模较大的时候,就会消耗更多的内存资源。浮点数由一位符号位、八位指数位和尾数位三部分组成。完成浮点数加减主要一下四步:
(1)0操作数的检查,如果在两个操作数中有零值,则可以直接得到结果。
(2)比较阶码大小完成对接。
(3)尾数进行加减操作。
(4)结果规格化并进行舍入处理。

因此对于该问题,通过降低权重和输出精度的方案进行模型加速,并使用位运算(bit-wise operator)代表普通的运算方式(arithmatic operator)。

对于网络模型中的权重,可以采用二值化的方式进行压缩。
(1)直接将大于0的参数设置为1,小于0的参数设置为-1 。
(2)将绝对值大于的参数设置为1, 将绝对值小于的参数根据距离±1的远近按概率随机设置为±1。

由于参数和各层的输出都是二值化的,但是梯度不得不使用较高精度的实数而不是二值进行存储。梯度很小,那么无法使用低精度来正确的表示梯度,同时梯度是具有高斯白噪声的,只有累加梯度才能抵消噪音。另一方面,二值化相当于正则化的功能,可以防止模型过拟合。

举例,如果是一个3*3的卷积核进行二值化,只有 2 的 9 次方个的卷积核。原本每个参数 32bit,压缩后每个参数 1bit。在 GPU 上还可以进行 SWAR(single instruction,multiple data within register)的处理,对 xnor 进行优化,SWAR 的基本思想是将 32 个二进制变量组连接成 32 位寄存器,从而在按位操作(例如 XNOR)上获得 32 倍的加速。

2.3 Ternary-Net

多权值相对比于二值化具有更好的网络泛化能力。权值的分布接近于一个正态分布和一个均匀分布的组合。使用一个 scale 参数去最小化三值化前的权值和三值化之后的权值的 L2 距离。

3.实验分析

3.1 Binary-Net in MNIST

https://github.com/tensorlayer/tensorlayer/blob/master/example/tutorial_binarynet_mnist_cnn.py

net = tl.layers.InputLayer(x, name='input')
# n_filter=32  filter_size=(5, 5)  strides=(1, 1)  bias don’t init
net = tl.layers.BinaryConv2d(net, 32, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn1')
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool1')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn1')

net = tl.layers.SignLayer(net)
net = tl.layers.BinaryConv2d(net, 64, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn2')
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool2')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn2')

net = tl.layers.FlattenLayer(net)
# net = tl.layers.DropoutLayer(net, 0.8, True, is_train, name='drop1')
net = tl.layers.SignLayer(net)
net = tl.layers.BinaryDenseLayer(net, 256, b_init=None, name='dense')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn3')

# net = tl.layers.DropoutLayer(net, 0.8, True, is_train, name='drop2')
net = tl.layers.SignLayer(net)
net = tl.layers.BinaryDenseLayer(net, 10, b_init=None, name='bout')
net = tl.layers.BatchNormLayer(net, is_train=is_train, name='bno')

3.2 Ternary-Net in MNIST

net = tl.layers.InputLayer(x, name='input')
net = tl.layers.TernaryConv2d(net, 32, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn1')
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool1')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn1')
net = tl.layers.TernaryConv2d(net, 64, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn2')
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool2')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn2')

net = tl.layers.FlattenLayer(net)
net = tl.layers.TernaryDenseLayer(net, 256, b_init=None, name='dense')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn3')
net = tl.layers.TernaryDenseLayer(net, 10, b_init=None, name='bout')
net = tl.layers.BatchNormLayer(net, is_train=is_train, name='bno')

3.3 DoReFa-Net in MNIST

net = tl.layers.InputLayer(x, name='input')
net = tl.layers.DorefaConv2d(net, 1, 3, 32, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn1')  #pylint: disable=bare-except
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool1')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn1')
net = tl.layers.DorefaConv2d(net, 1, 3, 64, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn2')  #pylint: disable=bare-except
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool2')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn2')

net = tl.layers.FlattenLayer(net)
net = tl.layers.DorefaDenseLayer(net, 1, 3, 256, b_init=None, name='dense')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn3')
net = tl.layers.DenseLayer(net, 10, b_init=None, name='bout')
net = tl.layers.BatchNormLayer(net, is_train=is_train, name='bno')

4.卷积优化

4.1 内存换时间

如果深度学习中每一层的卷积都是针对同一张图片,那么所有的卷积核可以一起对这张图片进行卷积运算,然后再分别存储到不同的位置,这就可以增加内存的使用率,一次加载图片,产生多次的数据,而不需要多次访问图片,这就是用内存来换时间。

4.2 乘法优化

将卷积核展开为矩阵,将图像也展开为矩阵,那么原本的多次卷积核运算就转换为了一个大矩阵的乘法。

4.3 GPU优化

对于GPU优化的主要问题在于IO的时间与计算时间之间的协调过程。
(1)了解IO的访问情况以及IO的性能
(2)多线程的并行计算特性
(3)IO和并行计算间的计算时间重叠
NVIDIA的GPU通过增加计算流处理器中间的缓存来提高性能,加快IO速度。

4.4 Strassen算法

根据CNN的线性代数特性,增加算法减少乘法,降低卷积运算的计算复杂度。
这里写图片描述

4.5 卷积中的数据重用

权重固定:可以最小化权重读取的消耗,最大化卷积和权重的重复利用。
输出固定:最小化部分和R/W能量消耗,最大化本地积累。
NLR(No Local Reuse):使用大型全局缓冲区共享存储,减少DRAM的访问消耗。
RS:在内部的寄存器中最大化重用和累加,针对整体能源效率进行优化,而不是只针对某种数据类型。

参考文献:https://www.jiqizhixin.com/articles/2018-06-01-11

猜你喜欢

转载自blog.csdn.net/twt520ly/article/details/80909552