了解tf.contrib.lite.TFLiteConverter量化参数

时间:2019-02-22 16:00:18

标签: python tensorflow deep-learning tensorflow-lite quantization

我试图在将张量流模型转换为tflite模型时使用UINT8量化:

如果使用post_training_quantize = True,则模型大小比原始fp32模型小x4,因此我假设模型权重为uint8,但是当我加载模型并通过interpreter_aligner.get_input_details()[0]['dtype']获取输入类型时,它是float32。量化模型的输出与原始模型大致相同。

converter = tf.contrib.lite.TFLiteConverter.from_frozen_graph(
        graph_def_file='tflite-models/tf_model.pb',
        input_arrays=input_node_names,
        output_arrays=output_node_names)
converter.post_training_quantize = True
tflite_model = converter.convert()

已转换模型的输入/输出:

print(interpreter_aligner.get_input_details())
print(interpreter_aligner.get_output_details())
[{'name': 'input_1_1', 'index': 47, 'shape': array([  1, 128, 128,   3], dtype=int32), 'dtype': <class 'numpy.float32'>, 'quantization': (0.0, 0)}]
[{'name': 'global_average_pooling2d_1_1/Mean', 'index': 45, 'shape': array([  1, 156], dtype=int32), 'dtype': <class 'numpy.float32'>, 'quantization': (0.0, 0)}]

另一个选择是显式指定更多参数: 模型尺寸比原始fp32模型小x4,模型输入类型为uint8,但模型输出更像垃圾。

converter = tf.contrib.lite.TFLiteConverter.from_frozen_graph(
        graph_def_file='tflite-models/tf_model.pb',
        input_arrays=input_node_names,
        output_arrays=output_node_names)
converter.post_training_quantize = True
converter.inference_type = tf.contrib.lite.constants.QUANTIZED_UINT8
converter.quantized_input_stats = {input_node_names[0]: (0.0, 255.0)}  # (mean, stddev)
converter.default_ranges_stats = (-100, +100)
tflite_model = converter.convert()

已转换模型的输入/输出:

[{'name': 'input_1_1', 'index': 47, 'shape': array([  1, 128, 128,   3], dtype=int32), 'dtype': <class 'numpy.uint8'>, 'quantization': (0.003921568859368563, 0)}]
[{'name': 'global_average_pooling2d_1_1/Mean', 'index': 45, 'shape': array([  1, 156], dtype=int32), 'dtype': <class 'numpy.uint8'>, 'quantization': (0.7843137383460999, 128)}]

所以我的问题是:

  1. 仅设置post_training_quantize = True时会发生什么情况?即为什么第一种情况可以正常工作,而第二种情况却不行。
  2. 如何估计第二种情况的均值,标准差和范围参数?
  3. 在第二种情况下,模型推断看起来更快,这取决于模型输入是uint8的事实吗?
  4. 在第一种情况下'quantization': (0.0, 0)和在第二种情况下'quantization': (0.003921568859368563, 0)'quantization': (0.7843137383460999, 128)意味着什么?

更新

发现问题4的答案What does 'quantization' mean in interpreter.get_input_details()?

2 个答案:

答案 0 :(得分:2)

  

仅设置post_training_quantize = True时会发生什么情况?即为什么第一种情况可以正常工作,而第二种情况却不能。

在TF 1.14中,这似乎只是量化存储在磁盘上.tflite文件中的权重。本身并不能将推理模式设置为量化推理。

ie,您可以拥有一个tflite模型,其推理类型为float32,但是为了减小磁盘大小和在运行时更快地加载模型,对模型权重进行了量化(使用post_training_quantize=True)。 。

  

如何估计第二种情况的均值,标准差和范围参数?

