Inizia a utilizzare LiteRT

Questa guida illustra il processo di esecuzione di un modello LiteRT sul dispositivo per fare previsioni in base ai dati di input. Ciò si ottiene con LiteRT un interprete, che utilizza un ordinamento statico del grafico e un'immagine personalizzata (meno dinamica) per garantire un carico, l'inizializzazione e la latenza di esecuzione minimi.

L'inferenza LiteRT in genere segue questi passaggi:

  1. Caricamento di un modello: carica il modello .tflite in memoria, che contiene al grafico di esecuzione del modello.

  2. Trasformazione dei dati: trasforma i dati di input nel formato previsto e dimensioni. I dati di input non elaborati per il modello in genere non corrispondono all'input formato dei dati previsto dal modello. Ad esempio, potresti dover ridimensionare o cambiare il formato dell'immagine in modo che sia compatibile con il modello.

  3. Inferenza in esecuzione: esegui il modello LiteRT per fare previsioni. Questo prevede l'utilizzo dell'API LiteRT per eseguire il modello. Implica alcune passaggi come la creazione dell'interprete e l'assegnazione dei tensori.

  4. Interpretazione dell'output: interpreta i tensori di output in modo significativo utile nella tua applicazione. Ad esempio, un modello potrebbe restituire solo delle probabilità. Sta a te mappare le probabilità in base categorie e formattare l'output.

Questa guida descrive come accedere all'interprete LiteRT ed eseguire una l'inferenza utilizzando C++, Java e Python.

Piattaforme supportate

Le API di inferenza di TensorFlow sono fornite per la maggior parte dei modelli come Android, iOS e Linux, in più linguaggi di programmazione.

Nella maggior parte dei casi, la progettazione dell'API riflette una preferenza per le prestazioni piuttosto che per la facilità per gli utilizzi odierni. LiteRT è progettato per un'inferenza rapida su dispositivi di piccole dimensioni, quindi le API evitano copie non necessarie a scapito della comodità.

In tutte le librerie, l'API LiteRT consente di caricare modelli, input di feed per recuperare gli output di inferenza.

Piattaforma Android

Su Android, l'inferenza LiteRT può essere eseguita utilizzando le API Java o C++. La Le API Java offrono praticità e possono essere utilizzate direttamente nel tuo Android Corsi di attività. Le API C++ offrono maggiore flessibilità e velocità, ma potrebbero richiedere scrivere wrapper JNI per spostare dati tra gli strati Java e C++.

Vedi le sezioni C++ e Java per ulteriori informazioni, oppure segui la guida rapida di Android.

Piattaforma iOS

Su iOS, LiteRT è disponibile in Swift e Obiettivo-C librerie iOS. Puoi anche utilizzare C tramite Google Cloud direttamente nel codice Objective-C.

Vedi le API Swift, Objective-C e C o segui la guida rapida di iOS.

Piattaforma Linux

Sulle piattaforme Linux, puoi eseguire le inferenze utilizzando le API LiteRT disponibili in C++.

Carica ed esegui un modello

Il caricamento e l'esecuzione di un modello LiteRT prevede i seguenti passaggi:

  1. Caricamento del modello in memoria.
  2. Creazione di un Interpreter in base a un modello esistente.
  3. Impostazione dei valori del tensore di input.
  4. Richiamo di inferenze.
  5. Output dei valori di un tensore.

Android (Java)

L'API Java per l'esecuzione di inferenze con LiteRT è progettata principalmente per l'uso con Android, quindi è disponibile come dipendenza dalla libreria Android: com.google.ai.edge.litert.

In Java, utilizzerai la classe Interpreter per caricare un modello e un modello di Drive l'inferenza. In molti casi, potrebbe essere l'unica API necessaria.

Puoi inizializzare un Interpreter utilizzando un file FlatBuffers (.tflite):

public Interpreter(@NotNull File modelFile);

Oppure con MappedByteBuffer:

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

In entrambi i casi, devi fornire un modello LiteRT valido o l'API genera IllegalArgumentException. Se usi MappedByteBuffer per inizializzare un oggetto Interpreter, deve rimanere invariata per l'intera durata dell'istanza Interpreter.

Il modo migliore per eseguire l'inferenza su un modello è usare le firme - Disponibile per i modelli convertiti con avvio di 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");
}

Il metodo runSignature accetta tre argomenti:

  • Input : mappa gli input dal nome dell'input nella firma a un input .

  • Output : mappa per la mappatura dell'output dal nome dell'output nella firma all'output e i dati di Google Cloud.

  • (Facoltativo) Nome firma: nome della firma (può essere lasciato vuoto se la modello ha una firma singola).

Un altro modo per eseguire le inferenze quando il modello non ha firme definite. Chiama Interpreter.run(). Ad esempio:

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

Il metodo run() accetta un solo input e restituisce un solo output. Quindi se ha più input o output, usa invece:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

In questo caso, ogni voce in inputs corrisponde a un tensore di input e map_of_indices_to_outputs mappa gli indici dei tensori di output ai corrispondenti di output.

In entrambi i casi, gli indici del tensore devono corrispondere ai valori assegnati il convertitore LiteRT quando hai creato il modello. Attenzione che l'ordine dei tensori in input deve corrispondere all'ordine fornito a LiteRT Convertitore.

Il corso Interpreter offre anche pratiche funzioni per ottenere indice di qualsiasi input o output del modello utilizzando il nome di un'operazione:

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

Se opName non è un'operazione valida nel modello, genera un IllegalArgumentException.

