2、TFLite模型基础:从TensorFlow SavedModel到TFLite的转换流程、量化原理(FP32->INT8)、使用Python API进行模型转换与验证。
好,咱们直接进入正题。上一章我们把环境搭好了,这一章就来玩点真格的——把训练好的模型变成能在嵌入式设备上跑的东西。
说白了,TensorFlow训练出来的模型是个“大家伙”,动辄几百兆。你想想看,一个Cortex-M4的Flash才多大?所以必须得瘦身、压缩。这个瘦身过程,就是今天要讲的模型转换与量化。
2.1 从SavedModel到TFLite:转换流程全解析
我个人习惯把转换流程分成三步走:
- 导出SavedModel:训练完的模型先存成标准格式
- 调用Converter:用TFLite的Python API做转换
- 生成.tflite文件:最终产物,可以直接部署
听起来简单吧?但坑都在细节里。我在项目中遇到过最典型的问题——模型里用了某些高级算子,转换时直接报错。嗯,这里要注意,TFLite支持的算子集是有限的。
2.1.1 导出SavedModel的正确姿势
假设你有一个训练好的Keras模型,导出方式如下:
import tensorflow as tf
# 假设model是你训练好的Keras模型
model.save('my_model', save_format='tf') # 这就是SavedModel格式
导出后你会看到一个文件夹,里面有assets、variables和saved_model.pb。这个.pb文件就是模型的计算图定义。
2.1.2 使用TFLiteConverter进行转换
核心代码其实就几行:
import tensorflow as tf
# 加载SavedModel
converter = tf.lite.TFLiteConverter.from_saved_model('my_model')
# 执行转换
tflite_model = converter.convert()
# 保存为.tflite文件
with open('model.tflite', 'wb') as f:
f.write(tflite_model)
你看,就这么简单。但如果你直接这么干,出来的模型还是FP32的,体积没怎么变。真正让模型“瘦身”的,是量化。
2.2 量化原理:FP32到INT8的魔法
为什么会想到量化?说白了,嵌入式设备上跑浮点运算太慢了。很多MCU根本没有FPU,全靠软件模拟浮点,那速度简直感人。
量化的核心思想很简单:用更少的比特数来表示数值。FP32用32位表示一个数,INT8只用8位。体积直接缩小4倍,推理速度也能提升2-4倍。
2.2.1 量化映射关系
从FP32到INT8,不是简单截断,而是做一个线性映射:
| FP32范围 | INT8范围 | 映射公式 |
|---|---|---|
| [min, max] | [-128, 127] | q = round(r / scale) + zero_point |
其中scale是缩放因子,zero_point是零点偏移。这两个参数是通过校准数据集计算出来的。
2.2.2 两种量化方式
TFLite支持两种量化策略:
- 训练后量化(Post-training Quantization):最常用,不需要重新训练
- 量化感知训练(Quantization-aware Training):精度更高,但需要重新训练
对于大多数场景,训练后量化就够了。我建议你先试试这个,不行再上量化感知训练。
2.3 使用Python API进行模型转换与验证
好,理论说完了,咱们动手实操。下面是一个完整的转换+验证流程。
2.3.1 带量化的转换代码
import tensorflow as tf
import numpy as np
# 加载SavedModel
converter = tf.lite.TFLiteConverter.from_saved_model('my_model')
# 开启量化
converter.optimizations = [tf.lite.Optimize.DEFAULT]
# 提供校准数据集(关键!)
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
# 指定目标类型为INT8
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8
# 执行转换
tflite_quant_model = converter.convert()
# 保存
with open('model_quant.tflite', 'wb') as f:
f.write(tflite_quant_model)
print(f"量化模型大小:{len(tflite_quant_model) / 1024:.2f} KB")
2.3.2 验证转换后的模型
转换完不能直接扔到板子上,得先验证一下:
# 加载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()
print(f"输入shape: {input_details[0]['shape']}")
print(f"输入dtype: {input_details[0]['dtype']}")
print(f"输出shape: {output_details[0]['shape']}")
print(f"输出dtype: {output_details[0]['dtype']}")
# 准备测试数据
test_data = np.random.randn(1, 224, 224, 3).astype(np.int8)
# 推理
interpreter.set_tensor(input_details[0]['index'], test_data)
interpreter.invoke()
output = interpreter.get_tensor(output_details[0]['index'])
print(f"推理结果shape: {output.shape}")
这里要注意,量化后的模型输入输出都是INT8类型。如果你在板子上推理,记得把摄像头采集的数据也做同样的量化映射。
2.3.3 精度对比验证
最后一步,对比量化前后的精度:
def evaluate_model(interpreter, test_images, test_labels):
correct = 0
for i in range(len(test_images)):
interpreter.set_tensor(input_details[0]['index'], test_images[i])
interpreter.invoke()
output = interpreter.get_tensor(output_details[0]['index'])
if np.argmax(output) == test_labels[i]:
correct += 1
return correct / len(test_images)
# 假设你有测试集
# fp32_acc = evaluate_model(fp32_interpreter, test_images, test_labels)
# int8_acc = evaluate_model(int8_interpreter, test_images, test_labels)
# print(f"FP32精度: {fp32_acc:.4f}, INT8精度: {int8_acc:.4f}")
2.4 本章小结
这一章我们走完了从SavedModel到TFLite的完整流程。核心就三件事:
- 用TFLiteConverter做模型转换
- 用量化把FP32变成INT8,体积缩小4倍
- 用Python API验证转换结果
下一章,咱们要把这个量化好的模型部署到嵌入式Linux上,真正跑起来。到时候你会发现,之前做的这些准备工作,每一分努力都值了。