Cuantización posterior al entrenamiento

La cuantización posterior al entrenamiento es una técnica de conversión que puede reducir el tamaño del modelo y, al mismo tiempo, mejorar la latencia del acelerador de hardware y la CPU, con poca degradación en la exactitud del modelo. Puedes cuantizar un modelo de TensorFlow de número de punto flotante ya entrenado cuando lo conviertes al formato de TensorFlow Lite con el convertidor de TensorFlow Lite.

Métodos de optimización

Existen varias opciones de cuantización posterior al entrenamiento entre las que puedes elegir. A continuación, se muestra una tabla de resumen de las opciones y los beneficios que proporcionan:

Técnica Beneficios Hardware
La cuantización de rango dinámico 4 veces más pequeño, 2 o 3 veces más rápido CPU
Cuantización de números enteros completa 4 veces más pequeño, 3 veces más rápido CPU, Edge TPU, microcontroladores
Cuantización de float16 2 veces más pequeña, la aceleración de la GPU CPU, GPU

El siguiente árbol de decisión puede ayudar a determinar qué método de cuantización posterior al entrenamiento es el mejor para tu caso práctico:

opciones de optimización posteriores al entrenamiento

Sin cuantización

Convertir a un modelo de TFLite sin cuantización es un punto de partida recomendado. Esto generará un modelo de TFLite de número de punto flotante.

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
tflite_quant_model = converter.convert()

Te recomendamos que lo hagas como un paso inicial para verificar que los operadores del modelo de TF original sean compatibles con TFLite y que también se puedan usar como modelo de referencia para depurar errores de cuantización ingresados por métodos de cuantización posteriores al entrenamiento. Por ejemplo, si un modelo de TFLite cuantizado produce resultados inesperados, mientras que el modelo de TFLite flotante es exacto, podemos reducir el problema a errores ingresados por la versión cuantizada de los operadores de TFLite.

Cuantización de rango dinámico

La cuantización de rango dinámico proporciona un uso de memoria reducido y un procesamiento más rápido sin que tengas que proporcionar un conjunto de datos representativo para la calibración. Este tipo de cuantización cuantiza estáticamente solo los pesos del punto flotante a un número entero en el momento de la conversión, lo que proporciona 8 bits de precisión:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()

Para reducir aún más la latencia durante la inferencia, los operadores de "rango dinámico" cuantifican dinámicamente las activaciones en función de su rango a 8 bits y realizan cálculos con activaciones y ponderaciones de 8 bits. Esta optimización proporciona latencias cercanas a las inferencias de punto fijo por completo. Sin embargo, las salidas aún se almacenan con punto flotante, por lo que la mayor velocidad de las operaciones de rango dinámico es menor que un cálculo completo de punto fijo.

Cuantización completa de números enteros

Puedes obtener más mejoras en la latencia, reducciones en el uso máximo de memoria y compatibilidad con dispositivos de hardware o aceleradores de solo números enteros si te aseguras de que todos los cálculos del modelo estén cuantificados en números enteros.

Para una cuantización completa de números enteros, debes calibrar o estimar el rango, es decir, (mín., máx.) de todos los tensores de punto flotante en el modelo. A diferencia de los tensores constantes, como los pesos y los sesgos, los tensores variables como la entrada del modelo, las activaciones (salidas de capas intermedias) y la salida del modelo no se pueden calibrar a menos que ejecutemos algunos ciclos de inferencia. Como resultado, el conversor requiere un conjunto de datos representativo para calibrarlos. Este conjunto de datos puede ser un pequeño subconjunto (alrededor de 100 a 500 muestras) de los datos de entrenamiento o validación. Consulta la función representative_dataset() a continuación.

A partir de la versión de TensorFlow 2.7, puedes especificar el conjunto de datos representativo a través de una firma, como en el siguiente ejemplo:

def representative_dataset():
  for data in dataset:
    yield {
      "image": data.image,
      "bias": data.bias,
    }

Si hay más de una firma en el modelo de TensorFlow dado, puedes especificar los conjuntos de datos múltiples mediante la especificación de las claves de firma:

def representative_dataset():
  # Feed data set for the "encode" signature.
  for data in encode_signature_dataset:
    yield (
      "encode", {
        "image": data.image,
        "bias": data.bias,
      }
    )

  # Feed data set for the "decode" signature.
  for data in decode_signature_dataset:
    yield (
      "decode", {
        "image": data.image,
        "hint": data.hint,
      },
    )

Puedes generar el conjunto de datos representativo proporcionando una lista de tensores de entrada:

def representative_dataset():
  for data in tf.data.Dataset.from_tensor_slices((images)).batch(1).take(100):
    yield [tf.dtypes.cast(data, tf.float32)]

Desde la versión de TensorFlow 2.7, recomendamos usar el enfoque basado en firmas en lugar del enfoque basado en listas de tensores de entrada porque el orden de tensores de entrada se puede invertir con facilidad.

Para realizar pruebas, puedes usar un conjunto de datos ficticio de la siguiente manera:

def representative_dataset():
    for _ in range(100):
      data = np.random.rand(1, 244, 244, 3)
      yield [data.astype(np.float32)]
 

Número entero con resguardo de número de punto flotante (con entrada/salida de número de punto flotante predeterminada)

