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精度的。对于微控制器来说,还是太大了。
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个样本就够。它的作用是让转换器「看看」真实数据的分布,从而算出激活值的量化参数。
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。微调太多反而可能过拟合。
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。省时间。
最后说一句,量化不是银弹。有些模型天生对量化不敏感,比如MobileNetV2。有些模型则很敏感,比如带有大量BatchNorm层的模型。我建议你多试几种量化方式,找到最适合你模型的那一种。
好了,这一章就到这里。下一章我们聊聊怎么在微控制器上实际跑这些量化后的模型。到时候你会看到,int8模型在Cortex-M4上跑起来,那速度是真的快。