Inférence TensorFlow Lite

Le terme inférence désigne le processus d'exécution d'un modèle TensorFlow Lite sur l'appareil afin de réaliser des prédictions à partir des données d'entrée. Pour effectuer une inférence avec un modèle TensorFlow Lite, vous devez passer par un interpréteur. L'interpréteur TensorFlow Lite est conçu pour être léger et rapide. L'interpréteur utilise un ordre de graphe statique et un outil d'allocation de mémoire personnalisé (moins dynamique) pour garantir un minimum de charge, d'initialisation et de latence d'exécution.

Cette page explique comment accéder à l'interpréteur TensorFlow Lite et comment effectuer une inférence à l'aide de C++, Java et Python. Elle contient également des liens vers d'autres ressources pour chaque plate-forme compatible.

Concepts importants

L'inférence TensorFlow Lite suit généralement les étapes suivantes:

  1. Charger un modèle

    Vous devez charger le modèle .tflite en mémoire, qui contient le graphique d'exécution du modèle.

  2. Transformer les données

    Les données d'entrée brutes du modèle ne correspondent généralement pas au format de données d'entrée attendu par le modèle. Par exemple, vous devrez peut-être redimensionner une image ou modifier son format pour qu'elle soit compatible avec le modèle.

  3. Exécuter une inférence

    Cette étape implique l'exécution du modèle à l'aide de l'API TensorFlow Lite. Cela implique quelques étapes, telles que la création de l'interpréteur et l'allocation de Tensors, comme décrit dans les sections suivantes.

  4. Interpréter le résultat

    Lorsque vous recevez des résultats de l'inférence de modèle, vous devez interpréter les Tensors d'une manière significative et utile dans votre application.

    Par exemple, un modèle peut ne renvoyer qu'une liste de probabilités. Il vous appartient de mapper les probabilités sur les catégories pertinentes et de les présenter à l'utilisateur final.

Plates-formes compatibles

Les API d'inférence TensorFlow sont fournies pour la plupart des plates-formes mobiles/intégrées courantes, telles que Android, iOS et Linux, dans plusieurs langages de programmation.

Dans la plupart des cas, la conception de l'API reflète une préférence pour les performances plutôt que la facilité d'utilisation. TensorFlow Lite est conçu pour une inférence rapide sur les petits appareils. Il n'est donc pas surprenant que les API essaient d'éviter les copies inutiles pour des raisons de commodité. De même, la cohérence avec les API TensorFlow n'était pas un objectif explicite, et il faut s'attendre à des variations entre les langues.

Dans toutes les bibliothèques, l'API TensorFlow Lite vous permet de charger des modèles, de fluxer des entrées et de récupérer des sorties d'inférence.

Plate-forme Android

Sous Android, l'inférence TensorFlow Lite peut être effectuée à l'aide d'API Java ou C++. Les API Java sont pratiques et peuvent être utilisées directement dans vos classes d'activité Android. Les API C++ offrent plus de flexibilité et de rapidité, mais peuvent nécessiter l'écriture de wrappers JNI pour déplacer des données entre les couches Java et C++.

Consultez les informations ci-dessous pour en savoir plus sur l'utilisation de C++ et de Java, ou suivez le guide de démarrage rapide Android pour accéder à un tutoriel et à un exemple de code.

Générateur de code de wrapper Android TensorFlow Lite

Pour le modèle TensorFlow Lite amélioré avec des métadonnées, les développeurs peuvent utiliser le générateur de code du wrapper Android TensorFlow Lite afin de créer un code de wrapper spécifique à la plate-forme. Le code du wrapper évite d'avoir à interagir directement avec ByteBuffer sur Android. À la place, les développeurs peuvent interagir avec le modèle TensorFlow Lite avec des objets typés tels que Bitmap et Rect. Pour en savoir plus, reportez-vous au générateur de code du wrapper Android TensorFlow Lite.

Plate-forme iOS

Sur iOS, TensorFlow Lite est disponible avec les bibliothèques iOS natives écrites en Swift et Objective-C. Vous pouvez également utiliser l'API C directement dans les codes Objective-C.

Consultez la section ci-dessous pour en savoir plus sur l'utilisation de Swift, d'Objective-C et de l'API C, ou suivez le guide de démarrage rapide pour iOS pour accéder à un tutoriel et à un exemple de code.

Plate-forme Linux

Sur les plates-formes Linux (y compris Raspberry Pi), vous pouvez exécuter des inférences à l'aide des API TensorFlow Lite disponibles en C++ et Python, comme indiqué dans les sections suivantes.

Exécuter un modèle

