Ten przewodnik przedstawia proces korzystania z LiteRT (skrót od Lite). środowiska wykonawczego) i generowanie prognoz na podstawie danych wejściowych. Jest to możliwe dzięki interpreterowi LiteRT, który używa stałego uporządkowania grafu i niestandardowego (mniej dynamicznego) przydziału pamięci, aby zapewnić minimalne opóźnienie ładowania, inicjalizacji i wykonania.
Wnioskowanie LiteRT zwykle odbywa się w następujący sposób:
Ładowanie modelu: wczytaj do pamięci model
.tflite
, który zawiera graf wykonania modelu.Przekształcanie danych: przekształcanie danych wejściowych w oczekiwaną postać i wymiary. Nieprzetworzone dane wejściowe modelu zazwyczaj nie odpowiadają danym wejściowym format danych oczekiwany przez model. Możesz na przykład zmienić rozmiar lub zmień format obrazu, aby był zgodny z modelem.
Aktywne wnioskowanie: uruchom model LiteRT, aby wygenerować prognozy. W tym kroku używasz interfejsu LiteRT API do wykonania modelu. Wiąże się to takich jak konstruowanie interpretera czy przydzielanie tensorów.
Interpretowanie danych wyjściowych: interpretuj wyjściowe tensory w sposób użyteczny w aplikacji. Model może na przykład zwrócić tylko listę prawdopodobieństw. Od Ciebie zależy, czy przypiszesz prawdopodobieństwa do odpowiednich i sformatować dane wyjściowe.
W tym przewodniku znajdziesz informacje o tym, jak uzyskać dostęp do tłumaczenia rozmowy LiteRT i wykonać za pomocą C++, Javy i Pythona.
Obsługiwane platformy
Udostępniamy interfejsy wnioskowania interfejsów API TensorFlow dla najpopularniejszych urządzeń mobilnych i wbudowanych takich jak Android, iOS i Linux, wiele języków programowania.
W większości przypadków projekt interfejsu API odzwierciedla preferencję skuteczności nad łatwością obsługi. LiteRT jest przeznaczony do szybkiego wnioskowania na małych urządzeniach, więc interfejsy API unikają zbędnych kopii kosztem wygody.
Interfejs LiteRT API umożliwia wczytywanie modeli, podawanie danych wejściowych i pobieranie wyników wnioskowania we wszystkich bibliotekach.
Platforma
W przypadku Androida wnioskowanie na podstawie LiteRT można przeprowadzać przy użyciu interfejsów API w języku Java lub C++. Interfejsy API Java są wygodne i można ich używać bezpośrednio w Androidzie Zajęcia. Interfejsy API języka C++ zapewniają większą elastyczność i szybkość, ale mogą wymagać pisania kodów JNI w celu przenoszenia danych między warstwami Java i C++.
Więcej informacji znajdziesz w sekcji C++ i Java lub w krótkim wprowadzeniu do Androida.
Platforma iOS
Na iOS LiteRT jest dostępny w bibliotekach iOS Swift i Objective-C. Możesz też używać C API bezpośrednio w kodzie Objective-C.
Zobacz interfejsy Swift, Objective-C i C API lub zapoznaj się z krótkim wprowadzeniem do iOS.
Platforma Linux
Na platformach z systemem Linux możesz uruchamiać wnioskowania za pomocą interfejsów LiteRT API dostępnych w tych językach: C++.
Wczytywanie i uruchamianie modelu
Aby wczytać i uruchomić model LiteRT, wykonaj te czynności:
- Wczytuję model do pamięci.
- Tworzę
Interpreter
na podstawie istniejącego modelu. - Ustawianie wartości tensora wejściowego.
- Wywoływanie wniosków.
- Przekazuję wartości tensorów.
Android (Java)
Interfejs Java API do uruchamiania wnioskowania za pomocą LiteRT jest przeznaczony przede wszystkim do zastosowań
na Androidzie, dlatego może być dostępna jako zależność biblioteki Androida:
com.google.ai.edge.litert
W Javie użyjesz klasy Interpreter
, aby wczytać model i uruchomić go
wnioskowania. W wielu przypadkach może to być jedyny potrzebny interfejs API.
Możesz zainicjować Interpreter
za pomocą pliku FlatBuffers (.tflite
):
public Interpreter(@NotNull File modelFile);
Lub za pomocą MappedByteBuffer
:
public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);
W obu przypadkach musisz podać prawidłowy model LiteRT lub wywołania interfejsu API
IllegalArgumentException
Jeśli używasz MappedByteBuffer
do zainicjowania
Interpreter
, musi pozostać niezmienione przez cały okres istnienia
Interpreter
Preferowanym sposobem wnioskowania na modelu jest użycie podpisów – dostępne dla modeli przekonwertowanych wersji 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");
}
Metoda runSignature
przyjmuje 3 argumenty:
Dane wejściowe : zmapuj dane wejściowe z nazwy wejściowej w podpisie na dane wejściowe. obiektu.
Dane wyjściowe : mapowanie danych wyjściowych z nazwy danych wyjściowych w podpisie na dane wyjściowe. i skalowalnych danych.
Nazwa podpisu (opcjonalnie): nazwa podpisu (może być pusta, jeśli model ma jeden podpis).
Inny sposób uruchamiania wnioskowania, gdy model nie ma zdefiniowanych podpisów.
Wystarczy zadzwonić pod numer Interpreter.run()
. Na przykład:
try (Interpreter interpreter = new Interpreter(file_of_a_tensorflowlite_model)) {
interpreter.run(input, output);
}
Metoda run()
przyjmuje tylko 1 dane wejściowe i zwraca tylko 1 wyjściowy. Jeśli więc Twój model ma wiele wejść lub wyjść, użyj:
interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);
W tym przypadku każdy wpis w tablicy inputs
odpowiada wejściowemu tensorowi, a element map_of_indices_to_outputs
mapuje indeksy wyjściowych tensorów na odpowiadające im dane wyjściowe.
W obu przypadkach indeksy tensorów powinny odpowiadać wartościom podanym
LiteRT Converter przy tworzeniu modelu. Pamiętaj, że kolejność tensorów w input
musi być zgodna z kolejnością podaną w LiteRTConverter.
Klasa Interpreter
udostępnia też wygodne funkcje umożliwiające uzyskanie
indeks dowolnych danych wejściowych lub wyjściowych modelu przy użyciu nazwy operacji:
public int getInputIndex(String opName);
public int getOutputIndex(String opName);
Jeśli opName
nie jest prawidłową operacją w modelu, zostanie wywołana funkcja IllegalArgumentException
.
Pamiętaj też, że Interpreter
jest właścicielem zasobów. Aby uniknąć wycieku pamięci, zasoby muszą zostać zwolnione po użyciu:
interpreter.close();
Przykładowy projekt w języku Java znajdziesz w aplikacji na Androida do wykrywania obiektów.
Obsługiwane typy danych
Aby korzystać z LiteRT, typy danych tensorów wejściowych i wyjściowych muszą należeć do jednego z tych typów podstawowych:
float
int
long
byte
Obsługiwane są też typy String
, ale są one kodowane inaczej niż typy prymitywne. W szczególności kształt tensora ciągu określa liczbę i układ ciągów w tensorze, przy czym każdy element jest ciągiem o zmiennej długości. W tym sensie rozmiar (w bajtach) Tensor nie może
są obliczane wyłącznie na podstawie kształtu i typu czcionki, w związku z czym ciągi znaków nie mogą
podana jako pojedynczy, płaski argument ByteBuffer
.
Jeśli używane są inne typy danych, w tym typy zdefiniowane, takie jak Integer
i Float
, zostanie wywołany błąd IllegalArgumentException
.
Dane wejściowe
Każde wejście powinno być tablicą lub wielowymiarową tablicą typów prymitywnych lub surowym ByteBuffer
o odpowiedniej wielkości. Jeśli dane wejściowe są tablicą lub tablicą wielowymiarową, powiązany z nimi tensor wejściowy zostanie automatycznie dopasowany do wymiarów tablicy w momencie wnioskowania. Jeśli dane wejściowe to ByteBuffer, wywołujący musi najpierw ręcznie zmienić rozmiar powiązanego tensora wejściowego (za pomocą Interpreter.resizeInput()
) przed uruchomieniem wnioskowania.
Korzystając z funkcji ByteBuffer
, używaj bezpośrednich buforów bajtów, ponieważ pozwala to Interpreter
uniknąć zbędnych kopii. Jeśli ByteBuffer
to bezpośredni bufor bajtów, jego kolejność musi być ByteOrder.nativeOrder()
. Po użyciu go przez
wnioskowania na modelu, musi on pozostać niezmieniony do chwili zakończenia wnioskowania na podstawie modelu.
Wyniki
Każdy element wyjściowy powinien być tablicą lub wielowymiarową tablicą obsługiwanych typów prymitywnych albo ByteBuffer o odpowiedniej wielkości. Pamiętaj, że niektóre modele mają dynamiczne dane wyjściowe, przy czym kształt tensorów wyjściowych może różnić się w zależności dane wejściowe. Trudno radzić sobie z tym w prosty sposób Java Inference API, ale zaplanowane rozszerzenia dają taką możliwość.
iOS (Swift)
Swift
API jest dostępny w TensorFlowLiteSwift
Pod z Cocoapods.
Najpierw musisz zaimportować moduł 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)
Plik Objective-C
Interfejs API
jest dostępny w LiteRTObjC
podze Cocoapods.
Najpierw musisz zaimportować moduł 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... */ }
C API w kodzie Objective-C
Interfejs Objective-C API nie obsługuje przedstawicieli. Aby używać delegowanych funkcji w kodzie Objective-C, musisz bezpośrednio wywołać podstawowe interfejs API C.
#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++
Interfejs API w C++ do wykonywania wnioskowania za pomocą LiteRT jest zgodny z platformami Android, iOS i Linux. Interfejs API C++ w systemie iOS jest dostępny tylko w przypadku korzystania z bazel.
W C++ model jest przechowywany w
FlatBufferModel
.
Zawiera ona model LiteRT, który można tworzyć na kilka różnych sposobów, w zależności od tego, gdzie jest przechowywany:
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);
};
Masz już model jako obiekt FlatBufferModel
, więc możesz go wykonać
z
Interpreter
Pojedynczego pola FlatBufferModel
może używać jednocześnie więcej niż jeden
Interpreter
Ważne części interfejsu API Interpreter
są widoczne we fragmencie kodu
poniżej. Należy pamiętać, że:
- Tensory są reprezentowane przez liczby całkowite, co pozwala uniknąć porównywania ciągów znaków. (i stałą zależność od bibliotek ciągów znaków).
- Nie można uzyskać dostępu do tłumaczenia rozmowy podczas równoczesnych wątków.
- Przydzielanie pamięci dla tensorów wejściowych i wyjściowych musi być wywoływane przez wywołanie funkcji
AllocateTensors()
bezpośrednio po zmianie rozmiaru tensorów.
Najprostsze użycie LiteRT w języku C++ wygląda tak:
// 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);
Więcej przykładowego kodu znajdziesz tutaj
minimal.cc
oraz
label_image.cc
Python
Interfejs Python API do wykonywania wnioskowań używa interfejsu Interpreter
do wczytywania modelu i wykonywania wnioskowań.
Zainstaluj pakiet LiteRT:
$ python3 -m pip install ai-edge-litert
Importowanie interpretera LiteRT
from ai_edge_litert.interpreter import Interpreter
Interpreter = Interpreter(model_path=args.model.file)
Z przykładu poniżej dowiesz się, jak za pomocą interpretera Pythona wczytać
pliku FlatBuffers (.tflite
) i uruchamiaj wnioskowanie na podstawie losowych danych wejściowych:
Zalecamy użycie tego przykładu, jeśli konwertujesz model zapisany z zdefiniowanym 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'])
Innym przykładem jest sytuacja, gdy model nie ma zdefiniowanej właściwości SignatureDefs
.
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)
Zamiast wczytywać model jako wcześniej przekonwertowany plik .tflite
, możesz połączyć kod z kompilatorem LiteRT, aby przekonwertować model Keras do formatu LiteRT, a następnie przeprowadzić wnioskowanie:
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...
Więcej przykładowego kodu w Pythonie znajdziesz tutaj
label_image.py
Uruchom wnioskowanie z użyciem dynamicznego modelu kształtu
Jeśli chcesz uruchomić model z dynamicznym kształtem danych wejściowych, przed uruchomieniem wnioskowania zmień rozmiar tego kształtu. W przeciwnym razie kształt None
w modelach TensorFlow zostanie zastąpiony przez obiekt zastępczy 1
w modelach LiteRT.
W przykładach poniżej pokazujemy, jak zmienić rozmiar kształtu wejściowego przed uruchomieniem
wnioskowania w różnych językach. Wszystkie przykłady zakładają, że kształt wejściowy jest zdefiniowany jako [1/None, 10]
i należy zmienić jego rozmiar na [3, 10]
.
Przykład w C++:
// Resize input tensors before allocate tensors
interpreter->ResizeInputTensor(/*tensor_index=*/0, std::vector<int>{3,10});
interpreter->AllocateTensors();
Przykład w Pythonie:
# 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()