Inferenza TensorFlow Lite

Il termine inferenza si riferisce al processo di esecuzione di un modello TensorFlow Lite sul dispositivo al fine di effettuare previsioni basate sui dati di input. Per eseguire un'inferenza con un modello TensorFlow Lite, è necessario eseguirlo tramite un interprete . L'interprete TensorFlow Lite è progettato per essere snello e veloce. L'interprete utilizza un ordinamento del grafico statico e un allocatore di memoria personalizzato (meno dinamico) per garantire carico, inizializzazione e latenza di esecuzione minimi.

Questa pagina descrive come accedere all'interprete TensorFlow Lite ed eseguire un'inferenza utilizzando C++, Java e Python, oltre a collegamenti ad altre risorse per ciascuna piattaforma supportata .

Concetti importanti

L'inferenza di TensorFlow Lite segue in genere i seguenti passaggi:

  1. Caricamento di un modello

    È necessario caricare in memoria il modello .tflite , che contiene il grafico di esecuzione del modello.

  2. Trasformazione dei dati

    I dati di input grezzi per il modello generalmente non corrispondono al formato dei dati di input previsto dal modello. Ad esempio, potrebbe essere necessario ridimensionare un'immagine o modificare il formato dell'immagine per renderlo compatibile con il modello.

  3. Esecuzione di inferenza

    Questo passaggio prevede l'utilizzo dell'API TensorFlow Lite per eseguire il modello. Implica alcuni passaggi come la costruzione dell'interprete e l'allocazione dei tensori, come descritto nelle sezioni seguenti.

  4. Interpretare l'output

    Quando ricevi risultati dall'inferenza del modello, devi interpretare i tensori in un modo significativo che sia utile nella tua applicazione.

    Ad esempio, un modello potrebbe restituire solo un elenco di probabilità. Sta a te mappare le probabilità in categorie pertinenti e presentarle al tuo utente finale.

Piattaforme supportate

Le API di inferenza di TensorFlow sono fornite per le piattaforme mobili/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 le prestazioni rispetto alla facilità d'uso. TensorFlow Lite è progettato per un'inferenza rapida su dispositivi di piccole dimensioni, quindi non dovrebbe sorprendere che le API cerchino di evitare copie non necessarie a scapito della comodità. Allo stesso modo, la coerenza con le API TensorFlow non era un obiettivo esplicito ed è prevedibile una certa variazione tra i linguaggi.

In tutte le librerie, l'API TensorFlow Lite consente di caricare modelli, alimentare input e recuperare output di inferenza.

Piattaforma Android

Su Android, l'inferenza di TensorFlow Lite può essere eseguita utilizzando API Java o C++. Le API Java offrono comodità e possono essere utilizzate direttamente nelle classi di attività 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++.

Vedi di seguito per i dettagli sull'utilizzo di C++ e Java oppure segui la guida rapida di Android per un tutorial e un codice di esempio.

Generatore di codice wrapper Android TensorFlow Lite

Per il modello TensorFlow Lite migliorato con metadati , gli sviluppatori possono utilizzare il generatore di codice wrapper Android TensorFlow Lite per creare codice wrapper specifico per la piattaforma. Il codice wrapper elimina la necessità di interagire direttamente con ByteBuffer su Android. Invece, gli sviluppatori possono interagire con il modello TensorFlow Lite con oggetti tipizzati come Bitmap e Rect . Per ulteriori informazioni, fare riferimento al generatore di codice wrapper Android TensorFlow Lite .

Piattaforma iOS

Su iOS, TensorFlow Lite è disponibile con librerie iOS native scritte in Swift e Objective-C . Puoi anche utilizzare l'API C direttamente nei codici Objective-C.

Vedi di seguito per i dettagli sull'utilizzo di Swift , Objective-C e l' API C oppure segui la guida rapida di iOS per un tutorial e un codice di esempio.

Piattaforma Linux

Sulle piattaforme Linux (incluso Raspberry Pi ), puoi eseguire inferenze utilizzando le API TensorFlow Lite disponibili in C++ e Python , come mostrato nelle sezioni seguenti.

Esecuzione di un modello

L'esecuzione di un modello TensorFlow Lite prevede alcuni semplici passaggi:

  1. Caricare il modello in memoria.
  2. Costruisci un Interpreter basato su un modello esistente.
  3. Imposta i valori del tensore di input. (Facoltativamente, ridimensionare i tensori di input se le dimensioni predefinite non sono desiderate.)
  4. Invocare l'inferenza.
  5. Leggere i valori del tensore di uscita.

Le sezioni seguenti descrivono come eseguire questi passaggi in ciascuna lingua.

Carica ed esegui un modello in Java

Piattaforma: Android

L'API Java per l'esecuzione di un'inferenza con TensorFlow Lite è progettata principalmente per l'uso con Android, quindi è disponibile come dipendenza della libreria Android: org.tensorflow:tensorflow-lite .

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

public Interpreter(@NotNull File modelFile);

O con un MappedByteBuffer :

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

