Capítulo 8: Aprendizaje Automático en la Nube y Computación en el Borde
8.2 Introducción a TensorFlow Lite y ONNX para Dispositivos de Borde
El rápido avance de la computación en el borde ha revolucionado la implementación de modelos de machine learning en una amplia gama de dispositivos, incluidos teléfonos inteligentes, tabletas, wearables y dispositivos IoT. Este cambio hacia la IA basada en el borde presenta tanto oportunidades como desafíos, ya que estos dispositivos suelen tener limitaciones en términos de recursos computacionales, capacidad de memoria y consumo de energía, que no están presentes en las infraestructuras basadas en la nube.
Para abordar estas limitaciones y permitir una IA eficiente en el borde, han surgido frameworks especializados como TensorFlow Lite (TFLite) y ONNX (Open Neural Network Exchange). Estas potentes herramientas proporcionan a los desarrolladores los medios para optimizar, convertir y ejecutar modelos de machine learning en dispositivos de borde con una eficiencia notable.
Al minimizar el overhead y maximizar el rendimiento, TFLite y ONNX son fundamentales para llevar capacidades de IA sofisticadas a entornos con recursos limitados, abriendo nuevas posibilidades para aplicaciones inteligentes en el borde en diversas industrias.
8.2.1 TensorFlow Lite (TFLite)
TensorFlow Lite (TFLite) es un framework poderoso específicamente diseñado para implementar modelos de machine learning en dispositivos con recursos limitados, como teléfonos inteligentes, dispositivos IoT y sistemas embebidos. Ofrece una suite completa de herramientas y optimizaciones que permiten a los desarrolladores reducir significativamente el tamaño del modelo y mejorar la velocidad de inferencia, manteniendo al mismo tiempo un alto grado de precisión.
El flujo de trabajo de TensorFlow Lite consta de dos etapas principales:
- Conversión y optimización del modelo:
Esta fase crucial implica transformar un modelo estándar de TensorFlow en un formato optimizado para TensorFlow Lite. El proceso utiliza el sofisticado TFLite Converter, que emplea varias técnicas para optimizar el modelo:
- Cuantización: Esta técnica reduce la precisión de los pesos y activaciones del modelo, típicamente de puntos flotantes de 32 bits a enteros de 8 bits. Esto no solo disminuye el tamaño del modelo, sino que también acelera los cálculos en dispositivos con poca potencia de procesamiento.
- Poda: Al eliminar conexiones y neuronas innecesarias, la poda reduce aún más el tamaño del modelo y los requisitos computacionales.
- Fusión de operadores: Esta optimización combina múltiples operaciones en una sola operación más eficiente, reduciendo el acceso a la memoria y mejorando el rendimiento general.
- Implementación e inferencia del modelo:
Después de la optimización, el modelo TensorFlow Lite está listo para su implementación en dispositivos de borde. Esta etapa aprovecha el TFLite Interpreter, un motor de ejecución liviano diseñado para la ejecución eficiente de modelos:
- El intérprete es responsable de cargar el modelo optimizado y ejecutar la inferencia con una utilización mínima de recursos.
- Admite aceleración de hardware en varias plataformas, incluidos CPUs ARM, GPUs y aceleradores de IA especializados como el Edge TPU.
- TensorFlow Lite también ofrece APIs específicas para plataformas que permiten una integración fluida con sistemas Android, iOS y Linux embebidos, facilitando la incorporación de capacidades de machine learning en aplicaciones móviles e IoT.
Al aprovechar estas características avanzadas, TensorFlow Lite permite a los desarrolladores llevar capacidades de IA sofisticadas a dispositivos de borde, abriendo nuevas posibilidades para el machine learning en dispositivos locales en una amplia gama de aplicaciones e industrias.
Ejemplo: Convertir un Modelo de TensorFlow a TensorFlow Lite
Comencemos entrenando un modelo simple de TensorFlow y luego lo convertiremos a TensorFlow Lite para su implementación en el borde.
import tensorflow as tf
import numpy as np
# Define a simple model for MNIST digit classification
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10, activation='softmax')
])
# Compile the model
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# Load and preprocess the MNIST dataset
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
# Train the model
model.fit(x_train, y_train, epochs=5, validation_split=0.2)
# Evaluate the model
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=2)
print(f'\nTest accuracy: {test_acc}')
# Save the model in TensorFlow format
model.save('mnist_model.h5')
# Convert the model to TensorFlow Lite format
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
# Save the TFLite model to a file
with open('mnist_model.tflite', 'wb') as f:
f.write(tflite_model)
print("Model successfully converted to TensorFlow Lite format.")
# Function to run inference on TFLite model
def run_tflite_inference(tflite_model, input_data):
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
interpreter.set_tensor(input_details[0]['index'], input_data)
interpreter.invoke()
output = interpreter.get_tensor(output_details[0]['index'])
return output
# Test the TFLite model
test_image = x_test[0]
test_image = np.expand_dims(test_image, axis=0).astype(np.float32)
tflite_output = run_tflite_inference(tflite_model, test_image)
tflite_prediction = np.argmax(tflite_output)
print(f"TFLite Model Prediction: {tflite_prediction}")
print(f"Actual Label: {y_test[0]}")
Este ejemplo de código demuestra un flujo de trabajo integral para crear, entrenar, convertir y probar un modelo de TensorFlow para la clasificación de dígitos MNIST usando TensorFlow Lite.
Desglosemos paso a paso:
- Importación de las bibliotecas necesarias:
Importamos TensorFlow y NumPy, que necesitaremos para la creación del modelo, entrenamiento y manipulación de datos.
- Definición del modelo:
Creamos un modelo secuencial simple para la clasificación de dígitos MNIST. Consiste en una capa Flatten para convertir imágenes 2D en 1D, una capa Dense con activación ReLU, una capa Dropout para regularización, y una capa Dense final con activación softmax para clasificación de 10 clases.
- Compilación del modelo:
Compilamos el modelo usando el optimizador Adam, la pérdida de entropía cruzada categórica dispersa (adecuada para etiquetas enteras) y la precisión como métrica.
- Carga y preprocesamiento de datos:
Cargamos el conjunto de datos MNIST utilizando la función integrada de Keras y normalizamos los valores de píxeles para que estén entre 0 y 1.
- Entrenamiento del modelo:
Entrenamos el modelo durante 5 épocas, utilizando el 20% de los datos de entrenamiento para validación.
- Evaluación del modelo:
Evaluamos el rendimiento del modelo en el conjunto de prueba y mostramos la precisión.
- Guardado del modelo:
Guardamos el modelo entrenado en el formato estándar de TensorFlow (.h5).
- Conversión a TensorFlow Lite:
Utilizamos el TFLiteConverter para convertir el modelo de Keras al formato TensorFlow Lite.
- Guardado del modelo TFLite:
Guardamos el modelo TFLite convertido en un archivo.
- Definición de una función de inferencia:
Creamos una función
run_tflite_inference
que carga un modelo TFLite, lo prepara para inferencia y ejecuta predicciones en los datos de entrada dados. - Prueba del modelo TFLite:
Seleccionamos la primera imagen de prueba, la remodelamos para que coincida con la forma de entrada del modelo y ejecutamos la inferencia utilizando nuestro modelo TFLite. Luego, comparamos la predicción con la etiqueta real.
Este ejemplo integral muestra todo el proceso, desde la creación del modelo hasta el despliegue y prueba con TensorFlow Lite, proporcionando una demostración práctica de cómo preparar un modelo para su implementación en el borde utilizando TensorFlow Lite.
Despliegue de modelos TensorFlow Lite en Android
Una vez que tienes un modelo de TensorFlow Lite, puedes integrarlo sin problemas en una aplicación Android. TensorFlow Lite ofrece una robusta API Java que simplifica el proceso de cargar el modelo y ejecutar inferencias en dispositivos Android. Esta API proporciona a los desarrolladores un conjunto de herramientas potentes para incorporar eficientemente capacidades de machine learning en sus aplicaciones móviles.
La API Java de TensorFlow Lite permite a los desarrolladores realizar varias operaciones clave:
- Carga del modelo: Carga fácilmente tu modelo de TensorFlow Lite desde los activos de la aplicación o desde el almacenamiento externo.
- Gestión de tensores de entrada/salida: Maneja de manera eficiente los tensores de entrada y salida, incluida la conversión de tipos de datos y la manipulación de formas.
- Ejecución de inferencias: Ejecuta inferencias del modelo con rendimiento optimizado en dispositivos Android.
- Aceleración de hardware: Aprovecha la API Neural Networks (NNAPI) de Android para la aceleración de hardware en dispositivos compatibles.
Al utilizar esta API, los desarrolladores pueden crear aplicaciones Android sofisticadas que realicen tareas de machine learning en el dispositivo con latencia y consumo de recursos mínimos. Este enfoque permite una amplia gama de casos de uso, desde la clasificación de imágenes en tiempo real y la detección de objetos hasta el procesamiento de lenguaje natural y recomendaciones personalizadas, todo mientras se mantiene la privacidad del usuario al mantener los datos en el dispositivo.
A continuación se muestra un fragmento de cómo se puede hacer esto:
import org.tensorflow.lite.Interpreter;
import org.tensorflow.lite.gpu.GpuDelegate;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel.MapMode;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
public class MyModel {
private Interpreter tflite;
private static final int NUM_THREADS = 4;
private static final int OUTPUT_CLASSES = 10;
private GpuDelegate gpuDelegate = null;
public MyModel(AssetManager assetManager, String modelPath, boolean useGPU) throws IOException {
ByteBuffer modelBuffer = loadModelFile(assetManager, modelPath);
Interpreter.Options options = new Interpreter.Options();
options.setNumThreads(NUM_THREADS);
if (useGPU) {
gpuDelegate = new GpuDelegate();
options.addDelegate(gpuDelegate);
}
tflite = new Interpreter(modelBuffer, options);
}
private MappedByteBuffer loadModelFile(AssetManager assetManager, String modelPath) throws IOException {
AssetFileDescriptor fileDescriptor = assetManager.openFd(modelPath);
FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
FileChannel fileChannel = inputStream.getChannel();
long startOffset = fileDescriptor.getStartOffset();
long declaredLength = fileDescriptor.getDeclaredLength();
return fileChannel.map(MapMode.READ_ONLY, startOffset, declaredLength);
}
public float[] runInference(float[] inputData) {
if (tflite == null) {
throw new IllegalStateException("TFLite Interpreter has not been initialized.");
}
ByteBuffer inputBuffer = ByteBuffer.allocateDirect(inputData.length * 4).order(ByteOrder.nativeOrder());
for (float value : inputData) {
inputBuffer.putFloat(value);
}
inputBuffer.rewind();
ByteBuffer outputBuffer = ByteBuffer.allocateDirect(OUTPUT_CLASSES * 4).order(ByteOrder.nativeOrder());
tflite.run(inputBuffer, outputBuffer);
outputBuffer.rewind();
float[] outputData = new float[OUTPUT_CLASSES];
outputBuffer.asFloatBuffer().get(outputData);
return outputData;
}
public void close() {
if (tflite != null) {
tflite.close();
tflite = null;
}
if (gpuDelegate != null) {
gpuDelegate.close();
gpuDelegate = null;
}
}
}
Este ejemplo proporciona una implementación integral de la clase MyModel para desplegar modelos de TensorFlow Lite en dispositivos Android.
Analicemos los componentes y mejoras principales:
- Importaciones:
- Se agregaron importaciones para
GpuDelegate
y elAssetManager
de Android. - Se incluyeron las clases necesarias de Java I/O para el manejo de archivos, garantizando una carga eficiente del modelo.
- Se agregaron importaciones para
- Variables de Clase:
- Se introdujo
NUM_THREADS
para especificar el número de hilos para el intérprete. - Se agregó
OUTPUT_CLASSES
para definir el número de clases de salida (asumiendo 10 en este ejemplo). - Se agregó una variable
GpuDelegate
para gestionar la aceleración GPU cuando está habilitada.
- Se introdujo
- Constructor:
- Se incluyó un parámetro
useGPU
para habilitar opcionalmente la aceleración GPU para una inferencia optimizada. - Se implementó
Interpreter.Options
para configurar el intérprete TFLite con hilos de CPU y delegación GPU opcional. - Se aseguró el manejo adecuado de la creación del intérprete con asignación dinámica de recursos.
- Se incluyó un parámetro
- Carga del Modelo:
- Se mejoró la carga del modelo usando
AssetManager.openFd()
para recuperar correctamente los activos del sistema de almacenamiento de Android. - Se utilizó
MappedByteBuffer
para un mapeo eficiente de memoria, reduciendo la sobrecarga de carga. - Se incluyó un manejo apropiado de excepciones para una gestión robusta de archivos.
- Se mejoró la carga del modelo usando
- Método de Inferencia:
- Se implementaron verificaciones de nulos para el intérprete TFLite para prevenir fallos inesperados.
- Se utilizó
ByteBuffer
tanto para entrada como salida para asegurar la compatibilidad con operaciones de TensorFlow Lite. - Se convirtieron los datos de entrada de tipo float a
ByteBuffer
antes de ejecutar la inferencia. - Se extrajeron las predicciones del buffer de salida para devolver el array de flotantes procesado.
- Gestión de Recursos:
- Se implementó un método
close()
para liberar adecuadamente los recursos de TensorFlow Lite cuando ya no se necesitan. - Se aseguró que el
GpuDelegate
se cierre si fue utilizado para evitar fugas de memoria.
- Se implementó un método
Esta implementación mejorada proporciona un alto rendimiento, manejo de errores y gestión eficiente de recursos. También permite la aceleración GPU opcional, que puede mejorar significativamente la velocidad de inferencia en dispositivos compatibles. El código es robusto y adecuado para uso en producción en aplicaciones Android.
8.2.2 ONNX (Open Neural Network Exchange)
ONNX (Open Neural Network Exchange) es un formato versátil y de código abierto para representar modelos de machine learning. Desarrollado en colaboración por Microsoft y Facebook, ONNX sirve como un puente entre diferentes marcos de machine learning, permitiendo la portabilidad de modelos de manera fluida. Esta interoperabilidad permite que los modelos entrenados en marcos populares como PyTorch o TensorFlow se transfieran y ejecuten fácilmente en diversos entornos.
La popularidad de ONNX para el despliegue en dispositivos de borde se debe a su capacidad para unificar modelos de diversas fuentes en un formato estandarizado. Esta representación unificada puede luego optimizarse y ejecutarse de manera eficiente utilizando el ONNX Runtime, un motor de inferencia de alto rendimiento diseñado para maximizar el potencial de los modelos ONNX en diferentes plataformas.
Una de las principales fortalezas de ONNX radica en su extenso soporte de hardware. El formato es compatible con una amplia gama de plataformas, desde potentes servidores en la nube hasta dispositivos IoT con recursos limitados. Esta compatibilidad asegura que los desarrolladores puedan desplegar sus modelos en diversos ecosistemas de hardware sin necesidad de modificaciones significativas.
Además, ONNX incorpora optimizaciones integradas específicamente diseñadas para dispositivos de borde. Estas optimizaciones abordan los desafíos únicos que presentan los recursos computacionales limitados, las restricciones de memoria y los requisitos de eficiencia energética típicos en los entornos de computación en el borde. Al aprovechar estas optimizaciones, los desarrolladores pueden mejorar significativamente el rendimiento de sus modelos en dispositivos de borde, permitiendo inferencias en tiempo real y mejorando la experiencia general del usuario.
La combinación de compatibilidad entre marcos, amplio soporte de hardware y optimizaciones específicas para el borde hace que ONNX sea una opción ideal para desplegar modelos de machine learning en entornos con recursos limitados. Ya sea un dispositivo inteligente para el hogar, una aplicación móvil o un sensor IoT industrial, ONNX proporciona las herramientas y la flexibilidad necesarias para llevar capacidades avanzadas de IA al borde, abriendo nuevas posibilidades para soluciones de computación en el borde inteligentes, receptivas y eficientes.
Ejemplo: Convertir un modelo de PyTorch a ONNX
Tomemos un modelo de PyTorch, lo convertimos al formato ONNX y lo ejecutamos utilizando el ONNX Runtime.
import torch
import torch.nn as nn
import torch.optim as optim
import onnx
import onnxruntime as ort
import numpy as np
# Define a simple PyTorch model
class SimpleModel(nn.Module):
def __init__(self):
super(SimpleModel, self).__init__()
self.fc1 = nn.Linear(784, 128)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
# Create an instance of the model
model = SimpleModel()
# Train the model (simplified for demonstration)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters())
# Dummy training data
dummy_input = torch.randn(100, 784)
dummy_target = torch.randint(0, 10, (100,))
for epoch in range(5):
optimizer.zero_grad()
output = model(dummy_input)
loss = criterion(output, dummy_target)
loss.backward()
optimizer.step()
print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")
# Prepare dummy input for ONNX export
dummy_input = torch.randn(1, 784)
# Export the model to ONNX format
torch.onnx.export(model, dummy_input, "model.onnx", verbose=True)
print("Model successfully converted to ONNX format.")
# Load and run the ONNX model using ONNX Runtime
ort_session = ort.InferenceSession("model.onnx")
def to_numpy(tensor):
return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()
# Run inference
input_data = to_numpy(dummy_input)
ort_inputs = {ort_session.get_inputs()[0].name: input_data}
ort_outputs = ort_session.run(None, ort_inputs)
print("ONNX Model Inference Output shape:", ort_outputs[0].shape)
print("ONNX Model Inference Output (first 5 values):", ort_outputs[0][0][:5])
# Compare PyTorch and ONNX Runtime outputs
pytorch_output = model(dummy_input)
np.testing.assert_allclose(to_numpy(pytorch_output), ort_outputs[0], rtol=1e-03, atol=1e-05)
print("PyTorch and ONNX Runtime outputs are similar")
# Save and load ONNX model
onnx_model = onnx.load("model.onnx")
onnx.checker.check_model(onnx_model)
print("The model is checked!")
Este ejemplo de código proporciona una demostración integral del trabajo con modelos de PyTorch y ONNX.
Desglosemos los pasos:
- Definición y entrenamiento del modelo:
- Definimos un modelo ligeramente más complejo con dos capas completamente conectadas y una activación ReLU.
- El modelo se entrena durante 5 épocas con datos ficticios para simular un escenario del mundo real.
- Conversión a ONNX:
- El modelo entrenado de PyTorch se exporta al formato ONNX usando
torch.onnx.export()
. - Usamos
verbose=True
para obtener información detallada sobre el proceso de exportación.
- El modelo entrenado de PyTorch se exporta al formato ONNX usando
- Inferencia con ONNX Runtime:
- Cargamos el modelo ONNX usando onnxruntime y creamos una
InferenceSession
. - Se define la función
to_numpy()
para convertir tensores de PyTorch a matrices NumPy. - Ejecutamos inferencia en el modelo ONNX utilizando la misma entrada ficticia usada para la exportación.
- Cargamos el modelo ONNX usando onnxruntime y creamos una
- Comparación de resultados:
- Comparamos las salidas del modelo de PyTorch y del modelo en ONNX Runtime para asegurarnos de que sean similares.
- Usamos
numpy.testing.assert_allclose()
para verificar si las salidas son cercanas dentro de una tolerancia.
- Validación del modelo ONNX:
- Cargamos el modelo ONNX guardado usando
onnx.load()
. - La función
onnx.checker.check_model()
se utiliza para validar la estructura del modelo ONNX.
- Cargamos el modelo ONNX guardado usando
Este ejemplo integral demuestra todo el flujo de trabajo, desde la definición y entrenamiento de un modelo de PyTorch, hasta su exportación al formato ONNX, la ejecución de la inferencia con ONNX Runtime y la validación de los resultados. Proporciona una base sólida para trabajar con ONNX en proyectos de machine learning del mundo real.
Optimización de modelos ONNX para dispositivos de borde
Los modelos ONNX pueden optimizarse aún más utilizando herramientas potentes como ONNX Runtime y ONNX Quantization. Estas técnicas avanzadas de optimización son fundamentales para desplegar modelos de machine learning en dispositivos con recursos limitados, como teléfonos móviles, dispositivos IoT y sistemas embebidos. Al aprovechar estas herramientas, los desarrolladores pueden reducir significativamente el tamaño del modelo y aumentar la velocidad de inferencia, lo que hace posible ejecutar modelos de IA complejos en dispositivos con capacidad computacional y memoria limitadas.
El ONNX Runtime es un motor de inferencia de código abierto diseñado para acelerar modelos de machine learning en diferentes plataformas de hardware. Proporciona una amplia gama de optimizaciones, incluidas la fusión de operadores, la planificación de memoria y la aceleración específica de hardware. Estas optimizaciones pueden generar mejoras sustanciales en el rendimiento, especialmente en dispositivos de borde con recursos limitados.
ONNX Quantization es otra técnica poderosa que reduce la precisión de los pesos y activaciones del modelo, pasando de números de punto flotante de 32 bits a representaciones de menor ancho de bits, como enteros de 8 bits. Este proceso no solo reduce el tamaño del modelo, sino que también acelera los cálculos, lo que lo hace particularmente beneficioso para el despliegue en dispositivos de borde. La cuantización a menudo se puede aplicar con un impacto mínimo en la precisión del modelo, logrando un equilibrio entre rendimiento y precisión.
Juntas, estas herramientas de optimización permiten a los desarrolladores crear aplicaciones de IA eficientes y de alto rendimiento que pueden ejecutarse sin problemas en una amplia gama de dispositivos, desde potentes servidores en la nube hasta dispositivos de borde con recursos limitados. Esta capacidad es cada vez más importante a medida que crece la demanda de IA en dispositivos en una variedad de industrias y aplicaciones.
Por ejemplo, para aplicar la cuantización a un modelo ONNX, puedes usar la biblioteca onnxruntime.quantization:
import onnx
from onnxruntime.quantization import quantize_dynamic, QuantType
import numpy as np
import onnxruntime as ort
# Load the ONNX model
model_path = "model.onnx"
onnx_model = onnx.load(model_path)
# Perform dynamic quantization
quantized_model_path = "model_quantized.onnx"
quantize_dynamic(model_path, quantized_model_path, weight_type=QuantType.QUInt8)
print("Model successfully quantized for edge deployment.")
# Compare model sizes
import os
original_size = os.path.getsize(model_path)
quantized_size = os.path.getsize(quantized_model_path)
print(f"Original model size: {original_size/1024:.2f} KB")
print(f"Quantized model size: {quantized_size/1024:.2f} KB")
print(f"Size reduction: {(1 - quantized_size/original_size)*100:.2f}%")
# Run inference on both models and compare results
def run_inference(session, input_data):
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
return session.run([output_name], {input_name: input_data})[0]
# Create a dummy input
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
# Run inference on original model
original_session = ort.InferenceSession(model_path)
original_output = run_inference(original_session, input_data)
# Run inference on quantized model
quantized_session = ort.InferenceSession(quantized_model_path)
quantized_output = run_inference(quantized_session, input_data)
# Compare outputs
mse = np.mean((original_output - quantized_output)**2)
print(f"Mean Squared Error between original and quantized model outputs: {mse}")
# Measure inference time
import time
def measure_inference_time(session, input_data, num_runs=100):
total_time = 0
for _ in range(num_runs):
start_time = time.time()
_ = run_inference(session, input_data)
total_time += time.time() - start_time
return total_time / num_runs
original_time = measure_inference_time(original_session, input_data)
quantized_time = measure_inference_time(quantized_session, input_data)
print(f"Average inference time (original model): {original_time*1000:.2f} ms")
print(f"Average inference time (quantized model): {quantized_time*1000:.2f} ms")
print(f"Speedup: {original_time/quantized_time:.2f}x")
Este ejemplo demuestra un flujo de trabajo integral para cuantificar un modelo ONNX y evaluar su rendimiento.
Desglosemos los pasos:
- Carga y cuantificación del modelo:
- Comenzamos cargando el modelo ONNX original usando la biblioteca onnx.
- Luego, utilizamos la función
quantize_dynamic
para realizar la cuantificación dinámica en el modelo, convirtiendo los pesos a enteros sin signo de 8 bits (QUInt8).
- Comparación de tamaño del modelo:
- Comparamos los tamaños de archivo del modelo original y el modelo cuantificado para demostrar la reducción en el tamaño del modelo logrado a través de la cuantificación.
- Configuración de inferencia:
- Se define una función auxiliar
run_inference
para simplificar la ejecución de la inferencia en ambos modelos, el original y el cuantificado. - Creamos un tensor de entrada ficticio para usarlo en la inferencia.
- Se define una función auxiliar
- Ejecución de la inferencia:
- Creamos sesiones de ONNX Runtime tanto para el modelo original como para el cuantificado.
- La inferencia se ejecuta en ambos modelos usando los mismos datos de entrada.
- Comparación de salida:
- Calculamos el error cuadrático medio (MSE) entre las salidas de los modelos original y cuantificado para cuantificar cualquier pérdida de precisión debido a la cuantificación.
- Medición del rendimiento:
- Se define una función
measure_inference_time
para medir con precisión el tiempo promedio de inferencia a lo largo de múltiples ejecuciones. - Medimos y comparamos los tiempos de inferencia de ambos modelos, el original y el cuantificado.
- Se define una función
Este ejemplo integral no solo demuestra cómo cuantificar un modelo ONNX, sino que también proporciona un análisis exhaustivo de los efectos de la cuantificación, incluida la reducción del tamaño del modelo, el impacto potencial en la precisión y las mejoras en la velocidad de inferencia. Este enfoque ofrece a los desarrolladores una visión clara de los compromisos involucrados en la cuantificación de modelos para su implementación en dispositivos de borde.
8.2.3 Comparación entre TensorFlow Lite y ONNX para despliegue en dispositivos de borde
Tanto TensorFlow Lite (TFLite) como Open Neural Network Exchange (ONNX) ofrecen potentes capacidades para desplegar modelos de machine learning en dispositivos de borde, cada uno con sus propias fortalezas y casos de uso. TensorFlow Lite es especialmente adecuado para flujos de trabajo basados en TensorFlow, ofreciendo una integración perfecta y herramientas de optimización específicamente diseñadas para el ecosistema de TensorFlow.
8.2 Introducción a TensorFlow Lite y ONNX para Dispositivos de Borde
El rápido avance de la computación en el borde ha revolucionado la implementación de modelos de machine learning en una amplia gama de dispositivos, incluidos teléfonos inteligentes, tabletas, wearables y dispositivos IoT. Este cambio hacia la IA basada en el borde presenta tanto oportunidades como desafíos, ya que estos dispositivos suelen tener limitaciones en términos de recursos computacionales, capacidad de memoria y consumo de energía, que no están presentes en las infraestructuras basadas en la nube.
Para abordar estas limitaciones y permitir una IA eficiente en el borde, han surgido frameworks especializados como TensorFlow Lite (TFLite) y ONNX (Open Neural Network Exchange). Estas potentes herramientas proporcionan a los desarrolladores los medios para optimizar, convertir y ejecutar modelos de machine learning en dispositivos de borde con una eficiencia notable.
Al minimizar el overhead y maximizar el rendimiento, TFLite y ONNX son fundamentales para llevar capacidades de IA sofisticadas a entornos con recursos limitados, abriendo nuevas posibilidades para aplicaciones inteligentes en el borde en diversas industrias.
8.2.1 TensorFlow Lite (TFLite)
TensorFlow Lite (TFLite) es un framework poderoso específicamente diseñado para implementar modelos de machine learning en dispositivos con recursos limitados, como teléfonos inteligentes, dispositivos IoT y sistemas embebidos. Ofrece una suite completa de herramientas y optimizaciones que permiten a los desarrolladores reducir significativamente el tamaño del modelo y mejorar la velocidad de inferencia, manteniendo al mismo tiempo un alto grado de precisión.
El flujo de trabajo de TensorFlow Lite consta de dos etapas principales:
- Conversión y optimización del modelo:
Esta fase crucial implica transformar un modelo estándar de TensorFlow en un formato optimizado para TensorFlow Lite. El proceso utiliza el sofisticado TFLite Converter, que emplea varias técnicas para optimizar el modelo:
- Cuantización: Esta técnica reduce la precisión de los pesos y activaciones del modelo, típicamente de puntos flotantes de 32 bits a enteros de 8 bits. Esto no solo disminuye el tamaño del modelo, sino que también acelera los cálculos en dispositivos con poca potencia de procesamiento.
- Poda: Al eliminar conexiones y neuronas innecesarias, la poda reduce aún más el tamaño del modelo y los requisitos computacionales.
- Fusión de operadores: Esta optimización combina múltiples operaciones en una sola operación más eficiente, reduciendo el acceso a la memoria y mejorando el rendimiento general.
- Implementación e inferencia del modelo:
Después de la optimización, el modelo TensorFlow Lite está listo para su implementación en dispositivos de borde. Esta etapa aprovecha el TFLite Interpreter, un motor de ejecución liviano diseñado para la ejecución eficiente de modelos:
- El intérprete es responsable de cargar el modelo optimizado y ejecutar la inferencia con una utilización mínima de recursos.
- Admite aceleración de hardware en varias plataformas, incluidos CPUs ARM, GPUs y aceleradores de IA especializados como el Edge TPU.
- TensorFlow Lite también ofrece APIs específicas para plataformas que permiten una integración fluida con sistemas Android, iOS y Linux embebidos, facilitando la incorporación de capacidades de machine learning en aplicaciones móviles e IoT.
Al aprovechar estas características avanzadas, TensorFlow Lite permite a los desarrolladores llevar capacidades de IA sofisticadas a dispositivos de borde, abriendo nuevas posibilidades para el machine learning en dispositivos locales en una amplia gama de aplicaciones e industrias.
Ejemplo: Convertir un Modelo de TensorFlow a TensorFlow Lite
Comencemos entrenando un modelo simple de TensorFlow y luego lo convertiremos a TensorFlow Lite para su implementación en el borde.
import tensorflow as tf
import numpy as np
# Define a simple model for MNIST digit classification
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10, activation='softmax')
])
# Compile the model
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# Load and preprocess the MNIST dataset
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
# Train the model
model.fit(x_train, y_train, epochs=5, validation_split=0.2)
# Evaluate the model
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=2)
print(f'\nTest accuracy: {test_acc}')
# Save the model in TensorFlow format
model.save('mnist_model.h5')
# Convert the model to TensorFlow Lite format
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
# Save the TFLite model to a file
with open('mnist_model.tflite', 'wb') as f:
f.write(tflite_model)
print("Model successfully converted to TensorFlow Lite format.")
# Function to run inference on TFLite model
def run_tflite_inference(tflite_model, input_data):
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
interpreter.set_tensor(input_details[0]['index'], input_data)
interpreter.invoke()
output = interpreter.get_tensor(output_details[0]['index'])
return output
# Test the TFLite model
test_image = x_test[0]
test_image = np.expand_dims(test_image, axis=0).astype(np.float32)
tflite_output = run_tflite_inference(tflite_model, test_image)
tflite_prediction = np.argmax(tflite_output)
print(f"TFLite Model Prediction: {tflite_prediction}")
print(f"Actual Label: {y_test[0]}")
Este ejemplo de código demuestra un flujo de trabajo integral para crear, entrenar, convertir y probar un modelo de TensorFlow para la clasificación de dígitos MNIST usando TensorFlow Lite.
Desglosemos paso a paso:
- Importación de las bibliotecas necesarias:
Importamos TensorFlow y NumPy, que necesitaremos para la creación del modelo, entrenamiento y manipulación de datos.
- Definición del modelo:
Creamos un modelo secuencial simple para la clasificación de dígitos MNIST. Consiste en una capa Flatten para convertir imágenes 2D en 1D, una capa Dense con activación ReLU, una capa Dropout para regularización, y una capa Dense final con activación softmax para clasificación de 10 clases.
- Compilación del modelo:
Compilamos el modelo usando el optimizador Adam, la pérdida de entropía cruzada categórica dispersa (adecuada para etiquetas enteras) y la precisión como métrica.
- Carga y preprocesamiento de datos:
Cargamos el conjunto de datos MNIST utilizando la función integrada de Keras y normalizamos los valores de píxeles para que estén entre 0 y 1.
- Entrenamiento del modelo:
Entrenamos el modelo durante 5 épocas, utilizando el 20% de los datos de entrenamiento para validación.
- Evaluación del modelo:
Evaluamos el rendimiento del modelo en el conjunto de prueba y mostramos la precisión.
- Guardado del modelo:
Guardamos el modelo entrenado en el formato estándar de TensorFlow (.h5).
- Conversión a TensorFlow Lite:
Utilizamos el TFLiteConverter para convertir el modelo de Keras al formato TensorFlow Lite.
- Guardado del modelo TFLite:
Guardamos el modelo TFLite convertido en un archivo.
- Definición de una función de inferencia:
Creamos una función
run_tflite_inference
que carga un modelo TFLite, lo prepara para inferencia y ejecuta predicciones en los datos de entrada dados. - Prueba del modelo TFLite:
Seleccionamos la primera imagen de prueba, la remodelamos para que coincida con la forma de entrada del modelo y ejecutamos la inferencia utilizando nuestro modelo TFLite. Luego, comparamos la predicción con la etiqueta real.
Este ejemplo integral muestra todo el proceso, desde la creación del modelo hasta el despliegue y prueba con TensorFlow Lite, proporcionando una demostración práctica de cómo preparar un modelo para su implementación en el borde utilizando TensorFlow Lite.
Despliegue de modelos TensorFlow Lite en Android
Una vez que tienes un modelo de TensorFlow Lite, puedes integrarlo sin problemas en una aplicación Android. TensorFlow Lite ofrece una robusta API Java que simplifica el proceso de cargar el modelo y ejecutar inferencias en dispositivos Android. Esta API proporciona a los desarrolladores un conjunto de herramientas potentes para incorporar eficientemente capacidades de machine learning en sus aplicaciones móviles.
La API Java de TensorFlow Lite permite a los desarrolladores realizar varias operaciones clave:
- Carga del modelo: Carga fácilmente tu modelo de TensorFlow Lite desde los activos de la aplicación o desde el almacenamiento externo.
- Gestión de tensores de entrada/salida: Maneja de manera eficiente los tensores de entrada y salida, incluida la conversión de tipos de datos y la manipulación de formas.
- Ejecución de inferencias: Ejecuta inferencias del modelo con rendimiento optimizado en dispositivos Android.
- Aceleración de hardware: Aprovecha la API Neural Networks (NNAPI) de Android para la aceleración de hardware en dispositivos compatibles.
Al utilizar esta API, los desarrolladores pueden crear aplicaciones Android sofisticadas que realicen tareas de machine learning en el dispositivo con latencia y consumo de recursos mínimos. Este enfoque permite una amplia gama de casos de uso, desde la clasificación de imágenes en tiempo real y la detección de objetos hasta el procesamiento de lenguaje natural y recomendaciones personalizadas, todo mientras se mantiene la privacidad del usuario al mantener los datos en el dispositivo.
A continuación se muestra un fragmento de cómo se puede hacer esto:
import org.tensorflow.lite.Interpreter;
import org.tensorflow.lite.gpu.GpuDelegate;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel.MapMode;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
public class MyModel {
private Interpreter tflite;
private static final int NUM_THREADS = 4;
private static final int OUTPUT_CLASSES = 10;
private GpuDelegate gpuDelegate = null;
public MyModel(AssetManager assetManager, String modelPath, boolean useGPU) throws IOException {
ByteBuffer modelBuffer = loadModelFile(assetManager, modelPath);
Interpreter.Options options = new Interpreter.Options();
options.setNumThreads(NUM_THREADS);
if (useGPU) {
gpuDelegate = new GpuDelegate();
options.addDelegate(gpuDelegate);
}
tflite = new Interpreter(modelBuffer, options);
}
private MappedByteBuffer loadModelFile(AssetManager assetManager, String modelPath) throws IOException {
AssetFileDescriptor fileDescriptor = assetManager.openFd(modelPath);
FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
FileChannel fileChannel = inputStream.getChannel();
long startOffset = fileDescriptor.getStartOffset();
long declaredLength = fileDescriptor.getDeclaredLength();
return fileChannel.map(MapMode.READ_ONLY, startOffset, declaredLength);
}
public float[] runInference(float[] inputData) {
if (tflite == null) {
throw new IllegalStateException("TFLite Interpreter has not been initialized.");
}
ByteBuffer inputBuffer = ByteBuffer.allocateDirect(inputData.length * 4).order(ByteOrder.nativeOrder());
for (float value : inputData) {
inputBuffer.putFloat(value);
}
inputBuffer.rewind();
ByteBuffer outputBuffer = ByteBuffer.allocateDirect(OUTPUT_CLASSES * 4).order(ByteOrder.nativeOrder());
tflite.run(inputBuffer, outputBuffer);
outputBuffer.rewind();
float[] outputData = new float[OUTPUT_CLASSES];
outputBuffer.asFloatBuffer().get(outputData);
return outputData;
}
public void close() {
if (tflite != null) {
tflite.close();
tflite = null;
}
if (gpuDelegate != null) {
gpuDelegate.close();
gpuDelegate = null;
}
}
}
Este ejemplo proporciona una implementación integral de la clase MyModel para desplegar modelos de TensorFlow Lite en dispositivos Android.
Analicemos los componentes y mejoras principales:
- Importaciones:
- Se agregaron importaciones para
GpuDelegate
y elAssetManager
de Android. - Se incluyeron las clases necesarias de Java I/O para el manejo de archivos, garantizando una carga eficiente del modelo.
- Se agregaron importaciones para
- Variables de Clase:
- Se introdujo
NUM_THREADS
para especificar el número de hilos para el intérprete. - Se agregó
OUTPUT_CLASSES
para definir el número de clases de salida (asumiendo 10 en este ejemplo). - Se agregó una variable
GpuDelegate
para gestionar la aceleración GPU cuando está habilitada.
- Se introdujo
- Constructor:
- Se incluyó un parámetro
useGPU
para habilitar opcionalmente la aceleración GPU para una inferencia optimizada. - Se implementó
Interpreter.Options
para configurar el intérprete TFLite con hilos de CPU y delegación GPU opcional. - Se aseguró el manejo adecuado de la creación del intérprete con asignación dinámica de recursos.
- Se incluyó un parámetro
- Carga del Modelo:
- Se mejoró la carga del modelo usando
AssetManager.openFd()
para recuperar correctamente los activos del sistema de almacenamiento de Android. - Se utilizó
MappedByteBuffer
para un mapeo eficiente de memoria, reduciendo la sobrecarga de carga. - Se incluyó un manejo apropiado de excepciones para una gestión robusta de archivos.
- Se mejoró la carga del modelo usando
- Método de Inferencia:
- Se implementaron verificaciones de nulos para el intérprete TFLite para prevenir fallos inesperados.
- Se utilizó
ByteBuffer
tanto para entrada como salida para asegurar la compatibilidad con operaciones de TensorFlow Lite. - Se convirtieron los datos de entrada de tipo float a
ByteBuffer
antes de ejecutar la inferencia. - Se extrajeron las predicciones del buffer de salida para devolver el array de flotantes procesado.
- Gestión de Recursos:
- Se implementó un método
close()
para liberar adecuadamente los recursos de TensorFlow Lite cuando ya no se necesitan. - Se aseguró que el
GpuDelegate
se cierre si fue utilizado para evitar fugas de memoria.
- Se implementó un método
Esta implementación mejorada proporciona un alto rendimiento, manejo de errores y gestión eficiente de recursos. También permite la aceleración GPU opcional, que puede mejorar significativamente la velocidad de inferencia en dispositivos compatibles. El código es robusto y adecuado para uso en producción en aplicaciones Android.
8.2.2 ONNX (Open Neural Network Exchange)
ONNX (Open Neural Network Exchange) es un formato versátil y de código abierto para representar modelos de machine learning. Desarrollado en colaboración por Microsoft y Facebook, ONNX sirve como un puente entre diferentes marcos de machine learning, permitiendo la portabilidad de modelos de manera fluida. Esta interoperabilidad permite que los modelos entrenados en marcos populares como PyTorch o TensorFlow se transfieran y ejecuten fácilmente en diversos entornos.
La popularidad de ONNX para el despliegue en dispositivos de borde se debe a su capacidad para unificar modelos de diversas fuentes en un formato estandarizado. Esta representación unificada puede luego optimizarse y ejecutarse de manera eficiente utilizando el ONNX Runtime, un motor de inferencia de alto rendimiento diseñado para maximizar el potencial de los modelos ONNX en diferentes plataformas.
Una de las principales fortalezas de ONNX radica en su extenso soporte de hardware. El formato es compatible con una amplia gama de plataformas, desde potentes servidores en la nube hasta dispositivos IoT con recursos limitados. Esta compatibilidad asegura que los desarrolladores puedan desplegar sus modelos en diversos ecosistemas de hardware sin necesidad de modificaciones significativas.
Además, ONNX incorpora optimizaciones integradas específicamente diseñadas para dispositivos de borde. Estas optimizaciones abordan los desafíos únicos que presentan los recursos computacionales limitados, las restricciones de memoria y los requisitos de eficiencia energética típicos en los entornos de computación en el borde. Al aprovechar estas optimizaciones, los desarrolladores pueden mejorar significativamente el rendimiento de sus modelos en dispositivos de borde, permitiendo inferencias en tiempo real y mejorando la experiencia general del usuario.
La combinación de compatibilidad entre marcos, amplio soporte de hardware y optimizaciones específicas para el borde hace que ONNX sea una opción ideal para desplegar modelos de machine learning en entornos con recursos limitados. Ya sea un dispositivo inteligente para el hogar, una aplicación móvil o un sensor IoT industrial, ONNX proporciona las herramientas y la flexibilidad necesarias para llevar capacidades avanzadas de IA al borde, abriendo nuevas posibilidades para soluciones de computación en el borde inteligentes, receptivas y eficientes.
Ejemplo: Convertir un modelo de PyTorch a ONNX
Tomemos un modelo de PyTorch, lo convertimos al formato ONNX y lo ejecutamos utilizando el ONNX Runtime.
import torch
import torch.nn as nn
import torch.optim as optim
import onnx
import onnxruntime as ort
import numpy as np
# Define a simple PyTorch model
class SimpleModel(nn.Module):
def __init__(self):
super(SimpleModel, self).__init__()
self.fc1 = nn.Linear(784, 128)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
# Create an instance of the model
model = SimpleModel()
# Train the model (simplified for demonstration)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters())
# Dummy training data
dummy_input = torch.randn(100, 784)
dummy_target = torch.randint(0, 10, (100,))
for epoch in range(5):
optimizer.zero_grad()
output = model(dummy_input)
loss = criterion(output, dummy_target)
loss.backward()
optimizer.step()
print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")
# Prepare dummy input for ONNX export
dummy_input = torch.randn(1, 784)
# Export the model to ONNX format
torch.onnx.export(model, dummy_input, "model.onnx", verbose=True)
print("Model successfully converted to ONNX format.")
# Load and run the ONNX model using ONNX Runtime
ort_session = ort.InferenceSession("model.onnx")
def to_numpy(tensor):
return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()
# Run inference
input_data = to_numpy(dummy_input)
ort_inputs = {ort_session.get_inputs()[0].name: input_data}
ort_outputs = ort_session.run(None, ort_inputs)
print("ONNX Model Inference Output shape:", ort_outputs[0].shape)
print("ONNX Model Inference Output (first 5 values):", ort_outputs[0][0][:5])
# Compare PyTorch and ONNX Runtime outputs
pytorch_output = model(dummy_input)
np.testing.assert_allclose(to_numpy(pytorch_output), ort_outputs[0], rtol=1e-03, atol=1e-05)
print("PyTorch and ONNX Runtime outputs are similar")
# Save and load ONNX model
onnx_model = onnx.load("model.onnx")
onnx.checker.check_model(onnx_model)
print("The model is checked!")
Este ejemplo de código proporciona una demostración integral del trabajo con modelos de PyTorch y ONNX.
Desglosemos los pasos:
- Definición y entrenamiento del modelo:
- Definimos un modelo ligeramente más complejo con dos capas completamente conectadas y una activación ReLU.
- El modelo se entrena durante 5 épocas con datos ficticios para simular un escenario del mundo real.
- Conversión a ONNX:
- El modelo entrenado de PyTorch se exporta al formato ONNX usando
torch.onnx.export()
. - Usamos
verbose=True
para obtener información detallada sobre el proceso de exportación.
- El modelo entrenado de PyTorch se exporta al formato ONNX usando
- Inferencia con ONNX Runtime:
- Cargamos el modelo ONNX usando onnxruntime y creamos una
InferenceSession
. - Se define la función
to_numpy()
para convertir tensores de PyTorch a matrices NumPy. - Ejecutamos inferencia en el modelo ONNX utilizando la misma entrada ficticia usada para la exportación.
- Cargamos el modelo ONNX usando onnxruntime y creamos una
- Comparación de resultados:
- Comparamos las salidas del modelo de PyTorch y del modelo en ONNX Runtime para asegurarnos de que sean similares.
- Usamos
numpy.testing.assert_allclose()
para verificar si las salidas son cercanas dentro de una tolerancia.
- Validación del modelo ONNX:
- Cargamos el modelo ONNX guardado usando
onnx.load()
. - La función
onnx.checker.check_model()
se utiliza para validar la estructura del modelo ONNX.
- Cargamos el modelo ONNX guardado usando
Este ejemplo integral demuestra todo el flujo de trabajo, desde la definición y entrenamiento de un modelo de PyTorch, hasta su exportación al formato ONNX, la ejecución de la inferencia con ONNX Runtime y la validación de los resultados. Proporciona una base sólida para trabajar con ONNX en proyectos de machine learning del mundo real.
Optimización de modelos ONNX para dispositivos de borde
Los modelos ONNX pueden optimizarse aún más utilizando herramientas potentes como ONNX Runtime y ONNX Quantization. Estas técnicas avanzadas de optimización son fundamentales para desplegar modelos de machine learning en dispositivos con recursos limitados, como teléfonos móviles, dispositivos IoT y sistemas embebidos. Al aprovechar estas herramientas, los desarrolladores pueden reducir significativamente el tamaño del modelo y aumentar la velocidad de inferencia, lo que hace posible ejecutar modelos de IA complejos en dispositivos con capacidad computacional y memoria limitadas.
El ONNX Runtime es un motor de inferencia de código abierto diseñado para acelerar modelos de machine learning en diferentes plataformas de hardware. Proporciona una amplia gama de optimizaciones, incluidas la fusión de operadores, la planificación de memoria y la aceleración específica de hardware. Estas optimizaciones pueden generar mejoras sustanciales en el rendimiento, especialmente en dispositivos de borde con recursos limitados.
ONNX Quantization es otra técnica poderosa que reduce la precisión de los pesos y activaciones del modelo, pasando de números de punto flotante de 32 bits a representaciones de menor ancho de bits, como enteros de 8 bits. Este proceso no solo reduce el tamaño del modelo, sino que también acelera los cálculos, lo que lo hace particularmente beneficioso para el despliegue en dispositivos de borde. La cuantización a menudo se puede aplicar con un impacto mínimo en la precisión del modelo, logrando un equilibrio entre rendimiento y precisión.
Juntas, estas herramientas de optimización permiten a los desarrolladores crear aplicaciones de IA eficientes y de alto rendimiento que pueden ejecutarse sin problemas en una amplia gama de dispositivos, desde potentes servidores en la nube hasta dispositivos de borde con recursos limitados. Esta capacidad es cada vez más importante a medida que crece la demanda de IA en dispositivos en una variedad de industrias y aplicaciones.
Por ejemplo, para aplicar la cuantización a un modelo ONNX, puedes usar la biblioteca onnxruntime.quantization:
import onnx
from onnxruntime.quantization import quantize_dynamic, QuantType
import numpy as np
import onnxruntime as ort
# Load the ONNX model
model_path = "model.onnx"
onnx_model = onnx.load(model_path)
# Perform dynamic quantization
quantized_model_path = "model_quantized.onnx"
quantize_dynamic(model_path, quantized_model_path, weight_type=QuantType.QUInt8)
print("Model successfully quantized for edge deployment.")
# Compare model sizes
import os
original_size = os.path.getsize(model_path)
quantized_size = os.path.getsize(quantized_model_path)
print(f"Original model size: {original_size/1024:.2f} KB")
print(f"Quantized model size: {quantized_size/1024:.2f} KB")
print(f"Size reduction: {(1 - quantized_size/original_size)*100:.2f}%")
# Run inference on both models and compare results
def run_inference(session, input_data):
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
return session.run([output_name], {input_name: input_data})[0]
# Create a dummy input
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
# Run inference on original model
original_session = ort.InferenceSession(model_path)
original_output = run_inference(original_session, input_data)
# Run inference on quantized model
quantized_session = ort.InferenceSession(quantized_model_path)
quantized_output = run_inference(quantized_session, input_data)
# Compare outputs
mse = np.mean((original_output - quantized_output)**2)
print(f"Mean Squared Error between original and quantized model outputs: {mse}")
# Measure inference time
import time
def measure_inference_time(session, input_data, num_runs=100):
total_time = 0
for _ in range(num_runs):
start_time = time.time()
_ = run_inference(session, input_data)
total_time += time.time() - start_time
return total_time / num_runs
original_time = measure_inference_time(original_session, input_data)
quantized_time = measure_inference_time(quantized_session, input_data)
print(f"Average inference time (original model): {original_time*1000:.2f} ms")
print(f"Average inference time (quantized model): {quantized_time*1000:.2f} ms")
print(f"Speedup: {original_time/quantized_time:.2f}x")
Este ejemplo demuestra un flujo de trabajo integral para cuantificar un modelo ONNX y evaluar su rendimiento.
Desglosemos los pasos:
- Carga y cuantificación del modelo:
- Comenzamos cargando el modelo ONNX original usando la biblioteca onnx.
- Luego, utilizamos la función
quantize_dynamic
para realizar la cuantificación dinámica en el modelo, convirtiendo los pesos a enteros sin signo de 8 bits (QUInt8).
- Comparación de tamaño del modelo:
- Comparamos los tamaños de archivo del modelo original y el modelo cuantificado para demostrar la reducción en el tamaño del modelo logrado a través de la cuantificación.
- Configuración de inferencia:
- Se define una función auxiliar
run_inference
para simplificar la ejecución de la inferencia en ambos modelos, el original y el cuantificado. - Creamos un tensor de entrada ficticio para usarlo en la inferencia.
- Se define una función auxiliar
- Ejecución de la inferencia:
- Creamos sesiones de ONNX Runtime tanto para el modelo original como para el cuantificado.
- La inferencia se ejecuta en ambos modelos usando los mismos datos de entrada.
- Comparación de salida:
- Calculamos el error cuadrático medio (MSE) entre las salidas de los modelos original y cuantificado para cuantificar cualquier pérdida de precisión debido a la cuantificación.
- Medición del rendimiento:
- Se define una función
measure_inference_time
para medir con precisión el tiempo promedio de inferencia a lo largo de múltiples ejecuciones. - Medimos y comparamos los tiempos de inferencia de ambos modelos, el original y el cuantificado.
- Se define una función
Este ejemplo integral no solo demuestra cómo cuantificar un modelo ONNX, sino que también proporciona un análisis exhaustivo de los efectos de la cuantificación, incluida la reducción del tamaño del modelo, el impacto potencial en la precisión y las mejoras en la velocidad de inferencia. Este enfoque ofrece a los desarrolladores una visión clara de los compromisos involucrados en la cuantificación de modelos para su implementación en dispositivos de borde.
8.2.3 Comparación entre TensorFlow Lite y ONNX para despliegue en dispositivos de borde
Tanto TensorFlow Lite (TFLite) como Open Neural Network Exchange (ONNX) ofrecen potentes capacidades para desplegar modelos de machine learning en dispositivos de borde, cada uno con sus propias fortalezas y casos de uso. TensorFlow Lite es especialmente adecuado para flujos de trabajo basados en TensorFlow, ofreciendo una integración perfecta y herramientas de optimización específicamente diseñadas para el ecosistema de TensorFlow.
8.2 Introducción a TensorFlow Lite y ONNX para Dispositivos de Borde
El rápido avance de la computación en el borde ha revolucionado la implementación de modelos de machine learning en una amplia gama de dispositivos, incluidos teléfonos inteligentes, tabletas, wearables y dispositivos IoT. Este cambio hacia la IA basada en el borde presenta tanto oportunidades como desafíos, ya que estos dispositivos suelen tener limitaciones en términos de recursos computacionales, capacidad de memoria y consumo de energía, que no están presentes en las infraestructuras basadas en la nube.
Para abordar estas limitaciones y permitir una IA eficiente en el borde, han surgido frameworks especializados como TensorFlow Lite (TFLite) y ONNX (Open Neural Network Exchange). Estas potentes herramientas proporcionan a los desarrolladores los medios para optimizar, convertir y ejecutar modelos de machine learning en dispositivos de borde con una eficiencia notable.
Al minimizar el overhead y maximizar el rendimiento, TFLite y ONNX son fundamentales para llevar capacidades de IA sofisticadas a entornos con recursos limitados, abriendo nuevas posibilidades para aplicaciones inteligentes en el borde en diversas industrias.
8.2.1 TensorFlow Lite (TFLite)
TensorFlow Lite (TFLite) es un framework poderoso específicamente diseñado para implementar modelos de machine learning en dispositivos con recursos limitados, como teléfonos inteligentes, dispositivos IoT y sistemas embebidos. Ofrece una suite completa de herramientas y optimizaciones que permiten a los desarrolladores reducir significativamente el tamaño del modelo y mejorar la velocidad de inferencia, manteniendo al mismo tiempo un alto grado de precisión.
El flujo de trabajo de TensorFlow Lite consta de dos etapas principales:
- Conversión y optimización del modelo:
Esta fase crucial implica transformar un modelo estándar de TensorFlow en un formato optimizado para TensorFlow Lite. El proceso utiliza el sofisticado TFLite Converter, que emplea varias técnicas para optimizar el modelo:
- Cuantización: Esta técnica reduce la precisión de los pesos y activaciones del modelo, típicamente de puntos flotantes de 32 bits a enteros de 8 bits. Esto no solo disminuye el tamaño del modelo, sino que también acelera los cálculos en dispositivos con poca potencia de procesamiento.
- Poda: Al eliminar conexiones y neuronas innecesarias, la poda reduce aún más el tamaño del modelo y los requisitos computacionales.
- Fusión de operadores: Esta optimización combina múltiples operaciones en una sola operación más eficiente, reduciendo el acceso a la memoria y mejorando el rendimiento general.
- Implementación e inferencia del modelo:
Después de la optimización, el modelo TensorFlow Lite está listo para su implementación en dispositivos de borde. Esta etapa aprovecha el TFLite Interpreter, un motor de ejecución liviano diseñado para la ejecución eficiente de modelos:
- El intérprete es responsable de cargar el modelo optimizado y ejecutar la inferencia con una utilización mínima de recursos.
- Admite aceleración de hardware en varias plataformas, incluidos CPUs ARM, GPUs y aceleradores de IA especializados como el Edge TPU.
- TensorFlow Lite también ofrece APIs específicas para plataformas que permiten una integración fluida con sistemas Android, iOS y Linux embebidos, facilitando la incorporación de capacidades de machine learning en aplicaciones móviles e IoT.
Al aprovechar estas características avanzadas, TensorFlow Lite permite a los desarrolladores llevar capacidades de IA sofisticadas a dispositivos de borde, abriendo nuevas posibilidades para el machine learning en dispositivos locales en una amplia gama de aplicaciones e industrias.
Ejemplo: Convertir un Modelo de TensorFlow a TensorFlow Lite
Comencemos entrenando un modelo simple de TensorFlow y luego lo convertiremos a TensorFlow Lite para su implementación en el borde.
import tensorflow as tf
import numpy as np
# Define a simple model for MNIST digit classification
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10, activation='softmax')
])
# Compile the model
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# Load and preprocess the MNIST dataset
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
# Train the model
model.fit(x_train, y_train, epochs=5, validation_split=0.2)
# Evaluate the model
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=2)
print(f'\nTest accuracy: {test_acc}')
# Save the model in TensorFlow format
model.save('mnist_model.h5')
# Convert the model to TensorFlow Lite format
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
# Save the TFLite model to a file
with open('mnist_model.tflite', 'wb') as f:
f.write(tflite_model)
print("Model successfully converted to TensorFlow Lite format.")
# Function to run inference on TFLite model
def run_tflite_inference(tflite_model, input_data):
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
interpreter.set_tensor(input_details[0]['index'], input_data)
interpreter.invoke()
output = interpreter.get_tensor(output_details[0]['index'])
return output
# Test the TFLite model
test_image = x_test[0]
test_image = np.expand_dims(test_image, axis=0).astype(np.float32)
tflite_output = run_tflite_inference(tflite_model, test_image)
tflite_prediction = np.argmax(tflite_output)
print(f"TFLite Model Prediction: {tflite_prediction}")
print(f"Actual Label: {y_test[0]}")
Este ejemplo de código demuestra un flujo de trabajo integral para crear, entrenar, convertir y probar un modelo de TensorFlow para la clasificación de dígitos MNIST usando TensorFlow Lite.
Desglosemos paso a paso:
- Importación de las bibliotecas necesarias:
Importamos TensorFlow y NumPy, que necesitaremos para la creación del modelo, entrenamiento y manipulación de datos.
- Definición del modelo:
Creamos un modelo secuencial simple para la clasificación de dígitos MNIST. Consiste en una capa Flatten para convertir imágenes 2D en 1D, una capa Dense con activación ReLU, una capa Dropout para regularización, y una capa Dense final con activación softmax para clasificación de 10 clases.
- Compilación del modelo:
Compilamos el modelo usando el optimizador Adam, la pérdida de entropía cruzada categórica dispersa (adecuada para etiquetas enteras) y la precisión como métrica.
- Carga y preprocesamiento de datos:
Cargamos el conjunto de datos MNIST utilizando la función integrada de Keras y normalizamos los valores de píxeles para que estén entre 0 y 1.
- Entrenamiento del modelo:
Entrenamos el modelo durante 5 épocas, utilizando el 20% de los datos de entrenamiento para validación.
- Evaluación del modelo:
Evaluamos el rendimiento del modelo en el conjunto de prueba y mostramos la precisión.
- Guardado del modelo:
Guardamos el modelo entrenado en el formato estándar de TensorFlow (.h5).
- Conversión a TensorFlow Lite:
Utilizamos el TFLiteConverter para convertir el modelo de Keras al formato TensorFlow Lite.
- Guardado del modelo TFLite:
Guardamos el modelo TFLite convertido en un archivo.
- Definición de una función de inferencia:
Creamos una función
run_tflite_inference
que carga un modelo TFLite, lo prepara para inferencia y ejecuta predicciones en los datos de entrada dados. - Prueba del modelo TFLite:
Seleccionamos la primera imagen de prueba, la remodelamos para que coincida con la forma de entrada del modelo y ejecutamos la inferencia utilizando nuestro modelo TFLite. Luego, comparamos la predicción con la etiqueta real.
Este ejemplo integral muestra todo el proceso, desde la creación del modelo hasta el despliegue y prueba con TensorFlow Lite, proporcionando una demostración práctica de cómo preparar un modelo para su implementación en el borde utilizando TensorFlow Lite.
Despliegue de modelos TensorFlow Lite en Android
Una vez que tienes un modelo de TensorFlow Lite, puedes integrarlo sin problemas en una aplicación Android. TensorFlow Lite ofrece una robusta API Java que simplifica el proceso de cargar el modelo y ejecutar inferencias en dispositivos Android. Esta API proporciona a los desarrolladores un conjunto de herramientas potentes para incorporar eficientemente capacidades de machine learning en sus aplicaciones móviles.
La API Java de TensorFlow Lite permite a los desarrolladores realizar varias operaciones clave:
- Carga del modelo: Carga fácilmente tu modelo de TensorFlow Lite desde los activos de la aplicación o desde el almacenamiento externo.
- Gestión de tensores de entrada/salida: Maneja de manera eficiente los tensores de entrada y salida, incluida la conversión de tipos de datos y la manipulación de formas.
- Ejecución de inferencias: Ejecuta inferencias del modelo con rendimiento optimizado en dispositivos Android.
- Aceleración de hardware: Aprovecha la API Neural Networks (NNAPI) de Android para la aceleración de hardware en dispositivos compatibles.
Al utilizar esta API, los desarrolladores pueden crear aplicaciones Android sofisticadas que realicen tareas de machine learning en el dispositivo con latencia y consumo de recursos mínimos. Este enfoque permite una amplia gama de casos de uso, desde la clasificación de imágenes en tiempo real y la detección de objetos hasta el procesamiento de lenguaje natural y recomendaciones personalizadas, todo mientras se mantiene la privacidad del usuario al mantener los datos en el dispositivo.
A continuación se muestra un fragmento de cómo se puede hacer esto:
import org.tensorflow.lite.Interpreter;
import org.tensorflow.lite.gpu.GpuDelegate;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel.MapMode;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
public class MyModel {
private Interpreter tflite;
private static final int NUM_THREADS = 4;
private static final int OUTPUT_CLASSES = 10;
private GpuDelegate gpuDelegate = null;
public MyModel(AssetManager assetManager, String modelPath, boolean useGPU) throws IOException {
ByteBuffer modelBuffer = loadModelFile(assetManager, modelPath);
Interpreter.Options options = new Interpreter.Options();
options.setNumThreads(NUM_THREADS);
if (useGPU) {
gpuDelegate = new GpuDelegate();
options.addDelegate(gpuDelegate);
}
tflite = new Interpreter(modelBuffer, options);
}
private MappedByteBuffer loadModelFile(AssetManager assetManager, String modelPath) throws IOException {
AssetFileDescriptor fileDescriptor = assetManager.openFd(modelPath);
FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
FileChannel fileChannel = inputStream.getChannel();
long startOffset = fileDescriptor.getStartOffset();
long declaredLength = fileDescriptor.getDeclaredLength();
return fileChannel.map(MapMode.READ_ONLY, startOffset, declaredLength);
}
public float[] runInference(float[] inputData) {
if (tflite == null) {
throw new IllegalStateException("TFLite Interpreter has not been initialized.");
}
ByteBuffer inputBuffer = ByteBuffer.allocateDirect(inputData.length * 4).order(ByteOrder.nativeOrder());
for (float value : inputData) {
inputBuffer.putFloat(value);
}
inputBuffer.rewind();
ByteBuffer outputBuffer = ByteBuffer.allocateDirect(OUTPUT_CLASSES * 4).order(ByteOrder.nativeOrder());
tflite.run(inputBuffer, outputBuffer);
outputBuffer.rewind();
float[] outputData = new float[OUTPUT_CLASSES];
outputBuffer.asFloatBuffer().get(outputData);
return outputData;
}
public void close() {
if (tflite != null) {
tflite.close();
tflite = null;
}
if (gpuDelegate != null) {
gpuDelegate.close();
gpuDelegate = null;
}
}
}
Este ejemplo proporciona una implementación integral de la clase MyModel para desplegar modelos de TensorFlow Lite en dispositivos Android.
Analicemos los componentes y mejoras principales:
- Importaciones:
- Se agregaron importaciones para
GpuDelegate
y elAssetManager
de Android. - Se incluyeron las clases necesarias de Java I/O para el manejo de archivos, garantizando una carga eficiente del modelo.
- Se agregaron importaciones para
- Variables de Clase:
- Se introdujo
NUM_THREADS
para especificar el número de hilos para el intérprete. - Se agregó
OUTPUT_CLASSES
para definir el número de clases de salida (asumiendo 10 en este ejemplo). - Se agregó una variable
GpuDelegate
para gestionar la aceleración GPU cuando está habilitada.
- Se introdujo
- Constructor:
- Se incluyó un parámetro
useGPU
para habilitar opcionalmente la aceleración GPU para una inferencia optimizada. - Se implementó
Interpreter.Options
para configurar el intérprete TFLite con hilos de CPU y delegación GPU opcional. - Se aseguró el manejo adecuado de la creación del intérprete con asignación dinámica de recursos.
- Se incluyó un parámetro
- Carga del Modelo:
- Se mejoró la carga del modelo usando
AssetManager.openFd()
para recuperar correctamente los activos del sistema de almacenamiento de Android. - Se utilizó
MappedByteBuffer
para un mapeo eficiente de memoria, reduciendo la sobrecarga de carga. - Se incluyó un manejo apropiado de excepciones para una gestión robusta de archivos.
- Se mejoró la carga del modelo usando
- Método de Inferencia:
- Se implementaron verificaciones de nulos para el intérprete TFLite para prevenir fallos inesperados.
- Se utilizó
ByteBuffer
tanto para entrada como salida para asegurar la compatibilidad con operaciones de TensorFlow Lite. - Se convirtieron los datos de entrada de tipo float a
ByteBuffer
antes de ejecutar la inferencia. - Se extrajeron las predicciones del buffer de salida para devolver el array de flotantes procesado.
- Gestión de Recursos:
- Se implementó un método
close()
para liberar adecuadamente los recursos de TensorFlow Lite cuando ya no se necesitan. - Se aseguró que el
GpuDelegate
se cierre si fue utilizado para evitar fugas de memoria.
- Se implementó un método
Esta implementación mejorada proporciona un alto rendimiento, manejo de errores y gestión eficiente de recursos. También permite la aceleración GPU opcional, que puede mejorar significativamente la velocidad de inferencia en dispositivos compatibles. El código es robusto y adecuado para uso en producción en aplicaciones Android.
8.2.2 ONNX (Open Neural Network Exchange)
ONNX (Open Neural Network Exchange) es un formato versátil y de código abierto para representar modelos de machine learning. Desarrollado en colaboración por Microsoft y Facebook, ONNX sirve como un puente entre diferentes marcos de machine learning, permitiendo la portabilidad de modelos de manera fluida. Esta interoperabilidad permite que los modelos entrenados en marcos populares como PyTorch o TensorFlow se transfieran y ejecuten fácilmente en diversos entornos.
La popularidad de ONNX para el despliegue en dispositivos de borde se debe a su capacidad para unificar modelos de diversas fuentes en un formato estandarizado. Esta representación unificada puede luego optimizarse y ejecutarse de manera eficiente utilizando el ONNX Runtime, un motor de inferencia de alto rendimiento diseñado para maximizar el potencial de los modelos ONNX en diferentes plataformas.
Una de las principales fortalezas de ONNX radica en su extenso soporte de hardware. El formato es compatible con una amplia gama de plataformas, desde potentes servidores en la nube hasta dispositivos IoT con recursos limitados. Esta compatibilidad asegura que los desarrolladores puedan desplegar sus modelos en diversos ecosistemas de hardware sin necesidad de modificaciones significativas.
Además, ONNX incorpora optimizaciones integradas específicamente diseñadas para dispositivos de borde. Estas optimizaciones abordan los desafíos únicos que presentan los recursos computacionales limitados, las restricciones de memoria y los requisitos de eficiencia energética típicos en los entornos de computación en el borde. Al aprovechar estas optimizaciones, los desarrolladores pueden mejorar significativamente el rendimiento de sus modelos en dispositivos de borde, permitiendo inferencias en tiempo real y mejorando la experiencia general del usuario.
La combinación de compatibilidad entre marcos, amplio soporte de hardware y optimizaciones específicas para el borde hace que ONNX sea una opción ideal para desplegar modelos de machine learning en entornos con recursos limitados. Ya sea un dispositivo inteligente para el hogar, una aplicación móvil o un sensor IoT industrial, ONNX proporciona las herramientas y la flexibilidad necesarias para llevar capacidades avanzadas de IA al borde, abriendo nuevas posibilidades para soluciones de computación en el borde inteligentes, receptivas y eficientes.
Ejemplo: Convertir un modelo de PyTorch a ONNX
Tomemos un modelo de PyTorch, lo convertimos al formato ONNX y lo ejecutamos utilizando el ONNX Runtime.
import torch
import torch.nn as nn
import torch.optim as optim
import onnx
import onnxruntime as ort
import numpy as np
# Define a simple PyTorch model
class SimpleModel(nn.Module):
def __init__(self):
super(SimpleModel, self).__init__()
self.fc1 = nn.Linear(784, 128)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
# Create an instance of the model
model = SimpleModel()
# Train the model (simplified for demonstration)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters())
# Dummy training data
dummy_input = torch.randn(100, 784)
dummy_target = torch.randint(0, 10, (100,))
for epoch in range(5):
optimizer.zero_grad()
output = model(dummy_input)
loss = criterion(output, dummy_target)
loss.backward()
optimizer.step()
print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")
# Prepare dummy input for ONNX export
dummy_input = torch.randn(1, 784)
# Export the model to ONNX format
torch.onnx.export(model, dummy_input, "model.onnx", verbose=True)
print("Model successfully converted to ONNX format.")
# Load and run the ONNX model using ONNX Runtime
ort_session = ort.InferenceSession("model.onnx")
def to_numpy(tensor):
return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()
# Run inference
input_data = to_numpy(dummy_input)
ort_inputs = {ort_session.get_inputs()[0].name: input_data}
ort_outputs = ort_session.run(None, ort_inputs)
print("ONNX Model Inference Output shape:", ort_outputs[0].shape)
print("ONNX Model Inference Output (first 5 values):", ort_outputs[0][0][:5])
# Compare PyTorch and ONNX Runtime outputs
pytorch_output = model(dummy_input)
np.testing.assert_allclose(to_numpy(pytorch_output), ort_outputs[0], rtol=1e-03, atol=1e-05)
print("PyTorch and ONNX Runtime outputs are similar")
# Save and load ONNX model
onnx_model = onnx.load("model.onnx")
onnx.checker.check_model(onnx_model)
print("The model is checked!")
Este ejemplo de código proporciona una demostración integral del trabajo con modelos de PyTorch y ONNX.
Desglosemos los pasos:
- Definición y entrenamiento del modelo:
- Definimos un modelo ligeramente más complejo con dos capas completamente conectadas y una activación ReLU.
- El modelo se entrena durante 5 épocas con datos ficticios para simular un escenario del mundo real.
- Conversión a ONNX:
- El modelo entrenado de PyTorch se exporta al formato ONNX usando
torch.onnx.export()
. - Usamos
verbose=True
para obtener información detallada sobre el proceso de exportación.
- El modelo entrenado de PyTorch se exporta al formato ONNX usando
- Inferencia con ONNX Runtime:
- Cargamos el modelo ONNX usando onnxruntime y creamos una
InferenceSession
. - Se define la función
to_numpy()
para convertir tensores de PyTorch a matrices NumPy. - Ejecutamos inferencia en el modelo ONNX utilizando la misma entrada ficticia usada para la exportación.
- Cargamos el modelo ONNX usando onnxruntime y creamos una
- Comparación de resultados:
- Comparamos las salidas del modelo de PyTorch y del modelo en ONNX Runtime para asegurarnos de que sean similares.
- Usamos
numpy.testing.assert_allclose()
para verificar si las salidas son cercanas dentro de una tolerancia.
- Validación del modelo ONNX:
- Cargamos el modelo ONNX guardado usando
onnx.load()
. - La función
onnx.checker.check_model()
se utiliza para validar la estructura del modelo ONNX.
- Cargamos el modelo ONNX guardado usando
Este ejemplo integral demuestra todo el flujo de trabajo, desde la definición y entrenamiento de un modelo de PyTorch, hasta su exportación al formato ONNX, la ejecución de la inferencia con ONNX Runtime y la validación de los resultados. Proporciona una base sólida para trabajar con ONNX en proyectos de machine learning del mundo real.
Optimización de modelos ONNX para dispositivos de borde
Los modelos ONNX pueden optimizarse aún más utilizando herramientas potentes como ONNX Runtime y ONNX Quantization. Estas técnicas avanzadas de optimización son fundamentales para desplegar modelos de machine learning en dispositivos con recursos limitados, como teléfonos móviles, dispositivos IoT y sistemas embebidos. Al aprovechar estas herramientas, los desarrolladores pueden reducir significativamente el tamaño del modelo y aumentar la velocidad de inferencia, lo que hace posible ejecutar modelos de IA complejos en dispositivos con capacidad computacional y memoria limitadas.
El ONNX Runtime es un motor de inferencia de código abierto diseñado para acelerar modelos de machine learning en diferentes plataformas de hardware. Proporciona una amplia gama de optimizaciones, incluidas la fusión de operadores, la planificación de memoria y la aceleración específica de hardware. Estas optimizaciones pueden generar mejoras sustanciales en el rendimiento, especialmente en dispositivos de borde con recursos limitados.
ONNX Quantization es otra técnica poderosa que reduce la precisión de los pesos y activaciones del modelo, pasando de números de punto flotante de 32 bits a representaciones de menor ancho de bits, como enteros de 8 bits. Este proceso no solo reduce el tamaño del modelo, sino que también acelera los cálculos, lo que lo hace particularmente beneficioso para el despliegue en dispositivos de borde. La cuantización a menudo se puede aplicar con un impacto mínimo en la precisión del modelo, logrando un equilibrio entre rendimiento y precisión.
Juntas, estas herramientas de optimización permiten a los desarrolladores crear aplicaciones de IA eficientes y de alto rendimiento que pueden ejecutarse sin problemas en una amplia gama de dispositivos, desde potentes servidores en la nube hasta dispositivos de borde con recursos limitados. Esta capacidad es cada vez más importante a medida que crece la demanda de IA en dispositivos en una variedad de industrias y aplicaciones.
Por ejemplo, para aplicar la cuantización a un modelo ONNX, puedes usar la biblioteca onnxruntime.quantization:
import onnx
from onnxruntime.quantization import quantize_dynamic, QuantType
import numpy as np
import onnxruntime as ort
# Load the ONNX model
model_path = "model.onnx"
onnx_model = onnx.load(model_path)
# Perform dynamic quantization
quantized_model_path = "model_quantized.onnx"
quantize_dynamic(model_path, quantized_model_path, weight_type=QuantType.QUInt8)
print("Model successfully quantized for edge deployment.")
# Compare model sizes
import os
original_size = os.path.getsize(model_path)
quantized_size = os.path.getsize(quantized_model_path)
print(f"Original model size: {original_size/1024:.2f} KB")
print(f"Quantized model size: {quantized_size/1024:.2f} KB")
print(f"Size reduction: {(1 - quantized_size/original_size)*100:.2f}%")
# Run inference on both models and compare results
def run_inference(session, input_data):
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
return session.run([output_name], {input_name: input_data})[0]
# Create a dummy input
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
# Run inference on original model
original_session = ort.InferenceSession(model_path)
original_output = run_inference(original_session, input_data)
# Run inference on quantized model
quantized_session = ort.InferenceSession(quantized_model_path)
quantized_output = run_inference(quantized_session, input_data)
# Compare outputs
mse = np.mean((original_output - quantized_output)**2)
print(f"Mean Squared Error between original and quantized model outputs: {mse}")
# Measure inference time
import time
def measure_inference_time(session, input_data, num_runs=100):
total_time = 0
for _ in range(num_runs):
start_time = time.time()
_ = run_inference(session, input_data)
total_time += time.time() - start_time
return total_time / num_runs
original_time = measure_inference_time(original_session, input_data)
quantized_time = measure_inference_time(quantized_session, input_data)
print(f"Average inference time (original model): {original_time*1000:.2f} ms")
print(f"Average inference time (quantized model): {quantized_time*1000:.2f} ms")
print(f"Speedup: {original_time/quantized_time:.2f}x")
Este ejemplo demuestra un flujo de trabajo integral para cuantificar un modelo ONNX y evaluar su rendimiento.
Desglosemos los pasos:
- Carga y cuantificación del modelo:
- Comenzamos cargando el modelo ONNX original usando la biblioteca onnx.
- Luego, utilizamos la función
quantize_dynamic
para realizar la cuantificación dinámica en el modelo, convirtiendo los pesos a enteros sin signo de 8 bits (QUInt8).
- Comparación de tamaño del modelo:
- Comparamos los tamaños de archivo del modelo original y el modelo cuantificado para demostrar la reducción en el tamaño del modelo logrado a través de la cuantificación.
- Configuración de inferencia:
- Se define una función auxiliar
run_inference
para simplificar la ejecución de la inferencia en ambos modelos, el original y el cuantificado. - Creamos un tensor de entrada ficticio para usarlo en la inferencia.
- Se define una función auxiliar
- Ejecución de la inferencia:
- Creamos sesiones de ONNX Runtime tanto para el modelo original como para el cuantificado.
- La inferencia se ejecuta en ambos modelos usando los mismos datos de entrada.
- Comparación de salida:
- Calculamos el error cuadrático medio (MSE) entre las salidas de los modelos original y cuantificado para cuantificar cualquier pérdida de precisión debido a la cuantificación.
- Medición del rendimiento:
- Se define una función
measure_inference_time
para medir con precisión el tiempo promedio de inferencia a lo largo de múltiples ejecuciones. - Medimos y comparamos los tiempos de inferencia de ambos modelos, el original y el cuantificado.
- Se define una función
Este ejemplo integral no solo demuestra cómo cuantificar un modelo ONNX, sino que también proporciona un análisis exhaustivo de los efectos de la cuantificación, incluida la reducción del tamaño del modelo, el impacto potencial en la precisión y las mejoras en la velocidad de inferencia. Este enfoque ofrece a los desarrolladores una visión clara de los compromisos involucrados en la cuantificación de modelos para su implementación en dispositivos de borde.
8.2.3 Comparación entre TensorFlow Lite y ONNX para despliegue en dispositivos de borde
Tanto TensorFlow Lite (TFLite) como Open Neural Network Exchange (ONNX) ofrecen potentes capacidades para desplegar modelos de machine learning en dispositivos de borde, cada uno con sus propias fortalezas y casos de uso. TensorFlow Lite es especialmente adecuado para flujos de trabajo basados en TensorFlow, ofreciendo una integración perfecta y herramientas de optimización específicamente diseñadas para el ecosistema de TensorFlow.
8.2 Introducción a TensorFlow Lite y ONNX para Dispositivos de Borde
El rápido avance de la computación en el borde ha revolucionado la implementación de modelos de machine learning en una amplia gama de dispositivos, incluidos teléfonos inteligentes, tabletas, wearables y dispositivos IoT. Este cambio hacia la IA basada en el borde presenta tanto oportunidades como desafíos, ya que estos dispositivos suelen tener limitaciones en términos de recursos computacionales, capacidad de memoria y consumo de energía, que no están presentes en las infraestructuras basadas en la nube.
Para abordar estas limitaciones y permitir una IA eficiente en el borde, han surgido frameworks especializados como TensorFlow Lite (TFLite) y ONNX (Open Neural Network Exchange). Estas potentes herramientas proporcionan a los desarrolladores los medios para optimizar, convertir y ejecutar modelos de machine learning en dispositivos de borde con una eficiencia notable.
Al minimizar el overhead y maximizar el rendimiento, TFLite y ONNX son fundamentales para llevar capacidades de IA sofisticadas a entornos con recursos limitados, abriendo nuevas posibilidades para aplicaciones inteligentes en el borde en diversas industrias.
8.2.1 TensorFlow Lite (TFLite)
TensorFlow Lite (TFLite) es un framework poderoso específicamente diseñado para implementar modelos de machine learning en dispositivos con recursos limitados, como teléfonos inteligentes, dispositivos IoT y sistemas embebidos. Ofrece una suite completa de herramientas y optimizaciones que permiten a los desarrolladores reducir significativamente el tamaño del modelo y mejorar la velocidad de inferencia, manteniendo al mismo tiempo un alto grado de precisión.
El flujo de trabajo de TensorFlow Lite consta de dos etapas principales:
- Conversión y optimización del modelo:
Esta fase crucial implica transformar un modelo estándar de TensorFlow en un formato optimizado para TensorFlow Lite. El proceso utiliza el sofisticado TFLite Converter, que emplea varias técnicas para optimizar el modelo:
- Cuantización: Esta técnica reduce la precisión de los pesos y activaciones del modelo, típicamente de puntos flotantes de 32 bits a enteros de 8 bits. Esto no solo disminuye el tamaño del modelo, sino que también acelera los cálculos en dispositivos con poca potencia de procesamiento.
- Poda: Al eliminar conexiones y neuronas innecesarias, la poda reduce aún más el tamaño del modelo y los requisitos computacionales.
- Fusión de operadores: Esta optimización combina múltiples operaciones en una sola operación más eficiente, reduciendo el acceso a la memoria y mejorando el rendimiento general.
- Implementación e inferencia del modelo:
Después de la optimización, el modelo TensorFlow Lite está listo para su implementación en dispositivos de borde. Esta etapa aprovecha el TFLite Interpreter, un motor de ejecución liviano diseñado para la ejecución eficiente de modelos:
- El intérprete es responsable de cargar el modelo optimizado y ejecutar la inferencia con una utilización mínima de recursos.
- Admite aceleración de hardware en varias plataformas, incluidos CPUs ARM, GPUs y aceleradores de IA especializados como el Edge TPU.
- TensorFlow Lite también ofrece APIs específicas para plataformas que permiten una integración fluida con sistemas Android, iOS y Linux embebidos, facilitando la incorporación de capacidades de machine learning en aplicaciones móviles e IoT.
Al aprovechar estas características avanzadas, TensorFlow Lite permite a los desarrolladores llevar capacidades de IA sofisticadas a dispositivos de borde, abriendo nuevas posibilidades para el machine learning en dispositivos locales en una amplia gama de aplicaciones e industrias.
Ejemplo: Convertir un Modelo de TensorFlow a TensorFlow Lite
Comencemos entrenando un modelo simple de TensorFlow y luego lo convertiremos a TensorFlow Lite para su implementación en el borde.
import tensorflow as tf
import numpy as np
# Define a simple model for MNIST digit classification
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10, activation='softmax')
])
# Compile the model
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# Load and preprocess the MNIST dataset
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
# Train the model
model.fit(x_train, y_train, epochs=5, validation_split=0.2)
# Evaluate the model
test_loss, test_acc = model.evaluate(x_test, y_test, verbose=2)
print(f'\nTest accuracy: {test_acc}')
# Save the model in TensorFlow format
model.save('mnist_model.h5')
# Convert the model to TensorFlow Lite format
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
# Save the TFLite model to a file
with open('mnist_model.tflite', 'wb') as f:
f.write(tflite_model)
print("Model successfully converted to TensorFlow Lite format.")
# Function to run inference on TFLite model
def run_tflite_inference(tflite_model, input_data):
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
interpreter.set_tensor(input_details[0]['index'], input_data)
interpreter.invoke()
output = interpreter.get_tensor(output_details[0]['index'])
return output
# Test the TFLite model
test_image = x_test[0]
test_image = np.expand_dims(test_image, axis=0).astype(np.float32)
tflite_output = run_tflite_inference(tflite_model, test_image)
tflite_prediction = np.argmax(tflite_output)
print(f"TFLite Model Prediction: {tflite_prediction}")
print(f"Actual Label: {y_test[0]}")
Este ejemplo de código demuestra un flujo de trabajo integral para crear, entrenar, convertir y probar un modelo de TensorFlow para la clasificación de dígitos MNIST usando TensorFlow Lite.
Desglosemos paso a paso:
- Importación de las bibliotecas necesarias:
Importamos TensorFlow y NumPy, que necesitaremos para la creación del modelo, entrenamiento y manipulación de datos.
- Definición del modelo:
Creamos un modelo secuencial simple para la clasificación de dígitos MNIST. Consiste en una capa Flatten para convertir imágenes 2D en 1D, una capa Dense con activación ReLU, una capa Dropout para regularización, y una capa Dense final con activación softmax para clasificación de 10 clases.
- Compilación del modelo:
Compilamos el modelo usando el optimizador Adam, la pérdida de entropía cruzada categórica dispersa (adecuada para etiquetas enteras) y la precisión como métrica.
- Carga y preprocesamiento de datos:
Cargamos el conjunto de datos MNIST utilizando la función integrada de Keras y normalizamos los valores de píxeles para que estén entre 0 y 1.
- Entrenamiento del modelo:
Entrenamos el modelo durante 5 épocas, utilizando el 20% de los datos de entrenamiento para validación.
- Evaluación del modelo:
Evaluamos el rendimiento del modelo en el conjunto de prueba y mostramos la precisión.
- Guardado del modelo:
Guardamos el modelo entrenado en el formato estándar de TensorFlow (.h5).
- Conversión a TensorFlow Lite:
Utilizamos el TFLiteConverter para convertir el modelo de Keras al formato TensorFlow Lite.
- Guardado del modelo TFLite:
Guardamos el modelo TFLite convertido en un archivo.
- Definición de una función de inferencia:
Creamos una función
run_tflite_inference
que carga un modelo TFLite, lo prepara para inferencia y ejecuta predicciones en los datos de entrada dados. - Prueba del modelo TFLite:
Seleccionamos la primera imagen de prueba, la remodelamos para que coincida con la forma de entrada del modelo y ejecutamos la inferencia utilizando nuestro modelo TFLite. Luego, comparamos la predicción con la etiqueta real.
Este ejemplo integral muestra todo el proceso, desde la creación del modelo hasta el despliegue y prueba con TensorFlow Lite, proporcionando una demostración práctica de cómo preparar un modelo para su implementación en el borde utilizando TensorFlow Lite.
Despliegue de modelos TensorFlow Lite en Android
Una vez que tienes un modelo de TensorFlow Lite, puedes integrarlo sin problemas en una aplicación Android. TensorFlow Lite ofrece una robusta API Java que simplifica el proceso de cargar el modelo y ejecutar inferencias en dispositivos Android. Esta API proporciona a los desarrolladores un conjunto de herramientas potentes para incorporar eficientemente capacidades de machine learning en sus aplicaciones móviles.
La API Java de TensorFlow Lite permite a los desarrolladores realizar varias operaciones clave:
- Carga del modelo: Carga fácilmente tu modelo de TensorFlow Lite desde los activos de la aplicación o desde el almacenamiento externo.
- Gestión de tensores de entrada/salida: Maneja de manera eficiente los tensores de entrada y salida, incluida la conversión de tipos de datos y la manipulación de formas.
- Ejecución de inferencias: Ejecuta inferencias del modelo con rendimiento optimizado en dispositivos Android.
- Aceleración de hardware: Aprovecha la API Neural Networks (NNAPI) de Android para la aceleración de hardware en dispositivos compatibles.
Al utilizar esta API, los desarrolladores pueden crear aplicaciones Android sofisticadas que realicen tareas de machine learning en el dispositivo con latencia y consumo de recursos mínimos. Este enfoque permite una amplia gama de casos de uso, desde la clasificación de imágenes en tiempo real y la detección de objetos hasta el procesamiento de lenguaje natural y recomendaciones personalizadas, todo mientras se mantiene la privacidad del usuario al mantener los datos en el dispositivo.
A continuación se muestra un fragmento de cómo se puede hacer esto:
import org.tensorflow.lite.Interpreter;
import org.tensorflow.lite.gpu.GpuDelegate;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel.MapMode;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
public class MyModel {
private Interpreter tflite;
private static final int NUM_THREADS = 4;
private static final int OUTPUT_CLASSES = 10;
private GpuDelegate gpuDelegate = null;
public MyModel(AssetManager assetManager, String modelPath, boolean useGPU) throws IOException {
ByteBuffer modelBuffer = loadModelFile(assetManager, modelPath);
Interpreter.Options options = new Interpreter.Options();
options.setNumThreads(NUM_THREADS);
if (useGPU) {
gpuDelegate = new GpuDelegate();
options.addDelegate(gpuDelegate);
}
tflite = new Interpreter(modelBuffer, options);
}
private MappedByteBuffer loadModelFile(AssetManager assetManager, String modelPath) throws IOException {
AssetFileDescriptor fileDescriptor = assetManager.openFd(modelPath);
FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
FileChannel fileChannel = inputStream.getChannel();
long startOffset = fileDescriptor.getStartOffset();
long declaredLength = fileDescriptor.getDeclaredLength();
return fileChannel.map(MapMode.READ_ONLY, startOffset, declaredLength);
}
public float[] runInference(float[] inputData) {
if (tflite == null) {
throw new IllegalStateException("TFLite Interpreter has not been initialized.");
}
ByteBuffer inputBuffer = ByteBuffer.allocateDirect(inputData.length * 4).order(ByteOrder.nativeOrder());
for (float value : inputData) {
inputBuffer.putFloat(value);
}
inputBuffer.rewind();
ByteBuffer outputBuffer = ByteBuffer.allocateDirect(OUTPUT_CLASSES * 4).order(ByteOrder.nativeOrder());
tflite.run(inputBuffer, outputBuffer);
outputBuffer.rewind();
float[] outputData = new float[OUTPUT_CLASSES];
outputBuffer.asFloatBuffer().get(outputData);
return outputData;
}
public void close() {
if (tflite != null) {
tflite.close();
tflite = null;
}
if (gpuDelegate != null) {
gpuDelegate.close();
gpuDelegate = null;
}
}
}
Este ejemplo proporciona una implementación integral de la clase MyModel para desplegar modelos de TensorFlow Lite en dispositivos Android.
Analicemos los componentes y mejoras principales:
- Importaciones:
- Se agregaron importaciones para
GpuDelegate
y elAssetManager
de Android. - Se incluyeron las clases necesarias de Java I/O para el manejo de archivos, garantizando una carga eficiente del modelo.
- Se agregaron importaciones para
- Variables de Clase:
- Se introdujo
NUM_THREADS
para especificar el número de hilos para el intérprete. - Se agregó
OUTPUT_CLASSES
para definir el número de clases de salida (asumiendo 10 en este ejemplo). - Se agregó una variable
GpuDelegate
para gestionar la aceleración GPU cuando está habilitada.
- Se introdujo
- Constructor:
- Se incluyó un parámetro
useGPU
para habilitar opcionalmente la aceleración GPU para una inferencia optimizada. - Se implementó
Interpreter.Options
para configurar el intérprete TFLite con hilos de CPU y delegación GPU opcional. - Se aseguró el manejo adecuado de la creación del intérprete con asignación dinámica de recursos.
- Se incluyó un parámetro
- Carga del Modelo:
- Se mejoró la carga del modelo usando
AssetManager.openFd()
para recuperar correctamente los activos del sistema de almacenamiento de Android. - Se utilizó
MappedByteBuffer
para un mapeo eficiente de memoria, reduciendo la sobrecarga de carga. - Se incluyó un manejo apropiado de excepciones para una gestión robusta de archivos.
- Se mejoró la carga del modelo usando
- Método de Inferencia:
- Se implementaron verificaciones de nulos para el intérprete TFLite para prevenir fallos inesperados.
- Se utilizó
ByteBuffer
tanto para entrada como salida para asegurar la compatibilidad con operaciones de TensorFlow Lite. - Se convirtieron los datos de entrada de tipo float a
ByteBuffer
antes de ejecutar la inferencia. - Se extrajeron las predicciones del buffer de salida para devolver el array de flotantes procesado.
- Gestión de Recursos:
- Se implementó un método
close()
para liberar adecuadamente los recursos de TensorFlow Lite cuando ya no se necesitan. - Se aseguró que el
GpuDelegate
se cierre si fue utilizado para evitar fugas de memoria.
- Se implementó un método
Esta implementación mejorada proporciona un alto rendimiento, manejo de errores y gestión eficiente de recursos. También permite la aceleración GPU opcional, que puede mejorar significativamente la velocidad de inferencia en dispositivos compatibles. El código es robusto y adecuado para uso en producción en aplicaciones Android.
8.2.2 ONNX (Open Neural Network Exchange)
ONNX (Open Neural Network Exchange) es un formato versátil y de código abierto para representar modelos de machine learning. Desarrollado en colaboración por Microsoft y Facebook, ONNX sirve como un puente entre diferentes marcos de machine learning, permitiendo la portabilidad de modelos de manera fluida. Esta interoperabilidad permite que los modelos entrenados en marcos populares como PyTorch o TensorFlow se transfieran y ejecuten fácilmente en diversos entornos.
La popularidad de ONNX para el despliegue en dispositivos de borde se debe a su capacidad para unificar modelos de diversas fuentes en un formato estandarizado. Esta representación unificada puede luego optimizarse y ejecutarse de manera eficiente utilizando el ONNX Runtime, un motor de inferencia de alto rendimiento diseñado para maximizar el potencial de los modelos ONNX en diferentes plataformas.
Una de las principales fortalezas de ONNX radica en su extenso soporte de hardware. El formato es compatible con una amplia gama de plataformas, desde potentes servidores en la nube hasta dispositivos IoT con recursos limitados. Esta compatibilidad asegura que los desarrolladores puedan desplegar sus modelos en diversos ecosistemas de hardware sin necesidad de modificaciones significativas.
Además, ONNX incorpora optimizaciones integradas específicamente diseñadas para dispositivos de borde. Estas optimizaciones abordan los desafíos únicos que presentan los recursos computacionales limitados, las restricciones de memoria y los requisitos de eficiencia energética típicos en los entornos de computación en el borde. Al aprovechar estas optimizaciones, los desarrolladores pueden mejorar significativamente el rendimiento de sus modelos en dispositivos de borde, permitiendo inferencias en tiempo real y mejorando la experiencia general del usuario.
La combinación de compatibilidad entre marcos, amplio soporte de hardware y optimizaciones específicas para el borde hace que ONNX sea una opción ideal para desplegar modelos de machine learning en entornos con recursos limitados. Ya sea un dispositivo inteligente para el hogar, una aplicación móvil o un sensor IoT industrial, ONNX proporciona las herramientas y la flexibilidad necesarias para llevar capacidades avanzadas de IA al borde, abriendo nuevas posibilidades para soluciones de computación en el borde inteligentes, receptivas y eficientes.
Ejemplo: Convertir un modelo de PyTorch a ONNX
Tomemos un modelo de PyTorch, lo convertimos al formato ONNX y lo ejecutamos utilizando el ONNX Runtime.
import torch
import torch.nn as nn
import torch.optim as optim
import onnx
import onnxruntime as ort
import numpy as np
# Define a simple PyTorch model
class SimpleModel(nn.Module):
def __init__(self):
super(SimpleModel, self).__init__()
self.fc1 = nn.Linear(784, 128)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
# Create an instance of the model
model = SimpleModel()
# Train the model (simplified for demonstration)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters())
# Dummy training data
dummy_input = torch.randn(100, 784)
dummy_target = torch.randint(0, 10, (100,))
for epoch in range(5):
optimizer.zero_grad()
output = model(dummy_input)
loss = criterion(output, dummy_target)
loss.backward()
optimizer.step()
print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")
# Prepare dummy input for ONNX export
dummy_input = torch.randn(1, 784)
# Export the model to ONNX format
torch.onnx.export(model, dummy_input, "model.onnx", verbose=True)
print("Model successfully converted to ONNX format.")
# Load and run the ONNX model using ONNX Runtime
ort_session = ort.InferenceSession("model.onnx")
def to_numpy(tensor):
return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()
# Run inference
input_data = to_numpy(dummy_input)
ort_inputs = {ort_session.get_inputs()[0].name: input_data}
ort_outputs = ort_session.run(None, ort_inputs)
print("ONNX Model Inference Output shape:", ort_outputs[0].shape)
print("ONNX Model Inference Output (first 5 values):", ort_outputs[0][0][:5])
# Compare PyTorch and ONNX Runtime outputs
pytorch_output = model(dummy_input)
np.testing.assert_allclose(to_numpy(pytorch_output), ort_outputs[0], rtol=1e-03, atol=1e-05)
print("PyTorch and ONNX Runtime outputs are similar")
# Save and load ONNX model
onnx_model = onnx.load("model.onnx")
onnx.checker.check_model(onnx_model)
print("The model is checked!")
Este ejemplo de código proporciona una demostración integral del trabajo con modelos de PyTorch y ONNX.
Desglosemos los pasos:
- Definición y entrenamiento del modelo:
- Definimos un modelo ligeramente más complejo con dos capas completamente conectadas y una activación ReLU.
- El modelo se entrena durante 5 épocas con datos ficticios para simular un escenario del mundo real.
- Conversión a ONNX:
- El modelo entrenado de PyTorch se exporta al formato ONNX usando
torch.onnx.export()
. - Usamos
verbose=True
para obtener información detallada sobre el proceso de exportación.
- El modelo entrenado de PyTorch se exporta al formato ONNX usando
- Inferencia con ONNX Runtime:
- Cargamos el modelo ONNX usando onnxruntime y creamos una
InferenceSession
. - Se define la función
to_numpy()
para convertir tensores de PyTorch a matrices NumPy. - Ejecutamos inferencia en el modelo ONNX utilizando la misma entrada ficticia usada para la exportación.
- Cargamos el modelo ONNX usando onnxruntime y creamos una
- Comparación de resultados:
- Comparamos las salidas del modelo de PyTorch y del modelo en ONNX Runtime para asegurarnos de que sean similares.
- Usamos
numpy.testing.assert_allclose()
para verificar si las salidas son cercanas dentro de una tolerancia.
- Validación del modelo ONNX:
- Cargamos el modelo ONNX guardado usando
onnx.load()
. - La función
onnx.checker.check_model()
se utiliza para validar la estructura del modelo ONNX.
- Cargamos el modelo ONNX guardado usando
Este ejemplo integral demuestra todo el flujo de trabajo, desde la definición y entrenamiento de un modelo de PyTorch, hasta su exportación al formato ONNX, la ejecución de la inferencia con ONNX Runtime y la validación de los resultados. Proporciona una base sólida para trabajar con ONNX en proyectos de machine learning del mundo real.
Optimización de modelos ONNX para dispositivos de borde
Los modelos ONNX pueden optimizarse aún más utilizando herramientas potentes como ONNX Runtime y ONNX Quantization. Estas técnicas avanzadas de optimización son fundamentales para desplegar modelos de machine learning en dispositivos con recursos limitados, como teléfonos móviles, dispositivos IoT y sistemas embebidos. Al aprovechar estas herramientas, los desarrolladores pueden reducir significativamente el tamaño del modelo y aumentar la velocidad de inferencia, lo que hace posible ejecutar modelos de IA complejos en dispositivos con capacidad computacional y memoria limitadas.
El ONNX Runtime es un motor de inferencia de código abierto diseñado para acelerar modelos de machine learning en diferentes plataformas de hardware. Proporciona una amplia gama de optimizaciones, incluidas la fusión de operadores, la planificación de memoria y la aceleración específica de hardware. Estas optimizaciones pueden generar mejoras sustanciales en el rendimiento, especialmente en dispositivos de borde con recursos limitados.
ONNX Quantization es otra técnica poderosa que reduce la precisión de los pesos y activaciones del modelo, pasando de números de punto flotante de 32 bits a representaciones de menor ancho de bits, como enteros de 8 bits. Este proceso no solo reduce el tamaño del modelo, sino que también acelera los cálculos, lo que lo hace particularmente beneficioso para el despliegue en dispositivos de borde. La cuantización a menudo se puede aplicar con un impacto mínimo en la precisión del modelo, logrando un equilibrio entre rendimiento y precisión.
Juntas, estas herramientas de optimización permiten a los desarrolladores crear aplicaciones de IA eficientes y de alto rendimiento que pueden ejecutarse sin problemas en una amplia gama de dispositivos, desde potentes servidores en la nube hasta dispositivos de borde con recursos limitados. Esta capacidad es cada vez más importante a medida que crece la demanda de IA en dispositivos en una variedad de industrias y aplicaciones.
Por ejemplo, para aplicar la cuantización a un modelo ONNX, puedes usar la biblioteca onnxruntime.quantization:
import onnx
from onnxruntime.quantization import quantize_dynamic, QuantType
import numpy as np
import onnxruntime as ort
# Load the ONNX model
model_path = "model.onnx"
onnx_model = onnx.load(model_path)
# Perform dynamic quantization
quantized_model_path = "model_quantized.onnx"
quantize_dynamic(model_path, quantized_model_path, weight_type=QuantType.QUInt8)
print("Model successfully quantized for edge deployment.")
# Compare model sizes
import os
original_size = os.path.getsize(model_path)
quantized_size = os.path.getsize(quantized_model_path)
print(f"Original model size: {original_size/1024:.2f} KB")
print(f"Quantized model size: {quantized_size/1024:.2f} KB")
print(f"Size reduction: {(1 - quantized_size/original_size)*100:.2f}%")
# Run inference on both models and compare results
def run_inference(session, input_data):
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
return session.run([output_name], {input_name: input_data})[0]
# Create a dummy input
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
# Run inference on original model
original_session = ort.InferenceSession(model_path)
original_output = run_inference(original_session, input_data)
# Run inference on quantized model
quantized_session = ort.InferenceSession(quantized_model_path)
quantized_output = run_inference(quantized_session, input_data)
# Compare outputs
mse = np.mean((original_output - quantized_output)**2)
print(f"Mean Squared Error between original and quantized model outputs: {mse}")
# Measure inference time
import time
def measure_inference_time(session, input_data, num_runs=100):
total_time = 0
for _ in range(num_runs):
start_time = time.time()
_ = run_inference(session, input_data)
total_time += time.time() - start_time
return total_time / num_runs
original_time = measure_inference_time(original_session, input_data)
quantized_time = measure_inference_time(quantized_session, input_data)
print(f"Average inference time (original model): {original_time*1000:.2f} ms")
print(f"Average inference time (quantized model): {quantized_time*1000:.2f} ms")
print(f"Speedup: {original_time/quantized_time:.2f}x")
Este ejemplo demuestra un flujo de trabajo integral para cuantificar un modelo ONNX y evaluar su rendimiento.
Desglosemos los pasos:
- Carga y cuantificación del modelo:
- Comenzamos cargando el modelo ONNX original usando la biblioteca onnx.
- Luego, utilizamos la función
quantize_dynamic
para realizar la cuantificación dinámica en el modelo, convirtiendo los pesos a enteros sin signo de 8 bits (QUInt8).
- Comparación de tamaño del modelo:
- Comparamos los tamaños de archivo del modelo original y el modelo cuantificado para demostrar la reducción en el tamaño del modelo logrado a través de la cuantificación.
- Configuración de inferencia:
- Se define una función auxiliar
run_inference
para simplificar la ejecución de la inferencia en ambos modelos, el original y el cuantificado. - Creamos un tensor de entrada ficticio para usarlo en la inferencia.
- Se define una función auxiliar
- Ejecución de la inferencia:
- Creamos sesiones de ONNX Runtime tanto para el modelo original como para el cuantificado.
- La inferencia se ejecuta en ambos modelos usando los mismos datos de entrada.
- Comparación de salida:
- Calculamos el error cuadrático medio (MSE) entre las salidas de los modelos original y cuantificado para cuantificar cualquier pérdida de precisión debido a la cuantificación.
- Medición del rendimiento:
- Se define una función
measure_inference_time
para medir con precisión el tiempo promedio de inferencia a lo largo de múltiples ejecuciones. - Medimos y comparamos los tiempos de inferencia de ambos modelos, el original y el cuantificado.
- Se define una función
Este ejemplo integral no solo demuestra cómo cuantificar un modelo ONNX, sino que también proporciona un análisis exhaustivo de los efectos de la cuantificación, incluida la reducción del tamaño del modelo, el impacto potencial en la precisión y las mejoras en la velocidad de inferencia. Este enfoque ofrece a los desarrolladores una visión clara de los compromisos involucrados en la cuantificación de modelos para su implementación en dispositivos de borde.
8.2.3 Comparación entre TensorFlow Lite y ONNX para despliegue en dispositivos de borde
Tanto TensorFlow Lite (TFLite) como Open Neural Network Exchange (ONNX) ofrecen potentes capacidades para desplegar modelos de machine learning en dispositivos de borde, cada uno con sus propias fortalezas y casos de uso. TensorFlow Lite es especialmente adecuado para flujos de trabajo basados en TensorFlow, ofreciendo una integración perfecta y herramientas de optimización específicamente diseñadas para el ecosistema de TensorFlow.