2、TFLite模型基础:从TensorFlow SavedModel到TFLite的转换流程、量化原理(FP32->INT8)、使用Python API进行模型转换与验证。

好,咱们直接进入正题。上一章我们把环境搭好了,这一章就来玩点真格的——把训练好的模型变成能在嵌入式设备上跑的东西。

说白了,TensorFlow训练出来的模型是个“大家伙”,动辄几百兆。你想想看,一个Cortex-M4的Flash才多大?所以必须得瘦身、压缩。这个瘦身过程,就是今天要讲的模型转换与量化。

2.1 从SavedModel到TFLite:转换流程全解析

我个人习惯把转换流程分成三步走:

  1. 导出SavedModel:训练完的模型先存成标准格式
  2. 调用Converter:用TFLite的Python API做转换
  3. 生成.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文件就是模型的计算图定义。

我的小建议: 导出前最好用model.summary()看一眼输入输出的shape。我吃过一次亏,输入shape写错了,转换出来的模型推理结果全是NaN。

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是零点偏移。这两个参数是通过校准数据集计算出来的。

关键点: 量化不是无损的。精度损失通常在1-2%以内,对于大多数嵌入式应用完全可以接受。我曾经把一个图像分类模型量化后,精度只掉了0.3%,但推理速度提升了3倍。

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")
避坑指南: 我曾经忘记提供representative_dataset,结果量化出来的模型精度直接崩了。校准数据集不需要很大,100张左右就够了,但一定要能代表真实数据分布。

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%,建议检查校准数据集是否合理,或者考虑使用量化感知训练。我在一个语音识别项目上遇到过精度掉5%的情况,最后发现是校准数据里噪声类型太少。

2.4 本章小结

这一章我们走完了从SavedModel到TFLite的完整流程。核心就三件事:

  • 用TFLiteConverter做模型转换
  • 用量化把FP32变成INT8,体积缩小4倍
  • 用Python API验证转换结果

下一章,咱们要把这个量化好的模型部署到嵌入式Linux上,真正跑起来。到时候你会发现,之前做的这些准备工作,每一分努力都值了。