利用量化调试器检查量化错误

在 TensorFlow.org 上查看 在 Google Colab 运行 在 GitHub 上查看源代码 Download notebook 查看 TF Hub 模型

尽管全整数量化能够改进模型大小和延迟,但量化后的模型并不总是像预期的那样工作。通常预计模型质量(例如准确率、mAP、WER)略低于原始浮点模型。但是,在某些情况下,模型质量可能会低于您的预期,或者生成完全错误的结果。

发生这种问题时,找出量化错误的根本原因既棘手又痛苦。为了协助这一模型检查过程,可使用量化调试器来识别有问题的层,而选择性量化可将这些有问题的层保留为浮点,以便以减少量化带来的好处为代价来恢复模型准确率。

注:此 API 是实验性的,在改进过程中可能会有一些突破性变化。

量化调试器

量化调试器使得在现有模型中进行量化质量指标分析成为可能。量化调试器可以自动化过程,以便使用调试数据集运行模型,并收集每个张量的量化质量指标。

注:量化调试器和选择性量化目前仅适用于使用 int8 激活的全整数量化。

先决条件

如果您已经有了量化模型的流水线,那么您就有了运行量化调试器所需的所有组件!

  • 要量化的模型
  • 有代表性的数据集

除了模型和数据之外,您还需要使用数据处理框架(例如 pandas、Google Sheet)来分析导出的结果。

安装

本部分会准备各种库、MobileNet v3 模型和包含 100 个图像的测试数据集。

# Quantization debugger is available from TensorFlow 2.7.0
pip uninstall -y tensorflow
pip install tf-nightly
pip install tensorflow_datasets --upgrade  # imagenet_v2 needs latest checksum
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf
import tensorflow_datasets as tfds
import tensorflow_hub as hub

Boilerplates and helpers

test_ds = ds.map(lambda data: (data['image'], data['label'] + 1)).batch(16)
loss, acc = model.evaluate(test_ds)
print(f'Top-5 accuracy (float): {acc * 100:.2f}%')
eval_tflite(quantized_model, ds)

我们可以看到,对于我们的小数据集,原始模型具有更高的 top-5 准确率,而量化模型的准确率损失很大。

第 1 步:调试器准备

使用量化调试器的最简单方式是提供您一直用于量化模型的 tf.lite.TFLiteConverter

converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset(ds)

# my_debug_dataset should have the same format as my_representative_dataset
debugger = tf.lite.experimental.QuantizationDebugger(
    converter=converter, debug_dataset=representative_dataset(ds))

第 2 步:运行调试器并获取结果

当您调用 QuantizationDebugger.run() 时,调试器将记录相同运算位置的浮点张量和量化张量之间的差异,并使用给定的指标来处理它们。

debugger.run()

可以使用 QuantizationDebugger.layer_statistics 来访问处理后的指标,也可以使用 QuantizationDebugger.layer_statistics_dump() 将其转储为 CSV 格式的文本文件。

RESULTS_FILE = '/tmp/debugger_results.csv'
with open(RESULTS_FILE, 'w') as f:
  debugger.layer_statistics_dump(f)
head /tmp/debugger_results.csv

对于转储中的每一行,首先是运算名称和索引,然后是量化参数和错误指标(如果有的话,包括用户定义的错误指标)。所得到的 CSV 文件可用于挑选具有较大量化错误指标的问题层。

我们可以使用 pandas 或其他数据处理库来检查详细的每层错误指标。

layer_stats = pd.read_csv(RESULTS_FILE)
layer_stats.head()

第 3 步:数据分析

有多种方法可以分析结果。首先,我们来添加一些从调试器输出中派生的有用指标。(scale 表示每个张量的量化尺度因子。)

  • 范围 (256 / scale)
  • RMSE / scale (sqrt(mean_squared_error) / scale)

当量化分布与原始浮点分布相似时,RMSE / scale 接近于 1 / sqrt(12) (~ 0.289),表明量化模型较好。该值越大,该层就越有可能没有被很好地量化。

layer_stats['range'] = 255.0 * layer_stats['scale']
layer_stats['rmse/scale'] = layer_stats.apply(
    lambda row: np.sqrt(row['mean_squared_error']) / row['scale'], axis=1)
layer_stats[['op_name', 'range', 'rmse/scale']].head()
plt.figure(figsize=(15, 5))
ax1 = plt.subplot(121)
ax1.bar(np.arange(len(layer_stats)), layer_stats['range'])
ax1.set_ylabel('range')
ax2 = plt.subplot(122)
ax2.bar(np.arange(len(layer_stats)), layer_stats['rmse/scale'])
ax2.set_ylabel('rmse/scale')
plt.show()

有许多范围很广的层,还有一些层的 RMSE/scale 值很高。我们来获取具有较高错误指标的层。

layer_stats[layer_stats['rmse/scale'] > 0.7][[
    'op_name', 'range', 'rmse/scale', 'tensor_name'
]]

对于这些层,您可以尝试选择性量化,以查看不量化这些层是否会提高模型质量。

suspected_layers = list(
    layer_stats[layer_stats['rmse/scale'] > 0.7]['tensor_name'])

此外,跳过前几层的量化也有助于提高量化模型的质量。

suspected_layers.extend(list(layer_stats[:5]['tensor_name']))

选择性量化

选择性量化会跳过对某些节点的量化,因此计算可以在原始的浮点域中进行。当跳过正确的层时,我们可以期待一些模型质量恢复,但代价是增加延迟和模型大小。

