2、基础知识:掌握浮点数、定点数、量化(INT8/FP16)等数值表示及其对精度的影响
TinyML 模型在 PC 端训练与验证时,通常使用 32 位浮点数(FP32)进行张量计算和梯度更新。然而,当模型被部署到微控制器(MCU)或嵌入式 NPU 上时,受限于存储带宽、SRAM 容量以及缺乏 FPU(浮点运算单元)的算力约束,模型权重和激活值往往需要转换为定点数或低比特浮点数(如 FP16、INT8)。这种数值表示的转换,是导致 PC 端输出与板端输出产生偏差的根源之一。理解浮点数、定点数与量化之间的数学关系与误差传播机制,是进行精度回归测试、定位对齐偏差的第一项基本功。
2.1 核心原理:从 FP32 到 INT8 的数值映射
在 PC 端,PyTorch 或 TensorFlow 默认使用 FP32 表示张量。FP32 由 1 位符号位、8 位指数位和 23 位尾数位组成,能够表示约 \(1.18 \times 10^{-38}\) 到 \(3.4 \times 10^{38}\) 的动态范围,且相对精度约为 \(10^{-7}\)。而 INT8 仅有 8 位,只能表示 256 个离散整数(通常为 -128 到 127 或 0 到 255)。将 FP32 映射到 INT8 的过程称为量化,其核心公式为:
real_value = (int8_value - zero_point) × scale
其中 scale 是一个浮点数,表示每个量化步长对应的实际数值大小;zero_point 是一个 INT8 整数,表示实数 0 对应的量化值。这种映射方式称为非对称量化,适用于激活值(通常非负分布)。对于权重,由于分布近似对称,常采用对称量化(zero_point=0)。
量化误差主要来自两个方面:舍入误差(将连续浮点数映射到离散整数时产生的精度损失)和截断误差(当浮点数超出 INT8 可表示范围时被强制截断为最大值或最小值)。例如,若某层激活值范围为 [0, 6.0],则 scale = 6.0/255 ≈ 0.0235。一个实际值 3.14159 将被量化为 round(3.14159/0.0235) = 134,反量化后为 134 × 0.0235 = 3.149,误差约 0.0074。这个误差在单层看似微小,但经过多层累加后,可能被放大到足以改变最终分类结果。
2.2 判断方法:如何评估数值表示对精度的影响
在 TinyML 精度回归测试中,我们需要系统性地评估不同数值表示对模型输出的影响。以下是标准化的评估流程:
- 建立基准输出:使用 PC 端 FP32 模型对一组固定测试样本进行推理,保存每一层的输出张量(logits、中间激活值)作为“黄金参考”。
- 生成量化模型:使用 TensorFlow Lite for Microcontrollers 或 STM32Cube.AI 等工具,将模型量化为 INT8 或 FP16,并部署到目标板卡。
- 逐层对比:在板端运行相同测试样本,导出各层输出,与 FP32 基准计算余弦相似度(Cosine Similarity)或均方误差(MSE)。
- 设定容差阈值:通常要求最终输出的余弦相似度 > 0.99,或 Top-1 分类结果一致率 > 95%。对于回归任务(如预测连续值),则要求归一化均方根误差(NRMSE)< 5%。
| 数值格式 | 位宽 | 动态范围 | 相对精度 | 典型应用场景 | 精度损失风险 |
|---|---|---|---|---|---|
| FP32 | 32 | ~1e-38 ~ 3e38 | ~1e-7 | PC 端训练与验证 | 基准,无损失 |
| FP16 | 16 | ~6e-5 ~ 6e4 | ~1e-3 | 带 FPU 的 MCU(如 Cortex-M7) | 低,但可能溢出 |
| INT8 | 8 | -128 ~ 127 | ~1/256 | 绝大多数 MCU 与 NPU | 中高,需校准 |
| 定点数 Q15 | 16 | -1.0 ~ 0.9999 | ~3e-5 | CMSIS-DSP 库常用 | 中,需防溢出 |
2.3 案例拆解:一个图像分类模型的量化偏差定位
假设我们有一个在 PC 上训练好的 MobileNetV1 图像分类模型(FP32,Top-1 准确率 85%),部署到 STM32F746 开发板上,使用 TFLite Micro 进行 INT8 量化推理。在精度回归测试中,发现板端 Top-1 准确率下降至 72%,且部分样本的 softmax 输出概率分布与 PC 端差异明显。
初始现象:对 1000 张测试图片进行对比,发现 130 张图片分类结果不一致。进一步分析,这些不一致样本的 PC 端输出概率通常在 0.4~0.6 之间(即置信度较低),而板端输出概率则偏向 0.2 或 0.8。
分析过程:我们首先检查了量化配置。该模型使用默认的“动态范围量化”,即仅对权重进行 INT8 量化,激活值仍为 FP32。但 TFLite Micro 在 MCU 上实际执行时,激活值也被强制转换为 INT8(因为 MCU 缺乏 FPU)。这导致激活值的量化误差被引入。我们使用 TensorFlow 的 representative_dataset 重新校准了量化参数,发现原始校准集仅包含 100 张图片,且未覆盖光照变化较大的场景,导致 scale 值偏大,激活值量化后信息丢失严重。
实际操作步骤:
- 将校准集扩充至 1000 张,覆盖不同光照、角度和背景的图片。
- 使用
tf.lite.TFLiteConverter重新生成量化模型,并设置optimizations=[tf.lite.Optimize.DEFAULT]。 - 在板端重新部署,并导出第 10 层(深度可分离卷积层)的输出张量,与 PC 端 FP32 输出计算余弦相似度。
- 结果显示,余弦相似度从 0.93 提升至 0.98,最终板端准确率回升至 82%。
结果对比与复盘:剩余的 3% 准确率差距主要来自 BatchNorm 层的折叠误差。在量化过程中,BatchNorm 的参数被折叠进卷积权重,但折叠操作在 FP32 下进行,随后才量化为 INT8,这引入了额外的舍入误差。我们通过将 BatchNorm 层显式保留为量化层(而非折叠)解决了该问题,最终准确率稳定在 84%。
2.4 常见误区与总结复盘
误区一:认为 FP16 一定比 INT8 精度高。实际上,FP16 虽然动态范围大于 INT8,但其尾数位仅 10 位,对于大数值(如 > 1024)的表示精度反而低于 INT8。在 TinyML 场景中,若模型权重范围在 [-1, 1] 之间,INT8 的精度(1/256 ≈ 0.0039)优于 FP16(在 1.0 附近精度约为 0.0005,但在 1000 附近精度约为 0.5)。因此,选择数值格式需结合具体张量分布。
误区二:忽略 zero_point 的影响。在对称量化中,zero_point 固定为 0,但非对称量化中,zero_point 的微小偏差会导致整体偏置。例如,若 zero_point 计算错误(偏差 1),则所有反量化值都会偏移一个 scale 单位,该误差在多层累加后可能被放大。
误区三:仅比较最终准确率,不检查中间层。最终准确率相同不代表中间层输出一致。两个模型可能在某些样本上得到相同的分类结果,但中间层特征图差异巨大,这会导致模型在对抗样本或分布外数据上表现迥异。精度回归测试必须包含逐层对比。
总结复盘:数值表示是 TinyML 精度回归测试的基石。工程师需要掌握以下核心技能:① 能够计算量化参数(scale/zero_point);② 能够使用工具(如 TFLite、PyTorch Quantization)生成量化模型;③ 能够编写脚本逐层对比 PC 与板端输出;④ 理解不同数值格式的误差边界。建议在项目初期就建立自动化的精度回归流水线,将 PC 端 FP32 输出保存为二进制文件,板端测试时直接加载并对比,避免每次测试都重新生成基准。