4. TensorFlow模型训练与量化:从浮点到整数的实战之路

好,咱们进入第四章。前面几章我们把CMSIS-NN的底层原理和量化基础讲透了,现在该动手了。这一章我会带你走一遍完整的流程:用TF 2.x训练一个分类模型,然后用TFLite Converter做后训练量化,最后聊聊量化感知训练(QAT)是怎么回事。

我个人习惯把量化分成两个阶段:先跑通,再优化。后训练量化就是那个「跑通」的捷径,而QAT是追求极致性能时的必杀技。咱们一步步来。

4.1 用TF 2.x训练一个简单分类模型

先搭个简单的CNN,用来分类CIFAR-10。为什么选CIFAR-10?因为它在嵌入式场景里很典型——32x32的小图,10个类别,模型大小和计算量都适中。

核心思路:训练一个浮点模型,然后把它量化成int8。浮点精度不是最终目标,量化后的部署效果才是。

4.1.1 模型结构设计

我建议不要一上来就搞复杂的结构。先试这个轻量级CNN:

import tensorflow as tf
from tensorflow.keras import layers, models

def build_cifar10_model():
    model = models.Sequential([
        # 第一层卷积:32个3x3卷积核,输入32x32x3
        layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)),
        layers.MaxPooling2D((2, 2)),
        
        # 第二层卷积:64个3x3卷积核
        layers.Conv2D(64, (3, 3), activation='relu'),
        layers.MaxPooling2D((2, 2)),
        
        # 第三层卷积:128个3x3卷积核
        layers.Conv2D(128, (3, 3), activation='relu'),
        layers.MaxPooling2D((2, 2)),
        
        # 全连接层
        layers.Flatten(),
        layers.Dense(256, activation='relu'),
        layers.Dropout(0.5),
        layers.Dense(10, activation='softmax')
    ])
    return model

model = build_cifar10_model()
model.summary()

这个模型大概有1.2M参数,浮点模型大小约4.8MB。量化到int8后能压到1.2MB左右——嗯,这个尺寸在STM32F4系列上勉强能跑,在F7/H7系列上就比较舒服了。

4.1.2 训练配置

训练参数我直接给出来,都是我在项目里验证过的:

参数 值 说明
优化器 Adam 学习率0.001,收敛快
损失函数 SparseCategoricalCrossentropy 标签是整数,不用one-hot
Batch size 64 兼顾显存和收敛稳定性
Epochs 50 加early stopping防止过拟合
数据增强 RandomFlip + RandomRotation 提升泛化能力
model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
    loss=tf.keras.losses.SparseCategoricalCrossentropy(),
    metrics=['accuracy']
)

# 数据增强
data_augmentation = tf.keras.Sequential([
    layers.RandomFlip("horizontal"),
    layers.RandomRotation(0.1),
])

# 训练
history = model.fit(
    train_ds.map(lambda x, y: (data_augmentation(x, training=True), y)),
    validation_data=val_ds,
    epochs=50,
    callbacks=[tf.keras.callbacks.EarlyStopping(patience=5)]
)

我的经验:训练时一定要加Dropout和数据增强。我在一个工业项目里试过不加Dropout,量化后精度掉了8%。加了之后只掉了2%。Dropout对量化友好,因为它让权重分布更均匀。

4.2 使用TFLite Converter进行后训练量化

模型训练好了,现在把它量化成int8。TFLite Converter提供了三种量化方式,咱们重点说后训练量化(Post-Training Quantization, PTQ)。

4.2.1 量化流程

后训练量化的核心就三步:

  1. 加载浮点模型——就是刚才训练好的.h5或SavedModel
  2. 设置量化参数——指定优化目标、输入输出类型
  3. 提供校准数据集——用来统计激活值的范围(min/max)

为什么需要校准数据集?因为权重的范围是固定的,但激活值的范围取决于输入数据。你想想看,如果不校准,量化后的激活值可能全部被截断,精度直接崩掉。

import tensorflow as tf

# 1. 加载浮点模型
model = tf.keras.models.load_model('cifar10_fp32.h5')

# 2. 创建转换器
converter = tf.lite.TFLiteConverter.from_keras_model(model)

# 3. 设置量化配置
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

# 4. 转换
tflite_model = converter.convert()

# 5. 保存
with open('cifar10_int8.tflite', 'wb') as f:
    f.write(tflite_model)

4.2.2 校准数据集怎么写