然而,如果您计划在仅限整数的加速器(例如,Hexagon DSP、Edge TPU)上运行量化模型,选择性量化将导致模型碎片化,并将导致较慢的推断延迟,这主要是由 CPU 和这些加速器之间的数据传输开销引起的。为防止出现这种情况,您可以考虑运行量化感知训练以保持所有层为整数,同时保持模型准确率。

量化调试器的选项接受 denylisted_nodesdenylisted_ops 选项,用于跳过特定层或特定运算的所有实例的量化。使用上一步准备的 suspected_layers,我们可以使用量化调试器来获得选择性量化的模型。

debug_options = tf.lite.experimental.QuantizationDebugOptions(
    denylisted_nodes=suspected_layers)
debugger = tf.lite.experimental.QuantizationDebugger(
    converter=converter,
    debug_dataset=representative_dataset(ds),
    debug_options=debug_options)
selective_quantized_model = debugger.get_nondebug_quantized_model()
eval_tflite(selective_quantized_model, ds)

与原始浮点模型相比,准确率仍然较低,但我们通过跳过 111 层中的约 10 层的量化,使整个量化模型有了显著的改善。

您还可以尝试不量化同一类中的所有运算。例如,要跳过所有均值运算的量化,可以将 MEAN 传递给 denylisted_ops

debug_options = tf.lite.experimental.QuantizationDebugOptions(
    denylisted_ops=['MEAN'])
debugger = tf.lite.experimental.QuantizationDebugger(
    converter=converter,
    debug_dataset=representative_dataset(ds),
    debug_options=debug_options)
selective_quantized_model = debugger.get_nondebug_quantized_model()
eval_tflite(selective_quantized_model, ds)

通过这些技术,我们能够提高量化后的 MobileNet V3 模型的准确率。接下来,我们将探索先进的技术来进一步提高模型的准确率。

高级用法

使用以下功能,您可以进一步自定义调试流水线。

自定义指标

默认情况下,量化调试器会为每个浮点量化差异发出五个指标:张量大小、标准差、平均误差、最大绝对误差和均方误差。您可以通过将更多自定义指标传递给选项来添加更多自定义指标。对于每个指标,结果应该是单个浮点值,结果指标将是所有示例中指标的平均值。

  • layer_debug_metrics:根据浮点和量化运算输出的每个运算输出的差异计算指标。
  • layer_direct_compare_metrics:这将基于原始浮点和量化张量及其量化参数(尺度、零点)计算指标,而不是仅获取差异
  • model_debug_metrics仅在将 float_model_(path|content) 传递给调试器时使用。除了运算级指标外,还将最终层输出与原始浮动模型的参考输出进行比较。
debug_options = tf.lite.experimental.QuantizationDebugOptions(
    layer_debug_metrics={
        'mean_abs_error': (lambda diff: np.mean(np.abs(diff)))
    },
    layer_direct_compare_metrics={
        'correlation':
            lambda f, q, s, zp: (np.corrcoef(f.flatten(),
                                             (q.flatten() - zp) / s)[0, 1])
    },
    model_debug_metrics={
        'argmax_accuracy': (lambda f, q: np.mean(np.argmax(f) == np.argmax(q)))
    })

debugger = tf.lite.experimental.QuantizationDebugger(
    converter=converter,
    debug_dataset=representative_dataset(ds),
    debug_options=debug_options)
debugger.run()
CUSTOM_RESULTS_FILE = '/tmp/debugger_results.csv'
with open(CUSTOM_RESULTS_FILE, 'w') as f:
  debugger.layer_statistics_dump(f)

custom_layer_stats = pd.read_csv(CUSTOM_RESULTS_FILE)
custom_layer_stats[['op_name', 'mean_abs_error', 'correlation']].tail()

model_debug_metrics 的结果可以单独在 debugger.model_statistics 中看到。

debugger.model_statistics

使用(内部)mlir_quantize API 访问深度功能

注:以下部分的某些功能,TFLiteConverter._experimental_calibrate_onlyconverter.mlir_quantize 为实验性内部 API,可能会有非向后兼容的更改。

from tensorflow.lite.python import convert

全模型验证模式

调试模型生成的默认行为是按层验证。在该模式下,浮点和量化运算对的输入来自相同的源(先前的量化运算)。另一种模式是全模型验证,其中浮点模型和量化模型是分开的。此模式将有助于观察错误是如何在模型中向下传播的。若要启用,请在手动生成调试模型时,将 enable_whole_model_verify=True 设置为 convert.mlir_quantize

converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.representative_dataset = representative_dataset(ds)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter._experimental_calibrate_only = True
calibrated_model = converter.convert()
# Note that enable_numeric_verify and enable_whole_model_verify are set.
quantized_model = convert.mlir_quantize(
    calibrated_model,
    enable_numeric_verify=True,
    enable_whole_model_verify=True)
debugger = tf.lite.experimental.QuantizationDebugger(
    quant_debug_model_content=quantized_model,
    debug_dataset=representative_dataset(ds))

来自已经校准的模型的选择性量化

您可以直接调用 convert.mlir_quantize 从已校准的模型中获取选择性量化模型。当您想要校准一次模型,并尝试各种拒绝列表组合时,这将特别有用。

selective_quantized_model = convert.mlir_quantize(
    calibrated_model, denylisted_nodes=suspected_layers)
eval_tflite(selective_quantized_model, ds)