Se usó la API de Cloud Translation para traducir esta página.
Switch to English

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 da como resultado un modelo más pequeño y una mayor velocidad de inferencia, lo que es valioso para dispositivos de baja potencia como los microcontroladores . Este formato de datos también lo requieren los aceleradores de solo enteros como Edge TPU .

En este tutorial, entrenará un modelo MNIST desde cero, lo convertirá en un archivo Tensorflow Lite y lo cuantificará mediante la cuantificación posterior al entrenamiento . Finalmente, 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, lea sobre la optimización del modelo de TensorFlow Lite .

Preparar

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

Construiremos un modelo simple para clasificar números 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)
)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
Epoch 1/5
1875/1875 [==============================] - 5s 2ms/step - loss: 0.2793 - accuracy: 0.9227 - val_loss: 0.1392 - val_accuracy: 0.9618
Epoch 2/5
1875/1875 [==============================] - 5s 2ms/step - loss: 0.1179 - accuracy: 0.9667 - val_loss: 0.0928 - val_accuracy: 0.9719
Epoch 3/5
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0860 - accuracy: 0.9754 - val_loss: 0.0742 - val_accuracy: 0.9755
Epoch 4/5
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0691 - accuracy: 0.9796 - val_loss: 0.0686 - val_accuracy: 0.9776
Epoch 5/5
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0589 - accuracy: 0.9823 - val_loss: 0.0654 - val_accuracy: 0.9787

<tensorflow.python.keras.callbacks.History at 0x7f69e0275a58>

Convertir a un modelo de TensorFlow Lite

Ahora puede convertir el modelo entrenado al formato TensorFlow Lite con la API TFLiteConverter y aplicar diversos grados de cuantificación.

Tenga en cuenta que algunas versiones de cuantificación dejan algunos 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()

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

Convertir utilizando cuantificación de rango dinámico

Ahora habilitemos la marca de optimizations predeterminada para cuantificar todos los parámetros fijos (como los 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/tmpcojyiqri/assets

INFO:tensorflow:Assets written to: /tmp/tmpcojyiqri/assets

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 (como la entrada / salida del modelo y los intermedios entre capas), debe proporcionar un conjunto de datos RepresentativeDataset . Esta es una función generadora que proporciona un conjunto de datos de entrada 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 entradas múltiples, 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/tmp1bvfr71i/assets

INFO:tensorflow:Assets written to: /tmp/tmp1bvfr71i/assets

Ahora todos los pesos y 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 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/tmpvnuxq9pa/assets

INFO:tensorflow:Assets written to: /tmp/tmpvnuxq9pa/assets

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 tiene un modelo cuantificado de enteros que usa datos enteros para los tensores de entrada y salida del modelo, por lo que es compatible con hardware solo de enteros como Edge TPU .

Guarde los modelos como archivos

Necesitará un archivo .tflite para implementar 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)
24720

Ejecuta los modelos de TensorFlow Lite

Ahora ejecutaremos inferencias con el Interpreter TensorFlow Lite para comparar las precisiones del modelo.

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 de TensorFlow Lite con datos de punto flotante.
  • tflite_model_quant_file es el último modelo que convertimos usando la cuantificación de números enteros (usa 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.8700% (Number of test samples=10000)

Evalúe el modelo cuantificado:

evaluate_model(tflite_model_quant_file, model_type="Quantized")
Quantized model accuracy is 97.8100% (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, lea sobre la optimización del modelo de TensorFlow Lite .