4、模型转换入门:从 Keras 模型导出 .tflite、量化选项(float32 vs int8)、转换脚本编写
好,咱们进入正题。模型转换,说白了就是把你在 PC 上训练好的模型,变成能在嵌入式设备上跑的东西。这一步要是没做好,后面全白搭。我见过不少新手,在 Keras 上跑得飞起,一转到单片机就崩了——嗯,大概率是转换这步出了问题。
4.1 为什么需要模型转换?
你想想看,TensorFlow 在 PC 上用的是 float32 精度,模型文件动不动几十上百 MB。但嵌入式设备呢?RAM 可能就几百 KB,Flash 也就几 MB。直接拿原始模型去跑,根本塞不进去。
所以我们需要一个「瘦身」过程:
- 格式转换:从 Keras 的 .h5 或 SavedModel 变成 .tflite
- 精度压缩:从 float32 降到 int8,体积直接缩到 1/4
- 算子优化:去掉一些 PC 上才有、嵌入式用不到的算子
我个人习惯把转换脚本放在项目根目录的 tools/ 文件夹下,命名成 convert.py。这样每次改模型,跑一遍脚本就行,不会手忙脚乱。
4.2 基础转换:从 Keras 到 .tflite(float32)
先来个最简单的。假设你有一个训练好的 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()
# 保存 .tflite 文件
with open('my_model_float32.tflite', 'wb') as f:
f.write(tflite_model)
print("转换完成!输出文件:my_model_float32.tflite")
就这么几行。跑完之后,你会得到一个 float32 精度的 .tflite 文件。体积和原来的 .h5 差不多,但格式已经变了,可以在 TFLite 的运行时里加载。
from_keras_model 换成 from_saved_model 就行。我个人更推荐用 SavedModel,因为它包含了模型的结构和权重,不容易出兼容性问题。
4.3 量化:从 float32 到 int8
好,重点来了。float32 模型在嵌入式上跑,说实话有点奢侈。一个 float32 数占 4 字节,int8 只占 1 字节。你想想看,一个 100MB 的模型,量化后直接变成 25MB。对于 Flash 只有 2MB 的 MCU 来说,这差距就是「能用」和「不能用」的区别。
量化的核心思想很简单:用更少的比特数来表示权重和激活值。但代价是精度会掉一点。我在项目中遇到过,有些模型量化后精度只掉了 0.5%,有些直接掉了 5%。所以量化后一定要做验证。
4.3.1 后训练量化(Post-Training Quantization)
这是最常用的方式。你不需要重新训练模型,直接在转换时加上量化参数就行。
import tensorflow as tf
model = tf.keras.models.load_model('my_model.h5')
# 创建转换器
converter = tf.lite.TFLiteConverter.from_keras_model(model)
# 开启 int8 量化
converter.optimizations = [tf.lite.Optimize.DEFAULT]
# 指定量化类型为 int8
converter.target_spec.supported_types = [tf.int8]
# 设置代表数据集(用于校准)
def representative_dataset():
# 假设你有 100 张图片用于校准
for _ in range(100):
# 这里需要从你的数据集中取样本
data = np.random.randn(1, 224, 224, 3).astype(np.float32)
yield [data]
converter.representative_dataset = representative_dataset
# 执行转换
tflite_quant_model = converter.convert()
# 保存
with open('my_model_int8.tflite', 'wb') as f:
f.write(tflite_quant_model)
4.3.2 float32 vs int8:怎么选?
我整理了一个对比表,方便你决策:
| 对比项 | float32 | int8 |
|---|---|---|
| 模型体积 | 大(基准) | 缩小约 4 倍 |
| 推理速度 | 慢(无硬件加速时) | 快(支持硬件加速) |
| 精度 | 高(基准) | 略有下降(通常 < 1%) |
| 硬件要求 | 需要 FPU | 普通 MCU 即可 |
| 适用场景 | 原型验证、高精度需求 | 量产部署、资源受限设备 |
我的建议是:先跑 float32 版本验证功能,确认无误后再量化成 int8。如果 int8 精度掉得太多,可以考虑用混合量化(部分层用 float32,部分层用 int8),或者干脆用 float16 量化——体积减半,精度几乎不变。
4.4 转换脚本编写:一个完整的模板
嗯,这里我直接给你一个我常用的脚本模板。你拿去改改就能用:
import tensorflow as tf
import numpy as np
import argparse
import os
def convert_model(model_path, output_path, quantize_type='float32'):
"""
模型转换函数
:param model_path: Keras 模型路径 (.h5 或 SavedModel)
:param output_path: 输出 .tflite 文件路径
:param quantize_type: 量化类型,可选 'float32', 'float16', 'int8'
"""
# 加载模型
print(f"正在加载模型:{model_path}")
model = tf.keras.models.load_model(model_path)
# 创建转换器
converter = tf.lite.TFLiteConverter.from_keras_model(model)
# 根据量化类型设置参数
if quantize_type == 'float32':
# 默认就是 float32,不需要额外设置
pass
elif quantize_type == 'float16':
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
elif quantize_type == 'int8':
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.int8]
# 代表数据集(这里需要你根据实际情况修改)
def representative_dataset():
# 从验证集中取 100 张图片
for i in range(100):
# 请替换为你的数据加载逻辑
data = np.random.randn(1, 224, 224, 3).astype(np.float32)
yield [data]
converter.representative_dataset = representative_dataset
else:
raise ValueError(f"不支持的量化类型:{quantize_type}")
# 执行转换
print(f"正在转换为 {quantize_type}...")
tflite_model = converter.convert()
# 保存文件
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, 'wb') as f:
f.write(tflite_model)
# 打印文件大小
size_mb = len(tflite_model) / (1024 * 1024)
print(f"转换完成!输出文件:{output_path}")
print(f"文件大小:{size_mb:.2f} MB")
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Keras 模型转 TFLite')
parser.add_argument('--model', type=str, required=True, help='输入模型路径')
parser.add_argument('--output', type=str, default='model.tflite', help='输出文件路径')
parser.add_argument('--quantize', type=str, default='float32',
choices=['float32', 'float16', 'int8'],
help='量化类型')
args = parser.parse_args()
convert_model(args.model, args.output, args.quantize)
用法示例:
# 转 float32
python convert.py --model my_model.h5 --output models/float32.tflite
# 转 int8
python convert.py --model my_model.h5 --output models/int8.tflite --quantize int8
4.5 验证转换后的模型
转换完别急着部署。先跑一下推理,看看结果对不对:
import tensorflow as tf
import numpy as np
# 加载 TFLite 模型
interpreter = tf.lite.Interpreter(model_path='my_model_int8.tflite')
interpreter.allocate_tensors()
# 获取输入输出信息
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
print(f"输入形状:{input_details[0]['shape']}")
print(f"输入类型:{input_details[0]['dtype']}")
print(f"输出形状:{output_details[0]['shape']}")
print(f"输出类型:{output_details[0]['dtype']}")
# 准备测试数据
test_data = np.random.randn(1, 224, 224, 3).astype(np.float32)
# 如果是 int8 模型,需要做量化
if input_details[0]['dtype'] == np.int8:
input_scale = input_details[0]['quantization'][0]
input_zero_point = input_details[0]['quantization'][1]
test_data = test_data / input_scale + input_zero_point
test_data = test_data.astype(np.int8)
# 推理
interpreter.set_tensor(input_details[0]['index'], test_data)
interpreter.invoke()
output = interpreter.get_tensor(output_details[0]['index'])
print(f"推理结果形状:{output.shape}")
print(f"推理结果前 5 个值:{output.flatten()[:5]}")
这一步能帮你快速发现转换中的问题。比如输入输出形状不对、量化参数没对齐等等。
4.6 小结
模型转换这件事,说难不难,说简单也不简单。核心就三点:
- 格式要转:从 Keras 到 TFLite,这是必经之路
- 量化要选对:float32 保精度,int8 保体积,根据你的硬件资源来定
- 验证不能省:转换完一定要跑推理,确认结果正确
下一章我们会讲怎么在嵌入式设备上部署这个 .tflite 文件,包括内存管理、算子注册、性能调优这些实战内容。到时候你会看到,转换只是第一步,真正的坑还在后面等着呢。