文档使很多人困惑。让我解释一下研究后得出的结论:

  1. 不幸的是,整个TF库和文档中,量化参数/统计信息具有3种等效形式/表示形式:
    • A)(mean, std_dev)
    • B)(zero_point, scale)
    • C)(min,max)
  2. 从B)和A)转换:
    • std_dev = 1.0 / scale
    • mean = zero_point
  3. 从C)到A)的转换:
    • mean = 255.0*min / (min - max)
    • std_dev = 255.0 / (max - min)
    • 说明:量化统计是用于将范围(0,255)映射到任意范围的参数,您可以从以下两个方程式开始:min / std_dev + mean = 0max / std_dev + mean = 255,然后按照数学公式进行计算转换公式
  4. 从A)转换为C):
    • min = - mean * std_dev
    • max = (255 - mean) * std_dev
  5. “ mean”和“ std_dev”这两个名称令人困惑,在很大程度上被认为是错误的。

要回答您的问题:,如果您输入的图片包含:

  • 范围(0,255),然后mean = 0, std_dev = 1
  • 范围(-1,1),然后mean = 127.5, std_dev = 127.5
  • 范围(0,1),然后mean = 0, std_dev = 255
  

在第二种情况下,模型推断看起来更快,这取决于模型输入是否为uint8吗?

可能。但是,除非您使用特定硬件的矢量化指令,否则量化模型通常会比较慢。 TFLite经过优化,可以为ARM处理器运行那些专用指令。从TF 1.14或1.15开始,如果您是在本地计算机x86 Intel或AMD上运行它,那么如果量化模型运行得更快,我会感到惊讶。 [更新:在TFLite的路线图上添加对x86矢量指令的一流支持,以使量化推理比浮点运算更快]

  

第一种情况下的'量化':(0.0,0)和第二种情况下的'量化':(0.003921568859368563,0),'量化':(0.7843137383460999,128)是什么意思?

此处格式为quantization: (std_dev, mean)

在第一种情况下,您仅激活了post_training_quantize=True,这不会使模型运行量化推理,因此无需将输入或输出从float转换为uint8。因此,这里的量化统计本质上是null,它表示为(0,0)

在第二种情况下,您通过提供inference_type = tf.contrib.lite.constants.QUANTIZED_UINT8激活了量化推理。因此,您同时具有输入和输出的量化参数,在将模型输入时将其float输入转换为uint8以及在输出时将uint8输出转换为float输出是必需的。

  • 在输入时,执行以下转换:uint8_array = (float_array / std_dev) + mean
  • 在输出时,执行以下转换:float_array = (uint8_array.astype(np.float32) - mean) * std_dev
  • 注意.astype(float32),这对于在python中获得正确的计算是必需的
  • 请注意,其他文本可能会使用scale而不是std_dev,因此除法将变成乘法,反之亦然。

这里的另一个令人困惑的事情是,即使在转换过程中指定了quantization_stats = (mean, std_dev)get_output_details也会返回quantization: (scale, zero_point),不仅形式不同(比例vs std_dev),而且顺序不一样!

现在要了解您为输入和输出获得的这些量化参数值,让我们使用上面的公式来推导输入和输出的实际值((min,max))的范围。使用以上公式,我们得到:

  • 输入范围:min = 0, max=1(是您通过提供quantized_input_stats = {input_node_names[0]: (0.0, 255.0)} # (mean, stddev)来指定的)
  • 输出范围:min = -100.39, max=99.6

答案 1 :(得分:1)

1)参见documantation。简而言之,这项技术使您可以得到量化的uint8图,其工作精度接近原始图,并且不需要进一步训练量化模型。但是,速度明显低于使用conventional量化的速度。

2)如果模型已经使用归一化的[-1.0,1.0]训练,则应设置converter.quantized_input_stats = {input_node_names[0]: (128, 127)},然后输入张量的量化将接近(0.003921568859368563, 0)mean是从0到255的整数值,它映射到浮点0.0f。 std_dev为255 /(float_max-float_min)。这将解决一个可能的问题

3)Uint8神经网络的推理速度(基于设备)快约2倍,然后是float32推理