3. 模型格式与转换基础:从训练到部署的桥梁

做嵌入式AI部署这几年,我最大的感触就是:模型格式转换往往是整个链路里最容易被低估的环节。很多人觉得训练完模型,导出个文件就完事了。但实际踩过坑才知道——格式不对、量化参数没调好,板子上跑出来的结果跟训练时完全两码事。

这一章,我们就来把几个核心模型格式掰开揉碎讲清楚。TensorFlow Lite、Keras、ONNX,还有模型量化原理。这些都是你后续用STM32Cube.AI工具链时绕不开的基础。

3.1 三种主流模型格式:各自扮演什么角色?

先看一张总览图,帮你快速建立整体认知。

模型格式与转换核心流程 训练框架 Keras / PyTorch / TF 中间交换格式 ONNX 部署格式 TFLite / C模型 导出 转换 量化路径:FP32 → INT8 / FP16 权重 + 激活值量化,减少模型体积,提升推理速度 Keras (.h5) 训练阶段主力 • 保存完整网络结构 • 包含权重 + 优化器状态 • 适合继续训练/微调 • 体积大,不适合部署 • 依赖TensorFlow环境 ONNX 模型交换标准 • 框架无关的中间格式 • 支持PyTorch→ONNX→TFLite • 算子覆盖率高 • 可做图优化 • 调试相对复杂 TFLite (.tflite) 嵌入式部署首选 • 专为移动/嵌入式设计 • 支持量化(INT8/FP16) • 体积小,推理快 • STM32Cube.AI直接支持 • 算子受限,需验证兼容性

3.2 Keras模型格式:训练阶段的“母版”

Keras的.h5格式,说白了就是训练阶段的完整快照。它不光存了网络结构和权重,连优化器的状态、当前学习率、训练轮次这些信息都一并打包了。

关键点:Keras模型是后续所有转换的起点。我建议你始终保留一份原始的Keras模型文件,不要只保存转换后的TFLite。为什么?因为一旦转换过程中出现问题,你需要回到原始模型重新调试。

保存一个Keras模型,代码很简单:

# 保存完整模型(结构+权重+优化器状态)
model.save('my_model.h5')

# 或者只保存权重(更轻量,适合版本管理)
model.save_weights('my_model_weights.h5')

我个人习惯在项目里同时保留这两种文件。完整模型用于继续训练或微调,权重文件用于快速加载到相同结构的网络中做推理验证。

3.3 TensorFlow Lite格式:嵌入式部署的“通行证”

TFLite是Google专门为移动和嵌入式设备设计的格式。它跟Keras模型最大的区别在于:TFLite做了大量优化,比如算子融合、常量折叠、量化等。这些优化让模型在资源受限的MCU上也能跑起来。

从Keras转TFLite,标准流程是这样的:

import tensorflow as tf

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

# 转换为TFLite
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()

# 保存为.tflite文件
with open('model.tflite', 'wb') as f:
    f.write(tflite_model)

嗯,这里要注意:默认转换出来的是FP32模型,体积跟Keras模型差不多。真正让TFLite发挥威力的是量化——我们后面会详细讲。

避坑指南:我曾经遇到过一个项目,模型在Keras上精度99%,转成TFLite后直接掉到85%。排查了半天,发现是模型里用了一个自定义层,TFLite转换器不认识。解决方案是在转换前把自定义层替换成标准算子,或者注册自定义算子。所以,设计网络时尽量用标准算子,能省掉很多麻烦。

3.4 ONNX格式:跨框架的“通用语言”

ONNX(Open Neural Network Exchange)是微软和Facebook联合推出的模型交换格式。它的定位很明确:让模型能在不同框架之间自由迁移。

举个例子,你在PyTorch里训练了一个模型,但STM32Cube.AI对PyTorch的原生支持有限。这时候ONNX就是桥梁:

# PyTorch → ONNX
import torch
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy_input, "model.onnx")

# ONNX → TensorFlow(通过onnx-tf)
import onnx
from onnx_tf.backend import prepare
onnx_model = onnx.load("model.onnx")
tf_rep = prepare(onnx_model)
tf_rep.export_graph("model_tf")

说实话,ONNX这条路我走过不少弯路。最大的问题是算子兼容性——有些PyTorch的算子ONNX不支持,或者转换后行为不一致。我的建议是:先用ONNX的验证工具检查一下,确认没问题再往下走。

警告:ONNX转换不是万能的。如果模型里有动态控制流(比如循环次数取决于输入数据),ONNX可能无法正确导出。这种情况下,我建议你直接在目标框架(TensorFlow/Keras)里重写模型。

3.5 模型量化原理:把精度换成速度

量化,说白了就是用更少的比特数来表示模型的权重和激活值。标准的深度学习模型用32位浮点数(FP32),量化后可以变成16位浮点(FP16)或者8位整数(INT8)。

为什么这么做?你看MCU的资源就知道了:

数据类型 内存占用 推理速度(相对) 精度损失
FP32 4字节/参数 1x(基准) 无
FP16 2字节/参数 约1.5-2x 极小(通常<0.5%)
INT8 1字节/参数 约3-4x 较小(通常<2%)

量化的核心原理是映射。把FP32的数值范围映射到INT8的0-255(或者-128到127)。这个映射需要两个参数:缩放因子(scale)和零点(zero_point)。

# 量化公式(对称量化)
int8_value = round(float32_value / scale)

# 反量化公式
float32_value = int8_value * scale

量化有两种常见方式:

  • 训练后量化(Post-Training Quantization, PTQ):模型训练完后,用一小部分校准数据统计激活值的范围,然后做量化。优点是简单快速,缺点是精度损失可能稍大。
  • 量化感知训练(Quantization-Aware Training, QAT):在训练过程中模拟量化效果,让模型学会适应量化误差。精度通常比PTQ高,但需要重新训练。

我的经验:对于大多数嵌入式视觉模型,PTQ就够用了。我做过一个分类模型,PTQ后精度只掉了0.3%,但模型体积从10MB降到了2.5MB,推理速度提升了3倍。只有在精度要求极高(比如医疗影像)的场景下,我才会考虑QAT。

3.6 在STM32Cube.AI中做量化

STM32Cube.AI对量化的支持很完善。你可以在工具链中直接指定量化方式:

# 使用STM32Cube.AI的Python API做量化
import stm32ai

# 配置量化参数
quant_config = {
    'quantization': 'int8',        # 目标量化类型
    'calibration_dataset': 'calib_data.npy',  # 校准数据集
    'calibration_samples': 200     # 校准样本数
}

# 转换并量化模型
stm32ai.convert('model.tflite', output_dir='./stm32_model', **quant_config)

我个人习惯在量化前先做一次精度基准测试——用原始FP32模型跑一遍验证集,记录准确率。量化后再跑一遍,对比差异。如果精度下降超过1%,我会考虑换用QAT或者调整校准数据集。

小技巧:校准数据集不需要很大,200-500张代表性样本通常就够了。但一定要覆盖模型可能遇到的各种场景——我见过有人只用晴天图片做校准,结果模型在阴天场景下精度暴跌。校准集的多样性比数量更重要。

3.7 本章小结

模型格式转换是嵌入式AI部署的“第一道坎”。记住几个要点:

  • Keras是训练阶段的母版,务必保留原始文件
  • TFLite是嵌入式部署的首选格式,STM32Cube.AI原生支持
  • ONNX是跨框架的桥梁,但要注意算子兼容性
  • 量化是提升MCU推理效率的关键,PTQ适合大多数场景