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.* ❌ 不支持 需要自定义算子

如果你的模型用了不支持的算子,别慌。有两个解决办法:

  1. 改模型结构:把冷门算子换成 TFLite 支持的替代品
  2. 自定义算子:自己写一个 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 模型。到时候你会发现,前面这些准备工作做得越扎实,后面部署就越顺利。