Inferencia de TensorFlow Lite

El término inferencia se refiere al proceso de ejecutar un modelo de TensorFlow Lite en el dispositivo para realizar predicciones basadas en datos de entrada. Para realizar una inferencia con un modelo de TensorFlow Lite, debes ejecutarla a través de un intérprete. El intérprete de TensorFlow Lite está diseñado para ser eficiente y rápido. El intérprete usa un ordenamiento de grafos estáticos y un localizador de memoria personalizado (menos dinámico) para garantizar una latencia mínima de carga, inicialización y ejecución.

En esta página, se describe cómo acceder al intérprete de TensorFlow Lite y cómo realizar una inferencia con C++, Java y Python, además de vínculos a otros recursos para cada plataforma compatible.

Conceptos importantes

En general, la inferencia de TensorFlow Lite sigue estos pasos:

  1. Cómo cargar un modelo

    Debes cargar el modelo .tflite en la memoria, que contiene el grafo de ejecución del modelo.

  2. Transformación de datos

    Los datos de entrada sin procesar para el modelo, por lo general, no coinciden con el formato de datos de entrada que espera el modelo. Por ejemplo, es posible que debas cambiar el tamaño de una imagen o su formato para que sea compatible con el modelo.

  3. Ejecuta la inferencia

    Este paso implica usar la API de TensorFlow Lite para ejecutar el modelo. Este proceso requiere algunos pasos, como la compilación del intérprete y la asignación de tensores, como se describe en las siguientes secciones.

  4. Cómo interpretar el resultado

    Cuando recibes resultados de la inferencia del modelo, debes interpretar los tensores de manera significativa que sea útil en tu aplicación.

    Por ejemplo, un modelo puede mostrar solo una lista de probabilidades. Depende de ti asignar las probabilidades a las categorías relevantes y presentárselas a tu usuario final.

Plataformas compatibles

Las APIs de inferencia de TensorFlow se proporcionan para la mayoría de las plataformas incorporadas o de dispositivos móviles, como Android, iOS y Linux, en varios lenguajes de programación.

En la mayoría de los casos, el diseño de la API refleja una preferencia por el rendimiento sobre la facilidad de uso. TensorFlow Lite se diseñó para realizar inferencias rápidas en dispositivos pequeños, por lo que no debería sorprenderte que las APIs intenten evitar copias innecesarias por conveniencia. De manera similar, la coherencia con las APIs de TensorFlow no era un objetivo explícito, y es esperable que haya cierta variación entre los lenguajes.

En todas las bibliotecas, la API de TensorFlow Lite te permite cargar modelos, ingresar entradas y recuperar resultados de inferencias.

Plataforma de Android

En Android, la inferencia de TensorFlow Lite se puede realizar con las APIs de Java o C++. Las APIs de Java son convenientes y se pueden usar directamente dentro de las clases de actividad de Android. Las APIs de C++ ofrecen más flexibilidad y velocidad, pero pueden requerir la escritura de wrappers de JNI para mover datos entre capas de Java y C++.

Consulta la siguiente sección para obtener detalles sobre el uso de C++ y Java, o sigue la guía de inicio rápido de Android para acceder a un instructivo y un código de ejemplo.

Generador de código de wrapper para Android con TensorFlow Lite

En el caso del modelo de TensorFlow Lite mejorado con metadatos, los desarrolladores pueden usar el generador de código wrapper de Android para TensorFlow Lite a fin de crear un código de wrapper específico para la plataforma. El código del wrapper quita la necesidad de interactuar directamente con ByteBuffer en Android. En su lugar, los desarrolladores pueden interactuar con el modelo de TensorFlow Lite mediante objetos escritos, como Bitmap y Rect. Para obtener más información, consulta el generador de código de wrapper de Android en TensorFlow Lite.

Plataforma iOS

En iOS, TensorFlow Lite está disponible con bibliotecas nativas de iOS escritas en Swift y Objective-C. También puedes usar la API de C directamente en los códigos de Objective-C.

Consulta la siguiente información para obtener detalles sobre el uso de Swift, Objective-C y la API de C, o sigue la guía de inicio rápido de iOS para ver un instructivo y un código de ejemplo.

Plataforma Linux

En las plataformas Linux (incluida Raspberry Pi), puedes ejecutar inferencias con las APIs de TensorFlow Lite disponibles en C++ y Python, como se muestra en las siguientes secciones.

