模型量化是用8bit整数去表示32bit浮点型小数的过程,模型量在移动端是比不可少的步骤,量化化的好处主要在于减少模型的体积,加快模型的计算速度,但在一定程度上会损失模型的精度。
模型量化的原理:
这里的S和Z均是量化参数,而Q和R均可由公式进行求值,不管是量化后的Q还是反推求得的浮点值R,如果它们超出各自可表示的最大范围,那么均需要进行截断处理,
具体可参考:https://zhuanlan.zhihu.com/p/79744430
量化参考:https://heartbeat.fritz.ai/8-bit-quantization-and-tensorflow-lite-speeding-up-mobile-inference-with-low-precision-a882dfcafbbd
模型量化是一个复杂的过程,网上虽然有不少的教程,但不少都讲的不是太清楚明了,本文主要是将tensorflow模型量化,
pytorch模型量化请参考:(quantization aware training)https://pytorch.org/tutorials/intermediate/quantized_transfer_learning_tutorial.html
模型量化主要有两种:
1. 训练量化(quantization-aware training)
训练量化是指在训练时会生成fake quantize node(虚量化节点),fake quant其本质是quantization-aware training,是train的过程中模拟量化。这个虚量化节点会记录训练时的max和min值,只要你想fully quantize一个模型,那就必须手动插入fake quant node去训练
2.训练后量化(Post-training quantization)
训练后量化也就是模型训练好以后再进行量化,这种量化是一种不完全量化,在inference时,模型会把uint8的weights再转换回float32来做矩阵乘法。所以,这种方法其实依然相当于没做quantization。它的存在应当只是便于用户转换model,计算时依然是个非量化model
所以,要实现真正的全量化模型,需要使用方式1,模型训练时插入fake quantize node(当然,可能有些操作插入不成功,如RNN,这时需要手动插入),实现训练量化需要进行以下步骤:
a)训练模型时create_train_graph(), 模型参数可以保存为ckpt,官方提供了如何进行训练量化的demo:https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/speech_commands/train.py
b) freeze模型,在模型inference时创建create_eval_graph(), 进行参数restore,通过session获取graph(sess.graph_def),通过
tf.graph_util.convert_variables_to_constants(sess,graph,output_names)生成pb,这时的pb记录了training的max和min,可通过Netron进行可视化,会发现每个node都多出了max和min两个参数,这时的模型仍然是float32,官方提供了如何进行create_eval_graph()的demo:
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/speech_commands/freeze.py
c)然后对freeze的模型进行量化,通过下面代码可以将pb模型进行量化,量化后的模型才是int8,这种方式不需要通过toco工具就可以直接进行量化:
# 如果模型在训练时没有做归一化,模型的输入范围是(0,255),输入是numpy.uint8数据类型,converter的参数进行设置如下
converter = tf.lite.TFLiteConverter.from_frozen_graph(
graph_def_file, input_arrays, output_arrays)
converter.inference_type = tf.lite.constants.QUANTIZED_UINT8
converter.quantized_input_stats = {"input_image" : (0., 1.)} //mean,std
converter.default_ranges_stats=(0, 255) //如果op没有min和max,则用该参数自动补上
tflite_model = converter.convert()
open("converted_model.tflite", "wb").write(tflite_model)
# 如果模型在训练时做了归一化,模型的输入范围是(0,1),输入是float32类型,模型输出需要乘以255,converter的参数进行设置如下
converter = tf.lite.TFLiteConverter.from_frozen_graph(
graph_def_file, input_arrays, output_arrays)
tflite_model = converter.convert()
open("converted_model.tflite", "wb").write(tflite_model)
其中,graph_def_file是freeze生成的pb文件,input_arrays是模型输入数组,output_arrays是模型输出数组(多个输出节点用逗号隔开,如["clone/output1","clone/output2"]),quantized_input_stats和default_ranges_stats的值经过验证是正确的,这两个参数非常非常重要,设置不对dequantize可能出问题。quantized_input_stats参数含义以及设置参考:https://stackoverflow.com/questions/54830869/understanding-tf-contrib-lite-tfliteconverter-quantization-parameters,https://zhuanlan.zhihu.com/p/62047684当然,也可以通过toco工具进行量化
当时使用的TensorFlow-gpu==1.13.1,经过测试是能正常进行量化。
遇到的问题:
1. 量化使用tensorflow API进行量化,尽量避开toco转tflite,toco不同版本报错不一样,toco会报错某些算子(如resizeBilinear)不支持,其实tflite是支持的
2. tflite转成功并且不报任何错,并不代表量化成功,使用converter.interpreter进行推理时结果会不正确,导致的原因可能是多方面的,比如模型使用relu激活函数,并转tflite成功,但是结果不正确,relu替换为relu6结果就正常
3. softmax,strideslice算子不支持5维量化操作,softmax替换为sigmoid、strideslice使用split然后squeeze最后解决
4. 量化训练需要注意,尽量使用conv+bias+relu组合一起使用,因为量化时会对这三个op进行合并,如果只有conv有可能量化训练时可能conv之后不会插入虚节点,比如conv之后是add操作,如果conv没有relu那么conv后面是不会插入虚节点的,但实际上是需要的,因此,为了避免量化训练后生成虚节点的pb时出错,尽量使用slim.conv2d卷积(默认带有bias+relu),不要使用tf.nn.conv2d(默认没有bias,没有relu),如果使用tf.nn.conv2d进行卷积而忘记加bias+relu,生成带有虚节点pb模型会有问题的,如果某些op需要手动插入虚节点,可以通过下面的方式插入:
tf.quantize(conv,0.0,255,tf.qint8)
5. 尝试relu6替换relu(视频超分模型替换为relu,会让模型的效果变差),使用relu进行int8量化会出现问题时,尝试relu6进行替换,这一步不是必须,但实验发现确实是有用,不知道为什么
toco工具量化可参考:http://ask.ainoob.cn/article/7804
量化过程参考:https://blog.csdn.net/weixin_41864878/article/details/85615488
量化底层实现参考:https://blog.csdn.net/yifen4234/article/details/80382956
TensorFlow中文社区关于量化问题讨论:https://www.tensorflowers.cn/t/7136
Tensorflow2.0量化:https://segmentfault.com/a/1190000018741468