L'exécution d'un modèle TensorFlow Lite s'effectue en quelques étapes simples:

  1. Chargez le modèle en mémoire.
  2. Créez une Interpreter basée sur un modèle existant.
  3. Définissez les valeurs du Tensor d'entrée. Vous pouvez éventuellement redimensionner les Tensors d'entrée si les tailles prédéfinies ne sont pas souhaitées.
  4. Appeler l'inférence.
  5. Lire les valeurs de Tensor de sortie.

Les sections suivantes décrivent la procédure à suivre dans chaque langage.

Charger et exécuter un modèle en Java

Plate-forme: Android

L'API Java permettant d'exécuter une inférence avec TensorFlow Lite est principalement conçue pour Android. Elle est donc disponible en tant que dépendance de la bibliothèque Android : org.tensorflow:tensorflow-lite.

En Java, vous utiliserez la classe Interpreter pour charger un modèle et piloter l'inférence de modèle. Dans de nombreux cas, il s'agit de la seule API dont vous avez besoin.

Vous pouvez initialiser un Interpreter à l'aide d'un fichier .tflite:

public Interpreter(@NotNull File modelFile);

Ou avec un MappedByteBuffer:

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

Dans les deux cas, vous devez fournir un modèle TensorFlow Lite valide, sans quoi l'API génère une erreur IllegalArgumentException. Si vous utilisez MappedByteBuffer pour initialiser un Interpreter, il doit rester inchangé pendant toute la durée de vie de Interpreter.

La méthode privilégiée pour exécuter l'inférence sur un modèle consiste à utiliser des signatures. Cette fonctionnalité est disponible pour les modèles convertis à 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");
}

La méthode runSignature comporte trois arguments:

  • Inputs (Entrées) : mappez les entrées depuis le nom de l'entrée dans la signature vers un objet d'entrée.

  • Outputs (Sorties) : mappage du nom de la sortie dans la signature vers les données de sortie.

  • Signature Name (Nom de la signature) (facultatif): nom de la signature (peut être laissé vide si le modèle comporte une seule signature).

Une autre façon d'exécuter une inférence lorsque le modèle n'a pas de signature définie. Il vous suffit d'appeler Interpreter.run(). Exemple :

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

La méthode run() n'accepte qu'une seule entrée et ne renvoie qu'un seul résultat. Ainsi, si votre modèle comporte plusieurs entrées ou sorties, utilisez plutôt ce qui suit:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

Dans ce cas, chaque entrée dans inputs correspond à un Tensor d'entrée et map_of_indices_to_outputs mappe les index des Tensors de sortie aux données de sortie correspondantes.

Dans les deux cas, les index de Tensor doivent correspondre aux valeurs que vous avez fournies au convertisseur TensorFlow Lite lors de la création du modèle. Sachez que l'ordre des Tensors dans input doit correspondre à l'ordre donné au convertisseur TensorFlow Lite.

La classe Interpreter fournit également des fonctions pratiques vous permettant d'obtenir l'index de n'importe quelle entrée ou sortie du modèle à l'aide d'un nom d'opération:

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

Si opName n'est pas une opération valide dans le modèle, une exception IllegalArgumentException est générée.

Notez également que Interpreter est propriétaire des ressources. Pour éviter les fuites de mémoire, les ressources doivent être libérées après utilisation en:

interpreter.close();

Pour obtenir un exemple de projet avec Java, consultez l'exemple de classification d'images Android.

Types de données acceptés (en Java)

Pour utiliser TensorFlow Lite, les types de données des Tensors d'entrée et de sortie doivent correspondre à l'un des types primitifs suivants:

  • float
  • int
  • long
  • byte

Les types String sont également acceptés, mais ils sont encodés différemment des types primitifs. En particulier, la forme d'un Tensor de chaîne détermine le nombre et la disposition des chaînes dans le Tensor, chaque élément lui-même étant une chaîne de longueur variable. En ce sens, la taille (en octets) du Tensor ne peut pas être calculée à partir de la forme et du type seuls. Par conséquent, les chaînes ne peuvent pas être fournies sous la forme d'un seul argument ByteBuffer plat.

Si d'autres types de données, y compris des types encadrés tels que Integer et Float, sont utilisés, une erreur IllegalArgumentException est générée.

Entrées

Chaque entrée doit être un tableau ou un tableau multidimensionnel des types primitifs compatibles, ou une valeur ByteBuffer brute de taille appropriée. Si l'entrée est un tableau ou un tableau multidimensionnel, le Tensor d'entrée associé sera implicitement redimensionné en fonction des dimensions du tableau au moment de l'inférence. Si l'entrée est un ByteBuffer, l'appelant doit d'abord redimensionner manuellement le Tensor d'entrée associé (via Interpreter.resizeInput()) avant d'exécuter l'inférence.