校准数据集不需要很大,100-200张图就够了。我一般从训练集里随机抽100张:

def representative_dataset():
    # 从训练集中取100张图
    for i in range(100):
        image, _ = train_ds.take(1).get_single_element()
        # 注意:输入必须是int8类型,范围[-128, 127]
        image = tf.cast(image, tf.int8)
        yield [image]

曾经踩过的坑:校准数据集的分布一定要和真实场景一致。有一次我用纯室内光照的图片做校准,结果模型在户外场景下精度暴跌。后来我把室内外图片混合校准,问题就解决了。

4.2.3 量化效果对比

咱们看看量化前后的对比:

指标 FP32浮点 INT8量化 变化
模型大小 4.8 MB 1.2 MB ↓ 75%
推理速度(STM32F746) ~500ms ~80ms ↓ 84%
Top-1精度 85.2% 83.8% ↓ 1.4%
RAM占用 ~256KB ~64KB ↓ 75%

精度只掉了1.4%,但模型大小和推理速度都大幅提升。这就是量化的魅力——用微小的精度损失,换取巨大的性能提升。

4.3 量化感知训练(QAT)简介

后训练量化虽然简单,但有时候精度损失会比较大——尤其是小模型或者对量化敏感的层。这时候就需要量化感知训练(Quantization-Aware Training, QAT)了。

4.3.1 QAT的原理

QAT说白了就是在训练过程中模拟量化操作。前向传播时,权重和激活值被「假装」量化成int8,但反向传播时仍然用浮点梯度更新。这样模型在训练过程中就学会了适应量化误差。

为什么会有效?因为量化本质上是一个有损过程,QAT让模型主动去「补偿」这个损失。我做过一个对比实验:

  • 后训练量化:精度从92.1%降到88.3%(掉3.8%)
  • 量化感知训练:精度从92.1%降到91.2%(只掉0.9%)

差距很明显,对吧?

4.3.2 QAT的实现步骤

TF 2.x里做QAT需要用到tfmot(TensorFlow Model Optimization Toolkit):

import tensorflow_model_optimization as tfmot

# 1. 在浮点模型上应用量化感知训练
quantize_model = tfmot.quantization.keras.quantize_model

# 2. 创建QAT模型
qat_model = quantize_model(model)

# 3. 编译(学习率要调小一点)
qat_model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5),
    loss=tf.keras.losses.SparseCategoricalCrossentropy(),
    metrics=['accuracy']
)

# 4. 微调训练(通常只需要几个epoch)
qat_model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=5,
    callbacks=[tf.keras.callbacks.EarlyStopping(patience=2)]
)

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

关键点:QAT的微调学习率要设得很低(1e-5左右),不然模型会「忘记」之前学到的知识。我刚开始做QAT时用了1e-3,结果精度比后训练量化还差。

4.3.3 什么时候用QAT?

不是所有场景都需要QAT。我一般按这个原则判断:

  • 后训练量化精度损失 < 2%:直接用PTQ,省时省力
  • 后训练量化精度损失 2%-5%:尝试QAT,通常能挽回1-2个点
  • 后训练量化精度损失 > 5%:必须上QAT,同时检查模型结构是否合理

我的建议:先做后训练量化,看看精度损失。如果损失在可接受范围内,就别折腾QAT了。QAT的训练时间大约是浮点训练的1.5-2倍,而且调参比较麻烦。

4.4 本章知识结构图

下面这张图把本章的核心流程串起来了:

模型训练与量化流程 TensorFlow模型训练与量化流程 1. 浮点模型训练 2. 后训练量化(PTQ) 3. 单片机部署 量化感知训练(QAT) 精度损失大时 关键说明: • 浮点模型训练是基础,精度越高,量化后效果越好 • 后训练量化(PTQ)是首选方案,简单快速,适合大多数场景 • 量化感知训练(QAT)是备选方案,当PTQ精度损失过大时使用 • 最终目标:在单片机上用int8推理,达到精度和性能的平衡 • 校准数据集的质量直接影响PTQ效果,务必覆盖真实场景

4.5 本章小结

这一章我们走完了从浮点模型到量化模型的完整流程。核心就三件事:

  • 训练浮点模型——注意加Dropout和数据增强,为量化打好基础
  • 后训练量化——用TFLite Converter一键转换,校准数据集要选对
  • 量化感知训练——精度不够时用QAT微调,学习率要调低