Para cuantizar un modelo con números enteros, pero usar operadores flotantes cuando no tienen una implementación de números enteros (para garantizar que la conversión se realice sin problemas), sigue estos pasos:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
tflite_quant_model = converter.convert()

Solo números enteros

La creación de modelos de solo números enteros es un caso de uso común de TensorFlow Lite para microcontroladores y TPU de Coral Edge.

Además, para garantizar la compatibilidad con dispositivos que solo pueden ser enteros (como microcontroladores de 8 bits) y aceleradores (como Coral Edge TPU), puedes aplicar la cuantización de enteros completa para todas las operaciones, incluidas la entrada y la salida, mediante los siguientes pasos:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8  # or tf.uint8
converter.inference_output_type = tf.int8  # or tf.uint8
tflite_quant_model = converter.convert()

Cuantización de float16

Puedes reducir el tamaño de un modelo de punto flotante si cuantificas los pesos a float16, el estándar IEEE para números de punto flotante de 16 bits. Para habilitar la cuantización de pesos de float16, sigue estos pasos:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
tflite_quant_model = converter.convert()

Las ventajas de la cuantización de float16 son las siguientes:

  • Reduce el tamaño del modelo hasta la mitad (ya que todos los pesos se convierten en la mitad de su tamaño original).
  • provoca una pérdida mínima de exactitud.
  • Admite algunos delegados (p.ej., el delegado de GPU) que pueden operar directamente en datos de float16, lo que da como resultado una ejecución más rápida que los cálculos de float32.

Las desventajas de la cuantización de float16 son las siguientes:

  • No reduce la latencia tanto como una cuantización a una matemática de puntos fijos.
  • De forma predeterminada, un modelo cuantificado de float16 “descuantizará” los valores de las ponderaciones a float32 cuando se ejecute en la CPU. (Ten en cuenta que el delegado de la GPU no realizará esta descuantización, ya que puede operar en datos de float16).

Solo números enteros: activaciones de 16 bits con ponderaciones de 8 bits (experimental)

Este es un esquema de cuantización experimental. Es similar al esquema de "solo números enteros", pero las activaciones se cuantizan en función de su rango a 16 bits, los pesos se cuantifican en números enteros de 8 bits y la ordenada al origen de un número entero de 64 bits. Esto también se denomina cuantización de 16 × 8.

La ventaja principal de esta cuantización es que puede mejorar la exactitud de forma significativa, pero solo aumenta un poco el tamaño del modelo.

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.representative_dataset = representative_dataset
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8]
tflite_quant_model = converter.convert()

Si la cuantización de 16 × 8 no es compatible con algunos operadores del modelo, el modelo aún se puede cuantificar, pero los operadores no admitidos se mantienen en el punto flotante. Se debe agregar la siguiente opción a target_spec para permitirlo.

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.representative_dataset = representative_dataset
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8,
tf.lite.OpsSet.TFLITE_BUILTINS]
tflite_quant_model = converter.convert()

Entre los ejemplos de los casos de uso en los que las mejoras en la exactitud proporcionadas por este esquema de cuantización incluyen los siguientes:

  • super-resolution,
  • para el procesamiento de señales de audio, como la cancelación de ruido y la conformación de haces
  • quitar ruido de imagen,
  • Reconstrucción HDR a partir de una sola imagen.

La desventaja de esta cuantización es la siguiente:

  • En la actualidad, la inferencia es notablemente más lenta que los números enteros completos de 8 bits debido a la falta de una implementación optimizada del kernel.
  • En la actualidad, no es compatible con los delegados existentes de TFLite con aceleración de hardware.

Puedes encontrar un instructivo para este modo de cuantización aquí.

Precisión del modelo

Debido a que los pesos están cuantificados después del entrenamiento, podría haber una pérdida de precisión, en especial para redes más pequeñas. Se proporcionan modelos previamente entrenados y cuantificados por completo para redes específicas en TensorFlow Hub. Es importante verificar la exactitud del modelo cuantizado para comprobar que cualquier degradación de la exactitud se encuentre dentro de límites aceptables. Existen herramientas para evaluar la exactitud del modelo de TensorFlow Lite.

Como alternativa, si la disminución de exactitud es demasiado alta, considera usar el entrenamiento con cuantización. Sin embargo, hacer esto requiere modificaciones durante el entrenamiento de modelos para agregar nodos de cuantización falsos, mientras que las técnicas de cuantización posteriores al entrenamiento que se describen en esta página usan un modelo existente previamente entrenado.

Representación de tensores cuantificados

La cuantización de 8 bits se aproxima a los valores de punto flotante con la siguiente fórmula.

\[real\_value = (int8\_value - zero\_point) \times scale\]

La representación tiene dos partes principales:

  • Ponderaciones por eje (es decir, por canal) o por tensor representadas por los valores del complemento de dos int8 en el rango [-127, 127] con un punto cero igual a 0.

  • Activaciones o entradas por tensor representadas por los valores del complemento de dos int8 en el rango [-128, 127], con un punto cero en el rango [-128, 127].

Para obtener una vista detallada de nuestro esquema de cuantización, consulta nuestras especificaciones de cuantización. Se recomienda que los proveedores de hardware que deseen conectarse a la interfaz de delegado de TensorFlow Lite implementen el esquema de cuantización que se describe allí.