神经网络量化压缩学习笔记

293 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

0. 前言

近年来,基于神经网络的深度学习在图像处理、自然语言处理、语音识别等领域取得了显著效果。一般情况下,一个神经网络模型越大(一般指参数量越大),模型的拟合能力更强,准确度越高。这将进一步导致运行模型时需要的内存(包内存容量和内存带宽)、磁盘存储消耗增大,推理时延和功耗也会变大,从而限制模型的工业化落地。 为了解决这个问题,可以从两方面来下手:

  • 从原理上设计更高效地网络结构,即参数量更少的模型,并保持精度下降可以接受。
  • 不改变原有模型结构,而是对已有的模型进行参数压缩。

1. 量化压缩方法简介

量化压缩就是上一章节中的第二种解决方案,即对已有的模型进行参数压缩。所谓量化,就是用精度更低的类型来存储权重参数。例如,通常情况下模型默认以float32类型变量来存储权重,低精度就是以float16,int8甚至更低精度的类型来存储。最极端的情况下,可以采用一个bit来存储,也即二值神经网络。工业界中常采用的是int8类型。 在训练过程中,一般还是全精度(float32)类型,到了推理阶段,则有两种方案:一种是所有算子都支持量化后的类型的数据运算(以int8为例),因此模型的全过程中数据流都是int8;另一种是数据流仍是float32,每个算子前后分别有quantize层和dequantize层用以将float32转换为int8或者反过来。

2. 量化压缩原理

2.1. 定点数与浮点数

在计算机的存储中,int属于定点数,float和double属于浮点数。定点数与浮点数的介绍见www.jianshu.com/p/d39fb5792… 浮点数的存储与转换方式详解见www.cnblogs.com/lan0725/p/1… int8的值域为[-128,127],取值个数为28;2^{8};float32的值域为[(2232)×2127,(2223)×2127][(2^{-23}-2)\times 2^{127},(2-2^{-23})\times 2^{127}],取值个数约为2322^{32}。int8在整个值域上的精度分布是均匀的,而float32不是均匀的,0附近的精度越高,越往值域区间两边精度越低。这是因为,在给定指数时,float32在此指数对应的区间内数值个数是一定的,如下图所示(图片来源jackwish.net/2019/neural…在这里插入图片描述

图2.1 float类型的数值个数分布

2.2. 将浮点数量化为定点数

量化压缩的过程本质上是一个一个区间放缩到另一个区间的过程,这里仅讨论线性放缩:

xquantized=round(xfloat/xscale+xzero_point)(2-1)x_{quantized}=round(x_{float}/x_{scale}+x_{zero\_point})\tag{2-1}

round表示取整 首先确定放缩因子:

xscale=xfloatmaxxfloatminxquantizedmaxxquantizedmin(2-2)x_{scale}=\frac{x_{float}^{max}-x_{float}^{min}}{x_{quantized}^{max}-x_{quantized}^{min}}\tag{2-2}

(xquantizedmaxxquantized)×xscale=(xfloatmaxxfloat)(2-3)(x_{quantized}^{max}-x_{quantized})\times x_{scale}=(x_{float}^{max}-x_{float})\tag{2-3}

所以:

xquantized=xfloat/xscale+xquantizedmaxxfloatmax/xscale(2-4)x_{quantized}=x_{float}/x_{scale}+x_{quantized}^{max}-x_{float}^{max}/x_{scale}\tag{2-4}

也即:

xzero_point=xquantizedmaxxfloatmax/xscale(2-5)x_{zero\_point}=x_{quantized}^{max}-x_{float}^{max}/x_{scale}\tag{2-5}

在工程中,int8可能会取[0,255] (无符号整数)

有时float类型的极大值和极小值会太极端,从而使得放缩因子太大,导致最后量化后的结果太集中于某一个小的子区间,浪费了其他部分的值域空间。这时,可以读float类型的极大值极小值进行clip。例如,认为设为-1.0和1.0.

这里留一个小彩蛋,为什么(2-3)里面不用xxminx-x^{min}来与缩放因子相乘,而是采用xxmaxx-x^{max}呢?虽然从数学上是等价的,但是工程实现上的效果会有点不一样,读者可以自己思考一下

3. tf-lite中的量化方法

大体上分为两类:

  1. 训练后整数量化:输入、模型的weight和bias都是低bit(例如int8),模型在线上推理时,要求输入必须是int8,否则会报错;网络结构中的计算也是int8。
  2. 训练后动态范围量化:模型的weight是int8,bias是float32,每一层的输入是float32,在每层计算前先要进行float32->int8的转换。凡是支持量化计算的层(算子)都要进行这种转换,因此会增加一定的时延。输出仍是float32.

1又有两种实现方式,在tf1.15中,训练时搞定输入和权重的量化参数计算;在tf2.3中,训练时搞定权重的量化参数计算,训练完成后再用一个额外的数据集来获取输入的量化参数。

参考

jackwish.net/2019/neural… zhuanlan.zhihu.com/p/149659607