Inoltre, tieni presente che Interpreter è la proprietaria delle risorse. Per evitare fughe di memoria, Le risorse devono essere svincolate dopo l'utilizzo da parte di:

interpreter.close();

Per un progetto di esempio con Java, vedi l'esempio di rilevamento di oggetti Android Google Cloud.

Tipi di dati supportati

Per utilizzare LiteRT, i tipi di dati dei tensori di input e di output devono essere uno dei i seguenti tipi primitivi:

  • float
  • int
  • long
  • byte

Sono supportati anche i tipi String, che però sono codificati in modo diverso rispetto tipi primitivi. In particolare, la forma di una stringa Tensor determina il numero e la disposizione delle stringhe nel Tensor, dove ogni elemento stesso è un stringa di lunghezza variabile. In questo senso, la dimensione in (byte) del Tensor non può essere sono calcolati a partire dalla forma e dal tipo e, di conseguenza, le stringhe non possono fornito come singolo argomento ByteBuffer piatto.

Se vengono utilizzati altri tipi di dati, inclusi quelli con caselle come Integer e Float, verrà lanciato un IllegalArgumentException.

Input

Ogni input deve essere una matrice o un array multidimensionale dei tipi primitivi o un valore ByteBuffer non elaborato delle dimensioni appropriate. Se l'input è un array o una matrice multidimensionale, il tensore di input associato sarà ridimensionato implicitamente alle dimensioni dell'array al momento dell'inferenza. Se l'input è un byte, il chiamante deve prima ridimensionare manualmente l'input associato tensore (tramite Interpreter.resizeInput()) prima di eseguire l'inferenza.

Quando utilizzi ByteBuffer, preferisci usare buffer di byte diretti, poiché ciò consente Interpreter per evitare copie non necessarie. Se ByteBuffer è un byte diretto buffer, l'ordine deve essere ByteOrder.nativeOrder(). Dopo essere stato utilizzato per dell'inferenza del modello, deve rimanere invariata fino al termine dell'inferenza del modello.

Output

Ogni output deve essere una matrice o un array multidimensionale dei o un ByteBuffer di dimensioni appropriate. Tieni presente che alcuni modelli hanno output dinamici, la cui forma dei tensori di output può variare a seconda l'input. Non c'è un modo semplice per gestire questo aspetto con il modello l'API di inferenza Java, ma le estensioni pianificate lo renderanno possibile.

iOS (Swift)

Lo Swift tramite Google Cloud è disponibile nel pod TensorFlowLiteSwift di Cocoapods.

Innanzitutto, devi importare TensorFlowLite modulo.

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)

Lo strumento Objective-C tramite Google Cloud è disponibile nel pod LiteRTObjC di Cocoapods.

Innanzitutto, devi importare TensorFlowLiteObjC modulo.

@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 C nel codice Objective-C

L'API Objective-C non supporta i delegati. Per utilizzare delegati con Codice Objective-C, devi chiamare direttamente C sottostante tramite Google Cloud.

#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++

L'API C++ per l'esecuzione dell'inferenza con LiteRT è compatibile con Android, iOS, e alle piattaforme Linux. L'API C++ su iOS è disponibile solo quando si utilizza bazel.

In C++, il modello viene archiviato FlatBufferModel. Incapsula un modello LiteRT e puoi crearlo in un paio di in base a dove è archiviato il modello:

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

Ora che il modello è un oggetto FlatBufferModel, puoi eseguirlo con un Interpreter Un singolo FlatBufferModel può essere utilizzato contemporaneamente da più di un Interpreter.

Le parti importanti dell'API Interpreter sono mostrate nello snippet di codice di seguito. Tieni presente che:

  • I tensori sono rappresentati da numeri interi per evitare confronti di stringhe (ed eventuali dipendenze fisse dalle librerie di stringhe).
  • Non è necessario accedere a un interprete da thread simultanei.
  • L'allocazione della memoria per i tensori di input e output deve essere attivata chiamando AllocateTensors() subito dopo il ridimensionamento dei tensori.

L'utilizzo più semplice di LiteRT con C++ è simile al seguente:

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

Per altri esempi di codice, consulta minimal.cc e label_image.cc.

Python

L'API Python per l'esecuzione delle inferenze utilizza Interpreter per caricare un modello eseguire le inferenze.

Installa il pacchetto LiteRT:

$ python3 -m pip install ai-edge-litert

Importa l'Interprete LiteRT

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

L'esempio seguente mostra come utilizzare l'interprete Python per caricare un FlatBuffers (.tflite) ed esegue l'inferenza con dati di input casuali:

Questo esempio è consigliato se stai convertendo un SavedModel con un valore definito Def.Firma

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'])

Un altro esempio se per il modello non è stato definito il campo SignatureDefs.

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)

In alternativa al caricamento del modello come file .tflite preconvertito, puoi combinare il tuo codice con LiteRT Compilatore , consentendo di convertire il modello Keras nel formato LiteRT ed eseguire inferenza:

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

Per altro codice campione Python, consulta label_image.py

Esegui l'inferenza con un modello di forma dinamica

Se vuoi eseguire un modello con forma di input dinamico, ridimensiona la forma di input prima di eseguire l'inferenza. In caso contrario, la forma None nei modelli TensorFlow essere sostituita da un segnaposto di 1 nei modelli LiteRT.

I seguenti esempi mostrano come ridimensionare la forma di input prima di eseguirla in varie lingue. Tutti gli esempi presuppongono che la forma di input è definito come [1/None, 10] e deve essere ridimensionato a [3, 10].

Esempio C++:

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

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