Cómo comenzar a usar LiteRT

En esta guía, se presenta el proceso de ejecutar un modelo LiteRT en el dispositivo para y hacer predicciones basadas en los datos de entrada. Esto se logra con LiteRT. de Python, que usa un orden de grafo estático y un intérprete personalizado (menos dinámico) de memoria para garantizar una carga, inicialización y latencia de ejecución mínimas.

Por lo general, la inferencia de LiteRT sigue los siguientes pasos:

  1. Carga un modelo: Carga el modelo .tflite en la memoria, que contiene lo siguiente: el grafo de ejecución del modelo.

  2. Transformación de datos: Transforma los datos de entrada al formato esperado. dimensiones. Por lo general, los datos de entrada sin procesar del modelo no coinciden con la entrada el formato de datos que espera el modelo. Por ejemplo, es posible que debas cambiar el tamaño de un o cambiar su formato para que sea compatible con el modelo.

  3. Ejecución de inferencia: Ejecuta el modelo LiteRT para hacer predicciones. Esta implica usar la API de LiteRT para ejecutar el modelo. Incluye algunas como compilar el intérprete y asignar tensores.

  4. Interpretar los tensores de salida: Interpreta los tensores de salida de manera significativa. que será útil en tu aplicación. Por ejemplo, un modelo puede devolver solo un una lista de probabilidades. Depende de ti asignar las probabilidades a áreas relevantes categorías y dar formato al resultado.

En esta guía, se describe cómo acceder al intérprete de LiteRT y cómo realizar una inferencias con C++, Java y Python.

Plataformas compatibles

Las APIs de inferencia de TensorFlow se proporcionan para los dispositivos móviles e incorporados plataformas 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 usar. LiteRT está diseñado para la inferencia rápida en dispositivos pequeños, de manera que las APIs evitan copias innecesarias a expensas de la conveniencia.

En todas las bibliotecas, la API de LiteRT te permite cargar modelos, ingresar entradas y recuperar resultados de inferencia.

Plataforma de Android

En Android, la inferencia LiteRT se puede realizar con las APIs de Java o C++. El Las APIs de Java brindan comodidad y se pueden usar directamente en tu Clases de actividad. Las APIs de C++ ofrecen más flexibilidad y velocidad, pero pueden requerir Escribir wrappers de JNI para mover datos entre capas de Java y C++.

Consulta las secciones C++ y Java para obtener más información. sigue la guía de inicio rápido de Android.

Plataforma iOS

En iOS, LiteRT está disponible en Swift y Objective‐C bibliotecas de iOS. También puedes usar C API directamente en el código de Objective-C.

Consulta Swift, Objective-C y la API de C. o sigue la guía de inicio rápido de iOS.

Plataforma Linux

En plataformas de Linux, puedes ejecutar inferencias con las APIs de LiteRT disponibles C++

Carga y ejecuta un modelo

Para cargar y ejecutar un modelo LiteRT, sigue estos pasos:

  1. Cargar el modelo en la memoria
  2. Se está compilando un Interpreter basado en un modelo existente.
  3. Configuración de los valores de tensor de entrada.
  4. Invocar inferencias.
  5. Salida de valores de tensor

Android (Java)

La API de Java para ejecutar inferencias con LiteRT está diseñada principalmente para usarse con Android, por lo que está disponible como una dependencia de biblioteca de Android: com.google.ai.edge.litert

En Java, usarás la clase Interpreter para cargar un modelo y un modelo de unidad. la inferencia. En muchos casos, es posible que esta sea la única API que necesites.

Puedes inicializar un Interpreter con un archivo FlatBuffers (.tflite):

public Interpreter(@NotNull File modelFile);

O con un MappedByteBuffer:

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

En ambos casos, debes proporcionar un modelo LiteRT válido; de lo contrario, la API arrojará IllegalArgumentException Si usas MappedByteBuffer para inicializar un Interpreter, debe permanecer igual durante toda la vida útil de la Interpreter

La forma preferida de ejecutar inferencias en un modelo es usar firmas: disponibles para los 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 entradas del nombre de entrada en la firma a una entrada. .

  • Salidas : asigna la salida del nombre del resultado en la firma al resultado de datos no estructurados.

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

Es otra forma de ejecutar inferencias cuando el modelo no tiene firmas definidas. Simplemente llama 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. Entonces, si tu modelo tiene varias entradas o salidas, en su lugar, 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 los índices de los tensores de salida al correspondiente de datos de salida.

En ambos casos, los índices del tensor deben corresponder a los valores que proporcionaste a el convertidor de LiteRT cuando creaste el modelo. Atención que el orden de los tensores en input debe coincidir con el que se asignó a LiteRT Convertidor.

La clase Interpreter también proporciona funciones prácticas para que obtengas el índice de cualquier entrada o salida de modelo con un nombre de operación:

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

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

También ten en cuenta que Interpreter es propietario de los recursos. Para evitar fugas de memoria, la Los recursos deben liberarse después de que los usa:

interpreter.close();

