Cuantización de enteros posterior al entrenamiento

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Visión general

La cuantificación de enteros es una estrategia de optimización que convierte números de coma flotante de 32 bits (como pesos y salidas de activación) a los números de coma fija de 8 bits más cercanos. Esto resulta en un modelo más pequeño y mayor velocidad de la inferencia, que es valiosa para dispositivos de baja potencia tales como los microcontroladores . Este formato de datos también es requerido por número entero de sólo aceleradores tales como el Edge TPU .

En este tutorial, entrenar un modelo MNIST desde cero, convertirlo en un archivo Tensorflow Lite, y cuantifica usando cuantificación posterior al entrenamiento . Por último, comprobará la precisión del modelo convertido y lo comparará con el modelo flotante original.

De hecho, tiene varias opciones en cuanto a cuánto desea cuantificar un modelo. En este tutorial, realizará una "cuantificación completa de enteros", que convierte todos los pesos y salidas de activación en datos enteros de 8 bits, mientras que otras estrategias pueden dejar cierta cantidad de datos en punto flotante.

Para obtener más información sobre las diversas estrategias de cuantificación, lee la optimización modelo TensorFlow Lite .

Configuración

Para cuantificar los tensores de entrada y salida, necesitamos usar las API agregadas en TensorFlow r2.3:

import logging
logging.getLogger("tensorflow").setLevel(logging.DEBUG)

import tensorflow as tf
import numpy as np
assert float(tf.__version__[:3]) >= 2.3

Genera un modelo de TensorFlow

Vamos a construir un modelo simple de números classify del conjunto de datos MNIST .

Este entrenamiento no tomará mucho tiempo porque está entrenando el modelo por solo 5 épocas, lo que entrena a aproximadamente ~ 98% de precisión.

# Load MNIST dataset
mnist = tf.keras.datasets.mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

# Normalize the input image so that each pixel value is between 0 to 1.
train_images = train_images.astype(np.float32) / 255.0
test_images = test_images.astype(np.float32) / 255.0

# Define the model architecture
model = tf.keras.Sequential([
  tf.keras.layers.InputLayer(input_shape=(28, 28)),
  tf.keras.layers.Reshape(target_shape=(28, 28, 1)),
  tf.keras.layers.Conv2D(filters=12, kernel_size=(3, 3), activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(10)
])

# Train the digit classification model
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(
                  from_logits=True),
              metrics=['accuracy'])
model.fit(
  train_images,
  train_labels,
  epochs=5,
  validation_data=(test_images, test_labels)
)
2021-09-12 11:12:32.819553: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_SYSTEM_DRIVER_MISMATCH: system has unsupported display driver / cuda driver combination
2021-09-12 11:12:32.819712: E tensorflow/stream_executor/cuda/cuda_diagnostics.cc:313] kernel version 470.57.2 does not match DSO version 470.63.1 -- cannot find working devices in this configuration
Epoch 1/5
1875/1875 [==============================] - 10s 5ms/step - loss: 0.2801 - accuracy: 0.9208 - val_loss: 0.1311 - val_accuracy: 0.9640
Epoch 2/5
1875/1875 [==============================] - 9s 5ms/step - loss: 0.1110 - accuracy: 0.9684 - val_loss: 0.0896 - val_accuracy: 0.9730
Epoch 3/5
1875/1875 [==============================] - 10s 5ms/step - loss: 0.0814 - accuracy: 0.9768 - val_loss: 0.0708 - val_accuracy: 0.9765
Epoch 4/5
1875/1875 [==============================] - 10s 5ms/step - loss: 0.0678 - accuracy: 0.9803 - val_loss: 0.0619 - val_accuracy: 0.9790
Epoch 5/5
1875/1875 [==============================] - 9s 5ms/step - loss: 0.0590 - accuracy: 0.9826 - val_loss: 0.0594 - val_accuracy: 0.9799
<keras.callbacks.History at 0x7fe418a28ed0>

Convertir a un modelo de TensorFlow Lite

Ahora se puede convertir el modelo entrenado a formato TensorFlow Lite utilizando el TFLiteConverter API, y aplicar diferentes grados de cuantificación.

Tenga en cuenta que algunas versiones de cuantificación dejan algunos de los datos en formato flotante. Entonces, las siguientes secciones muestran cada opción con cantidades crecientes de cuantificación, hasta que obtenemos un modelo que es completamente de datos int8 o uint8. (Observe que duplicamos algo de código en cada sección para que pueda ver todos los pasos de cuantificación para cada opción).

Primero, aquí hay un modelo convertido sin cuantificación:

converter = tf.lite.TFLiteConverter.from_keras_model(model)

tflite_model = converter.convert()
2021-09-12 11:13:21.390430: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
INFO:tensorflow:Assets written to: /tmp/tmpcxslls_y/assets
2021-09-12 11:13:21.805193: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:351] Ignored output_format.
2021-09-12 11:13:21.805239: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:354] Ignored drop_control_dependency.