Ejecuta un modelo

Para ejecutar un modelo de TensorFlow Lite, es necesario seguir algunos pasos sencillos:

  1. Carga el modelo en la memoria.
  2. Compila un Interpreter basado en un modelo existente.
  3. Configura los valores del tensor de entrada. (Opcionalmente, cambia el tamaño de los tensores de entrada si no se desean los tamaños predefinidos).
  4. Invocar la inferencia.
  5. Lee valores de tensores de salida.

En las siguientes secciones, se describe cómo realizar estos pasos en cada idioma.

Carga y ejecuta un modelo en Java

Plataforma: Android

La API de Java para ejecutar una inferencia con TensorFlow Lite se diseñó principalmente para usarse con Android, por lo que está disponible como una dependencia de biblioteca de Android: org.tensorflow:tensorflow-lite.

En Java, usarás la clase Interpreter para cargar un modelo y controlar la inferencia del modelo. En muchos casos, esta puede ser la única API que necesitas.

Puedes inicializar un Interpreter con un archivo .tflite:

public Interpreter(@NotNull File modelFile);

O con MappedByteBuffer:

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

En ambos casos, debes proporcionar un modelo de TensorFlow Lite válido o la API arrojará una IllegalArgumentException. Si usas MappedByteBuffer para inicializar un Interpreter, debe permanecer igual durante toda la vida útil de Interpreter.

La forma preferida de ejecutar inferencias en un modelo es usar firmas (disponible para modelos convertidos a partir de TensorFlow 2.5)

try (Interpreter interpreter = new Interpreter(file_of_tensorflowlite_model)) {
  Map<String, Object> inputs = new HashMap<>();
  inputs.put("input_1", input1);
  inputs.put("input_2", input2);
  Map<String, Object> outputs = new HashMap<>();
  outputs.put("output_1", output1);
  interpreter.runSignature(inputs, outputs, "mySignature");
}

El método runSignature toma tres argumentos:

  • Entradas : Asigna las entradas del nombre de entrada en la firma a un objeto de entrada.

  • Salidas : Asignación para la asignación de salida del nombre de salida en la firma a los datos de salida.

  • Signature Name (opcional): Nombre de la firma (puede dejarse vacío si el modelo tiene una sola firma).

Otra forma de ejecutar una inferencia cuando el modelo no tiene firmas definidas. Solo debes llamar a Interpreter.run(). Por ejemplo:

try (Interpreter interpreter = new Interpreter(file_of_a_tensorflowlite_model)) {
  interpreter.run(input, output);
}

El método run() toma solo una entrada y muestra solo una salida. Por lo tanto, si tu modelo tiene varias entradas o salidas, usa lo siguiente:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

En este caso, cada entrada en inputs corresponde a un tensor de entrada y map_of_indices_to_outputs asigna índices de los tensores de salida a los datos de salida correspondientes.

En ambos casos, los índices del tensor deben corresponder a los valores que le diste al convertidor de TensorFlow Lite cuando creaste el modelo. Ten en cuenta que el orden de los tensores en input debe coincidir con el orden otorgado al convertidor de TensorFlow Lite.

La clase Interpreter también proporciona funciones convenientes para que obtengas el índice de cualquier entrada o salida del modelo con el nombre de una operación:

public int getInputIndex(String opName);
public int getOutputIndex(String opName);

Si opName no es una operación válida en el modelo, arroja una IllegalArgumentException.

Además, ten en cuenta que Interpreter posee recursos. Para evitar la fuga de memoria, los recursos deben liberarse después del uso de las siguientes acciones:

interpreter.close();

Para ver un proyecto de ejemplo con Java, consulta la muestra de clasificación de imágenes de Android.

Tipos de datos admitidos (en Java)

Para usar TensorFlow Lite, los tipos de datos de los tensores de entrada y salida deben ser uno de los siguientes tipos primitivos:

  • float
  • int
  • long
  • byte

También se admiten los tipos String, pero están codificados de manera diferente a los tipos primitivos. En particular, la forma de un tensor de string determina el número y la disposición de las strings en el tensor, y cada elemento es una string de longitud variable. En este sentido, el tamaño (en bytes) del tensor no se puede calcular solo a partir de la forma y el tipo, y, por lo tanto, las cadenas no se pueden proporcionar como un único argumento ByteBuffer plano.

