4、模型训练与量化准备:使用Keras训练一个简单分类模型(如MNIST),理解浮点模型与量化模型的关系

在TFLite Micro的部署链路中,模型训练与量化准备是决定端侧推理效果的第一道关口。MCU上的神经网络推理与服务器或移动端有本质差异:存储空间以KB计、RAM以数十KB计、无浮点单元(FPU)的裸机环境只能依赖整型运算。因此,理解浮点模型(Float32)与量化模型(Int8/uint8)之间的数值关系、精度损失来源以及转换流程,是后续C数组固化与MCU集成的必要前提。本章以MNIST手写数字分类为实战载体,使用Keras训练一个简单CNN模型,并系统讲解量化感知训练(QAT)与训练后量化(PTQ)的差异,以及如何为TFLite Micro准备真正可用的整型模型。

浮点模型 → 量化模型 → TFLite Micro 部署链路 Keras 训练 Float32 权重/激活 MNIST CNN 训练后量化 PTQ 校准数据集 动态范围/全整型 量化感知训练 QAT 伪量化节点 精度损失更小 TFLite Int8 模型 .tflite 量化核心参数(Scale & ZeroPoint) Scale (S) = (Rmax - Rmin) / (Qmax - Qmin) —— 浮点范围到整型范围的映射步长 ZeroPoint (Z) = Qmin - Rmin / S —— 浮点0对应的整型值,保证数值对齐 反量化公式:R = S × (Q - Z) —— 推理时整型运算,输出前反量化回浮点 精度对比(MNIST 测试集) Float32 原始模型:约 99.2% PTQ 全整型:约 98.8% ~ 99.0% QAT 全整型:约 99.1% ~ 99.2% MCU 部署约束 Flash:模型权重 + 代码 < 256KB(典型) RAM:激活缓冲区 < 32KB(典型) 运算:仅支持 Int8 乘加,无浮点单元

上图展示了从Keras浮点模型到TFLite Micro可部署整型模型的完整链路。核心在于理解量化参数(Scale和ZeroPoint)如何将浮点张量映射到整型空间,以及PTQ与QAT两条路径在精度和工程复杂度上的取舍。下面我们将从原理、操作、案例和误区四个维度展开。

4.1 核心原理:浮点模型与量化模型的数值映射关系

浮点模型中的权重和激活值通常以Float32存储,每个数值占用4字节。在MCU上,一个典型的MNIST CNN模型(约10万参数)需要400KB存储,远超常见MCU的Flash容量。量化模型将每个数值映射到Int8(1字节)或uint8,存储直接缩小4倍。但量化并非简单的截断,而是通过仿射变换保持数值分布。

对于任意浮点张量 r ∈ [r_min, r_max],量化后的整型值 q 满足:

q = round(r / S) + Z
其中:
S = (r_max - r_min) / (q_max - q_min)   # Scale,浮点步长
Z = q_min - r_min / S                    # ZeroPoint,浮点0对应的整型值
反量化:r = S × (q - Z)

在TFLite Micro中,推理过程完全使用整型运算:权重和激活均为Int8,偏置为Int32(累加器精度)。每一层卷积或全连接计算完成后,通过预先计算好的Scale和ZeroPoint进行反量化,再作为下一层的输入。这种设计避免了浮点运算,但引入了量化误差——误差来源于舍入和裁剪。

关键认知:量化不是“压缩”,而是“重新编码”。它改变了数值的表示精度,但保留了足够的动态范围。对于MNIST这类输入范围固定(0~1)的任务,量化误差通常小于0.5%,对最终分类结果影响极小。

4.2 判断方法与操作流程:PTQ与QAT的选择标准

选择PTQ还是QAT,取决于模型对量化误差的敏感度。以下是判断流程:

  1. 第一步:训练浮点模型,确保测试集精度达到目标(如MNIST > 99%)。
  2. 第二步:尝试PTQ,使用代表性校准数据集(通常500~1000张)统计每层激活值的动态范围。
  3. 第三步:评估量化模型精度,若精度下降 < 1%,直接采用PTQ;若下降 > 2%,改用QAT。
  4. 第四步:QAT训练,在浮点模型中插入伪量化节点,模拟量化误差,微调权重。

PTQ操作流程(使用TensorFlow 2.x):

import tensorflow as tf

# 加载训练好的浮点模型
model = tf.keras.models.load_model('mnist_fp32.h5')

# 定义校准数据集生成器(取训练集前1000张)
def representative_dataset():
    for i in range(1000):
        yield [x_train[i:i+1]]  # 形状需与模型输入一致

# 转换并量化
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8  # 强制输入为int8
converter.inference_output_type = tf.int8
tflite_model = converter.convert()

# 保存
with open('mnist_ptq.tflite', 'wb') as f:
    f.write(tflite_model)

