了解 tf.contrib.lite.TFLiteConverter 量化参数
Posted
技术标签:
【中文标题】了解 tf.contrib.lite.TFLiteConverter 量化参数【英文标题】:Understanding tf.contrib.lite.TFLiteConverter quantization parameters 【发布时间】:2019-07-16 18:30:40 【问题描述】:我在将 tensorflow 模型转换为 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)]
所以我的问题是:
-
仅设置
post_training_quantize = True
时会发生什么?即为什么第一种情况可以正常工作,但第二种情况不行。
如何估计第二种情况的均值、标准差和范围参数?
看起来在第二种情况下模型推理更快,这取决于模型输入是 uint8 的事实吗?
第一种情况下的'quantization': (0.0, 0)
和第二种情况下的'quantization': (0.003921568859368563, 0)
,'quantization': (0.7843137383460999, 128)
是什么意思?
converter.default_ranges_stats
是什么?
更新:
问题4的答案找到What does 'quantization' mean in interpreter.get_input_details()?
【问题讨论】:
@suharshs 看起来你与 tensorflow 的这一部分有关,你能详细说明一下吗? 4a。对于 float32 的 dtype,量化 被忽略 【参考方案1】:仅设置 post_training_quantize = True 时会发生什么?即为什么第一种情况可以正常工作,但第二种情况不行。
在 TF 1.14 中,这似乎只是量化了存储在磁盘上的 .tflite 文件中的权重。这本身不会将推理模式设置为量化推理。
即,您可以拥有一个推理类型为 float32
的 tflite 模型,但模型权重被量化(使用 post_training_quantize=True
)以减小磁盘大小,并在运行时更快地加载模型。
如何估计第二种情况的均值、标准差和范围参数?
文档让很多人感到困惑。让我解释一下我在研究后得出的结论:
-
不幸的是,量化参数/统计在 TF 库和文档中具有 3 个等效形式/表示:
答)
(mean, std_dev)
B) (zero_point, scale)
C)(min,max)
从 B) 和 A) 转换:
std_dev = 1.0 / scale
mean = zero_point
从 C) 到 A) 的转换:
mean = 255.0*min / (min - max)
std_dev = 255.0 / (max - min)
说明:量化统计是用于将范围(0,255)映射到任意范围的参数,可以从min / std_dev + mean = 0
和max / std_dev + mean = 255
这2个方程开始,然后按照数学得出上述转换公式李>
从 A) 到 C) 的转换:
min = - mean * std_dev
max = (255 - mean) * std_dev
命名“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: (scale, zero_point)
在第一种情况下,您只激活了post_training_quantize=True
,这不会使模型运行量化推理,因此无需将输入或输出从浮点数转换为 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
【讨论】:
你的答案中converter.default_ranges_stats
是否对应min
、max
?
同样遵循你的方程式if input image range [0,1] then mean = 0, std_dev = 255
,然后min = - mean * std_dev
,max = (255 - mean) * std_dev
-> min=0,max=255*255,那么min = -100.39, max=99.6
来自哪里?
是default_ranges_stats
指定(min,max)
对于你的输出你有get_output_details()
返回'quantization': (0.7843137383460999, 128)
所以min = -128 * 0.7434313 = -100.39
,max = (255-128)*0.7434313 = 99.6
。如果您发现错误,请告诉我
是的,default_range_stats
不仅会影响输出的量化参数,还会影响图中的每个张量。请注意,您永远不应该自己定义default_range_stats
。此选项仅可用于调试。正确的方法是对模型进行量化感知训练,以便每个张量根据训练数据都有自己的最小值/最大值:github.com/tensorflow/tensorflow/tree/r1.13/tensorflow/contrib/…【参考方案2】:
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 推理
【讨论】:
对于2)
,您的意思是图像预处理吗?我使用 bgr 图像作为输入除以 255.0,所以我的输入在 [0,1] 范围内,所以据我了解,在我的情况下,这将是 mean=0.0 和 std_dev=255.0 converter.default_ranges_stats
呢?
是的,这取决于图像预处理。关于 default_ranges_stats。通常,要创建量化的 tflite 图,所有张量都必须具有可能值的最小/最大信息。此信息将用于创建量化参数:scale 和 zero_point。如果缺少此 minmax 值,则将使用 default_ranges_stats 中的 minmax,在这种情况下,这意味着量化图推理将像垃圾一样
有什么方法可以看出 TFLite 模型确实通过了post_training_quantize
?在我的测试中,在两个模型上运行 //tensorflow/lite/tools:visualize 会得到相同的结果(不完全相同,缓冲区索引不同)。此外,两个模型(在 CPU 中)运行推理的时间成本在统计上没有变化。以上是关于了解 tf.contrib.lite.TFLiteConverter 量化参数的主要内容,如果未能解决你的问题,请参考以下文章