Ahora es un modelo de TensorFlow Lite, pero todavía usa valores flotantes de 32 bits para todos los datos de los parámetros.

Convertir usando cuantificación de rango dinámico

Ahora vamos a por defecto permiten la optimizations la bandera para cuantificar todos los parámetros fijos (como pesos):

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

tflite_model_quant = converter.convert()
INFO:tensorflow:Assets written to: /tmp/tmpz_srxxsd/assets
INFO:tensorflow:Assets written to: /tmp/tmpz_srxxsd/assets
2021-09-12 11:13:22.372952: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:351] Ignored output_format.
2021-09-12 11:13:22.372999: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:354] Ignored drop_control_dependency.

El modelo ahora es un poco más pequeño con pesos cuantificados, pero otros datos variables todavía están en formato flotante.

Convierta usando cuantización de respaldo flotante

Para cuantificar los datos variables (tales como entrada de modelo / salida y los intermedios entre las capas), es necesario proporcionar un RepresentativeDataset . Esta es una función generadora que proporciona un conjunto de datos de entrada que es lo suficientemente grande como para representar valores típicos. Permite al convertidor estimar un rango dinámico para todos los datos variables. (No es necesario que el conjunto de datos sea único en comparación con el conjunto de datos de entrenamiento o evaluación). Para admitir múltiples entradas, cada punto de datos representativo es una lista y los elementos de la lista se alimentan al modelo de acuerdo con sus índices.

def representative_data_gen():
  for input_value in tf.data.Dataset.from_tensor_slices(train_images).batch(1).take(100):
    # Model has only one input so each data point has one element.
    yield [input_value]

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

tflite_model_quant = converter.convert()
INFO:tensorflow:Assets written to: /tmp/tmpao6f2zfk/assets
INFO:tensorflow:Assets written to: /tmp/tmpao6f2zfk/assets
2021-09-12 11:13:22.933640: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:351] Ignored output_format.
2021-09-12 11:13:22.933678: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:354] Ignored drop_control_dependency.
fully_quantize: 0, inference_type: 6, input_inference_type: 0, output_inference_type: 0

Ahora todos los pesos y los datos variables están cuantificados, y el modelo es significativamente más pequeño en comparación con el modelo original de TensorFlow Lite.

Sin embargo, para mantener la compatibilidad con las aplicaciones que tradicionalmente usan tensores de entrada y salida del modelo flotante, TensorFlow Lite Converter deja los tensores de entrada y salida del modelo en flotación:

interpreter = tf.lite.Interpreter(model_content=tflite_model_quant)
input_type = interpreter.get_input_details()[0]['dtype']
print('input: ', input_type)
output_type = interpreter.get_output_details()[0]['dtype']
print('output: ', output_type)
input:  <class 'numpy.float32'>
output:  <class 'numpy.float32'>

Eso suele ser bueno para la compatibilidad, pero no será compatible con dispositivos que realizan solo operaciones basadas en números enteros, como Edge TPU.

Además, el proceso anterior puede dejar una operación en formato flotante si TensorFlow Lite no incluye una implementación cuantificada para esa operación. Esta estrategia permite que la conversión se complete para que tenga un modelo más pequeño y eficiente, pero nuevamente, no será compatible con hardware de solo enteros. (Todas las operaciones en este modelo MNIST tienen una implementación cuantificada).

Entonces, para garantizar un modelo de solo enteros de extremo a extremo, necesita un par de parámetros más ...

Convierta usando la cuantificación de solo enteros

Para cuantificar los tensores de entrada y salida, y hacer que el convertidor arroje un error si encuentra una operación que no puede cuantificar, convierta el modelo nuevamente con algunos parámetros adicionales:

def representative_data_gen():
  for input_value in tf.data.Dataset.from_tensor_slices(train_images).batch(1).take(100):
    yield [input_value]

converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_data_gen
# Ensure that if any ops can't be quantized, the converter throws an error
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
# Set the input and output tensors to uint8 (APIs added in r2.3)
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8

tflite_model_quant = converter.convert()
INFO:tensorflow:Assets written to: /tmp/tmpfu_lr0lq/assets
INFO:tensorflow:Assets written to: /tmp/tmpfu_lr0lq/assets
2021-09-12 11:13:24.044232: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:351] Ignored output_format.
2021-09-12 11:13:24.044271: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:354] Ignored drop_control_dependency.
fully_quantize: 0, inference_type: 6, input_inference_type: 3, output_inference_type: 3
WARNING:absl:For model inputs containing unsupported operations which cannot be quantized, the `inference_input_type` attribute will default to the original type.

La cuantificación interna sigue siendo la misma que la anterior, pero puede ver que los tensores de entrada y salida ahora tienen formato entero:

interpreter = tf.lite.Interpreter(model_content=tflite_model_quant)
input_type = interpreter.get_input_details()[0]['dtype']
print('input: ', input_type)
output_type = interpreter.get_output_details()[0]['dtype']
print('output: ', output_type)
input:  <class 'numpy.uint8'>
output:  <class 'numpy.uint8'>

Ahora usted tiene un modelo cuantizado número entero que utiliza enteros de datos para tensores de entrada y salida del modelo, por lo que es compatible con el hardware entero de sólo como el borde de TPU .

Guarde los modelos como archivos

Usted necesitará un .tflite archivo para desplegar su modelo en otros dispositivos. Así que guardemos los modelos convertidos en archivos y luego cargámoslos cuando hagamos inferencias a continuación.

import pathlib

tflite_models_dir = pathlib.Path("/tmp/mnist_tflite_models/")
tflite_models_dir.mkdir(exist_ok=True, parents=True)

# Save the unquantized/float model:
tflite_model_file = tflite_models_dir/"mnist_model.tflite"
tflite_model_file.write_bytes(tflite_model)
# Save the quantized model:
tflite_model_quant_file = tflite_models_dir/"mnist_model_quant.tflite"
tflite_model_quant_file.write_bytes(tflite_model_quant)
24280

Ejecute los modelos de TensorFlow Lite

Ahora vamos a ejecutar inferencias utilizando el TensorFlow Lite Interpreter para comparar la precisión de los modelos.

Primero, necesitamos una función que ejecute inferencia con un modelo e imágenes dados, y luego devuelva las predicciones:

# Helper function to run inference on a TFLite model
def run_tflite_model(tflite_file, test_image_indices):
  global test_images

  # Initialize the interpreter
  interpreter = tf.lite.Interpreter(model_path=str(tflite_file))
  interpreter.allocate_tensors()

  input_details = interpreter.get_input_details()[0]
  output_details = interpreter.get_output_details()[0]

  predictions = np.zeros((len(test_image_indices),), dtype=int)
  for i, test_image_index in enumerate(test_image_indices):
    test_image = test_images[test_image_index]
    test_label = test_labels[test_image_index]

    # Check if the input type is quantized, then rescale input data to uint8
    if input_details['dtype'] == np.uint8:
      input_scale, input_zero_point = input_details["quantization"]
      test_image = test_image / input_scale + input_zero_point

    test_image = np.expand_dims(test_image, axis=0).astype(input_details["dtype"])
    interpreter.set_tensor(input_details["index"], test_image)
    interpreter.invoke()
    output = interpreter.get_tensor(output_details["index"])[0]

    predictions[i] = output.argmax()

  return predictions

Pruebe los modelos en una imagen

Ahora compararemos el rendimiento del modelo flotante y el modelo cuantificado:

  • tflite_model_file es el modelo original TensorFlow Lite con datos de coma flotante.
  • tflite_model_quant_file es el último modelo que convierte usando cuantificación número entero de sólo (que utiliza datos uint8 para entrada y salida).

Creemos otra función para imprimir nuestras predicciones:

import matplotlib.pylab as plt

# Change this to test a different image
test_image_index = 1

## Helper function to test the models on one image
def test_model(tflite_file, test_image_index, model_type):
  global test_labels

  predictions = run_tflite_model(tflite_file, [test_image_index])

  plt.imshow(test_images[test_image_index])
  template = model_type + " Model \n True:{true}, Predicted:{predict}"
  _ = plt.title(template.format(true= str(test_labels[test_image_index]), predict=str(predictions[0])))
  plt.grid(False)

Ahora prueba el modelo de flotador:

test_model(tflite_model_file, test_image_index, model_type="Float")

png

Y pruebe el modelo cuantificado:

test_model(tflite_model_quant_file, test_image_index, model_type="Quantized")

png

Evaluar los modelos en todas las imágenes.

Ahora ejecutemos ambos modelos usando todas las imágenes de prueba que cargamos al comienzo de este tutorial:

# Helper function to evaluate a TFLite model on all images
def evaluate_model(tflite_file, model_type):
  global test_images
  global test_labels

  test_image_indices = range(test_images.shape[0])
  predictions = run_tflite_model(tflite_file, test_image_indices)

  accuracy = (np.sum(test_labels== predictions) * 100) / len(test_images)

  print('%s model accuracy is %.4f%% (Number of test samples=%d)' % (
      model_type, accuracy, len(test_images)))

Evalúe el modelo de flotador:

evaluate_model(tflite_model_file, model_type="Float")
Float model accuracy is 97.9900% (Number of test samples=10000)

Evalúe el modelo cuantificado:

evaluate_model(tflite_model_quant_file, model_type="Quantized")
Quantized model accuracy is 97.9800% (Number of test samples=10000)

Así que ahora tiene un modelo cuantificado entero con casi ninguna diferencia en la precisión, en comparación con el modelo flotante.

Para obtener más información sobre otras estrategias de cuantificación, lee la optimización modelo TensorFlow Lite .