3、模型转换基础:使用 TFLiteConverter 将 SavedModel 转换为 .tflite 格式
好,咱们进入实操环节。前面你训练好了模型,也导出了 SavedModel 格式。但边缘设备不认识那个大家伙,它只认 .tflite。今天我就带你走一遍这个转换流程。
3.1 为什么非要转成 .tflite?
你想想看,一个完整的 SavedModel 文件夹里,有变量文件、图谱定义、资产文件……乱七八糟一大堆。边缘设备哪有那么大的存储空间?
.tflite 文件说白了就是一个“瘦身版”的模型。它把所有的权重、计算图、甚至一些优化后的算子都打包进一个文件里。我见过一个 200MB 的 SavedModel,转成 .tflite 后只有 50MB。嗯,这就是差距。
核心优势:
- 文件体积小,适合嵌入式存储
- 加载速度快,不用解析复杂目录结构
- 支持硬件加速(比如 NPU、GPU)
- 跨平台,Android、iOS、MCU 都能用
3.2 准备工作:确认你的 SavedModel
在动手转换之前,先确认一下你的 SavedModel 是完好的。我在项目中遇到过好几次,模型训练完没保存好,结果转换时报一堆莫名其妙的错。
你可以用这个命令快速检查:
import tensorflow as tf
# 加载 SavedModel 看看
model = tf.saved_model.load('path/to/saved_model')
print(list(model.signatures.keys())) # 看看有哪些可用的签名
如果打印出了类似 ['serving_default'] 这样的内容,说明模型没问题。如果啥都没有,那你得回去重新导出。
我的小习惯:每次训练完模型,我都会先跑一遍这个检查脚本。别等到部署时才发现模型坏了,那可就尴尬了。
3.3 核心转换:一行代码搞定?
很多人以为转换就是一行代码的事。确实,最简单的用法就一行:
import tensorflow as tf
# 最简单的转换
converter = tf.lite.TFLiteConverter.from_saved_model('path/to/saved_model')
tflite_model = converter.convert()
# 保存到文件
with open('model.tflite', 'wb') as f:
f.write(tflite_model)
你看,就这么简单。但我要告诉你,实际项目中几乎没人这么用。为什么?因为这样转出来的模型精度可能会掉,而且体积也不够小。
3.4 进阶配置:量化是关键
量化,说白了就是把模型里的浮点数变成整数。比如把 3.14159 变成 3。你想想看,这样存储空间是不是省了很多?
我曾经在一个智能门锁项目里,模型原本 30MB,量化后只有 8MB。而且推理速度提升了将近 3 倍。代价呢?精度从 98.2% 降到了 97.8%。嗯,完全可以接受。
来看看带量化的完整转换流程:
import tensorflow as tf
# 创建转换器
converter = tf.lite.TFLiteConverter.from_saved_model('path/to/saved_model')
# 配置量化选项
converter.optimizations = [tf.lite.Optimize.DEFAULT]
# 设置代表数据集(用于校准量化参数)
def representative_dataset():
# 这里用你的验证集数据
for _ in range(100):
data = np.random.randn(1, 224, 224, 3).astype(np.float32)
yield [data]
converter.representative_dataset = representative_dataset
# 指定目标类型(全整数量化)
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
# 设置输入输出类型
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8
# 执行转换
tflite_model = converter.convert()
# 保存
with open('model_quantized.tflite', 'wb') as f:
f.write(tflite_model)
避坑指南:我曾经犯过一个错误——没有提供代表数据集就直接做全整数量化。结果模型推理结果全是错的。记住,representative_dataset 不是可选项,是必选项!
3.5 支持的算子:不是所有算子都能转
这里有个坑你要注意。TensorFlow 的算子有上千个,但 TFLite 支持的只有几百个。如果你模型里用了某些冷门算子,转换时会报错。
我整理了一个常用算子对照表:
| 算子类型 | TensorFlow 算子 | TFLite 支持 | 备注 |
|---|---|---|---|
| 卷积 | Conv2D | ✅ 支持 | 量化后性能更好 |
| 池化 | MaxPool2D, AvgPool2D | ✅ 支持 | 无特殊要求 |
| 激活 | ReLU, ReLU6, Sigmoid | ✅ 支持 | 量化友好 |
| 全连接 | Dense | ✅ 支持 | 注意输入维度 |
| 自定义 | tf.raw_ops.* | ❌ 不支持 | 需要自定义算子 |
如果你的模型用了不支持的算子,别慌。有两个解决办法:
- 改模型结构:把冷门算子换成 TFLite 支持的替代品
- 自定义算子:自己写一个 TFLite 算子,这个比较复杂,后面章节会讲
3.6 验证转换结果
转换完了,你得验证一下模型还能正常工作。我习惯写一个简单的测试脚本:
import numpy as np
import tensorflow as tf
# 加载 TFLite 模型
interpreter = tf.lite.Interpreter(model_path='model_quantized.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_input = np.random.randn(1, 224, 224, 3).astype(np.uint8)
# 推理
interpreter.set_tensor(input_details[0]['index'], test_input)
interpreter.invoke()
output = interpreter.get_tensor(output_details[0]['index'])
print(f"推理结果: {output}")
我的经验:验证时一定要用真实数据,别用随机数。我曾经用随机数测试通过了,结果部署到设备上发现完全不对。后来才发现是数据预处理方式不匹配。
3.7 常见问题与解决
转换过程中你可能会遇到这些问题:
- 错误:
Unsupported ops—— 模型里有不支持的算子,需要替换或自定义 - 错误:
Quantization not supported—— 某些层不支持量化,可以尝试只做动态范围量化 - 警告:
Constant folding failed—— 某些常量无法折叠,一般不影响使用 - 警告:
Shape mismatch—— 输入输出形状不匹配,检查你的数据预处理
嗯,今天就先讲到这里。下一章我会带你看看怎么在树莓派上跑这个 .tflite 模型。到时候你会发现,前面这些准备工作做得越扎实,后面部署就越顺利。