3、定点数与浮点数:定点数表示原理、Q格式、整数与浮点之间的转换关系、量化误差来源
在TensorFlow Lite的量化推理中,模型权重和激活值通常以8位或16位整数存储,而非浮点数。这一设计决策的根源在于:整数运算在CPU、DSP、NPU和微控制器上远比浮点运算高效,且功耗更低。然而,要将浮点模型转换为整数模型,必须理解定点数的数学本质。定点数并非一种新的数值系统,而是对整数的一种标定(Scaling)——通过一个比例因子和一个零点偏移,将浮点实数映射到整数区间。本章将深入剖析定点数的表示原理、Q格式的演变、整数与浮点之间的转换关系,以及量化误差的数学来源,为后续理解TensorFlow Lite的量化参数(scale和zero_point)奠定理论基础。
3.1 定点数表示原理:从整数到实数的桥梁
定点数(Fixed-Point Number)的核心思想是:用整数存储,用约定的小数点位置解释数值。与浮点数不同,定点数的小数点位置是固定的,因此不需要额外的指数位来动态调整精度。在嵌入式系统和深度学习推理中,定点数的主要优势在于:整数运算单元(ALU)可以直接处理,无需浮点协处理器,且运算速度更快、功耗更低。
一个定点数通常由三部分构成:符号位、整数位宽和小数位宽。例如,Q8.8格式表示8位整数部分和8位小数部分,总位宽16位。其数值计算公式为:
value = (-1)^sign × (整数部分 + 小数部分 / 2^小数位宽)
在TensorFlow Lite的量化方案中,实际使用的是仿射变换(Affine Transform),而非纯定点数。仿射变换将浮点数r映射到整数q:
q = round(r / S) + Z
其中S(scale)是浮点比例因子,Z(zero_point)是整数零点偏移。反变换为:
r = S × (q - Z)
这种映射方式比纯定点数更灵活,因为它允许浮点数的零点映射到任意整数Z,而不仅仅是0。例如,当浮点数据范围是[-1.5, 3.5]时,使用int8量化,S = (3.5 - (-1.5)) / 255 ≈ 0.0196,Z = round(-(-1.5) / 0.0196) ≈ 77。这样,浮点值-1.5映射到整数0,浮点值3.5映射到整数255。
3.2 Q格式详解:从Qm.n到TensorFlow Lite的scale
Q格式(Q Format)是数字信号处理中常用的定点数表示法,记作Qm.n,其中m表示整数位宽(不含符号位),n表示小数位宽,总位宽为1 + m + n(含符号位)。例如Q1.7表示8位有符号定点数,范围是[-1, 1 - 2^-7],精度为2^-7 ≈ 0.0078。
| Q格式 | 总位宽 | 整数范围 | 精度(分辨率) | 典型应用 |
|---|---|---|---|---|
| Q0.7 | 8位 | [-1, 1) | 2^-7 ≈ 0.0078 | 音频采样、归一化特征 |
| Q1.6 | 8位 | [-2, 2) | 2^-6 ≈ 0.0156 | 小范围权重 |
| Q3.4 | 8位 | [-8, 8) | 2^-4 = 0.0625 | 图像像素(0-255) |
| Q8.8 | 16位 | [-256, 256) | 2^-8 ≈ 0.0039 | 高精度中间累加 |
| Q15.16 | 32位 | [-32768, 32768) | 2^-16 ≈ 0.000015 | 高动态范围信号 |
TensorFlow Lite的量化方案并不直接使用Q格式,而是采用scale + zero_point的仿射映射。两者之间的关系可以这样理解:Q格式是scale = 2^-n、zero_point = 0的特例。TensorFlow Lite的scale是任意浮点数,这使得量化范围可以精确匹配数据的实际分布,从而减少量化误差。
在TensorFlow Lite中,scale和zero_point的确定通常遵循以下流程:
- 收集数据范围:统计权重或激活值的浮点最小值r_min和最大值r_max。
- 计算scale:S = (r_max - r_min) / (q_max - q_min),其中q_max和q_min是量化整数的范围(如int8为-128和127)。
- 计算zero_point:Z = round(q_min - r_min / S),并确保Z在[q_min, q_max]范围内。
- 验证映射:检查浮点0.0是否映射到整数Z,若Z超出范围则需调整。
3.3 整数与浮点之间的转换关系:量化与反量化
量化(Quantization)是将浮点数转换为整数的过程,反量化(Dequantization)是逆过程。在TensorFlow Lite推理中,权重在离线时量化,而激活值在运行时动态量化。转换公式如下:
// 量化:浮点 → 整数
q = clamp(round(r / S) + Z, q_min, q_max)
// 反量化:整数 → 浮点(用于调试或混合精度)
r = S × (q - Z)
其中clamp函数将结果限制在[q_min, q_max]范围内,防止溢出。round函数通常采用四舍五入(round-half-to-even),以减少统计偏差。
以一个实际案例说明:假设某层激活值的浮点范围是[0.0, 6.0],使用uint8量化(q_min=0, q_max=255)。
S = (6.0 - 0.0) / (255 - 0) = 0.023529
Z = round(0 - 0.0 / 0.023529) = 0
浮点值 3.14 → q = round(3.14 / 0.023529) + 0 = round(133.45) = 133
反量化:r = 0.023529 × (133 - 0) = 3.1294,误差 = 0.0106
这个误差就是量化误差。在深层网络中,每层的量化误差会累积,最终影响模型精度。TensorFlow Lite提供了量化感知训练(QAT)来缓解这一问题,在训练过程中模拟量化误差,使模型权重适应量化噪声。
3.4 量化误差来源:舍入、截断与精度损失
量化误差主要来自三个层面,理解这些来源有助于在实际部署中采取针对性措施。
3.4.1 舍入误差(Rounding Error)
当浮点数r除以S后得到的小数部分被舍入到最近的整数时,产生舍入误差。最大舍入误差为S/2。例如,S=0.1时,浮点值0.05可能被量化为0或1,误差为0.05。舍入误差是均匀分布的,在大量数据中会部分抵消,但极端情况下仍会造成精度损失。
3.4.2 截断误差(Clipping Error)
当浮点值超出量化范围时,会被截断到边界值。例如,int8范围[-128, 127],若浮点值对应整数为150,则被截断为127,误差为23×S。截断误差在激活值中尤为常见,特别是当输入分布有长尾时。截断误差是单向的,会导致系统性偏差。
3.4.3 精度损失(Precision Loss)
即使舍入和截断误差为零,scale S本身是浮点数,在反量化乘法中会引入浮点运算误差。此外,在TensorFlow Lite的整数内核中,乘法累加(MAC)操作使用32位整数累加器,但中间结果可能溢出,需要额外的缩放处理,这也会引入误差。
| 误差类型 | 产生原因 | 影响特征 | 缓解策略 |
|---|---|---|---|
| 舍入误差 | 除法后取整 | 随机分布,均值为0 | 使用更大的位宽(int16 vs int8) |
| 截断误差 | 超出量化范围 | 单向偏差,激活饱和 | 使用KL散度校准,截断离群值 |
| 精度损失 | scale浮点乘法 | 累积误差,深层网络放大 | 使用QAT,或混合精度(部分层用浮点) |
以一个实际部署案例说明误差控制的重要性:某图像分类模型在FP32下精度为92.3%,直接使用min/max量化后精度降至88.7%。分析发现,激活值中存在少量离群值(占0.1%),导致scale偏大,大部分数据映射到整数区间[0, 80],有效精度仅约6.3位。改用99.9%百分位数校准后,scale缩小,有效精度提升至7.2位,精度恢复至91.8%。进一步使用QAT微调后,精度达到92.1%,仅损失0.2%。