4. 模型文件管理:.tflite、.onnx、.h5 等模型文件的存储与差异化管理
模型文件管理这件事,说实话,我早期做 AI 嵌入式项目时踩过不少坑。那时候项目一多,桌面上全是 model_final.h5、model_final_v2.h5、model_final_真的不改了.h5…… 你想想看,三个月后连自己都分不清哪个版本是真正部署到产线上的。
这一章,我就把模型文件管理的经验拆开揉碎了讲。咱们重点解决三个问题:不同格式到底有什么区别?怎么存才不乱?怎么在 STM32 项目里高效切换模型?
4.1 三大模型格式的本质差异
先看一张对比表,把 .h5、.tflite、.onnx 的核心差异说清楚。
| 格式 | 来源框架 | 主要用途 | STM32 直接支持? | 文件大小(典型) | 量化支持 |
|---|---|---|---|---|---|
| .h5 | Keras / TensorFlow 1.x | 训练保存、断点续训 | ❌ 需转换 | 大(浮点权重) | 无原生量化 |
| .tflite | TensorFlow Lite | 移动端 / 嵌入式推理 | ✅ 直接导入 | 小(可量化至 1/4) | INT8 / FP16 |
| .onnx | PyTorch / TF / ONNX | 框架间转换、中间交换 | ⚠️ 需转 .tflite | 中等 | 需额外工具 |
核心结论:.h5 是「训练态」,.onnx 是「交换态」,.tflite 是「部署态」。STM32Cube AI 只认 .tflite,其他格式必须走转换流程。
我个人习惯在项目里同时保留三种格式,但用不同的目录隔离。为什么?因为 .h5 用来回滚训练参数,.onnx 用来跨团队协作,.tflite 才是最终烧进芯片的东西。缺一个,后面都可能抓瞎。
4.2 模型文件的存储结构设计
我在上一个工业视觉项目中,吃过「模型文件乱放」的亏。当时产线固件升级,发现烧进去的模型是旧版本,排查了两天才找到原因——同事把新模型覆盖了旧文件,但文件名没改。
后来我定了一套目录规范,分享给你参考:
models/
├── training/ # 训练原始文件
│ ├── v1.0/
│ │ ├── model.h5
│ │ └── training_log.txt
│ ├── v1.1/
│ │ ├── model.h5
│ │ └── training_log.txt
│ └── latest/ # 软链接指向当前训练版本
├── onnx/ # 中间交换格式
│ ├── v1.0_model.onnx
│ ├── v1.1_model.onnx
│ └── latest.onnx
├── tflite/ # 部署文件
│ ├── v1.0/
│ │ ├── model_float32.tflite
│ │ ├── model_int8.tflite
│ │ └── model_float16.tflite
│ ├── v1.1/
│ │ ├── model_float32.tflite
│ │ ├── model_int8.tflite
│ │ └── model_float16.tflite
│ └── current/ # 当前产线使用的版本
│ └── model.tflite
└── manifest.json # 版本清单文件
我的小技巧:在 manifest.json 里记录每个版本的 SHA256 校验值、量化方式、输入输出张量形状。这样固件升级时可以先校验文件完整性,避免烧进去一个损坏的模型。
4.3 格式转换的实战流程
从 .h5 到 .tflite,看起来就一行代码,但里面门道不少。我直接给你一个我项目里用熟的转换脚本框架:
import tensorflow as tf
# 加载训练好的 .h5 模型
model = tf.keras.models.load_model('models/training/v1.1/model.h5')
# 转换为 float32 的 .tflite(基准版本)
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_fp32 = converter.convert()
with open('models/tflite/v1.1/model_float32.tflite', 'wb') as f:
f.write(tflite_fp32)
# 转换为 int8 量化版本(STM32 最常用)
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset # 校准数据集
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 = converter.convert()
with open('models/tflite/v1.1/model_int8.tflite', 'wb') as f:
f.write(tflite_int8)
注意:int8 量化必须提供 representative_dataset,否则转换出来的模型精度可能崩掉。我曾经有一次偷懒没给校准集,结果模型在 STM32 上推理结果全是 0,排查了整整一个下午。
至于 .onnx 转 .tflite,我一般走两条路:
- 路径 A:.onnx → onnx2tf → .tflite(适合 PyTorch 导出的模型)
- 路径 B:.onnx → onnx2keras → .h5 → .tflite(适合需要中间调试的场景)
我个人更倾向路径 A,因为少一步转换就少一次精度损失。但如果你需要对比量化前后的输出差异,路径 B 更灵活。
4.4 模型文件的差异化管理策略
项目迭代过程中,模型文件会越来越多。怎么管理这些差异?我总结了三板斧:
- 命名即文档:文件名里必须包含版本号、量化类型、输入尺寸。比如
v1.1_int8_96x96.tflite,一看就知道是什么。 - 差异对比自动化:每次生成新模型,自动跑一个脚本,对比新旧模型的输出差异。我写过一个小工具,输入两张 .tflite,输出每个输出张量的最大绝对误差。
- 版本回滚机制:在 STM32 的固件里预留两个模型分区。一个跑当前版本,一个跑上一个稳定版本。如果新模型推理异常,自动回退。
避坑指南:我曾经在固件里只存了一个模型分区,结果 OTA 升级到一半断电了,模型文件损坏,设备直接变砖。从那以后,我坚持双分区 + 校验机制。
4.5 模型文件在 STM32 项目中的集成
STM32Cube AI 导入 .tflite 后,会生成一个 network.c / network.h 的 C 代码文件。但这里有个容易被忽略的点:模型文件本身要不要烧进 Flash?
我的做法是:
- 小模型(< 256 KB):直接编译进固件,作为常量数组放在 Flash 里。访问速度快,不需要外部存储。
- 大模型(256 KB ~ 2 MB):放在外部 SPI Flash 或 QSPI Flash 中,固件里只存加载地址和长度。
- 超大模型(> 2 MB):考虑模型剪枝或改用更小的网络结构。STM32 的 Flash 资源有限,硬塞进去会影响其他功能。
下面这张图展示了模型文件从训练到部署的完整流转路径:
4.6 模型文件管理的自动化脚本
手动管理模型文件太容易出错了。我写了一个 Python 脚本,每次训练完自动执行以下操作:
- 读取
manifest.json,确定下一个版本号 - 转换 .h5 → .tflite(float32 + int8 两个版本)
- 计算每个文件的 SHA256 并写入清单
- 运行新旧模型对比,输出精度差异报告
- 如果精度下降超过 1%,自动告警并阻止部署
效率提升:这个脚本帮我节省了至少 70% 的模型管理时间。以前每次迭代要手动操作 10 分钟,现在一键搞定。你可以在 GitHub 上找到类似的开源工具,但建议根据自己项目的目录结构改一改。
嗯,模型文件管理这块,说白了就是「存得清楚、转得规范、回得安全」。做到这三点,你的 STM32 AI 项目就不会在模型文件上翻车。