2. 模型转换基础:从 TensorFlow/Keras 到 .tflite 再到 STM32

好,咱们直接进入正题。模型转换这件事,说白了就是把一个「大胖子」模型,压缩成一个能在 STM32 上跑的「小瘦子」。我刚开始接触这块时,也踩过不少坑。今天就把我这几年的经验,一次性说清楚。

2.1 为什么要转换?直接跑 Keras 模型不行吗?

你想想看,STM32 的 Flash 通常就几百 KB 到 2MB。一个标准的 Keras 模型,光权重文件就可能几十 MB。这还不算,Keras 模型依赖 Python 运行时,而 STM32 跑的是 C 代码。所以,转换是必须的。

我个人习惯把转换过程分成三步:

  1. 训练阶段:用 TensorFlow/Keras 训练出高精度模型
  2. 压缩阶段:转换成 .tflite 格式,顺便做量化
  3. 部署阶段:用 STM32CubeAI 生成 C 代码

核心要点:.tflite 是 Google 为边缘设备设计的中间格式。它不依赖 Python,体积小,支持硬件加速。

2.2 从 Keras 到 .tflite:最简单的转换

假设你有一个训练好的 Keras 模型,叫 my_model.h5。转换代码其实就几行:

import tensorflow as tf

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

# 转换器
converter = tf.lite.TFLiteConverter.from_keras_model(model)

# 默认转换(浮点型)
tflite_model = converter.convert()

# 保存
with open('model_float.tflite', 'wb') as f:
    f.write(tflite_model)

嗯,这里要注意。上面这个转换出来的 .tflite 还是浮点型的。体积虽然比 Keras 小一些,但对 STM32 来说还是太大。我在项目中遇到过,一个 10MB 的 Keras 模型,转成浮点 .tflite 后还有 8MB。STM32 根本放不下。

2.3 量化:让模型真正能跑在 STM32 上

量化,就是把模型的权重和激活值从 32 位浮点数,变成 8 位整数。效果怎么样?

指标 浮点模型 量化模型(int8)
模型体积 10 MB 2.5 MB
推理速度 慢(无硬件加速) 快 3-5 倍
精度损失 基准 通常 < 1%
STM32 兼容性 部分支持 完全支持

看到了吧?体积直接缩到 1/4,速度还快了好几倍。精度损失?我做过十几个项目,大部分场景下损失不到 0.5%。

量化有两种方式:

2.3.1 训练后量化(Post-training Quantization)

这是最常用的方法。模型训练完后,直接转换时做量化。不需要重新训练。

converter = tf.lite.TFLiteConverter.from_keras_model(model)

# 开启 int8 量化
converter.optimizations = [tf.lite.Optimize.DEFAULT]

# 提供代表性数据集(用于校准)
def representative_dataset():
    for _ in range(100):
        data = np.random.randn(1, 32, 32, 3).astype(np.float32)
        yield [data]

converter.representative_dataset = representative_dataset

# 目标类型设为 int8
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]

tflite_quant_model = converter.convert()

我的经验:代表性数据集很重要。我建议用 100-200 张真实场景的图片做校准。如果随便用随机数,量化后的精度可能会掉 2-3%。

2.3.2 量化感知训练(Quantization Aware Training)

如果精度要求极高,可以用这个方法。它在训练过程中就模拟量化效果,让模型学会适应低精度。

import tensorflow_model_optimization as tfmot

# 在模型上应用量化感知训练
quantize_model = tfmot.quantization.keras.quantize_model

# 假设 model 是你的原始模型
q_aware_model = quantize_model(model)

# 重新编译和训练
q_aware_model.compile(optimizer='adam', loss='sparse_categorical_crossentropy')
q_aware_model.fit(x_train, y_train, epochs=5)

# 转换时不需要再量化
converter = tf.lite.TFLiteConverter.from_keras_model(q_aware_model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()

避坑指南:我曾经在一个语音识别项目里,直接用了训练后量化,精度掉了 3%。后来改用量化感知训练,只掉了 0.8%。如果你的模型对精度特别敏感,建议直接用 QAT。

2.4 验证转换后的模型

转换完了,别急着往 STM32 上烧。先验证一下精度。

import numpy as np

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

# 获取输入输出信息
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

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

# 推理
interpreter.set_tensor(input_details[0]['index'], test_data)
interpreter.invoke()
output = interpreter.get_tensor(output_details[0]['index'])

print('推理结果:', output)

这一步我建议用 1000 张测试集图片,对比浮点模型和量化模型的输出。如果准确率差异在 1% 以内,就可以放心部署了。

2.5 从 .tflite 到 STM32:CubeAI 的导入

打开 STM32CubeMX,选择你的芯片型号。在「Software Packs」里找到 X-CUBE-AI,点击「Add」。然后:

  1. 选择「Network」标签页
  2. 点击「Add network」
  3. 选择你刚生成的 .tflite 文件
  4. CubeAI 会自动分析模型,显示 RAM/Flash 占用
  5. 点击「Analyze」查看详细报告

关键检查点:看 CubeAI 报告里的「RAM estimation」和「Flash estimation」。如果超出芯片规格,要么换更大的芯片,要么进一步压缩模型。

生成代码后,你会得到一个 network.hnetwork.c。调用推理函数很简单:

#include "network.h"

// 输入输出缓冲区
float input_data[32*32*3];
float output_data[10];

// 初始化
AI_Network_Init();

// 推理
AI_Network_Run(input_data, output_data);

// output_data 就是分类结果

2.6 常见问题与解决方案

  • 模型太大:试试更激进的量化,或者减少网络层数。我有个项目从 8 层减到 5 层,精度只掉了 1%,体积减了 40%。
  • 推理太慢:检查是否开启了硬件加速。STM32 的 Cortex-M4/M7 有 DSP 指令,CubeAI 默认会启用。
  • 精度异常:先确认浮点模型没问题,再检查量化校准数据集是否合理。

好了,模型转换这块的核心内容就这些。下一章我们会深入 CubeAI 的配置细节,包括如何调整优化选项、如何分析性能报告。到时候我会分享一个实际项目的完整流程,保证让你少走弯路。