# TensorFlow Lite Quantization Debugger

[TOC]

## Overview

When a quantized model is produced, it requires tedious and manual custom code
to debug the model in order to:

1. Verify if the quantized model is working as expected (spot errors, check
   accuracy, etc).
2. Compare the quantized model and the original float model.

This is now feasible using the TensorFlow Lite Quantization Debugger, as shown
below.

Note: Currently, this workflow is only supported for full integer (int8)
quantization. The debug model produced using this workflow should only be used
for debugging purposes only (and not for inference).

## Analysis with quantized model only

### Produce a debug model

Modify the
[TFLite full integer (int8) quantization steps](https://www.tensorflow.org/lite/performance/post_training_quantization#full_integer_quantization)
as shown below to produce a debug model (used for debugging purposes only, and
not inference)

#### How does this work?

With the help of the MLIR quantizer's debug mode feature, the debug model
produced has both the original float operators (or ops) and the quantized ops.
Additionally, `NumericVerify` ops are added to compare the outputs of the
original float and quantized ops and to also collect statistics. It has the name
in the format of `NumericVerify/{original tensor name}:{original tensor id}`

```python
# for mlir_quantize
from tensorflow.lite.python import convert

# set full-integer quantization parameters as usual.
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.representative_dataset = calibration_gen

# Create a TFLite model with new quantizer and numeric verify ops. Rather than
# calling convert() only, calibrate model first and call `mlir_quantize` to run
# the actual quantization, with `enable_numeric_verify` set to `True`.
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter._experimental_calibrate_only = True
calibrated = converter.convert()
return convert.mlir_quantize(calibrated, enable_numeric_verify=True)
```

### Run debugger with debug model

Initialize debugger with the debug model. This can be done in two ways.

```python
from tensorflow.lite.tools.optimize.debugging.python import debugger

# `debug_dataset` accpets the same type as `converter.representative_dataset`.
quant_debugger = debugger.QuantizationDebugger(
    quant_debug_model_content=quant_debug_model,
    debug_dataset=data_gen)

# OR

quant_debugger = debugger.QuantizationDebugger(
    quant_debug_model_path='/path/to/debug_model.tflite',
    debug_dataset=data_gen)

quant_debugger.run()
```

### Inspect statistics

When you call `quant_debugger.run()`, `quant_debugger.layer_statistics` is
filled with aggregated statistics for each `NumericVerify` ops. Some metrics
(i.e. stddev, mean square error) are calculated by default.

#### Example output
```python
# `quant_debugger.layer_statistics.metrics` is defaultdict, convert it to dict
# for readable output.
import pprint
for layer_name, metrics in quant_debugger.layer_statistics.items():
  print(layer_name)
  pprint.pprint(dict(metrics))
```

```python
# ...

NumericVerify/sequential/dense/MatMul;sequential/dense/BiasAdd3:77
{'max_abs_error': 0.05089309,
 'mean_error': -0.00017149668,
 'mean_squared_error': 0.00040816222,
 'num_elements': 256.0,
 'stddev': 0.02009948}
NumericVerify/sequential/dense_1/MatMul;sequential/dense_1/BiasAdd3:81
{'max_abs_error': 0.09744112,
 'mean_error': 0.0048679365,
 'mean_squared_error': 0.0036721828,
 'num_elements': 10.0,
 'stddev': 0.055745363}
NumericVerify/Identity2:85
{'max_abs_error': 0.0036417267,
 'mean_error': -0.00068773015,
 'mean_squared_error': 3.439951e-06,
 'num_elements': 10.0,
 'stddev': 0.0016223773}

# ...
```

## Adding custom metrics

More metrics can be added by passing `QuantizationDebugOptions` to the
initializer. For example, if you want to add mean absolute error, use following
snippet.

```python
debug_options = debugger.QuantizationDebugOptions(
    layer_debug_metrics={
        'mean_abs_error': lambda diffs: np.mean(np.abs(diffs))
    })

quant_debugger = debugger.QuantizationDebugger(
    quant_debug_model_content=quant_debug_model,
    debug_dataset=data_gen,
    debug_options=debug_options
)
quant_debugger.run()
```

Now `quant_debugger.layer_statistics` includes mean absoulte error for each
layer.

## Analysis with float and quantized models

In addition to single model analysis, the output of original float model and
quantized model can be compared when both models are given. This can be done
by providing a float model, and metrics to compare outputs. This can be `argmax`
for classification models, bit for more complex models like detection more
complicated logic should be given.

```python
# functions for model_debug_metrics gets all output tensors from float and
# quantized models, and returns a single metric value.
debug_options = debugger.QuantizationDebugOptions(
    model_debug_metrics={
        'argmax_accuracy': lambda f, q: np.argmax(f[0]) == np.argmax(q[0])
    })

float_model = converter.convert()  # converted without any optimizations.

quant_debugger = debugger.QuantizationDebugger(
    quant_debug_model_content=quant_debug_model,
    float_model_content=float_model,  # can pass `float_model_path` instead.
    debug_dataset=data_gen,
    debug_options=debug_options
)
quant_debugger.run()
```

The result is a single number per metric, so it's easier to inspect.

```python
>>> quant_debugger.model_statistics
{'argmax_accuracy': 0.89}
```

## Advanced usage: Export stats to csv, and import to pandas

`quant_debugger.layer_statistics_dump` function accepts file-like object, and
exports layer statistics to csv. This can be imported to other tools like
`pandas` for further processing. The exported data also has name of the op,
originating tensor ID, and quantization parameters (scales and zero points) for
quantized layer.

Note: scales and zero points are lists, and imported to `pandas` as text by
default. Additional processing to parse them is required before processing.

```python
import pandas as pd
import yaml  # used to parse lists

with open('/path/to/stats.csv', 'w') as f:
  quant_debugger.layer_statistics_dump(f)

data = pd.read_csv('/path/to/stats.csv', converters={
      'scales': yaml.safe_load, 'zero_points': yaml.safe_load})
```