Para ver un proyecto de ejemplo con Java, consulta el ejemplo de detección de objetos de Android de la app.

Tipos de datos admitidos

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

  • float
  • int
  • long
  • byte

También se admiten los tipos String, pero se codifican de forma diferente a la tipos primitivos. En particular, la forma de un tensor de cadena dicta el número y la disposición de cadenas en el tensor, en el que cada elemento es un una cadena de longitud variable. En este sentido, el tamaño (en bytes) del tensor no se puede calculada a partir de la forma y el tipo solo y, por lo tanto, las cadenas no pueden Se proporciona como un argumento ByteBuffer único y plano.

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

Entradas

Cada entrada debe ser un array o un array multidimensional de los tipos primitivos o un ByteBuffer sin procesar del tamaño adecuado. Si la entrada es en un array o array multidimensional, el tensor de entrada asociado será Se cambiará el tamaño de manera implícita a las dimensiones del array en el momento de la inferencia. Si la entrada es un ByteBuffer, el llamador primero debe cambiar manualmente el tamaño de la entrada asociada tensor (a través de Interpreter.resizeInput()) antes de ejecutar la inferencia.

Cuando uses ByteBuffer, es preferible usar búferes de bytes directos, ya que esto permite Interpreter para evitar copias innecesarias. Si ByteBuffer es un byte directo búfer, su orden debe ser ByteOrder.nativeOrder(). Después de usarlo durante un inferencia de modelo, debe permanecer igual hasta que finalice la inferencia de modelo.

Salidas

Cada resultado debe ser un array o un array multidimensional de los tipos primitivos 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 manera directa de manejar esto con la solución API de inferencia de Java, pero las extensiones planificadas lo harán posible.

iOS (Swift)

La opción Swift API 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...
}

iOS (Objective‐C)

El objetivo Objective-C API está disponible en el Pod LiteRTObjC de CocoaPods.

Primero, debes importar el módulo TensorFlowLiteObjC.

@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... */ }

API de C en código Objective-C

La API de Objective-C no admite delegados. Para usar delegados con Objective-C, debes llamar directamente a la C subyacente de la API de Google Ads.

#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);

C++

La API de C++ para ejecutar inferencias con LiteRT es compatible con Android, iOS, y plataformas de Linux. La API de C++ en iOS solo está disponible cuando se usa Bazel.

En C++, el modelo se almacena en FlatBufferModel. Encapsula un modelo LiteRT y puedes compilarlo en diferentes según dónde esté almacenado el modelo.

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 una puede usar un mismo elemento FlatBufferModel de forma simultánea Interpreter

Las partes importantes de la API de Interpreter se muestran en el fragmento de código. a continuación. Ten en cuenta lo siguiente:

  • Los tensores se representan con números enteros para evitar las comparaciones de cadenas. (y cualquier dependencia fija de 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 AllocateTensors() justo después de cambiar el tamaño de los tensores.

El uso más simple de LiteRT con C++ tiene el siguiente aspecto:

// 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 needed.
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

Python

La API de Python para ejecutar inferencias usa el Interpreter para cargar un modelo y ejecutar inferencias.

Instala el paquete LiteRT:

$ python3 -m pip install ai-edge-litert

Cómo importar el intérprete de LiteRT

from ai_edge_litert.interpreter import Interpreter
Interpreter = Interpreter(model_path=args.model.file)

En el siguiente ejemplo, se muestra cómo usar el intérprete de Python para cargar una FlatBuffers (.tflite) y ejecuta inferencias con datos de entrada aleatorios:

Se recomienda este ejemplo si realizas la conversión de un modelo guardado con un SignatureDef.

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 LiteRT model in LiteRT Interpreter
from ai_edge_litert.interpreter import Interpreter
interpreter = 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'])

Este es otro ejemplo si el modelo no tiene SignatureDefs definido.

import numpy as np
import tensorflow as tf

# Load the LiteRT model and allocate tensors.
from ai_edge_litert.interpreter import Interpreter
interpreter = Interpreter(TFLITE_FILE_PATH)
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 previamente convertido, puedes puede combinar tu código con LiteRT. Compilador , lo que te permite convertir tu modelo de Keras al formato LiteRT y, luego, ejecutarlo. inferencia:

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 LiteRT format
converter = tf.lite.TFLiteConverter.from_keras_model(tf.keras.models.Model(inputs=[img], outputs=[out]))
tflite_model = converter.convert()

# Load the LiteRT model and allocate tensors.
from ai_edge_litert.interpreter import Interpreter
interpreter = 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 inferencias con un modelo de forma dinámica

Si quieres 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 LiteRT.

En los siguientes ejemplos, se muestra cómo cambiar el tamaño de la forma de entrada antes de la ejecución la inferencia en diferentes lenguajes. En todos los ejemplos, se supone que la forma de entrada se define como [1/None, 10] y debe cambiarse de 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 LiteRT model in LiteRT Interpreter
from ai_edge_litert.interpreter import Interpreter
interpreter = 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()