4、线性量化原理:缩放因子(scale)和零点(zero point)的概念,以及量化公式(r = (q - zero_point) * scale)
在TensorFlow Lite的模型转换流程中,当我们把Float32模型转换为Int8模型时,本质上是在做一次数值域的映射。浮点模型中的权重和激活值通常是连续分布的实数,而Int8只能表示256个离散整数。线性量化就是建立从浮点实数域到整数域的线性映射关系,这个映射的核心就是两个参数:缩放因子(scale)和零点(zero point)。理解这两个参数的物理含义和数学关系,是掌握量化技术的第一步,也是后续进行量化误差分析、模型精度调优的基础。
4.1 核心原理:从连续到离散的线性映射
线性量化的本质是寻找一个仿射变换(即线性变换加平移),将浮点数值范围 [r_min, r_max] 映射到整数数值范围 [q_min, q_max]。对于Int8量化,q_min和q_max通常取0到255(无符号)或-128到127(有符号)。TensorFlow Lite默认使用无符号Int8表示激活值,而有符号Int8表示权重。
这个映射关系由两个参数唯一确定:
- 缩放因子(scale):表示整数中每个单位步长对应的浮点数值大小。计算公式为:
scale = (r_max - r_min) / (q_max - q_min)。它是一个正的浮点数,决定了量化的“粒度”。scale越小,量化精度越高,但能表示的浮点范围也越小。 - 零点(zero_point):表示浮点数值0.0对应的整数。计算公式为:
zero_point = q_min - r_min / scale,通常需要四舍五入取整。零点的作用是确保浮点0.0在量化后能被精确表示,这对于神经网络的padding操作和ReLU激活函数至关重要。
正向量化公式为:q = round(r / scale) + zero_point,其中round表示四舍五入到最近的整数。反向反量化公式为:r = (q - zero_point) * scale。这两个公式构成了量化和反量化的完整闭环。
关键理解:scale决定了量化步长,类似于ADC(模数转换器)的分辨率;zero_point则是一个偏移量,确保浮点0.0在整数域中有精确的对应点。两者共同作用,使得浮点数和整数之间可以双向转换,但转换过程中会引入量化误差(主要是舍入误差和截断误差)。
4.2 判断方法:如何确定scale和zero_point
在实际的TensorFlow Lite转换过程中,scale和zero_point的确定遵循以下流程:
- 收集浮点数值范围:对于权重,直接统计权重张量的最小值和最大值;对于激活值,需要通过校准数据集(calibration dataset)进行推理,收集每一层激活值的统计分布,通常取最小值和最大值,或者使用百分位数(如99.99%)来避免异常值的影响。
- 计算scale:根据公式
scale = (r_max - r_min) / (q_max - q_min)计算。注意,如果使用对称量化(symmetric quantization),则r_min = -r_max,此时zero_point = 0,计算更简单。 - 计算zero_point:根据公式
zero_point = q_min - r_min / scale计算,并四舍五入取整。对于无符号Int8,q_min=0,q_max=255;对于有符号Int8,q_min=-128,q_max=127。 - 验证映射关系:检查浮点0.0对应的整数是否在有效范围内,以及整数边界对应的浮点值是否合理。
| 量化类型 | 整数范围 | scale公式 | zero_point公式 | 适用场景 |
|---|---|---|---|---|
| 非对称量化(无符号) | [0, 255] | (r_max - r_min) / 255 | round(-r_min / scale) | 激活值(ReLU后非负) |
| 非对称量化(有符号) | [-128, 127] | (r_max - r_min) / 255 | round(-128 - r_min / scale) | 通用场景 |
| 对称量化(有符号) | [-127, 127] | max(|r_min|, |r_max|) / 127 | 0 | 权重(分布近似对称) |
4.3 案例拆解:一个具体张量的量化过程
假设我们有一个浮点权重张量,其最小值为-2.0,最大值为6.0。我们使用TensorFlow Lite默认的非对称量化方式(有符号Int8)来量化这个张量。
第一步:计算scale
r_min = -2.0, r_max = 6.0
q_min = -128, q_max = 127
scale = (6.0 - (-2.0)) / (127 - (-128)) = 8.0 / 255 ≈ 0.03137
第二步:计算zero_point
zero_point = round(q_min - r_min / scale)
= round(-128 - (-2.0) / 0.03137)
= round(-128 + 63.75)
= round(-64.25)
= -64
第三步:验证映射关系
浮点0.0对应的整数:q = round(0.0 / 0.03137) + (-64) = 0 - 64 = -64
反量化验证:r = (-64 - (-64)) * 0.03137 = 0.0 ✓
浮点6.0对应的整数:q = round(6.0 / 0.03137) + (-64) = round(191.27) - 64 = 191 - 64 = 127
反量化验证:r = (127 - (-64)) * 0.03137 = 191 * 0.03137 ≈ 5.99 ≈ 6.0 ✓
第四步:量化误差分析
量化误差主要来源于两个方面:一是舍入误差,即浮点值映射到整数时的四舍五入;二是截断误差,即超出 [r_min, r_max] 范围的浮点值被强制截断到边界值。在本例中,最大量化误差为 scale/2 ≈ 0.0157,对于权重来说这个精度通常足够。
实战经验:在TensorFlow Lite中,权重通常使用对称量化(zero_point=0),因为权重的分布往往近似关于0对称,这样可以减少一个参数的存储和计算。而激活值由于经过ReLU等操作后通常为非负,使用非对称量化(无符号Int8)可以获得更高的精度。
4.4 常见误区与总结复盘
在实际工程中,关于scale和zero_point的理解和使用存在几个常见误区:
- 误区一:认为zero_point一定是0。实际上,只有对称量化时zero_point才为0。非对称量化中,zero_point是一个重要的偏移量,忽略它会导致量化后的数值整体偏移,造成精度严重下降。
- 误区二:混淆scale和精度的关系。scale越小,量化精度越高,但可表示的浮点范围越小。如果浮点数据分布范围很大,强行使用小scale会导致大量数值溢出(截断),反而增加误差。
- 误区三:忽略zero_point的取整误差。zero_point计算后需要四舍五入取整,这个取整操作本身会引入误差。在极端情况下,取整误差可能导致浮点0.0无法被精确表示。
- 误区四:认为量化公式只能单向使用。实际上,量化和反量化是双向的,在推理过程中,权重和激活值在计算时会被反量化为浮点(或使用整数运算模拟),最终输出再量化为整数。
风险提示:在TensorFlow Lite转换过程中,如果校准数据集选择不当(例如只使用单一类别的图片),会导致激活值的统计范围不准确,进而计算出不合理的scale和zero_point,最终造成模型精度大幅下降。建议使用覆盖多种场景的校准数据集,并检查每一层的量化参数是否合理。