Pierwsze kroki z LiteRT

W tym przewodniku omawiamy proces uruchamiania modelu LiteRT na urządzeniu jak tworzyć prognozy na podstawie danych wejściowych. Jest to możliwe dzięki LiteRT tłumaczący, który wykorzystuje statyczny układ grafu i własny (mniej dynamiczny) alokatora pamięci, aby zapewnić minimalne obciążenie, inicjalizację i czas oczekiwania na wykonanie.

Wnioskowanie LiteRT zwykle odbywa się w następujący sposób:

  1. Wczytanie modelu: wczytaj do pamięci model .tflite, który zawiera do wykresu wykonania modelu.

  2. Przekształcanie danych: przekształcaj dane wejściowe do właściwego formatu wymiarów. 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.

  3. Aktywne wnioskowanie: uruchom model LiteRT, aby wygenerować prognozy. Ten wymaga użycia interfejsu LiteRT API do wykonania modelu. Wiąże się to z takich jak konstruowanie interpretera czy przydzielanie tensorów.

  4. Interpretowanie danych wyjściowych: zinterpretuj tensory wyjściowe w sensowny sposób. które mogą się przydać w Twojej 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 TensorFlow API 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 preferuje wydajność, a nie łatwość i ich używanie. LiteRT zostało zaprojektowane z myślą o szybkim wnioskowaniu na małych urządzeniach, dzięki czemu interfejsy API nie niepotrzebne kopie kosztem wygody.

We wszystkich bibliotekach interfejs LiteRT API umożliwia wczytywanie modeli, danych wejściowych pliku danych pobierania danych wyjściowych wnioskowania.

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 sekcjach C++ i Java. postępuj zgodnie z krótkim wprowadzeniem do Androida.

Platforma iOS

Na iOS aplikacja LiteRT jest dostępna w tych językach: Swift oraz Objective-C Biblioteki systemu iOS. Możesz też użyć skrótu C Interfejs 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:

  1. Wczytuję model do pamięci.
  2. Tworzę Interpreter na podstawie istniejącego modelu.
  3. Ustawianie wartości tensora wejściowego.
  4. Wywoływanie wniosków.
  5. 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 jest 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 interfejs API, którego potrzebujesz.

Interpreter można zainicjować przy użyciu 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.

  • Signature Name (Nazwa podpisu) (opcjonalnie): nazwa podpisu (może być pusta, jeśli parametr model ma jeden podpis).

Inny sposób uruchamiania wnioskowania, gdy model nie ma zdefiniowanych podpisów. Po prostu zadzwoń 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 model ma wiele danych wejściowych lub wiele danych wyjściowych, zamiast tego użyj:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

W tym przypadku każdy wpis w inputs odpowiada tensorowi wejściowemu i map_of_indices_to_outputs mapuje indeksy tensorów wyjściowych na odpowiednie wartości danych wyjściowych.

W obu przypadkach indeksy tensorów powinny odpowiadać wartościom podanym LiteRT Converter przy tworzeniu modelu. Świadomość że kolejność tensorów w input musi odpowiadać kolejności podanej w literaturze Użytkownik.

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, generuje IllegalArgumentException

Pamiętaj też, że Interpreter jest właścicielem zasobów. Aby uniknąć wycieku pamięci, zasoby muszą zostać zwolnione po ich użyciu przez:

interpreter.close();

Przykładowy projekt w języku Java zawiera przykład wykrywania obiektów na Androidzie

Obsługiwane typy danych

Aby można było używać LiteRT, typy danych tensorów wejściowych i wyjściowych muszą być jednym z tych typów podstawowych:

  • float
  • int
  • long
  • byte

Obsługiwane są również typy String, ale są one zakodowane inaczej niż podstawowych typów. W szczególności kształt ciągu Tensor określa liczbę i układu strun w Tensor, przy czym każdy element jest ciąg 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żywasz innych typów danych, w tym typów ramek, takich jak Integer i Float, otrzyma IllegalArgumentException.

Dane wejściowe

Każde dane wejściowe powinny być tablicą lub wielowymiarową tablicą obsługiwanych funkcji typów podstawowych lub nieprzetworzony element ByteBuffer o odpowiednim rozmiarze. Jeśli dane wejściowe to lub tablicy wielowymiarowej, powiązany tensor wejściowy będzie została domyślnie zmieniona do wymiarów tablicy w czasie wnioskowania. Jeśli dane wejściowe to ByteBuffer, element wywołujący powinien najpierw ręcznie zmienić rozmiar powiązanych danych wejściowych tensor (przez Interpreter.resizeInput()) przed uruchomieniem wnioskowania.

Przy korzystaniu z metody ByteBuffer lepiej jest używać bezpośrednich buforów bajtów, ponieważ umożliwia to Interpreter, aby uniknąć zbędnych kopii. Jeśli ByteBuffer jest bajtem bezpośrednim bufor, kolejność musi wynosić ByteOrder.nativeOrder(). Po użyciu go przez wnioskowania na modelu, musi on pozostać niezmieniony do chwili zakończenia wnioskowania na podstawie modelu.

Wyniki

Wszystkie dane wyjściowe powinny być tablicą lub wielowymiarową tablicą obsługiwanych funkcji lub typu ByteBuffer o odpowiednim rozmiarze. 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. Nie da się radzić sobie z tym w prosty sposób Java Inference API, ale zaplanowane rozszerzenia dają taką możliwość.

iOS (Swift)

Swift Interfejs API jest dostępny w TensorFlowLiteSwift podze 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 móc korzystać z przedstawicieli Kod Objective-C, musisz bezpośrednio wywołać metodę C 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++

Interfejs API C++ do wnioskowania przy użyciu LiteRT jest zgodny z Androidem, 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 model LiteRT i można go utworzyć w kilku różnych zależnie od tego, gdzie zapisany jest model:

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. Pamiętaj, ż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.
  • Przydział pamięci dla tensorów wejściowych i wyjściowych musi być aktywowany przez wywołanie AllocateTensors() zaraz 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 uruchamiania wnioskowania używa Interpreter, aby wczytać model, możesz przeprowadzać wnioskowanie.

Zainstaluj pakiet LiteRT:

$ python3 -m pip install ai-edge-litert

Importowanie tłumaczenia rozmowy 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:

Ten przykład jest zalecany, jeśli konwertujesz z SavedModel o zdefiniowanej wartości Funkcja 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'])

Inny przykład, gdy model nie ma zdefiniowanego elementu 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 wstępnie przekonwertowany plik .tflite możesz mogą połączyć Twój kod z LiteRT, Kompilator , co pozwala przekonwertować model Keras na format LiteRT 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, zmień rozmiar kształtu wejściowego przed uruchomieniem wnioskowania. W przeciwnym razie kształt None w modelach Tensorflow zostaną zastąpione obiektem zastępczym 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. We wszystkich przykładach założono, że dane wejściowe jest zdefiniowanych jako [1/None, 10] i należy go zmienić 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()