3. 模型准备与转换:从Keras/TensorFlow到.tflite

好,咱们进入第三章。这一章要解决一个核心问题:你电脑上训练好的模型,怎么才能塞进STM32里?

说白了,PC上的TensorFlow模型是“大鱼大肉”,MCU只能“吃糠咽菜”。你得把模型压缩、量化、转换,变成MCU能消化的格式——.tflite。这个过程,我习惯叫它“模型瘦身”。

3.1 为什么不能直接部署Keras模型?

你想想看,一个典型的Keras模型(.h5),参数都是32位浮点数。STM32的Flash通常就几百KB到2MB,RAM更是捉襟见肘。直接塞进去,Flash爆了不说,推理速度也慢得没法用。

所以,我们需要做三件事:

  • 格式转换:从Keras的.h5或TensorFlow的.pb,转成TensorFlow Lite的.tflite。
  • 量化:把32位浮点数,变成8位整数。模型体积直接缩到1/4。
  • 算子映射:确保模型里的操作(Conv2D、Dense等),X-CUBE-AI的运行时库都能支持。

核心原则:MCU部署不是“跑模型”,而是“跑量化后的模型”。精度和体积的平衡,是你要一直盯着的。

3.2 模型转换三步走

我个人习惯把转换流程拆成三步,每一步都有坑,咱们一个个说。

3.2.1 第一步:导出Keras模型

假设你已经在Keras里训练好了一个模型。保存的时候,我建议用SavedModel格式,而不是老旧的.h5。为什么?因为TensorFlow Lite Converter对SavedModel的支持更完整。

# 保存为SavedModel格式
model.save('my_model', save_format='tf')

嗯,这里要注意:如果你的模型里有自定义层(Custom Layer),转换前必须先注册。否则转换器会直接报错。我在项目里遇到过,一个自定义的归一化层,忘了注册,排查了半天。

3.2.2 第二步:TensorFlow Lite Converter转换

这是核心步骤。用TFLiteConverter把模型转成.tflite。关键参数是量化方式。

import tensorflow as tf

# 加载SavedModel
converter = tf.lite.TFLiteConverter.from_saved_model('my_model')

# 设置量化:默认的float16量化
converter.optimizations = [tf.lite.Optimize.DEFAULT]

# 转换为TFLite模型
tflite_model = converter.convert()

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

这段代码用的是float16量化。模型体积减半,精度损失极小。但STM32的硬件加速器通常只支持int8,所以咱们最终目标还是int8量化。

我的经验:先做float16量化,验证模型功能正常。再切到int8量化,对比精度。一步到位int8,万一精度掉得厉害,你都不知道是量化的问题,还是模型本身的问题。

3.2.3 第三步:int8量化与校准

int8量化需要校准数据集。转换器会跑一遍校准数据,统计每层激活值的动态范围,然后映射到0-255。

# int8量化需要代表性数据集
def representative_dataset():
    for _ in range(100):
        # 从验证集中取一个batch,shape要与模型输入一致
        data = np.random.randn(1, 32, 32, 3).astype(np.float32)
        yield [data]

converter = tf.lite.TFLiteConverter.from_saved_model('my_model')
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
# 强制所有操作转为int8
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()

校准数据集很关键。我建议从训练集的验证集里随机抽100-500个样本。样本太少,量化后的模型精度可能崩;样本太多,转换时间太长,也没必要。

避坑指南:我曾经用全0数据做校准,结果模型推理结果全是0。校准数据必须覆盖真实输入的分布。哪怕用随机噪声,也比全0强。

3.3 模型验证:别急着部署

转换完,别急着往STM32里烧。先在PC上验证一下,确保转换后的模型输出和原始模型一致。

我一般用TensorFlow Lite Interpreter做推理对比。

import numpy as np
import tensorflow as tf

# 加载TFLite模型
interpreter = tf.lite.Interpreter(model_path='model_int8.tflite')
interpreter.allocate_tensors()

# 获取输入输出细节
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

# 准备输入数据(注意:int8模型输入也是int8)
input_data = np.random.randn(1, 32, 32, 3).astype(np.int8)
interpreter.set_tensor(input_details[0]['index'], input_data)

# 推理
interpreter.invoke()

# 获取输出
output_data = interpreter.get_tensor(output_details[0]['index'])
print('TFLite output:', output_data)

对比原始Keras模型的输出,看误差有多大。一般来说,int8量化后的输出误差在5%以内,都是可以接受的。如果误差超过10%,你得检查校准数据集,或者考虑用float16量化。

3.4 X-CUBE-AI的模型验证

PC验证通过后,再用X-CUBE-AI做一次验证。X-CUBE-AI会解析.tflite模型,生成C代码,并提供一个验证工具。

在STM32CubeMX里,配置好X-CUBE-AI,导入.tflite文件。它会自动分析:

  • 模型结构:各层类型、参数数量
  • 内存占用:Flash和RAM的预估
  • 推理时间:基于MCU主频的估算
  • 算子支持:检查是否有不支持的算子

我个人习惯,在CubeMX里先做一次“Analyze”,看看有没有红色警告。如果有不支持的算子,就得回PC端修改模型,或者用TensorFlow Lite的“Select TF ops”选项,把不支持的操作回退到TensorFlow内核执行。但这样会增大运行时库的体积,慎用。

3.5 本章知识结构图

下面这张图,概括了从Keras模型到STM32部署的完整流程。你可以把它当作一个检查清单。

模型转换与部署流程 模型转换与部署流程 Keras模型 (.h5/.pb) TFLite Converter .tflite 模型 量化方式选择 float16 量化 int8 量化 动态范围量化 PC端验证:TFLite Interpreter 推理对比 X-CUBE-AI 分析:内存、算子、推理时间 STM32 部署:生成C代码,烧录运行 提示:每一步验证通过后再进入下一步,避免问题累积

3.6 常见问题与排查

问题 可能原因 解决方案
转换时报错“Unsupported ops” 模型使用了TFLite不支持的算子 替换为支持的算子,或启用“Select TF ops”
int8量化后精度暴跌 校准数据集不具代表性 增加校准样本数量,覆盖真实分布
X-CUBE-AI分析时Flash超限 模型太大,或量化不到位 检查是否用了int8量化,考虑剪枝或蒸馏
推理结果全是0或固定值 输入数据未正确量化 检查输入数据的dtype和scale/zero_point

我的习惯:每次转换后,都保留一份转换日志。包括量化方式、校准样本数、精度对比结果。这样出了问题,回溯起来很快。

好了,模型转换和验证就这些。记住,这一步做得越扎实,后面在STM32上调试就越省心。别急着烧录,先在PC上把模型“盘”明白了。