Resumen

Este codelab fue creado para explorar algunas técnicas de optimización y su implementación en una ESP32. Se espera que usted al finalizar esté en capacidad de:

  • Reconocer las diferentes técnicas de optimización de parámetros en Sistemas Embebidos identificando posibles formas de implementación en una plataforma ESP32.

Fecha de Creación:

2024/03/01

Última Actualización:

2024/03/01

Requisitos Previos:

Adaptado de:

Referencias:

Escrito por:

Fredy Segura-Quijano

La cuantización es una de las técnicas más comunes y efectivas para optimizar modelos en TensorFlow Lite. La cuantización es una técnica que reduce la precisión de los pesos y las activaciones de un modelo de inteligencia artificial, generalmente de números de punto flotante de 32 bits (float32) a enteros de 8 bits (int8). Esta técnica es altamente efectiva para reducir el tamaño del modelo y mejorar la velocidad de inferencia sin una pérdida significativa de precisión. La cuantización puede ser aplicada de diferentes maneras, incluyendo la cuantización post-entrenamiento y la cuantización durante el entrenamiento. En la cuantización post-entrenamiento, el modelo es primero entrenado con precisión completa y luego convertido a un formato cuantizado. La cuantización durante el entrenamiento, por otro lado, incorpora el proceso de reducción de precisión en el flujo de trabajo de entrenamiento, permitiendo que el modelo se adapte mejor a las limitaciones de precisión reducida.

Existen varios tipos de cuantización:

La Cuantización Post-Entrenamiento (Post-Training Quantization) reduce la precisión de los pesos del modelo de flotantes de 32 bits a enteros de 8 bits después de entrenar el modelo. Esto reduce el tamaño del modelo y mejora la velocidad de inferencia. También existe la Cuantización Dinámica (Dynamic Range Quantization) que cuantiza solo los pesos y los convierte de flotantes de 32 bits a enteros de 8 bits. Las activaciones permanecen en flotante de 32 bits, lo que puede mejorar la velocidad de inferencia sin una gran pérdida de precisión.

La Cuantización de Peso Completo (Full Integer Quantization) cuantiza tanto los pesos como las activaciones a enteros de 8 bits. Esto es útil para dispositivos que solo soportan operaciones de enteros y finalmente se tiene la Cuantización Flotante de 16 bits (Float16 Quantization) que convierte los pesos de flotantes de 32 bits a flotantes de 16 bits, lo que reduce el tamaño del modelo y mantiene una precisión cercana al modelo original.

Cada tipo de cuantización ofrece diferentes balances entre reducción de tamaño, mejora de velocidad y conservación de precisión, permitiendo una mayor flexibilidad para adaptarse a diversas aplicaciones y limitaciones de hardware.

A continuación, se presentan ejemplos de código que ilustran cómo aplicar las diferentes técnicas de optimización en TensorFlow Lite.

1. Cuantización Post-Entrenamiento

La cuantización post-entrenamiento es una técnica que convierte los pesos de un modelo de flotante de 32 bits a enteros de 8 bits después del entrenamiento.

import tensorflow as tf

# Cargar el modelo entrenado
model = tf.keras.models.load_model('mnist_model.h5')

# Convertir el modelo a TensorFlow Lite con cuantización post-entrenamiento
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()

# Guardar el modelo cuantizado
with open('mnist_model_quant.tflite', 'wb') as f:
    f.write(tflite_model)

2. Cuantización Dinámica

La cuantización dinámica cuantiza los pesos a enteros de 8 bits, pero mantiene las activaciones en flotantes de 32 bits.

import tensorflow as tf

# Cargar el modelo entrenado
model = tf.keras.models.load_model('mnist_model.h5')

# Convertir el modelo a TensorFlow Lite con cuantización dinámica
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.representative_dataset = representative_data_gen
tflite_model = converter.convert()

# Guardar el modelo cuantizado
with open('mnist_model_dynamic_quant.tflite', 'wb') as f:
    f.write(tflite_model)

3. Cuantización Flotante de 16 bits