Si se usan otros tipos de datos, incluidos los tipos encuadrados, como Integer y Float, se arrojará una IllegalArgumentException.

Entradas

Cada entrada debe ser un array o un array multidimensional de los tipos primitivos admitidos, o bien un ByteBuffer sin procesar del tamaño adecuado. Si la entrada es un array o un array multidimensional, el tensor de entrada asociado se cambiará de forma implícita a las dimensiones del array en el momento de la inferencia. Si la entrada es un ByteBuffer, el emisor debe cambiar de forma manual el tamaño del tensor de entrada asociado (a través de Interpreter.resizeInput()) antes de ejecutar la inferencia.

Cuando uses ByteBuffer, es preferible que utilices búferes de bytes directos, ya que esto permite que Interpreter evite copias innecesarias. Si ByteBuffer es un búfer de bytes directos, su orden debe ser ByteOrder.nativeOrder(). Después de usarse para una inferencia de modelo, debe permanecer sin cambios hasta que finalice la inferencia de modelo.

Salidas

Cada salida debe ser un array o un array multidimensional de los tipos primitivos admitidos, o un ByteBuffer del tamaño adecuado. Ten en cuenta que algunos modelos tienen salidas dinámicas, en las que la forma de los tensores de salida puede variar según la entrada. No hay una forma directa de manejar esto con la API de inferencia de Java existente, pero las extensiones planificadas lo harán posible.

Carga y ejecuta un modelo en Swift

Plataforma: iOS

La API de Swift está disponible en el Pod TensorFlowLiteSwift de CocoaPods.

Primero, debes importar el módulo TensorFlowLite.

import TensorFlowLite
// Getting model path
guard
  let modelPath = Bundle.main.path(forResource: "model", ofType: "tflite")
else {
  // Error handling...
}

do {
  // Initialize an interpreter with the model.
  let interpreter = try Interpreter(modelPath: modelPath)

  // Allocate memory for the model's input `Tensor`s.
  try interpreter.allocateTensors()

  let inputData: Data  // Should be initialized

  // input data preparation...

  // Copy the input data to the input `Tensor`.
  try self.interpreter.copy(inputData, toInputAt: 0)

  // Run inference by invoking the `Interpreter`.
  try self.interpreter.invoke()

  // Get the output `Tensor`
  let outputTensor = try self.interpreter.output(at: 0)

  // Copy output to `Data` to process the inference results.
  let outputSize = outputTensor.shape.dimensions.reduce(1, {x, y in x * y})
  let outputData =
        UnsafeMutableBufferPointer<Float32>.allocate(capacity: outputSize)
  outputTensor.data.copyBytes(to: outputData)

  if (error != nil) { /* Error handling... */ }
} catch error {
  // Error handling...
}

Carga y ejecuta un modelo en Objective-C

Plataforma: iOS

La API de Objective-C está disponible en el Pod TensorFlowLiteObjC de CocoaPods.

Primero, debes importar el módulo TensorFlowLite.

@import TensorFlowLite;
NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                      ofType:@"tflite"];
NSError *error;

// Initialize an interpreter with the model.
TFLInterpreter *interpreter = [[TFLInterpreter alloc] initWithModelPath:modelPath
                                                                  error:&error];
if (error != nil) { /* Error handling... */ }

// Allocate memory for the model's input `TFLTensor`s.
[interpreter allocateTensorsWithError:&error];
if (error != nil) { /* Error handling... */ }

NSMutableData *inputData;  // Should be initialized
// input data preparation...

// Get the input `TFLTensor`
TFLTensor *inputTensor = [interpreter inputTensorAtIndex:0 error:&error];
if (error != nil) { /* Error handling... */ }

// Copy the input data to the input `TFLTensor`.
[inputTensor copyData:inputData error:&error];
if (error != nil) { /* Error handling... */ }

// Run inference by invoking the `TFLInterpreter`.
[interpreter invokeWithError:&error];
if (error != nil) { /* Error handling... */ }

// Get the output `TFLTensor`
TFLTensor *outputTensor = [interpreter outputTensorAtIndex:0 error:&error];
if (error != nil) { /* Error handling... */ }

// Copy output to `NSData` to process the inference results.
NSData *outputData = [outputTensor dataWithError:&error];
if (error != nil) { /* Error handling... */ }

