2. TensorFlow Lite 模型量化:训练后量化、量化感知训练与混合量化策略

说到模型量化,很多刚入门的同学第一反应就是「把 float32 转成 int8 嘛,精度肯定掉得厉害」。说实话,我早年也这么想。直到我在一个智能门锁项目里,硬是把一个 4MB 的人脸识别模型压到了 800KB,跑在 STM32F4 上还能保持 97% 的准确率——从那以后,我对量化的看法彻底变了。

量化不是简单的精度换速度。它是一门平衡的艺术。今天我就把三种主流量化策略掰开揉碎讲清楚。

2.1 训练后量化(Post-training Quantization)

这是最省事的方法。模型训练完了,直接拿来做量化。不需要重新训练,不需要改网络结构。说白了,就是「事后补救」。

TensorFlow Lite 提供了三种训练后量化方式:

量化方式 权重类型 激活值类型 典型精度损失 适用场景
动态范围量化 int8 float32 < 1% 快速验证、资源充足
全整数量化 int8 int8 1-3% STM32 等嵌入式部署
float16 量化 float16 float32 < 0.5% GPU 加速场景

我个人习惯先用动态范围量化试水。为什么?因为它快。你只需要几行代码就能看到量化后的效果。

import tensorflow as tf

# 加载训练好的模型
model = tf.keras.models.load_model('my_model.h5')

# 动态范围量化
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()

# 保存量化模型
with open('model_dynamic.tflite', 'wb') as f:
    f.write(tflite_quant_model)

但注意了,动态范围量化只在推理时把权重转成 int8,激活值还是 float32。这意味着你的 MCU 还得保留浮点运算单元。对于没有 FPU 的 STM32 型号,比如 STM32F103,这招就不太灵了。

我曾经踩过的坑: 动态范围量化在 PC 上跑得好好的,部署到 STM32F103 上直接报错。原因就是目标芯片不支持浮点激活值计算。所以,如果你确定要部署到低端 MCU,直接上全整数量化。

2.2 全整数量化(Full Integer Quantization)

全整数量化才是嵌入式部署的「真香」方案。权重和激活值全部用 int8 表示。你想想看,一个 32 位的浮点数变成 8 位整数,模型体积直接缩到四分之一。

实现全整数量化需要一个小技巧——提供代表性数据集(Representative Dataset)。TensorFlow Lite 需要用它来校准激活值的动态范围。

def representative_dataset():
    # 从验证集中取 100 个样本
    for i in range(100):
        data = val_images[i:i+1]
        yield [data.astype(np.float32)]

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
converter.inference_output_type = tf.int8

tflite_quant_model = converter.convert()

这里有个关键点:supported_ops 设置成 TFLITE_BUILTINS_INT8,强制所有算子都用 int8 实现。如果模型里有不支持的算子,转换会失败。我遇到过好几次,最后发现是某个自定义层没注册量化实现。

我的建议: 在转换前先用 tf.lite.TFLiteConverter 的 allow_custom_ops 参数检查一下。或者干脆把模型里的自定义层换成标准层,省得后面折腾。

2.3 量化感知训练(Quantization-Aware Training, QAT)

训练后量化虽然方便,但精度损失有时候扛不住。尤其是小模型,比如 MobileNetV2 这种轻量网络,量化后准确率可能掉 3-5 个点。这时候就得请出量化感知训练了。

QAT 的原理其实不复杂:在训练过程中模拟量化效果,让模型自己去适应低精度表达。说白了,就是让模型「提前知道」自己将来要被量化,提前做好心理准备。

import tensorflow_model_optimization as tfmot

# 在模型上应用 QAT
quantize_model = tfmot.quantization.keras.quantize_model

# 注意:这里要用原始模型,不是已经训练好的量化模型
qat_model = quantize_model(model)

# 编译并训练
qat_model.compile(optimizer='adam',
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])

# 用较小的学习率微调几个 epoch
qat_model.fit(train_images, train_labels,
              epochs=5,
              validation_data=(val_images, val_labels))

# 转换为 TFLite
converter = tf.lite.TFLiteConverter.from_keras_model(qat_model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_qat_model = converter.convert()

我在一个工业缺陷检测项目里用过 QAT。原始模型 float32 精度 98.2%,训练后量化掉到 95.1%,差了 3 个点。用了 QAT 微调 5 个 epoch 后,精度回到 97.6%。虽然没完全恢复,但已经够用了。

QAT 的核心优势: 精度恢复能力强,尤其适合对精度敏感的嵌入式应用。代价是训练时间增加 20-30%,而且需要修改训练代码。

2.4 混合量化策略

实际项目中,我很少只用一种量化策略。更多时候是混合着来。什么叫混合量化?就是模型里一部分层用 int8,一部分层用 float32。

为什么要混合?因为有些层对量化特别敏感。比如注意力机制里的 softmax,量化后误差会累积。再比如某些 BatchNorm 层,量化后行为会变。

TensorFlow Lite 支持按层指定量化策略:

# 先做全整数量化
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset

# 指定某些层保持 float32
converter.target_spec.supported_ops = [
    tf.lite.OpsSet.TFLITE_BUILTINS_INT8,
    tf.lite.OpsSet.TFLITE_BUILTINS
]

# 或者用选择性量化
converter._experimental_disable_per_channel = False
converter._experimental_default_to_single_batch_inference = False

嗯,这里要注意:混合量化虽然灵活,但会引入额外的数据类型转换开销。每次从 int8 切到 float32,再切回来,都会消耗时钟周期。所以我的经验是:尽量让敏感层集中在一起,减少切换次数。

我曾经踩过的坑: 在一个语音唤醒词检测模型里,我把第一层卷积和最后一层全连接保留了 float32,中间全部 int8。结果推理时间反而比全 int8 还慢。后来分析发现,频繁的数据类型转换把时间都吃掉了。最后我改成只保留最后一层 softmax 为 float32,效果才好。

2.5 三种策略对比与选择

说了这么多,到底该选哪种?我整理了一个对比表,方便你决策:

维度 训练后量化 全整数量化 量化感知训练
实现难度 低(几行代码) 中(需校准数据) 高(需修改训练流程)
精度损失 1-3% 1-3% < 1%
推理速度提升 2-3x 3-4x 3-4x
模型体积缩减 2x 4x 4x
适用阶段 快速原型验证 嵌入式部署 精度敏感场景

我的个人建议是:

  • 项目初期:先用动态范围量化快速验证可行性
  • 确定部署平台后:切换到全整数量化,用代表性数据集校准
  • 精度不达标时:引入 QAT,微调 3-5 个 epoch
  • 特殊敏感层:用混合量化策略单独处理
模型量化策略选择流程图 训练好的 float32 模型 是否对精度要求极高? 否 是 训练后量化 快速验证,精度损失小 量化感知训练 精度恢复,需微调 部署到 MCU? 否 是 动态 全整数量化 + 混合策略 提示 敏感层(如 softmax) 保留 float32

最后说一句:量化不是银弹。有些模型结构天生不适合量化,比如含有大量 LayerNorm 的 Transformer。遇到这种情况,别硬来,考虑换模型结构或者用知识蒸馏。