Lorsque vous utilisez ByteBuffer, il est préférable d'utiliser des tampons d'octets directs, car cela permet à Interpreter d'éviter les copies inutiles. Si ByteBuffer est un tampon d'octets direct, son ordre doit être ByteOrder.nativeOrder(). Une fois utilisée pour l'inférence de modèle, elle doit rester inchangée jusqu'à la fin de l'inférence.

Sorties

Chaque sortie doit être un tableau ou un tableau multidimensionnel des types primitifs compatibles, ou un objet ByteBuffer de la taille appropriée. Notez que certains modèles ont des sorties dynamiques, où la forme des Tensors de sortie peut varier en fonction de l'entrée. Il n'existe pas de méthode simple pour gérer cela avec l'API d'inférence Java existante, mais les extensions planifiées rendront cela possible.

Charger et exécuter un modèle dans Swift

Plate-forme: iOS

L'API Swift est disponible dans le pod TensorFlowLiteSwift à partir de Cocoapods.

Commencez par importer le module 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...
}

Charger et exécuter un modèle dans Objective-C

Plate-forme: iOS

L'API Objective-C est disponible dans le pod TensorFlowLiteObjC à partir de CocoaPods.

Commencez par importer le module 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... */ }

Utilisation de l'API C dans du code Objective-C

Actuellement, l'API Objective-C ne prend pas en charge les délégués. Pour utiliser des délégués avec le code Objective-C, vous devez appeler directement l'API C sous-jacente.

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

Charger et exécuter un modèle en C++

Plates-formes: Android, iOS et Linux

En C++, le modèle est stocké dans la classe FlatBufferModel. Il encapsule un modèle TensorFlow Lite et vous pouvez le créer de différentes manières, en fonction de l'emplacement de stockage du modèle:

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

Maintenant que vous avez le modèle en tant qu'objet FlatBufferModel, vous pouvez l'exécuter avec un Interpreter. Une même FlatBufferModel peut être utilisée simultanément par plusieurs Interpreter.

Les parties importantes de l'API Interpreter sont présentées dans l'extrait de code ci-dessous. Notez les points suivants:

  • Les Tensors sont représentés par des entiers afin d'éviter les comparaisons de chaînes (et toute dépendance fixe aux bibliothèques de chaînes).
  • Vous ne devez pas accéder à un interpréteur à partir de threads simultanés.
  • L'allocation de mémoire pour les Tensors d'entrée et de sortie doit être déclenchée en appelant AllocateTensors() juste après le redimensionnement des Tensors.

L'utilisation la plus simple de TensorFlow Lite avec C++ est la suivante:

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

Pour plus d'exemples de code, consultez minimal.cc et label_image.cc.

Charger et exécuter un modèle en Python

Plate-forme: Linux

L'API Python permettant d'exécuter une inférence n'a généralement besoin que de tf.lite.Interpreter pour charger un modèle et exécuter une inférence.

L'exemple suivant montre comment utiliser l'interpréteur Python pour charger un fichier .tflite et exécuter une inférence avec des données d'entrée aléatoires:

Cet exemple est recommandé si vous effectuez une conversion à partir d'un SavedModel avec un élément SignatureDef défini. Disponible à 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'])

Autre exemple si aucun SignatureDefs n'est défini pour le modèle.

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)

Au lieu de charger le modèle en tant que fichier .tflite pré-converti, vous pouvez combiner votre code avec l'API TensorFlow Lite Converter pour Python, ce qui vous permet de convertir votre modèle Keras au format TensorFlow Lite, puis d'exécuter une inférence:

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

Pour plus d'exemples de code Python, consultez label_image.py.

Exécuter une inférence avec un modèle de forme dynamique

Si vous souhaitez exécuter un modèle avec une forme d'entrée dynamique, redimensionnez-la avant d'exécuter l'inférence. Sinon, la forme None des modèles TensorFlow sera remplacée par un espace réservé 1 dans les modèles TFLite.

Les exemples suivants montrent comment redimensionner la forme d'entrée avant d'exécuter l'inférence dans différentes langues. Tous les exemples supposent que la forme d'entrée est définie comme [1/None, 10] et doit être redimensionnée en [3, 10].

Exemple C++:

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

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

Opérations compatibles

TensorFlow Lite est compatible avec un sous-ensemble d'opérations TensorFlow, avec certaines limites. Pour obtenir la liste complète des opérations et des limites, consultez la page sur les opérations de TF Lite.