3、模型转换与量化工具:TFLite Converter使用、Post-training quantization、量化感知训练(QAT)简介、模型大小与精度权衡

好,咱们进入第三章。这一章,说白了就是讲怎么把训练好的大模型「塞进」微控制器里。

你想想看,一个几十MB的模型,怎么可能跑在只有几百KB Flash的单片机上?答案就是——转换和量化。我刚开始接触TFLite Micro时,也天真地以为直接扔个.h5文件进去就行,结果编译器直接报错内存不足。嗯,从那以后,我才老老实实研究这套工具链。

3.1 TFLite Converter:模型格式的「翻译官」

TensorFlow训练出来的模型,通常是SavedModel或者Keras的.h5格式。微控制器不认识这些。它只认一种格式——.tflite

TFLite Converter就是那个翻译官。它的工作很简单:把训练好的模型,转成微控制器能吃的格式。

我个人习惯用Python API来做转换,因为灵活,还能顺手做量化。来看个最基础的例子:

import tensorflow as tf

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

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

# 执行转换
tflite_model = converter.convert()

# 保存为.tflite文件
with open('model.tflite', 'wb') as f:
    f.write(tflite_model)

就这么几行,模型就转换好了。但注意,这个转换后的模型还是float32精度的。对于微控制器来说,还是太大了。

我的小技巧: 转换前先用model.summary()看一下参数量。如果超过1MB,基本就要考虑量化了。我在项目中遇到过,一个2MB的float32模型,量化后直接降到512KB,效果还过得去。

3.2 Post-training quantization:最省事的量化方法

Post-training quantization,简称PTQ。意思就是模型训练完了,我再动手脚。

它的原理很简单:把float32的权重,映射到int8的整数范围。比如一个权重是0.123456,量化后就变成某个整数,比如42。同时记录一个缩放因子(scale)和零点(zero_point),用来反量化回去。

这样做的好处是——模型大小直接缩到1/4。因为float32是4字节,int8是1字节。

代码实现也很直接:

converter = tf.lite.TFLiteConverter.from_keras_model(model)

# 开启默认的PTQ优化
converter.optimizations = [tf.lite.Optimize.DEFAULT]

tflite_quant_model = converter.convert()

就这么简单?对,就这么简单。但这里有个坑——默认的PTQ只量化权重,不量化激活值。也就是说,模型推理时的中间结果还是float32的。这能省模型大小,但省不了运行时内存。

如果你想把激活值也量化,就需要提供代表性数据集

def representative_dataset():
    for _ in range(100):
        # 从训练集中随机取一个batch
        data = np.random.randn(1, 32, 32, 3).astype(np.float32)
        yield [data]

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_fully_quant_model = converter.convert()

这里的关键是representative_dataset。它不需要很多数据,100个样本就够。它的作用是让转换器「看看」真实数据的分布,从而算出激活值的量化参数。

我曾经踩过的坑: 代表性数据集一定要和训练数据同分布。有一次我偷懒,随便拿了100张噪声图做校准,结果量化后的模型在真实数据上准确率掉了15%。后来换成真实样本,才恢复正常。

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

PTQ虽然方便,但精度损失有时候让人头疼。尤其是模型本身比较小,或者对精度要求很高的时候。

这时候就要请出量化感知训练(QAT)了。

QAT的思路是:在训练过程中,就模拟量化带来的误差。让模型自己去适应这种「粗糙」的表示方式。

具体做法是,在模型里插入一些伪量化节点(FakeQuant)。这些节点在前向传播时模拟量化-反量化的过程,让权重和激活值「假装」被量化了。但反向传播时,梯度还是用float32来更新。

这样训练出来的模型,对量化天生就「免疫」。最后再用PTQ转成int8,精度损失通常很小,甚至没有。

代码实现稍微复杂一点:

import tensorflow_model_optimization as tfmot

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

# 注意:这个model是训练好的,但还没量化
qat_model = quantize_model(model)

# 重新编译
qat_model.compile(optimizer='adam',
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])

# 用少量数据微调几个epoch
qat_model.fit(x_train, y_train, epochs=5, batch_size=32)

# 转换时,QAT的伪量化节点会自动处理
converter = tf.lite.TFLiteConverter.from_keras_model(qat_model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_qat_model = converter.convert()

这里要注意,QAT需要重新微调。我一般只微调3-5个epoch,学习率调低一点,比如1e-5。微调太多反而可能过拟合。

什么时候用QAT? 我个人经验是:如果PTQ后精度下降超过2%,就值得试试QAT。如果下降在1%以内,PTQ完全够用,别折腾。

3.4 模型大小与精度权衡

说到底,量化就是一场交易——用精度换大小和速度。

我整理了一个表格,方便你直观对比:

量化方式 模型大小 推理速度 精度损失 适用场景
float32(无量化) 100% PC端、服务器
PTQ(仅权重) ~25% 中等 0.5%-2% 微控制器、边缘设备
PTQ(全整型) ~25% 1%-3% 资源极度受限的设备
QAT ~25% 0%-1% 对精度要求高的嵌入式场景

你看,QAT和PTQ在模型大小上是一样的,都是1/4。但QAT的精度损失更小。代价就是——训练时间变长,而且需要重新微调。

我在一个语音关键词识别项目里做过对比。原始float32模型准确率92%。PTQ全整型量化后,准确率掉到88%。用了QAT微调3个epoch后,准确率回到91.5%。嗯,这个trade-off我觉得值。

但也不是所有模型都适合QAT。如果模型本身就很浅,比如只有两三层卷积,QAT的效果可能不明显。我建议你先试PTQ,如果精度能接受,就别上QAT。省时间。

我的经验法则: 对于分类任务,PTQ后准确率下降不超过1%,直接部署。下降1%-3%,试试QAT。下降超过3%,先检查模型本身是不是有问题,比如过拟合或者欠拟合。

最后说一句,量化不是银弹。有些模型天生对量化不敏感,比如MobileNetV2。有些模型则很敏感,比如带有大量BatchNorm层的模型。我建议你多试几种量化方式,找到最适合你模型的那一种。

好了,这一章就到这里。下一章我们聊聊怎么在微控制器上实际跑这些量化后的模型。到时候你会看到,int8模型在Cortex-M4上跑起来,那速度是真的快。