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部署的完整流程。你可以把它当作一个检查清单。
3.6 常见问题与排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 转换时报错“Unsupported ops” | 模型使用了TFLite不支持的算子 | 替换为支持的算子,或启用“Select TF ops” |
| int8量化后精度暴跌 | 校准数据集不具代表性 | 增加校准样本数量,覆盖真实分布 |
| X-CUBE-AI分析时Flash超限 | 模型太大,或量化不到位 | 检查是否用了int8量化,考虑剪枝或蒸馏 |
| 推理结果全是0或固定值 | 输入数据未正确量化 | 检查输入数据的dtype和scale/zero_point |
我的习惯:每次转换后,都保留一份转换日志。包括量化方式、校准样本数、精度对比结果。这样出了问题,回溯起来很快。
好了,模型转换和验证就这些。记住,这一步做得越扎实,后面在STM32上调试就越省心。别急着烧录,先在PC上把模型“盘”明白了。