Cómo usar la API de C en el código de Objective-C

Por el momento, la API de Objective-C no admite delegados. Para usar los delegados con el código de Objective-C, debes llamar directamente a la API de C subyacente.

#include "tensorflow/lite/c/c_api.h"
TfLiteModel* model = TfLiteModelCreateFromFile([modelPath UTF8String]);
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();

// Create the interpreter.
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);

// Allocate tensors and populate the input tensor data.
TfLiteInterpreterAllocateTensors(interpreter);
TfLiteTensor* input_tensor =
    TfLiteInterpreterGetInputTensor(interpreter, 0);
TfLiteTensorCopyFromBuffer(input_tensor, input.data(),
                           input.size() * sizeof(float));

// Execute inference.
TfLiteInterpreterInvoke(interpreter);

// Extract the output tensor data.
const TfLiteTensor* output_tensor =
    TfLiteInterpreterGetOutputTensor(interpreter, 0);
TfLiteTensorCopyToBuffer(output_tensor, output.data(),
                         output.size() * sizeof(float));

// Dispose of the model and interpreter objects.
TfLiteInterpreterDelete(interpreter);
TfLiteInterpreterOptionsDelete(options);
TfLiteModelDelete(model);

Carga y ejecuta un modelo en C++

Plataformas: Android, iOS y Linux

En C++, el modelo se almacena en la clase FlatBufferModel. Encapsula un modelo de TensorFlow Lite y puedes compilarlo de distintas maneras, según dónde esté almacenado:

class FlatBufferModel {
  // Build a model based on a file. Return a nullptr in case of failure.
  static std::unique_ptr<FlatBufferModel> BuildFromFile(
      const char* filename,
      ErrorReporter* error_reporter);

  // Build a model based on a pre-loaded flatbuffer. The caller retains
  // ownership of the buffer and should keep it alive until the returned object
  // is destroyed. Return a nullptr in case of failure.
  static std::unique_ptr<FlatBufferModel> BuildFromBuffer(
      const char* buffer,
      size_t buffer_size,
      ErrorReporter* error_reporter);
};

Ahora que tienes el modelo como un objeto FlatBufferModel, puedes ejecutarlo con un Interpreter. Más de un Interpreter puede usar un mismo FlatBufferModel a la vez.

Las partes importantes de la API de Interpreter se muestran en el siguiente fragmento de código. Debes tener en cuenta lo siguiente:

  • Los tensores se representan con números enteros para evitar comparaciones de cadenas (y cualquier dependencia fija de las bibliotecas de cadenas).
  • No se debe acceder a un intérprete desde subprocesos simultáneos.
  • La asignación de memoria para los tensores de entrada y salida se debe activar llamando a AllocateTensors() justo después de cambiar el tamaño de los tensores.

El uso más sencillo de TensorFlow Lite con C++ se ve de la siguiente manera:

// Load the model
std::unique_ptr<tflite::FlatBufferModel> model =
    tflite::FlatBufferModel::BuildFromFile(filename);

// Build the interpreter
tflite::ops::builtin::BuiltinOpResolver resolver;
std::unique_ptr<tflite::Interpreter> interpreter;
tflite::InterpreterBuilder(*model, resolver)(&interpreter);

// Resize input tensors, if desired.
interpreter->AllocateTensors();

float* input = interpreter->typed_input_tensor<float>(0);
// Fill `input`.

interpreter->Invoke();

float* output = interpreter->typed_output_tensor<float>(0);

Para ver más ejemplos de código, consulta minimal.cc y label_image.cc.

Carga y ejecuta un modelo en Python

Plataforma: Linux

La API de Python para ejecutar una inferencia necesita solo tf.lite.Interpreter para cargar un modelo y ejecutar una inferencia.

En el siguiente ejemplo, se muestra cómo usar el intérprete de Python para cargar un archivo .tflite y ejecutar una inferencia con datos de entrada aleatorios:

Se recomienda este ejemplo si conviertes un modelo guardado con un SignatureDef definido. Disponible a partir de TensorFlow 2.5

class TestModel(tf.Module):
  def __init__(self):
    super(TestModel, self).__init__()

  @tf.function(input_signature=[tf.TensorSpec(shape=[1, 10], dtype=tf.float32)])
  def add(self, x):
    '''
    Simple method that accepts single input 'x' and returns 'x' + 4.
    '''
    # Name the output 'result' for convenience.
    return {'result' : x + 4}


