3. 模型格式与转换基础:从训练到部署的桥梁
做嵌入式AI部署这几年,我最大的感触就是:模型格式转换往往是整个链路里最容易被低估的环节。很多人觉得训练完模型,导出个文件就完事了。但实际踩过坑才知道——格式不对、量化参数没调好,板子上跑出来的结果跟训练时完全两码事。
这一章,我们就来把几个核心模型格式掰开揉碎讲清楚。TensorFlow Lite、Keras、ONNX,还有模型量化原理。这些都是你后续用STM32Cube.AI工具链时绕不开的基础。
3.1 三种主流模型格式:各自扮演什么角色?
先看一张总览图,帮你快速建立整体认知。
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适合大多数场景