3. 量化模型优化
任务目标
使用TensorFlow Lite Converter将Keras预训练的MobileNetV2转换为int8全量化模型,并在CPU上对比量化前后模型的推理速度与文件大小。int8量化通过将权重与激活从float32映射到8位整数,可显著减小模型体积并加速推理,适合部署到移动端或嵌入式设备。
环境依赖
需要TensorFlow 2.x(推荐2.13+),安装命令:pip install tensorflow。本案例使用TensorFlow内置的Keras应用模型,无需额外数据集——代表性数据集仅用于校准激活范围,使用随机输入即可完成量化流程。
完整实现代码
import tensorflow as tf
import numpy as np
import time
import os
# 1. 加载预训练MobileNetV2(ImageNet权重)
model = tf.keras.applications.MobileNetV2(
input_shape=(224, 224, 3),
weights='imagenet',
classes=1000
)
# 2. 构建代表性数据集(用于校准激活值范围)
def representative_dataset():
for _ in range(100):
# 随机生成与训练分布相近的输入(0~1范围)
data = np.random.rand(1, 224, 224, 3).astype(np.float32)
yield [data]
# 3. 转换为int8全量化模型
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8 # 输入也量化为int8
converter.inference_output_type = tf.int8 # 输出也量化为int8
int8_model = converter.convert()
# 4. 保存量化模型
with open('mobilenetv2_int8.tflite', 'wb') as f:
f.write(int8_model)
# 5. 同时保存float32原始模型作为对比基准
converter_fp32 = tf.lite.TFLiteConverter.from_keras_model(model)
fp32_model = converter_fp32.convert()
with open('mobilenetv2_fp32.tflite', 'wb') as f:
f.write(fp32_model)
# 6. 对比模型大小
size_fp32 = os.path.getsize('mobilenetv2_fp32.tflite') / 1024 / 1024
size_int8 = os.path.getsize('mobilenetv2_int8.tflite') / 1024 / 1024
print(f"FP32模型大小: {size_fp32:.2f} MB")
print(f"INT8模型大小: {size_int8:.2f} MB")
print(f"压缩比: {size_fp32 / size_int8:.2f}x")
# 7. 推理速度对比(使用相同随机输入)
def benchmark(model_path, input_dtype, num_runs=50):
interpreter = tf.lite.Interpreter(model_path=model_path)
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
# 构造与模型输入类型匹配的随机数据
if input_dtype == np.int8:
test_input = np.random.randint(0, 256, (1, 224, 224, 3), dtype=np.int8)
else:
test_input = np.random.rand(1, 224, 224, 3).astype(np.float32)
# 预热
interpreter.set_tensor(input_details[0]['index'], test_input)
interpreter.invoke()
# 计时
times = []
for _ in range(num_runs):
start = time.perf_counter()
interpreter.set_tensor(input_details[0]['index'], test_input)
interpreter.invoke()
times.append(time.perf_counter() - start)
avg_time = np.mean(times) * 1000 # 转换为毫秒
return avg_time
# 注意:int8模型输入需要uint8/int8类型,fp32模型输入为float32
latency_fp32 = benchmark('mobilenetv2_fp32.tflite', np.float32)
latency_int8 = benchmark('mobilenetv2_int8.tflite', np.int8)
print(f"FP32平均推理耗时: {latency_fp32:.2f} ms")
print(f"INT8平均推理耗时: {latency_int8:.2f} ms")
print(f"加速比: {latency_fp32 / latency_int8:.2f}x")
运行与验证
运行方式:将上述代码保存为quantize_mobilenetv2.py,在终端执行python quantize_mobilenetv2.py。首次运行会自动下载MobileNetV2权重(约14MB),需保持网络畅通。
验证判定标准:
- 程序正常结束且无报错,生成
mobilenetv2_fp32.tflite和mobilenetv2_int8.tflite两个文件。 - 控制台输出中,INT8模型大小应约为FP32模型的1/4(压缩比接近4x),因为int8每个参数占1字节而float32占4字节。
- 推理耗时方面,INT8模型在CPU上通常比FP32快1.5~3倍(具体取决于硬件与TensorFlow Lite的优化程度)。
- 若需验证模型正确性,可加载一张真实图片并比较量化前后Top-1预测类别是否一致(预期一致,但可能存在极小概率的类别翻转,属正常量化误差)。
预期输出示例(数值因硬件与TensorFlow版本而异):
FP32模型大小: 14.98 MB
INT8模型大小: 3.76 MB
压缩比: 3.98x
FP32平均推理耗时: 82.35 ms
INT8平均推理耗时: 41.20 ms
加速比: 2.00x
关键说明
本案例使用随机数据作为代表性数据集,仅用于确定激活值的动态范围。若需部署到实际场景,应使用真实训练数据分布的代表性样本(通常100~500张)进行校准,以获得更准确的量化参数。int8模型的输入输出均为int8类型,在应用侧需将输入像素值减去128转换为有符号8位整数(或使用uint8接口),输出需加128还原为0~255范围。