https://jackwish.net/2019/neural-network-quantization-introduction-chn.html
https://zhuanlan.zhihu.com/p/149659607
0. 前言
近年来,基于神经网络的深度学习在图像处理、自然语言处理、语音识别等领域取得了显著效果。一般情况下,一个神经网络模型越大(一般指参数量越大),模型的拟合能力更强,准确度越高。这将进一步导致运行模型时需要的内存(包内存容量和内存带宽)、磁盘存储消耗增大,推理时延和功耗也会变大,从而限制模型的工业化落地。
为了解决这个问题,可以从两方面来下手:
- 从原理上设计更高效地网络结构,即参数量更少的模型,并保持精度下降可以接受。
- 不改变原有模型结构,而是对已有的模型进行参数压缩。
1. 量化压缩方法简介
量化压缩就是上一章节中的第二种解决方案,即对已有的模型进行参数压缩。所谓量化,就是用精度更低的类型来存储权重参数。例如,通常情况下模型默认以float32类型变量来存储权重,低精度就是以float16,int8甚至更低精度的类型来存储。最极端的情况下,可以采用一个bit来存储,也即二值神经网络。工业界中常采用的是int8类型。
在训练过程中,一般还是全精度(float32)类型,到了推理阶段,则有两种方案:一种是所有算子都支持量化后的类型的数据运算(以int8为例),因此模型的全过程中数据流都是int8;另一种是数据流仍是float32,每个算子前后分别有quantize层和dequantize层用以将float32转换为int8或者反过来。
2. 量化压缩原理
2.1. 定点数与浮点数
在计算机的存储中,int属于定点数,float和double属于浮点数。定点数与浮点数的介绍见https://www.jianshu.com/p/d39fb5792ac8
浮点数的存储与转换方式详解见https://www.cnblogs.com/lan0725/p/11515584.html
int8的值域为[-128,127],取值个数为
2
8
;
2^{8};
28;float32的值域为
[
(
2
−
23
−
2
)
×
2
127
,
(
2
−
2
−
23
)
×
2
127
]
[(2^{-23}-2)\times 2^{127},(2-2^{-23})\times 2^{127}]
[(2−23−2)×2127,(2−2−23)×2127],取值个数约为
2
32
2^{32}
232。int8在整个值域上的精度分布是均匀的,而float32不是均匀的,0附近的精度越高,越往值域区间两边精度越低。这是因为,在给定指数时,float32在此指数对应的区间内数值个数是一定的,如下图所示(图片来源https://jackwish.net/2019/neural-network-quantization-introduction-chn.html)
2.2. 将浮点数量化为定点数
量化压缩的过程本质上是一个一个区间放缩到另一个区间的过程,这里仅讨论线性放缩:
x
q
u
a
n
t
i
z
e
d
=
r
o
u
n
d
(
x
f
l
o
a
t
/
x
s
c
a
l
e
+
x
z
e
r
o
_
p
o
i
n
t
)
(2-1)
x_{quantized}=round(x_{float}/x_{scale}+x_{zero\_point})\tag{2-1}
xquantized=round(xfloat/xscale+xzero_point)(2-1)
round表示取整
首先确定放缩因子:
x
s
c
a
l
e
=
x
f
l
o
a
t
m
a
x
−
x
f
l
o
a
t
m
i
n
x
q
u
a
n
t
i
z
e
d
m
a
x
−
x
q
u
a
n
t
i
z
e
d
m
i
n
(2-2)
x_{scale}=\frac{x_{float}^{max}-x_{float}^{min}}{x_{quantized}^{max}-x_{quantized}^{min}}\tag{2-2}
xscale=xquantizedmax−xquantizedminxfloatmax−xfloatmin(2-2)
而
(
x
q
u
a
n
t
i
z
e
d
m
a
x
−
x
q
u
a
n
t
i
z
e
d
)
×
x
s
c
a
l
e
=
(
x
f
l
o
a
t
m
a
x
−
x
f
l
o
a
t
)
(2-3)
(x_{quantized}^{max}-x_{quantized})\times x_{scale}=(x_{float}^{max}-x_{float})\tag{2-3}
(xquantizedmax−xquantized)×xscale=(xfloatmax−xfloat)(2-3)
所以:
x
q
u
a
n
t
i
z
e
d
=
x
f
l
o
a
t
/
x
s
c
a
l
e
+
x
q
u
a
n
t
i
z
e
d
m
a
x
−
x
f
l
o
a
t
m
a
x
/
x
s
c
a
l
e
(2-4)
x_{quantized}=x_{float}/x_{scale}+x_{quantized}^{max}-x_{float}^{max}/x_{scale}\tag{2-4}
xquantized=xfloat/xscale+xquantizedmax−xfloatmax/xscale(2-4)
也即:
x
z
e
r
o
_
p
o
i
n
t
=
x
q
u
a
n
t
i
z
e
d
m
a
x
−
x
f
l
o
a
t
m
a
x
/
x
s
c
a
l
e
(2-5)
x_{zero\_point}=x_{quantized}^{max}-x_{float}^{max}/x_{scale}\tag{2-5}
xzero_point=xquantizedmax−xfloatmax/xscale(2-5)
在工程中,int8可能会取[0,255] (无符号整数)
有时float类型的极大值和极小值会太极端,从而使得放缩因子太大,导致最后量化后的结果太集中于某一个小的子区间,浪费了其他部分的值域空间。这时,可以读float类型的极大值极小值进行clip。例如,认为设为-1.0和1.0.
这里留一个小彩蛋,为什么(2-3)里面不用 x − x m i n x-x^{min} x−xmin来与缩放因子相乘,而是采用 x − x m a x x-x^{max} x−xmax呢?虽然从数学上是等价的,但是工程实现上的效果会有点不一样,读者可以自己思考一下
3. tf-lite中的量化方法
大体上分为两类:
- 训练后整数量化:输入、模型的weight和bias都是低bit(例如int8),模型在线上推理时,要求输入必须是int8,否则会报错;网络结构中的计算也是int8。
- 训练后动态范围量化:模型的weight是int8,bias是float32,每一层的输入是float32,在每层计算前先要进行float32->int8的转换。凡是支持量化计算的层(算子)都要进行这种转换,因此会增加一定的时延。输出仍是float32.
1又有两种实现方式,在tf1.15中,训练时搞定输入和权重的量化参数计算;在tf2.3中,训练时搞定权重的量化参数计算,训练完成后再用一个额外的数据集来获取输入的量化参数。