Questa guida introduce la procedura di esecuzione di un modello LiteRT (abbreviazione di Lite Runtime) sul dispositivo per fare previsioni in base ai dati di input. Ciò si ottiene con l'interprete LiteRT, che utilizza un ordinamento statico dei grafici e un allocatore di memoria personalizzato (meno dinamico) per garantire latenza minima di caricamento, inizializzazione ed esecuzione.
L'inferenza LiteRT in genere segue i seguenti passaggi:
Caricamento di un modello: carica il modello
.tflitein memoria, che contiene il grafico di esecuzione del modello.Trasformazione dei dati: trasforma i dati di input nel formato e nelle dimensioni previsti. I dati di input non elaborati per il modello in genere non corrispondono al formato dei dati di input previsto dal modello. Ad esempio, potresti dover ridimensionare un'immagine o modificarne il formato per renderla compatibile con il modello.
Esecuzione dell'inferenza: esegui il modello LiteRT per fare previsioni. Questo passaggio prevede l'utilizzo dell'API LiteRT per eseguire il modello. Sono necessari alcuni passaggi, come la creazione dell'interprete e l'allocazione dei tensori.
Interpretazione dell'output: interpreta i tensori di output in modo significativo e utile nella tua applicazione. Ad esempio, un modello potrebbe restituire solo un elenco di probabilità. Spetta a te mappare le probabilità alle categorie pertinenti e formattare l'output.
Questa guida descrive come accedere all'interprete LiteRT ed eseguire un'inferenza utilizzando C++, Java e Python.
Piattaforme supportate
Le API di inferenza TensorFlow sono fornite per le piattaforme mobile e incorporate più comuni, come Android, iOS e Linux, in più linguaggi di programmazione.
Nella maggior parte dei casi, la progettazione dell'API riflette una preferenza per il rendimento rispetto alla facilità d'uso. LiteRT è progettato per un'inferenza rapida su dispositivi di piccole dimensioni, quindi le API evitano copie non necessarie a scapito della praticità.
In tutte le librerie, l'API LiteRT consente di caricare modelli, inserire input e recuperare gli output di inferenza.
Piattaforma Android
Su Android, l'inferenza LiteRT può essere eseguita utilizzando le API Java o C++. Le API Java offrono praticità e possono essere utilizzate direttamente nelle classi Activity di Android. Le API C++ offrono maggiore flessibilità e velocità, ma potrebbero richiedere la scrittura di wrapper JNI per spostare i dati tra i livelli Java e C++.
Per saperne di più, consulta le sezioni C++ e Java oppure segui la guida rapida di Android.
Piattaforma iOS
Su iOS, LiteRT è disponibile nelle librerie Swift e Objective-C di iOS. Puoi anche utilizzare l'API C direttamente nel codice Objective-C.
Consulta le sezioni Swift, Objective-C e API C oppure segui la Guida rapida per iOS.
Piattaforma Linux
Sulle piattaforme Linux, puoi eseguire 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:
- Caricamento del modello in memoria.
- Creazione di un
Interpreterbasato su un modello esistente. - Impostazione dei valori del tensore di input.
- Richiamo delle inferenze.
- Output dei valori dei tensori.
Android (Java)
L'API Java per l'esecuzione di inferenze con LiteRT è progettata principalmente per l'utilizzo
con Android, quindi è disponibile come dipendenza della libreria Android:
com.google.ai.edge.litert.
In Java, utilizzerai la classe Interpreter per caricare un modello e guidare l'inferenza del modello. In molti casi, questa potrebbe essere l'unica API di cui hai bisogno.
Puoi inizializzare un Interpreter utilizzando un file FlatBuffers (.tflite):
public Interpreter(@NotNull File modelFile);
Oppure con un MappedByteBuffer:
public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);
In entrambi i casi, devi fornire un modello LiteRT valido o l'API genera
IllegalArgumentException. Se utilizzi MappedByteBuffer per inizializzare un
Interpreter, questo deve rimanere invariato per tutta la durata del
Interpreter.
Il modo preferito per eseguire l'inferenza su un modello è utilizzare le firme. Disponibile per i modelli convertiti a partire da 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 per gli input dal nome dell'input nella firma a un oggetto input.
Output : mappa per la mappatura dell'output dal nome dell'output nella firma all'output dati.
Nome firma (facoltativo): nome della firma (può essere lasciato vuoto se il modello ha una sola firma).
Un altro modo per eseguire inferenze quando il modello non ha firme definite.
Chiama il numero 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 il tuo
modello ha più input o output, utilizza 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 dati di output
corrispondenti.
In entrambi i casi, gli indici dei tensori devono corrispondere ai valori che hai assegnato a
LiteRT Converter quando hai creato il
modello. Tieni presente che l'ordine dei tensori in input deve corrispondere all'ordine fornito
al convertitore LiteRT.
La classe Interpreter fornisce anche funzioni pratiche per ottenere l'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, viene generato un
IllegalArgumentException.
Tieni inoltre presente che Interpreter possiede risorse. Per evitare perdite di memoria, le
risorse devono essere rilasciate dopo l'uso da:
interpreter.close();
Per un progetto di esempio con Java, consulta l'app di esempio per il rilevamento degli oggetti Android.
Tipi di dati supportati
Per utilizzare LiteRT, i tipi di dati dei tensori di input e output devono essere uno dei seguenti tipi primitivi:
floatintlongbyte
Sono supportati anche i tipi String, ma sono codificati in modo diverso rispetto ai tipi primitivi. In particolare, la forma di un tensore di stringhe determina il numero
e la disposizione delle stringhe nel tensore, con ogni elemento che è una
stringa di lunghezza variabile. In questo senso, la dimensione (in byte) del tensore non può essere
calcolata solo in base alla forma e al tipo e, di conseguenza, le stringhe non possono essere
fornite come singolo argomento ByteBuffer.
Se vengono utilizzati altri tipi di dati, inclusi i tipi in scatola come Integer e Float,
verrà generato un IllegalArgumentException.
Input
Ogni input deve essere un array o un array multidimensionale dei tipi primitivi supportati oppure un ByteBuffer non elaborato delle dimensioni appropriate. Se l'input è
una matrice o una matrice multidimensionale, il tensore di input associato verrà
ridimensionato implicitamente in base alle dimensioni della matrice al momento dell'inferenza. Se l'input è
un ByteBuffer, il chiamante deve prima ridimensionare manualmente il tensore di input associato (tramite Interpreter.resizeInput()) prima di eseguire l'inferenza.
Quando utilizzi ByteBuffer, preferisci utilizzare buffer di byte diretti, in quanto ciò consente a Interpreter di evitare copie non necessarie. Se ByteBuffer è un buffer di byte diretto, il suo ordine deve essere ByteOrder.nativeOrder(). Dopo essere stato utilizzato per l'inferenza di un modello, deve rimanere invariato fino al termine dell'inferenza.
Output
Ogni output deve essere un array o un array multidimensionale dei tipi primitivi supportati oppure un ByteBuffer della dimensione appropriata. Tieni presente che alcuni modelli hanno output dinamici, in cui la forma dei tensori di output può variare a seconda dell'input. Non esiste un modo semplice per gestire questa situazione con l'API Java inference esistente, ma le estensioni pianificate lo renderanno possibile.
iOS (Swift)
L'API
Swift
è disponibile nel pod TensorFlowLiteSwift di CocoaPods.
Per prima cosa, devi importare il modulo 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)
L'API
Objective-C
è disponibile nel pod LiteRTObjC di CocoaPods.
Per prima cosa, devi importare il modulo 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 C nel codice Objective-C
L'API Objective-C non supporta i delegati. Per utilizzare i delegati con il codice Objective-C, devi chiamare direttamente l'API C sottostante.
#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 le piattaforme Android, iOS e Linux. L'API C++ su iOS è disponibile solo quando utilizzi Bazel.
In C++, il modello è archiviato nella classe
FlatBufferModel.
Contiene un modello LiteRT e puoi crearlo in due modi diversi, a seconda di dove è memorizzato 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 hai il modello come 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 (e qualsiasi dipendenza fissa dalle librerie di stringhe).
- Non è possibile 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++ è il 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, vedi
minimal.cc
e
label_image.cc.
Python
L'API Python per l'esecuzione di inferenze utilizza
Interpreter per caricare un modello ed
eseguire 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)
Il seguente esempio mostra come utilizzare l'interprete Python per caricare un file FlatBuffers (.tflite) ed eseguire l'inferenza con dati di input casuali:
Questo esempio è consigliato se esegui la conversione da SavedModel con un SignatureDef definito.
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 il modello non ha SignatureDefs definito.
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 pre-convertito, puoi combinare il codice con il compilatore LiteRT, che ti consente di convertire il modello Keras nel formato LiteRT ed eseguire l'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 altri esempi di codice Python, vedi
label_image.py.
Esegui l'inferenza con il modello di forma dinamica
Se vuoi eseguire un modello con una forma di input dinamica, ridimensiona la forma di input
prima di eseguire l'inferenza. In caso contrario, la forma None nei modelli TensorFlow verrà sostituita da un segnaposto di 1 nei modelli LiteRT.
I seguenti esempi mostrano come ridimensionare la forma di input prima di eseguire
l'inferenza in diverse lingue. Tutti gli esempi presuppongono che la forma di input
sia definita come [1/None, 10] e debba essere ridimensionata a [3, 10].
Esempio in C++:
// Resize input tensors before allocate tensors
interpreter->ResizeInputTensor(/*tensor_index=*/0, std::vector<int>{3,10});
interpreter->AllocateTensors();
Esempio 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()