第3章:模型基础与格式——TensorFlow/Keras、ONNX、TFLite

做嵌入式AI,说白了就是跟模型格式打交道。你训练好的模型再牛,到了STM32上不认,那就是白搭。这一章,咱们把几种主流模型格式掰开揉碎讲清楚。

我个人习惯把模型格式分成两类:训练格式和部署格式。TensorFlow/Keras是训练用的,ONNX是中间桥梁,TFLite才是真正给边缘设备吃的。你想想看,这三者的关系,就像原材料、半成品和成品。

核心观点:模型格式转换不是简单的文件后缀改名。每一步转换都可能引入精度损失或算子不兼容,必须理解底层逻辑。
模型格式转换流程 Keras模型 .h5 / SavedModel 转换 ONNX模型 .onnx 量化 TFLite模型 .tflite STM32部署 Cube.AI / X-CUBE-AI ⚠ 关键点 • Keras→ONNX:算子映射可能失败 • ONNX→TFLite:量化参数需校准数据集 • TFLite→STM32:内存对齐和算子支持检查

3.1 TensorFlow/Keras模型基础

Keras现在已经是TensorFlow的官方高级API了。我最早用Keras还是1.x时代,那时候它还是独立的库。现在你只要装TensorFlow,Keras就跟着来了。

一个典型的Keras模型,说白了就是层的堆叠。但要注意,嵌入式部署最怕的就是花里胡哨的层。我在项目中遇到过有人用Lambda层写自定义操作,结果转ONNX时直接报错,查了两天才发现是算子不兼容。

我的建议:做嵌入式AI,Keras模型尽量用标准层。Conv2D、Dense、ReLU、MaxPooling这些最稳。BatchNormalization可以用,但推理时要折叠进前一层。

保存Keras模型,我习惯用两种方式:

  • SavedModel格式(推荐):保存完整图结构和权重,跨版本兼容性好
  • .h5格式:单个文件,方便传输,但版本敏感
# 保存模型
model.save('my_model.keras')        # Keras v3推荐格式
model.save('my_model.h5')           # 旧版h5格式
tf.saved_model.save(model, 'saved_model_dir')  # SavedModel格式

# 加载模型
model = tf.keras.models.load_model('my_model.keras')

嗯,这里要注意:保存时用的TensorFlow版本,加载时最好一致。我曾经在2.10训练、2.15加载,结果报了一堆警告,虽然能跑但心里不踏实。

3.2 ONNX模型格式

ONNX是什么?说白了就是模型界的通用语言。你拿PyTorch训练,我用TensorFlow训练,大家都能转成ONNX,然后互相用。对于STM32部署来说,ONNX是连接训练框架和Cube.AI的桥梁。

我个人觉得ONNX最大的价值在于算子标准化。不同框架的Conv实现细节不一样,但ONNX定义了一套统一的算子集。只要你的模型算子都在ONNX支持列表里,转换就很顺畅。

框架 转ONNX工具 常见问题
TensorFlow/Keras tf2onnx 动态shape、控制流算子
PyTorch torch.onnx.export 动态轴、if条件分支
Scikit-learn skl2onnx 树模型转ONNX精度问题
避坑指南:我曾经把一个带Reshape层的Keras模型转ONNX,结果Reshape的shape参数是动态计算的,ONNX不支持这种动态操作。最后只能改模型结构,把Reshape换成Flatten+Dense。记住:ONNX喜欢静态、确定性的操作。

转换命令其实很简单:

# Keras转ONNX
python -m tf2onnx.convert \
  --saved-model saved_model_dir \
  --output model.onnx \
  --opset 13

# 验证ONNX模型
import onnx
model = onnx.load('model.onnx')
onnx.checker.check_model(model)
print(onnx.helper.printable_graph(model.graph))

opset版本我建议用13或以上。太老的opset不支持一些新算子,太新的可能工具链还没跟上。

3.3 TFLite模型格式

TFLite是Google专门为移动和嵌入式设备设计的格式。它比ONNX更轻量,而且支持硬件加速。对于STM32来说,TFLite是Cube.AI直接吃的格式之一。

你想想看,一个100MB的Keras模型,转成TFLite并做量化后,可能只有10MB甚至更小。这就是TFLite的魅力——为了部署,牺牲一点精度换体积和速度。

TFLite支持三种量化方式:

  1. 动态范围量化:权重转int8,激活保持float32。精度损失最小,但加速有限。
  2. 全整数量化:权重和激活都转int8。需要校准数据集,精度损失可控。
  3. float16量化:权重转float16,精度损失极小,但需要硬件支持。
# 转换TFLite模型
converter = tf.lite.TFLiteConverter.from_saved_model('saved_model_dir')

# 默认:float32
tflite_model = converter.convert()

# 动态范围量化
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model_quant = converter.convert()

# 全整数量化(需要代表性数据集)
def representative_dataset():
    for _ in range(100):
        data = np.random.randn(1, 224, 224, 3).astype(np.float32)
        yield [data]

converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
tflite_model_int8 = converter.convert()
关键参数:全整数量化时,representative_dataset必须提供。这个数据集不需要很大,100张左右就够,但要能代表真实数据的分布。我见过有人随便传随机噪声做校准,结果量化后模型直接废了。

3.4 模型导出与转换实战

这一节咱们走一遍完整的流程。从Keras模型开始,经过ONNX,再到TFLite,最后看看怎么验证转换后的模型没问题。

我习惯先画个流程图,心里有数再动手:

  1. 训练:Keras训练,保存SavedModel
  2. 转ONNX:tf2onnx转换,检查算子兼容性
  3. ONNX优化:onnx-simplifier简化图结构
  4. 转TFLite:直接转或通过ONNX转
  5. 验证:对比转换前后推理结果

验证这一步很多人会跳过,但我建议一定不能省。我曾经跳过验证直接部署,结果STM32上推理结果全是错的,查了两天才发现是量化参数没对齐。

# 验证转换精度
import numpy as np
import tensorflow as tf

# 加载原始模型
original_model = tf.keras.models.load_model('original_model.h5')

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

# 准备测试数据
test_input = np.random.randn(1, 32, 32, 3).astype(np.float32)

# 原始模型推理
original_output = original_model(test_input).numpy()

# TFLite推理
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
interpreter.set_tensor(input_details[0]['index'], test_input)
interpreter.invoke()
tflite_output = interpreter.get_tensor(output_details[0]['index'])

# 计算误差
mse = np.mean((original_output - tflite_output) ** 2)
print(f'MSE: {mse:.6f}')

# 检查最大误差
max_error = np.max(np.abs(original_output - tflite_output))
print(f'Max error: {max_error:.6f}')
经验值:全整数量化后,MSE通常在1e-3到1e-2之间。如果MSE超过0.1,说明量化出了问题。常见原因:校准数据集分布不对、模型中有敏感层(如softmax前的logits范围太大)。

最后说一句,模型格式转换不是终点。转完TFLite后,还要用Cube.AI的工具链做进一步优化,比如算子融合、内存重排。这些咱们后面章节会详细讲。

嗯,这一章的内容就到这里。记住:格式转换是基本功,但也是最容易出坑的地方。多验证、多对比,才能保证部署顺利。