Erste Schritte mit LiteRT

In diesem Leitfaden wird die Ausführung eines LiteRT-Modells auf dem Gerät beschrieben, Vorhersagen basierend auf Eingabedaten zu treffen. Dies wird mit dem LiteRT Interpreter, der die Sortierung des statischen Graphen und eine benutzerdefinierte (weniger dynamische) Methode Arbeitsspeicherzuweisung, um die Last, Initialisierung und Ausführungslatenz gering zu halten.

Die LiteRT-Inferenz umfasst normalerweise die folgenden Schritte:

  1. Modell laden: Das Modell .tflite wird in den Arbeitsspeicher geladen. Es enthält das Ausführungsdiagramm des Modells.

  2. Daten transformieren: Eingabedaten in das erwartete Format umwandeln und Dimensionen. Die Rohdaten für das Modell stimmen in der Regel nicht mit der Eingabe überein das vom Modell erwartete Datenformat. Beispielsweise kann es sein, dass Sie die Größe Bild ändern oder das Bildformat so ändern, dass es mit dem Modell kompatibel ist.

  3. Laufende Inferenz: Führen Sie das LiteRT-Modell aus, um Vorhersagen zu treffen. Dieses zur Ausführung des Modells mithilfe der LiteRT API. Dazu sind einige wie das Erstellen des Interpreters und das Zuweisen von Tensoren.

  4. Ausgabe interpretieren: Interpretieren Sie die Ausgabetensoren aussagekräftig. die in Ihrer Anwendung nützlich sind. Beispielsweise könnte ein Modell nur eine eine Liste der Wahrscheinlichkeiten. Es liegt an Ihnen, die Wahrscheinlichkeiten den relevanten und die Ausgabe formatieren.

In diesem Handbuch wird beschrieben, wie Sie auf den LiteRT-Interpreter zugreifen und eine mit C++, Java und Python.

Unterstützte Plattformen

TensorFlow-Inferenz-APIs werden für die gängigsten mobilen und eingebetteten wie Android, iOS und Linux, in mehrere Programmiersprachen.

In den meisten Fällen spiegelt das API-Design eine Präferenz für Leistung statt Nutzerfreundlichkeit wider. verwenden. LiteRT wurde für schnelle Inferenz auf kleinen Geräten entwickelt. unnötige Kopien zulasten der Nutzerfreundlichkeit.

Mit der LiteRT API können Sie in allen Bibliotheken Modelle, Feedeingaben und Inferenzausgaben abrufen können.

Android-Plattform

Unter Android kann LiteRT mit Java oder C++ APIs abgeleitet werden. Die Java APIs sind praktisch und können direkt in Ihrem Android-Gerät Aktivkurse. Die C++ APIs bieten mehr Flexibilität und Geschwindigkeit, erfordern aber JNI-Wrapper zum Verschieben von Daten zwischen Java- und C++-Schichten geschrieben.

Weitere Informationen finden Sie in den Abschnitten zu C++ und Java oder folgen Sie der Android-Kurzanleitung.

iOS-Plattform

Für iOS ist LiteRT in Swift und Objective-C iOS-Bibliotheken. Sie können auch C API direkt im Objective-C-Code.

Weitere Informationen finden Sie in der Dokumentation zu Swift, Objective-C und C API. oder folgen Sie der Kurzanleitung für iOS.

Linux-Plattform

Auf Linux-Plattformen können Sie Inferenzen mit LiteRT-APIs ausführen, die in C++

Modell laden und ausführen

Das Laden und Ausführen eines LiteRT-Modells umfasst die folgenden Schritte:

  1. Modell in den Arbeitsspeicher laden.
  2. Interpreter auf Basis eines vorhandenen Modells erstellen
  3. Eingabetensorwerte festlegen.
  4. Inferenzen aufrufen.
  5. Tensorwerte ausgeben.

Android (Java)

Die Java API zum Ausführen von Inferenzen mit LiteRT ist primär für die Verwendung bei Android, sodass es als Abhängigkeit von der Android-Bibliothek verfügbar ist: com.google.ai.edge.litert

In Java verwenden Sie die Klasse Interpreter, um ein Modell zu laden und zu steuern Inferenz. In vielen Fällen ist dies die einzige API, die Sie benötigen.

Sie können ein Interpreter mit einer FlatBuffers-Datei (.tflite) initialisieren:

public Interpreter(@NotNull File modelFile);