La cuantización flotante de 16 bits reduce los pesos de flotantes de 32 bits a flotantes de 16 bits.c

import tensorflow as tf

# Cargar el modelo entrenado
model = tf.keras.models.load_model('mnist_model.h5')

# Convertir el modelo a TensorFlow Lite con cuantización flotante de 16 bits
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
tflite_model = converter.convert()

# Guardar el modelo cuantizado
with open('mnist_model_float16.tflite', 'wb') as f:
    f.write(tflite_model)

La poda es otra técnica de optimización que elimina pesos, conexiones o unidades completas que son consideradas innecesarias o redundantes en una red neuronal. Existen dos tipos principales de poda: la poda estructural y la poda no estructural. La poda estructural elimina componentes completos del modelo, como neuronas o filtros de capas convolucionales, mientras que la poda no estructural elimina pesos individuales sin eliminar completamente las unidades a las que pertenecen. La poda puede reducir significativamente el tamaño del modelo y mejorar su eficiencia sin una gran pérdida de precisión, ya que los elementos eliminados contribuyen mínimamente a la salida final del modelo.

Técnicas de Poda

La poda puede ser implementada utilizando herramientas como el TensorFlow Model Optimization Toolkit, que facilita la integración de estas técnicas en el flujo de trabajo de entrenamiento y optimización de modelos.

A continuación, se presentan ejemplos de código que ilustran cómo aplicar las diferentes técnicas de optimización en TensorFlow Lite.

1. Pruning (Poda)

La poda elimina conexiones innecesarias en la red neuronal para reducir el tamaño del modelo. Se utiliza TensorFlow Model Optimization Toolkit para realizar la poda.

import tensorflow as tf
import tensorflow_model_optimization as tfmot

# Cargar el modelo entrenado
model = tf.keras.models.load_model('mnist_model.h5')

# Aplicar poda
prune_low_magnitude = tfmot.sparsity.keras.prune_low_magnitude

# Definir el modelo podado
pruning_params = {
    'pruning_schedule': tfmot.sparsity.keras.PolynomialDecay(initial_sparsity=0.50,
                                                             final_sparsity=0.80,
                                                             begin_step=2000,
                                                             end_step=6000)
}

model_for_pruning = prune_low_magnitude(model, **pruning_params)

# Compilar el modelo podado
model_for_pruning.compile(optimizer='adam',
                          loss=tf.keras.losses.categorical_crossentropy,
                          metrics=['accuracy'])

# Entrenar el modelo podado
model_for_pruning.fit(x_train, y_train,
                      batch_size=128,
                      epochs=2,
                      validation_split=0.1)

# Eliminar las operaciones de poda del modelo
model_for_export = tfmot.sparsity.keras.strip_pruning(model_for_pruning)

# Convertir el modelo podado a TensorFlow Lite
converter = tf.lite.TFLiteConverter.from_keras_model(model_for_export)
tflite_model = converter.convert()

# Guardar el modelo podado
with open('mnist_model_pruned.tflite', 'wb') as f:
    f.write(tflite_model)

La destilación de conocimiento es una técnica en la que un modelo grande y preciso (conocido como el maestro) entrena a un modelo más pequeño y eficiente (conocido como el estudiante). Durante este proceso, el modelo estudiante aprende a imitar el comportamiento del modelo maestro, capturando la mayoría de sus capacidades mientras reduce significativamente el tamaño y la complejidad. Esta técnica es especialmente útil cuando se requiere desplegar modelos en dispositivos con recursos limitados, ya que permite mantener una alta precisión con un modelo mucho más compacto.

Proceso de Destilación: El proceso de destilación de conocimiento generalmente involucra los siguientes pasos:

Esta técnica puede ser implementada utilizando frameworks de machine learning como TensorFlow y PyTorch, que proporcionan herramientas para la implementación de destilación de conocimiento.

A continuación, se presentan ejemplos de código que ilustran cómo aplicar las diferentes técnicas de optimización en TensorFlow Lite.

1. Knowledge Distillation

La destilación de conocimiento implica entrenar un modelo pequeño para imitar el comportamiento de un modelo grande.

