Cuantificación posterior al entrenamiento

La cuantificació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 CPU y hardware, con poca degradación en la precisión del modelo. Puede cuantificar un modelo flotante de TensorFlow ya entrenado cuando lo convierte al formato TensorFlow Lite con TensorFlow Lite Converter .

Métodos de optimización

Hay varias opciones de cuantificación posteriores al entrenamiento para elegir. Aquí hay una tabla resumen de las opciones y los beneficios que brindan:

Técnica Beneficios Hardware
Cuantificación de rango dinámico 4x más pequeño, 2x-3x de aceleración UPC
Cuantificación completa de enteros 4x más pequeño, 3x+ de aceleración CPU, Edge TPU, Microcontroladores
Cuantificación de Float16 2 veces más pequeño, aceleración de GPU CPU, GPU

El siguiente árbol de decisiones puede ayudar a determinar qué método de cuantificación posterior al entrenamiento es mejor para su caso de uso:

opciones de optimización post-entrenamiento

Cuantificación de rango dinámico

La cuantificación del rango dinámico es un punto de partida recomendado porque proporciona un uso de memoria reducido y un cálculo más rápido sin tener que proporcionar un conjunto de datos representativo para la calibración. Este tipo de cuantificación cuantifica estáticamente solo los pesos de punto flotante a 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 de 8 bits y realizan cálculos con pesos y activaciones de 8 bits. Esta optimización proporciona latencias cercanas a las inferencias de punto completamente fijo. Sin embargo, las salidas aún se almacenan usando coma flotante, por lo que la mayor velocidad de las operaciones de rango dinámico es menor que un cálculo completo de punto fijo.

Cuantificación completa de enteros

Puede obtener más mejoras de latencia, reducciones en el uso máximo de memoria y compatibilidad con aceleradores o dispositivos de hardware de números enteros al asegurarse de que todas las matemáticas del modelo estén cuantificadas en números enteros.

Para la cuantificación completa de enteros, debe 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 las capas intermedias) y la salida del modelo, no se pueden calibrar a menos que ejecutemos algunos ciclos de inferencia. Como resultado, el convertidor requiere un conjunto de datos representativo para calibrarlos. Este conjunto de datos puede ser un pequeño subconjunto (alrededor de ~100-500 muestras) de los datos de entrenamiento o validación. Consulte la función representative_dataset() a continuación.

Desde la versión TensorFlow 2.7, puede especificar el conjunto de datos representativo a través de una firma como 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 TensorFlow dado, puede especificar el conjunto de datos múltiple especificando 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,
      },
    )

Puede 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 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 los tensores de entrada se puede invertir fácilmente.

Para fines de prueba, puede 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)]
 

Entero con respaldo flotante (usando la entrada/salida flotante predeterminada)

Para cuantificar completamente un modelo con números enteros, pero use operadores flotantes cuando no tengan una implementación de números enteros (para garantizar que la conversión se realice sin problemas), use 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
tflite_quant_model = converter.convert()

Solo entero

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

Además, para garantizar la compatibilidad con dispositivos de solo números enteros (como microcontroladores de 8 bits) y aceleradores (como Coral Edge TPU), puede aplicar la cuantificación completa de enteros 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()

Cuantificación de Float16

Puede reducir el tamaño de un modelo de coma flotante cuantificando los pesos a float16, el estándar IEEE para números de coma flotante de 16 bits. Para habilitar la cuantificación float16 de pesos, utilice los siguientes 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 cuantificación float16 son las siguientes:

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

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

  • No reduce la latencia tanto como una cuantificación matemática de punto fijo.
  • De forma predeterminada, un modelo cuantificado float16 "descuantificará" los valores de ponderación a float32 cuando se ejecute en la CPU. (Tenga en cuenta que el delegado de GPU no realizará esta descuantificación, ya que puede operar con datos float16).

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

Este es un esquema de cuantificación experimental. Es similar al esquema de "solo números enteros", pero las activaciones se cuantifican en función de su rango a 16 bits, los pesos se cuantifican en enteros de 8 bits y el sesgo se cuantifica en enteros de 64 bits. Esto se conoce como cuantificación 16x8 adicionalmente.

La principal ventaja de esta cuantificación es que puede mejorar significativamente la precisión, pero solo aumenta ligeramente 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 cuantificación 16x8 no es compatible con algunos operadores en el modelo, entonces el modelo aún puede cuantificarse, pero los operadores no admitidos se mantienen en flotación. La siguiente opción debe agregarse a target_spec para permitir esto.

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()

Ejemplos de casos de uso donde las mejoras de precisión proporcionadas por este esquema de cuantificación incluyen:

  • súper resolución,
  • procesamiento de señales de audio, como cancelación de ruido y formación de haces,
  • eliminación de ruido de la imagen,
  • Reconstrucción HDR a partir de una sola imagen.

La desventaja de esta cuantización es:

  • Actualmente, la inferencia es notablemente más lenta que un entero completo de 8 bits debido a la falta de implementación optimizada del kernel.
  • Actualmente es incompatible con los delegados TFLite acelerados por hardware existentes.

Puede encontrar un tutorial para este modo de cuantificación aquí .

Precisión del modelo

Dado que los pesos se cuantifican después del entrenamiento, podría haber una pérdida de precisión, especialmente para redes más pequeñas. Se proporcionan modelos preentrenados totalmente cuantificados para redes específicas en TensorFlow Hub . Es importante verificar la precisión del modelo cuantificado para verificar que cualquier degradación en la precisión esté dentro de los límites aceptables. Hay herramientas para evaluar la precisión del modelo de TensorFlow Lite .

Como alternativa, si la caída de la precisión es demasiado alta, considere usar el entrenamiento consciente de la cuantificación . Sin embargo, hacerlo requiere modificaciones durante el entrenamiento del modelo para agregar nodos de cuantificación falsos, mientras que las técnicas de cuantificación posteriores al entrenamiento en esta página usan un modelo preentrenado existente.

Representación para tensores cuantizados

La cuantificación de 8 bits aproxima los valores de punto flotante utilizando la siguiente fórmula.

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

La representación tiene dos partes principales:

  • Pesos por eje (también conocido como por canal) o por tensor representados por valores de complemento a dos int8 en el rango [-127, 127] con punto cero igual a 0.

  • Activaciones/entradas por tensor representadas por valores de complemento a 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 cuantificación, consulte nuestra especificación de cuantificación . Se recomienda a los proveedores de hardware que deseen conectarse a la interfaz de delegado de TensorFlow Lite que implementen el esquema de cuantificación que se describe allí.