Oder mit einem MappedByteBuffer:

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

In beiden Fällen müssen Sie ein gültiges LiteRT-Modell angeben, oder die API wirft IllegalArgumentException Wenn Sie MappedByteBuffer zum Initialisieren eines Interpreter darf der Wert für die gesamte Lebensdauer des Elements unverändert bleiben. Interpreter.

Die bevorzugte Methode zum Ausführen einer Inferenz für ein Modell ist die Verwendung von Signaturen – Verfügbar für Modelle, die ab TensorFlow 2.5 umgewandelt wurden

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

Die Methode runSignature verwendet drei Argumente:

  • Eingaben : Zuordnung für Eingaben aus dem Eingabenamen in der Signatur zu einer Eingabe -Objekt enthält.

  • Ausgaben : Zuordnung für die Ausgabezuordnung vom Ausgabenamen in der Signatur zur Ausgabe Daten.

  • Signature Name (Name der Signatur (optional): Der Name der Signatur kann leer bleiben, wenn die Modell hat eine einzelne Signatur).

Eine andere Möglichkeit, Inferenzen auszuführen, wenn das Modell keine definierten Signaturen hat. Rufen Sie einfach Interpreter.run() an. Beispiel:

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

Die Methode run() nimmt nur eine Eingabe an und gibt nur eine Ausgabe zurück. Wenn Ihre Modell mehrere Eingaben oder Ausgaben hat. Verwenden Sie stattdessen:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

In diesem Fall entspricht jeder Eintrag in inputs einem Eingabetensor und map_of_indices_to_outputs ordnet Indexe der Ausgabetensoren der entsprechenden und Ausgabedaten.

In beiden Fällen sollten die Tensor-Indizes den Werten entsprechen, die Sie den LiteRT Converter ein, als Sie das Modell erstellt haben. Vorsicht dass die Reihenfolge der Tensoren in input der von LiteRT vorgegebenen Reihenfolge entsprechen muss Nutzer mit Conversion.

Die Interpreter-Klasse bietet auch praktische Funktionen zum Abrufen des Index jeder Modelleingabe oder -ausgabe unter Verwendung eines Vorgangsnamens:

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

Wenn opName im Modell kein gültiger Vorgang ist, wird der Fehler IllegalArgumentException.

Beachten Sie außerdem, dass Interpreter Inhaber von Ressourcen ist. Um Speicherlecks zu vermeiden, Ressourcen müssen nach ihrer Verwendung freigegeben werden:

interpreter.close();

Ein Beispielprojekt mit Java finden Sie unter Android-Objekterkennungsbeispiel App.

Unterstützte Datentypen

Zur Verwendung von LiteRT müssen die Datentypen der Eingabe- und Ausgabetensoren einer der folgenden sein: folgenden primitiven Typen:

  • float
  • int
  • long
  • byte

String-Typen werden ebenfalls unterstützt, sind aber anders codiert als der primitiven Typen. Insbesondere schreibt Tensor die Zahl der Zeichenfolgen vor, und die Anordnung von Zeichenfolgen im Tensor, wobei jedes Element selbst ein variabler Länge. In diesem Sinne kann die (Byte-)Größe des Tensors nur anhand der Form und des Typs berechnet werden, sodass Zeichenfolgen als einzelnes, flaches ByteBuffer-Argument angegeben.

Wenn andere Datentypen verwendet werden, einschließlich umrandeter Typen wie Integer und Float, wird IllegalArgumentException ausgelöst.

Eingaben

Jede Eingabe muss ein Array oder ein mehrdimensionales Array des unterstützten primitive Typen oder eine Roh-ByteBuffer der entsprechenden Größe. Wenn die Eingabe Array oder mehrdimensionales Array ist, ist der zugehörige Eingabetensor implizit an die Abmessungen des Arrays zum Zeitpunkt der Inferenz angepasst wird. Wenn die Eingabe einen ByteBuffer, der Aufrufer sollte zuerst die Größe der zugehörigen Eingabe manuell ändern Tensor (über Interpreter.resizeInput()) vor dem Ausführen der Inferenz.

Bei Verwendung von ByteBuffer sollten Sie direkte Byte-Zwischenspeicher verwenden, da dies den Interpreter, um unnötige Kopien zu vermeiden. Wenn ByteBuffer ein direktes Byte ist Zwischenspeicher verwenden, muss die Reihenfolge ByteOrder.nativeOrder() lauten. Nach der Verwendung Modellinferenz ist, darf sie unverändert bleiben, bis die Modellinferenz abgeschlossen ist.