import tensorflow as tf

# Cargar el modelo maestro entrenado
teacher_model = tf.keras.models.load_model('mnist_model_large.h5')

# Definir el modelo estudiante
student_model = Sequential([
    Flatten(input_shape=(28, 28)),
    Dense(64, activation='relu'),
    Dense(10, activation='softmax')
])

# Compilar el modelo estudiante
student_model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

# Entrenar el modelo estudiante con el conocimiento del modelo maestro
student_model.fit(x_train, teacher_model.predict(x_train),
                  batch_size=128,
                  epochs=5,
                  validation_data=(x_test, y_test))

# Convertir el modelo estudiante a TensorFlow Lite
converter = tf.lite.TFLiteConverter.from_keras_model(student_model)
tflite_model = converter.convert()

# Guardar el modelo estudiante
with open('mnist_model_student.tflite', 'wb') as f:
    f.write(tflite_model)

Existen otras técnicas para optimizar los modelos entre las cuales se destacan la reducción y compresión del Modelo. Por ejemplo en TensorFlow Lite Micro se pueden implementar modelos ligeros en ESP32. Estas librerías soportan técnicas de reducción de tamaño y optimización. También existe PyTorch Mobile que ofrece herramientas para convertir y optimizar modelos para dispositivos móviles y embebidos, aunque su compatibilidad con ESP32 es menos directa que TensorFlow Lite.

Otras alternativas permiten usar técnicas de inferencia eficiente como por ejemplo Inferencia Diferida (Lazy Evaluation) en donde solo se calculan las partes del modelo necesarias para la predicción actual. También se puede hacer Inferencia en Tiempo Real en donde se ajusta el modelo para que opere en modo tiempo real, reduciendo la latencia de la inferencia.

Algunas alternativas más extremas como la Binarización y Ternarización utilizan la reducción de pesos a valores binarios (0 y 1) o ternarios (-1, 0 y 1) lo cual puede disminuir el tamaño del modelo y acelerar las operaciones, aunque generalmente con una mayor pérdida de precisión que la cuantización, lo cual tiene sentido por la conversión y el valor matemático resultante en cada elemento del modelo. Finalmente existen algunos Algoritmos y Arquitecturas Eficientes como Modelos Compactos que son uso de modelos diseñados específicamente para eficiencia, como MobileNet, SqueezeNet, o TinyML.

No sobra destacar que buenas técnicas de software como optimización del código al escribir código eficiente y optimizado para el microcontrolador al igual que uso de Bibliotecas optimizadas para el hardware específico del ESP32; serán una buena forma de optimizar y mejorar los tiempos de inferencia de los modelos.

Fusión de Operaciones (Operator Fusion):

La fusión de operaciones es una técnica de optimización que combina múltiples operaciones consecutivas en una sola operación optimizada. Esta técnica reduce la sobrecarga de procesamiento y mejora la eficiencia del modelo al minimizar las operaciones redundantes y la comunicación entre componentes del hardware. Por ejemplo, una secuencia de operaciones de convolución, normalización y activación puede ser fusionada en una única operación, lo que reduce el tiempo de inferencia y el consumo de energía. Los beneficios de la Fusión de Operaciones son:

TensorFlow Lite realiza automáticamente la fusión de operaciones durante la conversión del modelo, optimizando el gráfico computacional para una ejecución más rápida y eficiente en dispositivos embebidos.

Optimización a Nivel de Hardware:

Optimizar el modelo para aprovechar las capacidades del hardware específico del dispositivo embebido puede mejorar significativamente su rendimiento. Los aceleradores de hardware, como las Unidades de Procesamiento de Tensores (TPUs), las Unidades de Procesamiento de Señales Digitales (DSPs) y las Unidades de Procesamiento Neuronal (NPUs), están diseñados para realizar operaciones de inferencia de IA de manera más eficiente que las CPUs generales.

Uso de Aceleradores de Hardware:

Configurar y programar estos aceleradores puede reducir el tiempo de inferencia y el consumo de energía, mejorando el rendimiento general del sistema.