2. 模型转换与量化基础

任务目标

本案例完成一个最小可复现的模型转换闭环:训练一个简单的Keras分类模型 → 转换为TFLite格式 → 应用动态范围量化 → 生成适用于STM32工程的C数组文件 → 验证转换前后模型输出一致性。

环境依赖

  • Python 3.8+,TensorFlow 2.12+(含Keras)
  • NumPy
  • 无需额外硬件,纯PC环境即可完成

完整实现代码

import numpy as np
import tensorflow as tf
from tensorflow import keras

# 1. 构造并训练一个极简Keras模型(用于演示转换流程)
model = keras.Sequential([
    keras.layers.Dense(8, activation='relu', input_shape=(4,)),
    keras.layers.Dense(3, activation='softmax')
])
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

# 生成固定随机种子的小型训练数据(保证可复现)
np.random.seed(42)
x_train = np.random.rand(200, 4).astype(np.float32)
y_train = keras.utils.to_categorical(np.random.randint(0, 3, 200), 3)
model.fit(x_train, y_train, epochs=3, verbose=0)

# 2. 转换为TFLite格式(float32基线)
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()

# 3. 应用动态范围量化(权重从float32量化为int8,激活仍为float32)
converter_quant = tf.lite.TFLiteConverter.from_keras_model(model)
converter_quant.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter_quant.convert()

# 4. 生成C数组文件(供STM32工程直接包含)
def generate_c_array(model_data, filename, array_name):
    hex_str = ', '.join(f'0x{b:02x}' for b in model_data)
    with open(filename, 'w') as f:
        f.write(f'const unsigned char {array_name}[] = {{\n{hex_str}\n}};\n')
        f.write(f'const unsigned int {array_name}_len = {len(model_data)};\n')

generate_c_array(tflite_model, 'model_float32.h', 'g_model_float32')
generate_c_array(tflite_quant_model, 'model_quant.h', 'g_model_quant')

# 5. 验证:比较原始Keras模型与两个TFLite模型的输出
def run_tflite_inference(interpreter, input_data):
    input_details = interpreter.get_input_details()
    output_details = interpreter.get_output_details()
    interpreter.set_tensor(input_details[0]['index'], input_data)
    interpreter.invoke()
    return interpreter.get_tensor(output_details[0]['index'])

# 固定测试输入
test_input = np.array([[0.1, 0.5, 0.3, 0.9]], dtype=np.float32)

# Keras模型输出
keras_output = model.predict(test_input, verbose=0)

# Float32 TFLite输出
interp_f32 = tf.lite.Interpreter(model_content=tflite_model)
interp_f32.allocate_tensors()
tflite_f32_output = run_tflite_inference(interp_f32, test_input)

# 量化TFLite输出
interp_quant = tf.lite.Interpreter(model_content=tflite_quant_model)
interp_quant.allocate_tensors()
tflite_quant_output = run_tflite_inference(interp_quant, test_input)

# 打印对比结果
print("Keras输出:     ", keras_output[0])
print("TFLite f32输出:", tflite_f32_output[0])
print("TFLite量化输出:", tflite_quant_output[0])
print("f32最大误差:", np.max(np.abs(keras_output[0] - tflite_f32_output[0])))
print("量化最大误差:", np.max(np.abs(keras_output[0] - tflite_quant_output[0])))

运行方式

  1. 将上述代码保存为 convert_tflite.py
  2. 执行 python convert_tflite.py
  3. 运行结束后,当前目录生成 model_float32.h 和 model_quant.h 两个C头文件

验证方法与通过标准

  • 文件生成验证:确认两个.h文件存在且非空,文件末尾包含对应的 _len 变量定义。
  • 输出一致性验证:程序打印的 f32最大误差 应接近0(通常<1e-6),证明转换过程无损;量化最大误差 通常<0.05,证明动态范围量化在可接受精度损失范围内。
  • C数组可用性验证:在STM32工程中,通过 #include "model_quant.h" 即可引用 g_model_quant 数组,配合TensorFlow Lite Micro解释器加载该模型。

关键说明

  • 动态范围量化仅将权重从float32压缩为int8,激活值在推理时动态量化,因此无需校准数据集,适合快速部署。
  • 若需进一步压缩为全整型量化(int8激活),需提供代表性数据集进行校准,本案例不涉及。
  • 生成的C数组可直接用于STM32的TensorFlow Lite Micro框架,加载方式为 tflite::GetModel(g_model_quant)。