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

mrg*_*oom 9 python quantization deep-learning tensorflow tensorflow-lite

我正在尝试使用 UINT8 量化,同时将 tensorflow 模型转换为 tflite 模型:

如果使用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()
Run Code Online (Sandbox Code Playgroud)

转换模型的输入/输出:

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)}]
Run Code Online (Sandbox Code Playgroud)

另一种选择是明确指定更多参数:模型大小比原始 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()
Run Code Online (Sandbox Code Playgroud)

转换模型的输入/输出:

[{'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)}]
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:

  1. 当 onlypost_training_quantize = True被设置时会发生什么?即为什么第一种情况可以正常工作,但第二种情况则不能。
  2. 如何估计第二种情况的均值、标准差和范围参数?
  3. 看起来在第二种情况下模型推理更快,是否取决于模型输入是 uint8 的事实?
  4. 什么手段'quantization': (0.0, 0)在第一种情况下和'quantization': (0.003921568859368563, 0)'quantization': (0.7843137383460999, 128)在第二种情况?
  5. 什么是converter.default_ranges_stats

更新:

找到问题 4 的答案解释器.get_input_details() 中的“量化”是什么意思?

Moh*_*Ezz 14

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

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

即,您可以拥有一个具有推理类型的 tflite 模型,float32post_training_quantize=True为了减小磁盘大小并在运行时更快地加载模型,模型权重被量化(使用)。

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

该文档让许多人感到困惑。让我解释一下我经过一些研究得出的结论:

  1. 不幸的是,量化参数/统计数据在整个 TF 库和文档中有 3 种等效的形式/表示:
    • 一种) (mean, std_dev)
    • 乙) (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)
    • 说明:quantization stats 是用于将范围 (0,255) 映射到任意范围的参数,您可以从 2 个方程开始: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 向量化指令的一流支持以使量化推理比浮点更快]

'quantization': (0.0, 0) 在第一种情况下是什么意思,'quantization': (0.003921568859368563, 0),'quantization': (0.7843137383460999, 128) 在第二种情况下是什么意思?

这里的格式是 quantization: (scale, zero_point)

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

在第二种情况下,您通过提供inference_type = tf.contrib.lite.constants.QUANTIZED_UINT8. 因此,您有输入和输出的量化参数,在进入模型的途中将浮点输入转换为 uint8,在退出时将 uint8 输出转换为浮点输出。

  • 在输入处,进行转换: 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),不仅形式不同(比例与 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


小智 5

1) 见文档。简而言之,这种技术可以让您获得一个量化的 uint8 图,其工作精度接近原始图,并且不需要进一步训练量化模型。然而,速度明显低于使用传统量化的速度。

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推理