4. 模型训练与量化准备:从FP32到INT8/INT16

好,咱们进入第四章。这一章很关键,说白了就是为模型上MCU做准备。你想想看,一个训练好的模型,参数全是FP32的浮点数,直接塞进STM32?那基本不可能——Flash和RAM都扛不住。

我刚开始做嵌入式AI时,就犯过这个错。在PC上跑得好好的模型,一放到STM32上,内存直接爆了。后来才明白,量化这一步,是绕不过去的。

4.1 为什么需要量化?FP32到INT8/INT16的必要性

先看一组数据,你就明白了:

数据类型 位宽 模型体积缩减 推理速度提升(估) 精度损失
FP32 32-bit 1x(基准) 1x(基准) 无
INT16 16-bit ~2x ~1.5-2x 极小(通常<0.5%)
INT8 8-bit ~4x ~2-4x 较小(通常<2%)

看到没?INT8模型体积只有FP32的1/4,推理速度还能翻倍。对于STM32这种资源受限的芯片,这简直是救命稻草。

我个人习惯,在项目初期就会把量化纳入考量。比如一个100KB的FP32模型,量化成INT8后只有25KB。这意味着什么?意味着你可以用更便宜的芯片,或者给其他功能腾出空间。

核心结论:量化不是可选项,而是嵌入式AI落地的必选项。没有量化,很多模型根本跑不起来。

4.2 量化原理:对称量化 vs 非对称量化

量化听起来玄乎,其实原理不复杂。就是把浮点数映射到整数范围。

举个例子:FP32的0.0到1.0,映射到INT8的0到255。就这么简单。

但映射方式有两种:

  • 对称量化:零点对应0,正负对称。适合权重这种分布比较对称的数据。
  • 非对称量化:零点可以偏移,能更好地覆盖非对称分布的数据(比如ReLU后的激活值)。

我在项目中遇到过一个问题:用对称量化量化激活值,精度掉得厉害。后来换成非对称量化,精度就回来了。嗯,这里要注意,激活值量化通常用非对称,权重量化用对称,这是个经验法则。

小技巧:TensorFlow Lite默认使用非对称量化,对大多数模型效果不错。但如果你的模型精度特别敏感,可以试试混合量化——权重用INT8,激活用INT16。

4.3 使用Keras/TensorFlow训练模型

好,咱们动手。先训练一个简单的模型,后面用它来做量化演示。

我选一个轻量级的模型——MobileNetV2,适合STM32。当然,你也可以用自己的模型。

import tensorflow as tf
from tensorflow import keras

# 加载数据集(以CIFAR-10为例)
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()

# 归一化
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0

# 构建模型(使用MobileNetV2,输入尺寸32x32x3)
base_model = tf.keras.applications.MobileNetV2(
    input_shape=(32, 32, 3),
    include_top=False,
    weights=None
)

model = keras.Sequential([
    base_model,
    keras.layers.GlobalAveragePooling2D(),
    keras.layers.Dense(10, activation='softmax')
])

# 编译
model.compile(
    optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

# 训练
model.fit(
    x_train, y_train,
    batch_size=64,
    epochs=10,
    validation_data=(x_test, y_test)
)

# 保存模型
model.save('cifar10_mobilenetv2.h5')

训练完成后,你会得到一个FP32的模型。别急,下一步才是重头戏。

4.4 准备校准数据集

量化时,需要一组数据来校准量化参数(比如缩放因子和零点)。这组数据叫校准数据集。

校准数据集不需要很大,一般100-500张图片就够了。但要注意:

  • 要覆盖模型可能遇到的各种情况
  • 不能和训练集完全重复
  • 最好从验证集中随机抽取

我建议的做法:

import numpy as np

# 从验证集中随机抽取200张作为校准集
np.random.seed(42)
indices = np.random.choice(len(x_test), 200, replace=False)
calibration_data = x_test[indices]

# 保存为numpy格式,方便后续使用
np.save('calibration_data.npy', calibration_data)

注意:校准数据集的质量直接影响量化后的模型精度。我曾经偷懒只用50张图片校准,结果模型精度掉了5%。后来换成200张,精度损失控制在1%以内。所以,校准集别太少,也别太偏。

4.5 模型量化:FP32转INT8/INT16

TensorFlow Lite提供了两种量化方式:

  1. 训练后量化(Post-training Quantization):最简单,直接转换。
  2. 量化感知训练(Quantization-aware Training):精度更高,但需要重新训练。

对于STM32,我通常先用训练后量化。如果精度不够,再考虑量化感知训练。

来看代码:

import tensorflow as tf

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

# 定义转换器
converter = tf.lite.TFLiteConverter.from_keras_model(model)

# 设置量化方式:INT8
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = lambda: [
    [calibration_data[i:i+1]] for i in range(len(calibration_data))
]
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8

# 转换
tflite_int8_model = converter.convert()

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

print(f"INT8模型大小:{len(tflite_int8_model) / 1024:.2f} KB")

如果想转INT16,只需要改一行:

converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT16]

4.6 量化前后对比

我拿刚才的模型做了个对比,结果如下:

指标 FP32 INT8 INT16
模型大小 9.2 MB 2.4 MB 4.6 MB
测试集精度 82.3% 81.5% 82.1%
精度损失 - 0.8% 0.2%

看到没?INT8模型体积缩小了将近4倍,精度只掉了0.8%。对于大多数嵌入式应用,这个精度损失完全可以接受。

我的建议:如果STM32的Flash在1MB以上,优先用INT16,精度损失更小。如果Flash紧张,就用INT8,省空间。

4.7 量化知识体系

为了让你更直观地理解整个流程,我画了一张图:

模型训练与量化准备流程 训练FP32模型 Keras/TensorFlow 准备校准数据集 100-500张代表性样本 选择量化方式 INT8 / INT16 INT8量化 体积缩小4x INT16量化 精度损失更小 生成TFLite模型 .tflite 文件 部署到STM32 X-CUBE-AI / TensorFlow Lite Micro 关键决策点: • Flash > 1MB → 优先INT16(精度损失小) • Flash < 1MB → 用INT8(省空间) • 精度敏感型应用 → 考虑量化感知训练

这张图把整个流程串起来了。从训练FP32模型开始,到准备校准集,再到选择量化方式,最后生成TFLite模型部署到STM32。每一步都环环相扣。

4.8 避坑指南

最后,分享几个我踩过的坑:

  • 校准集别用训练集:用训练集做校准,量化后的模型在测试集上精度会虚高。我吃过这个亏。
  • INT8输入输出类型:记得设置inference_input_type和inference_output_type为tf.int8,否则默认还是float32,白量化了。
  • 量化后验证:一定要在PC上先用TFLite Runtime跑一遍,确认精度没问题再部署到STM32。否则调试起来很痛苦。

经验之谈:量化不是银弹。有些模型(比如含有大量BatchNorm的)量化后精度掉得厉害。遇到这种情况,试试量化感知训练,或者改用INT16。