Ce guide vous présente le processus d'exécution d'un modèle LiteRT sur l'appareil pour faire des prédictions sur la base des données d'entrée. Pour cela, la solution LiteRT qui utilise un ordre de graphique statique et un interpréteur personnalisé (moins dynamique) afin de limiter au maximum la latence de chargement, d'initialisation et d'exécution.
L'inférence LiteRT suit généralement les étapes suivantes:
Charger un modèle: chargez le modèle
.tflite
en mémoire, qui contient le graphe d'exécution du modèle.Transformation des données: transformer les données d'entrée au format attendu . Les données d'entrée brutes du modèle ne correspondent généralement pas aux données d'entrée format de données attendu par le modèle. Par exemple, vous devrez peut-être redimensionner ou modifier le format d'image pour qu'il soit compatible avec le modèle.
Exécuter une inférence: exécutez le modèle LiteRT pour réaliser des prédictions. Ce consiste à utiliser l'API LiteRT pour exécuter le modèle. Cela implique quelques étapes comme la création de l'interpréteur et l'allocation des Tensors.
Interprétation de la sortie: interpréter les Tensors de sortie de manière significative utiles dans votre application. Par exemple, un modèle peut ne renvoyer qu'une liste de probabilités. C'est à vous de mapper les probabilités avec des des catégories et formatez la sortie.
Ce guide explique comment accéder à l'interpréteur LiteRT et effectuer une à l'aide de C++, Java et Python.
Plates-formes compatibles
Les API d'inférence TensorFlow sont fournies pour la plupart des applications mobiles plates-formes telles qu'Android, iOS et Linux, plusieurs langages de programmation.
Dans la plupart des cas, la conception de l'API reflète une préférence pour les performances utiliser. LiteRT est conçu pour une inférence rapide sur les petits appareils. Par conséquent, les API évitent des copies inutiles au détriment de la commodité.
Dans toutes les bibliothèques, l'API LiteRT vous permet de charger des modèles, de transmettre des entrées pour récupérer les résultats d'inférence.
Plate-forme Android
Sur Android, l'inférence LiteRT peut être effectuée à l'aide des API Java ou C++. La Les API Java sont pratiques et peuvent être utilisées directement dans votre Cours d'activité Les API C++ offrent plus de flexibilité et de rapidité, mais peuvent nécessiter écrire des wrappers JNI pour déplacer des données entre les couches Java et C++.
Pour en savoir plus, consultez les sections C++ et Java. suivez le guide de démarrage rapide pour Android.
Plate-forme iOS
Sur iOS, LiteRT est disponible en Swift et Objective-C bibliothèques iOS. Vous pouvez également utiliser C API directement dans le code Objective-C.
Découvrez les API Swift, Objective-C et C. ou suivez le guide de démarrage rapide pour iOS.
Plate-forme Linux
Sur les plates-formes Linux, vous pouvez exécuter des inférences à l'aide des API LiteRT disponibles C++
Charger et exécuter un modèle
Pour charger et exécuter un modèle LiteRT, procédez comme suit:
- Chargement du modèle en mémoire
- Créer une
Interpreter
basée sur un modèle existant - Définir les valeurs de Tensor d'entrée.
- Appel d'inférences
- Générer des valeurs de Tensors
Android (Java)
L'API Java permettant d'exécuter des inférences avec LiteRT est conçue pour être utilisée
avec Android. Il est donc disponible en tant que dépendance de bibliothèque Android:
com.google.ai.edge.litert
En Java, vous utiliserez la classe Interpreter
pour charger un modèle et le piloter.
l'inférence. 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 FlatBuffers (.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 LiteRT valide, sinon l'API génère
IllegalArgumentException
Si vous utilisez MappedByteBuffer
pour initialiser une
Interpreter
, il doit rester inchangé pendant toute la durée de vie
Interpreter
La méthode privilégiée pour exécuter des inférences sur un modèle consiste à utiliser des signatures (disponibles). 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
utilise trois arguments:
Entrées : mappe les entrées du nom de l'entrée de la signature à une entrée .
Outputs (Sorties) : mappage des sorties entre le nom de la sortie dans la signature et la sortie. données.
Signature Name (Nom de la signature) (facultatif): nom de la signature (vous pouvez laisser ce champ vide si le a une seule signature).
Il s'agit d'une autre façon d'exécuter des inférences lorsque le modèle ne dispose pas de signatures définies.
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'une seule sortie. Si votre
comporte plusieurs entrées ou sorties, utilisez plutôt:
interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);
Dans ce cas, chaque entrée de inputs
correspond à un Tensor d'entrée.
map_of_indices_to_outputs
mappe les index des Tensors de sortie sur les
des données de sortie.
Dans les deux cas, les index de Tensor doivent correspondre aux valeurs définies pour
le convertisseur LiteRT lorsque vous avez créé le modèle. Attention
l'ordre des Tensors dans input
doit correspondre à l'ordre donné à la fonction
Convertisseur.
La classe Interpreter
fournit également des fonctions pratiques pour obtenir
index de toute entrée ou sortie de 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
Sachez également que Interpreter
est propriétaire des ressources. Pour éviter les fuites de mémoire,
ressources doivent être libérées après utilisation par:
interpreter.close();
Pour obtenir un exemple de projet avec Java, consultez l'exemple de détection d'objets Android l'application Nest.
Types de données acceptés
Pour utiliser LiteRT, 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
les types primitifs. Plus précisément, la forme d'une chaîne Tensor détermine le nombre
et l'agencement 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é à partir de la forme et du type uniquement. Par conséquent, les chaînes ne peuvent pas être
fourni sous la forme d'un argument ByteBuffer
unique et plat.
Si d'autres types de données, y compris des types encadrés tels que Integer
et Float
, sont utilisés :
une exception IllegalArgumentException
est générée.
Entrées
Chaque entrée doit être un tableau ou un tableau multidimensionnel des types
types primitifs ou un élément ByteBuffer
brut de la taille appropriée. Si l'entrée est
un tableau ou un tableau multidimensionnel, le Tensor d'entrée associé sera
implicitement redimensionnée aux dimensions du tableau au moment de l'inférence. Si l'entrée est
un ByteBuffer, l'appelant doit d'abord redimensionner manuellement l'entrée associée ;
Tensor (via Interpreter.resizeInput()
) avant d'exécuter l'inférence.
Lorsque vous utilisez ByteBuffer
, privilégiez les tampons d'octets directs, car cela permet au
Interpreter
pour éviter les copies inutiles. Si ByteBuffer
est un octet direct
, son ordre doit être ByteOrder.nativeOrder()
. Après l'avoir utilisée pendant
l'inférence de modèle, elle doit rester inchangée jusqu'à ce que l'inférence du modèle soit terminée.
Sorties
Chaque sortie doit être un tableau ou un tableau multidimensionnel des types des types primitifs ou un ByteBuffer de taille appropriée. Notez que certains modèles ont des sorties dynamiques, où la forme des Tensors de sortie peut varier en fonction l'entrée. Il n'existe pas de moyen simple de gérer cela avec les outils API d'inférence Java, mais avec les extensions planifiées rendront cela possible.
iOS (Swift)
The Swift
API
est disponible dans le pod TensorFlowLiteSwift
de CocoaPods.
Tout d'abord, vous devez 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...
}
iOS (Objective-C)
Le modèle Objective-C
API
est disponible dans le pod LiteRTObjC
de CocoaPods.
Tout d'abord, vous devez importer le module 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 dans le code Objective-C
L'API Objective-C n'est pas compatible avec les délégués. Afin d’utiliser des délégués avec code Objective-C, vous devez appeler directement le C sous-jacent API.
#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++ pour exécuter des inférences avec LiteRT est compatible avec Android, iOS, et Linux. L'API C++ sur iOS n'est disponible que lorsque vous utilisez Bazel.
En C++, le modèle est stocké
FlatBufferModel
.
Il encapsule un modèle LiteRT et vous pouvez le créer dans différentes
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 disposez du modèle en tant qu'objet FlatBufferModel
, vous pouvez l'exécuter
avec un
Interpreter
Un même FlatBufferModel
peut être utilisé 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 depuis des threads simultanés.
- L'allocation de mémoire pour les Tensors d'entrée et de sortie doit être déclenchée par un appel
AllocateTensors()
juste après le redimensionnement des Tensors.
Voici la version la plus simple d'utilisation de LiteRT en C++:
// 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);
Pour obtenir d'autres exemples de code, consultez
minimal.cc
et
label_image.cc
Python
L'API Python permettant d'exécuter des inférences utilise la
Interpreter
pour charger un modèle et
pour exécuter des inférences.
Installez le package LiteRT:
$ python3 -m pip install ai-edge-litert
Importer l'interpréteur LiteRT
from ai_edge_litert.interpreter import Interpreter
Interpreter = Interpreter(model_path=args.model.file)
L'exemple suivant montre comment utiliser l'interpréteur Python pour charger un
FlatBuffers (.tflite
) et exécuter l'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 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'])
Autre exemple si SignatureDefs
n'est pas défini pour le modèle.
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)
Au lieu de charger le modèle en tant que fichier .tflite
préconverti, vous pouvez
pouvez combiner votre code avec LiteRT
Compilateur
, vous permettant de convertir votre modèle Keras au format LiteRT, puis d'exécuter
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 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...
Pour obtenir d'autres 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
être remplacé par un espace réservé 1
dans les modèles LiteRT.
Les exemples suivants montrent comment redimensionner la forme d'entrée avant de l'exécuter
l'inférence dans différentes langues. Tous les exemples supposent que la forme d'entrée
est défini sur [1/None, 10]
et doit être redimensionné 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 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()