Ausgaben

Jede Ausgabe sollte ein Array oder ein mehrdimensionales Array der unterstützten primitive Typen oder einen ByteBuffer der entsprechenden Größe verwendet werden. Beachten Sie, dass einige Modelle dynamische Ausgaben haben, bei denen die Form der Ausgabetensoren je nach die Eingabe. Es gibt keine direkte Möglichkeit, dies mit dem bestehenden Java Inference API, doch dies wird durch geplante Erweiterungen ermöglicht.

iOS (Swift)

Der Swift API ist in TensorFlowLiteSwift Pod von CocoaPods verfügbar.

Zuerst müssen Sie das Modul TensorFlowLite importieren.

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)

Das Objective-C API ist in LiteRTObjC Pod von CocoaPods verfügbar.

Zuerst müssen Sie das Modul TensorFlowLiteObjC importieren.

@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... */ }

C API in Objective-C-Code

Die Objective-C API unterstützt keine Bevollmächtigten. Um Bevollmächtigte mit Objective-C-Code zu verwenden, müssen Sie den zugrunde liegenden C-Code direkt aufrufen, API hinzu.

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

Die C++ API zum Ausführen von Inferenzen mit LiteRT ist kompatibel mit Android, iOS, und Linux-Plattformen. Die C++ API für iOS ist nur bei Verwendung von Bali verfügbar.

In C++ wird das Modell in Klasse FlatBufferModel. Es enthält ein LiteRT-Modell, das Sie mit verschiedenen je nachdem, wo das Modell gespeichert ist:

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

Da Sie nun das Modell als FlatBufferModel-Objekt haben, können Sie es ausführen. mit einem Interpreter Eine einzelne FlatBufferModel kann von mehreren gleichzeitig verwendet werden Interpreter.

Die wichtigen Teile der Interpreter API werden im Code-Snippet gezeigt. unten. Dabei ist Folgendes zu beachten:

  • Tensoren werden durch Ganzzahlen dargestellt, um Stringvergleiche zu vermeiden (und alle festen Abhängigkeiten von Stringbibliotheken).
  • Auf einen Interpreter darf nicht über gleichzeitige Threads zugegriffen werden.
  • Die Arbeitsspeicherzuweisung für Eingabe- und Ausgabetensoren muss durch den Aufruf von AllocateTensors() direkt nach der Größenanpassung der Tensoren.

Die einfachste Verwendung von LiteRT mit C++ sieht so aus:

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

Weiteren Beispielcode finden Sie unter minimal.cc und label_image.cc

Python

Die Python API zum Ausführen von Inferenzen verwendet die Methode Interpreter zum Laden eines Modells und Inferenzen ausführen.

Installieren Sie das LiteRT-Paket:

$ python3 -m pip install ai-edge-litert

LiteRT-Interpreter importieren

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

Das folgende Beispiel zeigt, wie mit dem Python-Interpreter ein FlatBuffers-Datei (.tflite) und führt eine Inferenz mit zufälligen Eingabedaten aus:

Dieses Beispiel wird empfohlen, wenn Sie eine Konvertierung aus einem SavedModel mit einer definierten SignaturDef.

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

Ein weiteres Beispiel, wenn für das Modell SignatureDefs nicht definiert ist.

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)

Als Alternative zum Laden des Modells als vorkonvertierte .tflite-Datei können Sie Ihren Code mit der LiteRT API kombinieren Compiler , mit dem Sie Ihr Keras-Modell in das LiteRT-Format umwandeln und Inferenz:

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

Weiteren Python-Beispielcode finden Sie unter label_image.py

Inferenz mit dynamischem Shape-Modell ausführen

Wenn Sie ein Modell mit einer dynamischen Eingabeform ausführen möchten, passen Sie die Größe der Eingabeform an bevor die Inferenz ausgeführt wird. Andernfalls wird die Form None in TensorFlow-Modellen wird in LiteRT-Modellen durch den Platzhalter 1 ersetzt.

Die folgenden Beispiele zeigen, wie Sie die Größe der Eingabeform vor dem Ausführen ändern. Inferenz in verschiedenen Sprachen. Bei allen Beispielen wird davon ausgegangen, dass die Eingabeform ist als [1/None, 10] definiert und muss in [3, 10] geändert werden.

C++-Beispiel:

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

Beispiel für 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()