QAT操作流程(在训练阶段加入伪量化):

import tensorflow_model_optimization as tfmot

# 在原始模型上应用QAT
qat_model = tfmot.quantization.keras.quantize_model(model)

# 重新编译并训练(使用较小学习率,如1e-5)
qat_model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
qat_model.fit(x_train, y_train, epochs=3, validation_data=(x_test, y_test))

# 转换时无需再指定representative_dataset(QAT已内置量化范围)
converter = tf.lite.TFLiteConverter.from_keras_model(qat_model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8
tflite_model = converter.convert()
对比维度PTQ(训练后量化)QAT(量化感知训练)
实现复杂度低,无需重新训练高,需插入伪量化节点并微调
所需数据需校准数据集(500~1000张)需完整训练集和训练过程
精度损失通常 < 1%,敏感模型可达 2~5%通常 < 0.5%,接近浮点精度
适用场景模型较大、参数冗余度高小模型、对精度要求苛刻
MCU部署推荐优先尝试PTQ不满足精度时使用

4.3 案例拆解:MNIST CNN从浮点到Int8的完整实战

本案例使用一个3层卷积网络(Conv→Conv→Dense),参数量约12万,浮点模型大小约480KB。目标平台为STM32F746(Cortex-M7,无FPU),Flash 1MB,RAM 320KB。初始现象:直接部署浮点模型,Flash占用超标,且推理速度极慢(单次推理约3秒)。

关键输入数据:

  • 训练集:MNIST 60000张,测试集10000张,输入形状 (28, 28, 1)
  • 浮点模型精度:99.2%
  • 模型结构:Conv2D(32, 3×3) → Conv2D(64, 3×3) → MaxPool → Flatten → Dense(128) → Dense(10)

分析判断过程:首先尝试PTQ,使用1000张训练图像作为校准集。转换后模型大小降至120KB(缩小4倍),但测试精度降至98.5%,下降0.7%。对于MNIST任务,这个精度仍可接受,但考虑到MCU上可能存在输入噪声,决定尝试QAT以保留更多余量。

实际操作步骤:

  1. 加载浮点模型,应用QAT包装,使用学习率1e-5微调3个epoch。
  2. 转换得到QAT模型,精度恢复至99.1%,模型大小仍为120KB。
  3. 使用TFLite解释器验证量化模型输出与浮点模型输出的一致性(最大误差 < 0.02)。
  4. 将.tflite文件通过xxd -i命令转换为C数组,准备固化。

结果对比:

指标Float32PTQ Int8QAT Int8
模型大小480KB120KB120KB
测试精度99.2%98.5%99.1%
推理时间(模拟)3.2s0.4s0.4s
RAM占用180KB45KB45KB

风险与代价:QAT需要额外训练时间(约15分钟GPU),且需要保存训练脚本和权重。PTQ虽然省时,但若校准集选择不当(如只取前100张),可能导致激活值范围估计偏差,精度骤降至95%。

复盘经验:对于MNIST这类简单任务,PTQ已足够;但对于更复杂的图像分类(如CIFAR-10),QAT几乎是必须的。建议在项目初期就建立量化评估流水线,避免后期返工。

常见陷阱:校准数据集必须覆盖真实部署时的输入分布。如果MCU端摄像头采集的图像与训练集分布不同(如光照变化),量化模型的精度会显著下降。建议在部署前用真实硬件采集的数据进行验证。

4.4 常见误区与总结复盘

误区一:认为量化模型精度一定低于浮点模型。实际上,对于参数冗余的模型,量化有时能起到正则化作用,精度甚至略有提升。关键在于权重分布是否均匀。

误区二:忽略ZeroPoint的作用。有些开发者只关注Scale,将ZeroPoint设为0,这会导致有偏置的激活值(如ReLU输出)量化误差增大。TFLite会自动计算最优ZeroPoint,手动干预反而有害。

误区三:在转换时使用float16量化。float16在MCU上并不受支持,TFLite Micro仅支持int8/uint8。float16量化只适用于移动端GPU。

误区四:校准集数量越多越好。超过2000张后,校准集带来的精度提升趋于饱和,反而增加转换时间。1000张是经验最优值。

总结复盘:本章的核心是建立“浮点→量化”的数值桥梁。在TFLite Micro流水线中,量化准备直接决定了后续C数组的大小和推理速度。建议读者在完成本章实验后,记录以下数据:浮点模型大小、量化模型大小、精度变化、校准集大小、转换时间。这些数据将成为后续章节中模型压缩和硬件适配的基准。

工程建议:在训练脚本中同时保存浮点和量化模型,并编写自动化评估脚本。每次修改网络结构后,自动运行PTQ并输出精度报告,确保量化损失在可控范围内。