SAVED_MODEL_PATH = 'content/saved_models/test_variable'
TFLITE_FILE_PATH = 'content/test_variable.tflite'

# Save the model
module = TestModel()
# You can omit the signatures argument and a default signature name will be
# created with name 'serving_default'.
tf.saved_model.save(
    module, SAVED_MODEL_PATH,
    signatures={'my_signature':module.add.get_concrete_function()})

# Convert the model using TFLiteConverter
converter = tf.lite.TFLiteConverter.from_saved_model(SAVED_MODEL_PATH)
tflite_model = converter.convert()
with open(TFLITE_FILE_PATH, 'wb') as f:
  f.write(tflite_model)

# Load the TFLite model in TFLite Interpreter
interpreter = tf.lite.Interpreter(TFLITE_FILE_PATH)
# There is only 1 signature defined in the model,
# so it will return it by default.
# If there are multiple signatures then we can pass the name.
my_signature = interpreter.get_signature_runner()

# my_signature is callable with input as arguments.
output = my_signature(x=tf.constant([1.0], shape=(1,10), dtype=tf.float32))
# 'output' is dictionary with all outputs from the inference.
# In this case we have single output 'result'.
print(output['result'])

Otro ejemplo si el modelo no tiene SignatureDefs definido.

import numpy as np
import tensorflow as tf

# Load the TFLite model and allocate tensors.
interpreter = tf.lite.Interpreter(model_path="converted_model.tflite")
interpreter.allocate_tensors()

# Get input and output tensors.
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

# Test the model on random input data.
input_shape = input_details[0]['shape']
input_data = np.array(np.random.random_sample(input_shape), dtype=np.float32)
interpreter.set_tensor(input_details[0]['index'], input_data)

interpreter.invoke()

# The function `get_tensor()` returns a copy of the tensor data.
# Use `tensor()` in order to get a pointer to the tensor.
output_data = interpreter.get_tensor(output_details[0]['index'])
print(output_data)

Como alternativa a cargar el modelo como un archivo .tflite convertido previamente, puedes combinar tu código con la API de Python Converter de TensorFlow Lite, lo que te permite convertir tu modelo de Keras al formato de TensorFlow Lite y, luego, ejecutar inferencias:

import numpy as np
import tensorflow as tf

img = tf.keras.Input(shape=(64, 64, 3), name="img")
const = tf.constant([1., 2., 3.]) + tf.constant([1., 4., 4.])
val = img + const
out = tf.identity(val, name="out")

# Convert to TF Lite format
converter = tf.lite.TFLiteConverter.from_keras_model(tf.keras.models.Model(inputs=[img], outputs=[out]))
tflite_model = converter.convert()

# Load the TFLite model and allocate tensors.
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()

# Continue to get tensors and so forth, as shown above...

Para obtener más códigos de muestra de Python, consulta label_image.py.

Ejecuta la inferencia con un modelo de forma dinámico.

Si deseas ejecutar un modelo con forma de entrada dinámica, cambia el tamaño de la forma de entrada antes de ejecutar la inferencia. De lo contrario, la forma None en los modelos de TensorFlow se reemplazará por un marcador de posición 1 en los modelos de TFLite.

En los siguientes ejemplos, se muestra cómo cambiar el tamaño de la forma de entrada antes de ejecutar la inferencia en diferentes lenguajes. En todos los ejemplos, se supone que la forma de entrada se define como [1/None, 10] y se debe cambiar el tamaño a [3, 10].

Ejemplo de C++:

// Resize input tensors before allocate tensors
interpreter->ResizeInputTensor(/*tensor_index=*/0, std::vector<int>{3,10});
interpreter->AllocateTensors();

Ejemplo de Python:

# Load the TFLite model in TFLite Interpreter
interpreter = tf.lite.Interpreter(model_path=TFLITE_FILE_PATH)

# Resize input shape for dynamic shape model and allocate tensor
interpreter.resize_tensor_input(interpreter.get_input_details()[0]['index'], [3, 10])
interpreter.allocate_tensors()

# Get input and output tensors.
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

Operaciones admitidas

TensorFlow Lite admite un subconjunto de operaciones de TensorFlow con algunas limitaciones. Para obtener una lista completa de las operaciones y limitaciones, consulta la página de operaciones de TF Lite.