In entrambi i casi, devi fornire un modello TensorFlow Lite valido altrimenti l'API genererà IllegalArgumentException . Se usi MappedByteBuffer per inizializzare un Interpreter , deve rimanere invariato per tutta la vita 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 di input.

  • Uscite : mappa per la mappatura dell'output dal nome dell'output nella firma ai dati di output.

  • Nome firma [facoltativo]: nome della firma (può essere lasciato vuoto se il modello ha firma singola).

Un altro modo per eseguire un'inferenza quando il modello non dispone di firme definite. Chiama semplicemente Interpreter.run() . Per esempio:

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

Il metodo run() accetta solo un input e restituisce solo un output. Pertanto, se il tuo modello ha più input o più output, utilizza invece:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

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

In entrambi i casi, gli indici tensoriali dovrebbero corrispondere ai valori che hai fornito a TensorFlow Lite Converter quando hai creato il modello. Tieni presente che l'ordine dei tensori in input deve corrispondere all'ordine fornito a TensorFlow Lite Converter.

La classe Interpreter fornisce inoltre comode funzioni per ottenere l'indice di qualsiasi input o output del modello utilizzando un nome di operazione:

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

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

Fai anche attenzione che Interpreter possiede le 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' esempio di classificazione delle immagini Android .

Tipi di dati supportati (in Java)

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

  • float
  • int
  • long
  • byte

Sono supportati anche i tipi String , ma sono codificati in modo diverso rispetto ai tipi primitivi. In particolare, la forma di un tensore di stringa determina il numero e la disposizione delle stringhe nel tensore, essendo ciascun elemento stesso una stringa di lunghezza variabile. In questo senso, la dimensione (in byte) del Tensore non può essere calcolata solo dalla forma e dal tipo e di conseguenza le stringhe non possono essere fornite come argomento ByteBuffer singolo e semplice. Puoi vedere alcuni esempi in questa pagina .

Se vengono utilizzati altri tipi di dati, inclusi i tipi boxed come Integer e Float , verrà generata un'eccezione IllegalArgumentException .

Ingressi

Ogni input deve essere un array o un array multidimensionale dei tipi primitivi supportati o un ByteBuffer non elaborato della dimensione appropriata. Se l'input è un array o un array multidimensionale, il tensore di input associato verrà ridimensionato implicitamente alle dimensioni dell'array 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 si utilizza ByteBuffer , è preferibile utilizzare buffer di byte diretti, poiché ciò consente 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 del modello, deve rimanere invariato fino al termine dell'inferenza del modello.

Uscite

Ogni output deve essere un array o un array multidimensionale dei tipi primitivi supportati oppure un ByteBuffer della dimensione appropriata. Si noti che alcuni modelli hanno uscite dinamiche, dove la forma dei tensori di uscita può variare a seconda dell'ingresso. Non esiste un modo semplice per gestire questo problema con l'API di inferenza Java esistente, ma le estensioni pianificate lo renderanno possibile.

Carica ed esegui un modello in Swift

Piattaforma: iOS

L' API Swift è disponibile in TensorFlowLiteSwift Pod di Cocoapods.

Innanzitutto, 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...
}

Carica ed esegui un modello in Objective-C

Piattaforma: iOS

L' API Objective-C è disponibile nel pod TensorFlowLiteObjC di Cocoapods.

Innanzitutto, devi importare il modulo 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... */ }

Utilizzo dell'API C nel codice Objective-C

Attualmente l'API Objective-C non supporta i delegati. Per utilizzare i delegati con il codice Objective-C, è necessario 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);

Carica ed esegui un modello in C++

Piattaforme: Android, iOS e Linux

In C++, il modello viene archiviato nella classe FlatBufferModel . Incapsula un modello TensorFlow Lite e puoi crearlo in un paio di modi diversi, a seconda di 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 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 seguente. Si dovrebbe notare che:

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

L'utilizzo più semplice di TensorFlow Lite 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 desired.
interpreter->AllocateTensors();

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

interpreter->Invoke();

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

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

Carica ed esegui un modello in Python

Piattaforma: Linux

L'API Python per eseguire un'inferenza è fornita nel modulo tf.lite . Da cui, per lo più, è necessario solo tf.lite.Interpreter per caricare un modello ed eseguire un'inferenza.

L'esempio seguente mostra come utilizzare l'interprete Python per caricare un file .tflite ed eseguire l'inferenza con dati di input casuali:

Questo esempio è consigliato se stai convertendo da SavedModel con un SignatureDef definito. Disponibile a partire da 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'])

Un altro esempio se il modello non ha SignatureDefs definite.

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)

In alternativa al caricamento del modello come file .tflite preconvertito, puoi combinare il tuo codice con l' API Python di TensorFlow Lite Converter ( tf.lite.TFLiteConverter ), che ti consente di convertire il tuo modello Keras nel formato TensorFlow Lite e quindi 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 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...

Per ulteriore codice di esempio Python, consulta label_image.py .

Esegui l'inferenza con il modello di forma dinamica

Se desideri 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 pari a 1 nei modelli TFLite.

Gli esempi seguenti mostrano come ridimensionare la forma di input prima di eseguire l'inferenza in lingue diverse. Tutti gli esempi presuppongono che la forma di input sia definita come [1/None, 10] e debba essere ridimensionata a [3, 10] .

Esempio C++:

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

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