Wnioskowanie TensorFlow Lite

Termin wnioskowanie odnosi się do procesu wykonywania modelu TensorFlow Lite na urządzeniu w celu przewidywania na podstawie danych wejściowych. Aby przeprowadzić wnioskowanie z modelu TensorFlow Lite, należy uruchomić go za pomocą interpretera . Interpreter TensorFlow Lite został zaprojektowany tak, aby był smukły i szybki. Interpreter wykorzystuje statyczną kolejność grafów i niestandardowy (mniej dynamiczny) alokator pamięci, aby zapewnić minimalne opóźnienie ładowania, inicjalizacji i wykonania.

Na tej stronie opisano, jak uzyskać dostęp do interpretera TensorFlow Lite i przeprowadzić wnioskowanie przy użyciu języków C++, Java i Python, a także łącza do innych zasobów dla każdej obsługiwanej platformy .

Ważne pojęcia

Wnioskowanie TensorFlow Lite zazwyczaj przebiega w następujący sposób:

  1. Ładowanie modelu

    Należy załadować model .tflite do pamięci, która zawiera wykres wykonania modelu.

  2. Transformacja danych

    Surowe dane wejściowe modelu zazwyczaj nie odpowiadają formatowi danych wejściowych oczekiwanemu przez model. Na przykład może być konieczna zmiana rozmiaru obrazu lub zmiana formatu obrazu, aby był zgodny z modelem.

  3. Uruchamianie wnioskowania

    Ten krok polega na użyciu interfejsu API TensorFlow Lite do wykonania modelu. Obejmuje kilka kroków, takich jak budowanie interpretera i przydzielanie tensorów, jak opisano w poniższych sekcjach.

  4. Interpretacja wyników

    Gdy otrzymasz wyniki na podstawie wnioskowania o modelu, musisz zinterpretować tensory w znaczący sposób, który będzie przydatny w Twojej aplikacji.

    Na przykład model może zwrócić tylko listę prawdopodobieństw. Twoim zadaniem jest przypisanie prawdopodobieństw do odpowiednich kategorii i zaprezentowanie ich użytkownikowi końcowemu.

Obsługiwane platformy

Interfejsy API wnioskowania TensorFlow są dostępne dla najpopularniejszych platform mobilnych/wbudowanych, takich jak Android , iOS i Linux , w wielu językach programowania.

W większości przypadków projekt interfejsu API odzwierciedla preferencję wydajności nad łatwością użycia. TensorFlow Lite zaprojektowano z myślą o szybkim wnioskowaniu na małych urządzeniach, zatem nie powinno być zaskoczeniem, że interfejsy API starają się unikać niepotrzebnych kopii kosztem wygody. Podobnie spójność z interfejsami API TensorFlow nie była wyraźnym celem i należy spodziewać się pewnych rozbieżności między językami.

We wszystkich bibliotekach interfejs API TensorFlow Lite umożliwia ładowanie modeli, dostarczanie danych wejściowych i pobieranie wyników wnioskowania.

Platforma Androida

W systemie Android wnioskowanie TensorFlow Lite można przeprowadzić przy użyciu interfejsów API języka Java lub C++. Interfejsy API języka Java zapewniają wygodę i można ich używać bezpośrednio w klasach aktywności systemu Android. Interfejsy API C++ oferują większą elastyczność i szybkość, ale mogą wymagać pisania opakowań JNI w celu przenoszenia danych między warstwami Java i C++.

Poniżej znajdziesz szczegółowe informacje na temat używania języków C++ i Java lub skorzystaj z przewodnika Szybki start dla systemu Android , aby zapoznać się z samouczkiem i przykładowym kodem.

Generator kodu opakowania TensorFlow Lite na Androida

W przypadku modelu TensorFlow Lite wzbogaconego o metadane programiści mogą użyć generatora kodu opakowania TensorFlow Lite dla systemu Android, aby utworzyć kod opakowania specyficzny dla platformy. Kod opakowania eliminuje potrzebę bezpośredniej interakcji z ByteBuffer na Androidzie. Zamiast tego programiści mogą wchodzić w interakcję z modelem TensorFlow Lite za pomocą obiektów wpisanych, takich jak Bitmap i Rect . Aby uzyskać więcej informacji, zapoznaj się z generatorem kodu opakowania TensorFlow Lite dla systemu Android .

Platforma iOS

Na iOS TensorFlow Lite jest dostępny z natywnymi bibliotekami iOS napisanymi w Swift i Objective-C . Możesz także używać C API bezpośrednio w kodach Objective-C.

Poniżej znajdziesz szczegółowe informacje na temat korzystania z języka Swift , Objective-C i interfejsu API języka C lub skorzystaj z przewodnika Szybki start dla systemu iOS , aby zapoznać się z samouczkiem i przykładowym kodem.

Platforma Linuksowa

Na platformach Linux (w tym Raspberry Pi ) można uruchamiać wnioskowanie przy użyciu interfejsów API TensorFlow Lite dostępnych w językach C++ i Python , jak pokazano w poniższych sekcjach.

Prowadzenie modelu

Uruchomienie modelu TensorFlow Lite obejmuje kilka prostych kroków:

  1. Załaduj model do pamięci.
  2. Zbuduj Interpreter w oparciu o istniejący model.
  3. Ustaw wartości tensora wejściowego. (Opcjonalnie zmień rozmiar tensorów wejściowych, jeśli wstępnie zdefiniowane rozmiary nie są pożądane.)
  4. Wywołaj wnioskowanie.
  5. Odczytaj wartości tensora wyjściowego.

W poniższych sekcjach opisano, jak można wykonać te kroki w każdym języku.

Załaduj i uruchom model w Javie

Platforma: Android

Interfejs API Java do uruchamiania wnioskowania za pomocą TensorFlow Lite jest przeznaczony głównie do użytku z systemem Android, dlatego jest dostępny jako zależność biblioteki systemu Android: org.tensorflow:tensorflow-lite .

W Javie będziesz używać klasy Interpreter do ładowania modelu i wnioskowania o modelu. W wielu przypadkach może to być jedyny interfejs API, jakiego potrzebujesz.

Możesz zainicjować Interpreter za pomocą pliku .tflite :

public Interpreter(@NotNull File modelFile);

Lub z MappedByteBuffer :

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

W obu przypadkach musisz podać prawidłowy model TensorFlow Lite, w przeciwnym razie interfejs API zgłosi IllegalArgumentException . Jeśli używasz MappedByteBuffer do inicjacji Interpreter , musi on pozostać niezmieniony przez cały okres istnienia Interpreter .

Preferowanym sposobem uruchamiania wnioskowania na modelu jest użycie sygnatur — dostępne dla modeli przekonwertowanych począwszy od 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 trzy argumenty:

  • Wejścia : mapa wejść z nazwy wejściowej w podpisie na obiekt wejściowy.

  • Wyjścia : mapa do mapowania wyników od nazwy wyjścia w podpisie do danych wyjściowych.

  • Nazwa podpisu [opcjonalne]: Nazwa podpisu (można pozostawić puste, jeśli model ma pojedynczy podpis).

Inny sposób na uruchomienie wnioskowania, gdy model nie ma zdefiniowanych podpisów. Po prostu wywołaj Interpreter.run() . Na przykład:

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

Metoda run() pobiera tylko jedno wejście i zwraca tylko jedno wyjście. Jeśli więc Twój model ma wiele wejść lub wiele wyjść, zamiast tego użyj:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

W tym przypadku każdy wpis w inputs odpowiada tensorowi wejściowemu, a map_of_indices_to_outputs odwzorowuje indeksy tensorów wyjściowych na odpowiednie dane wyjściowe.

W obu przypadkach indeksy tensora powinny odpowiadać wartościom, które podałeś do konwertera TensorFlow Lite podczas tworzenia modelu. Należy pamiętać, że kolejność tensorów na input musi odpowiadać kolejności podanej w konwerterze TensorFlow Lite.

Klasa Interpreter udostępnia również wygodne funkcje umożliwiające uzyskanie indeksu wejścia lub wyjścia dowolnego 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, zgłasza wyjątek IllegalArgumentException .

Uważaj również, że Interpreter jest właścicielem zasobów. Aby uniknąć wycieku pamięci, zasoby muszą zostać zwolnione po użyciu przez:

interpreter.close();

Przykładowy projekt w języku Java można znaleźć w przykładzie klasyfikacji obrazów systemu Android .

Obsługiwane typy danych (w Javie)

Aby korzystać z TensorFlow Lite, typy danych tensorów wejściowych i wyjściowych muszą należeć do jednego z następujących typów pierwotnych:

  • float
  • int
  • long
  • byte

Obsługiwane są również typy String , ale są one kodowane inaczej niż typy pierwotne. W szczególności kształt Tensora struny dyktuje liczbę i rozmieszczenie strun w Tensorze, przy czym każdy element sam w sobie jest struną o zmiennej długości. W tym sensie rozmiaru (bajtów) Tensora nie można obliczyć na podstawie samego kształtu i typu, w związku z czym ciągi nie mogą być podawane jako pojedynczy, płaski argument ByteBuffer . Na tej stronie możesz zobaczyć kilka przykładów.

Jeśli używane są inne typy danych, w tym typy pudełkowe, takie jak Integer i Float , zostanie zgłoszony wyjątek IllegalArgumentException .

Wejścia

Każde dane wejściowe powinny być tablicą lub tablicą wielowymiarową obsługiwanych typów pierwotnych lub nieprzetworzonym buforem ByteBuffer o odpowiednim rozmiarze. Jeśli danymi wejściowymi jest tablica lub tablica wielowymiarowa, rozmiar powiązanego tensora wejściowego zostanie domyślnie zmieniony na wymiary tablicy w czasie wnioskowania. Jeśli danymi wejściowymi jest ByteBuffer, obiekt wywołujący powinien najpierw ręcznie zmienić rozmiar powiązanego tensora wejściowego (za pomocą Interpreter.resizeInput() ) przed uruchomieniem wnioskowania.

Używając ByteBuffer , preferuj używanie bezpośrednich buforów bajtowych, ponieważ pozwala to Interpreter uniknąć niepotrzebnych kopii. Jeśli ByteBuffer jest bezpośrednim buforem bajtów, jego kolejność musi być ByteOrder.nativeOrder() . Po użyciu go do wnioskowania o modelu musi pozostać niezmieniony aż do zakończenia wnioskowania o modelu.

Wyjścia

Każde wyjście powinno być tablicą lub tablicą wielowymiarową obsługiwanych typów pierwotnych lub buforem ByteBuffer o odpowiednim rozmiarze. Należy zauważyć, że niektóre modele mają wyjścia dynamiczne, w przypadku których kształt tensorów wyjściowych może się różnić w zależności od sygnału wejściowego. Nie ma prostego sposobu poradzenia sobie z tym za pomocą istniejącego interfejsu API wnioskowania w języku Java, ale planowane rozszerzenia to umożliwią.

Załaduj i uruchom model w Swift

Platforma: iOS

Interfejs API Swift jest dostępny w kapsule TensorFlowLiteSwift firmy 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...
}

Załaduj i uruchom model w Objective-C

Platforma: iOS

Interfejs API Objective-C jest dostępny w TensorFlowLiteObjC Pod od Cocoapods.

Najpierw musisz zaimportować moduł 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... */ }

Używanie C API w kodzie Objective-C

Obecnie interfejs API języka C nie obsługuje delegatów. Aby używać delegatów z kodem Objective-C, musisz bezpośrednio wywołać bazowy 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);

Załaduj i uruchom model w C++

Platformy: Android, iOS i Linux

W C++ model jest przechowywany w klasie FlatBufferModel . Zawiera model TensorFlow Lite i można go zbudować na kilka różnych sposobów, w zależności od miejsca przechowywania modelu:

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

Teraz, gdy masz model jako obiekt FlatBufferModel , możesz go wykonać za pomocą Interpreter . Pojedynczy FlatBufferModel może być używany jednocześnie przez więcej niż jednego Interpreter .

Ważne części interfejsu API Interpreter pokazano we fragmencie kodu poniżej. Należy zauważyć że:

  • Tensory są reprezentowane przez liczby całkowite, aby uniknąć porównań ciągów (i wszelkich ustalonych zależności od bibliotek ciągów).
  • Nie można uzyskać dostępu do interpretera ze współbieżnych wątków.
  • Przydział pamięci dla tensorów wejściowych i wyjściowych musi zostać wywołany przez wywołanie funkcji AllocateTensors() zaraz po zmianie rozmiaru tensorów.

Najprostsze użycie TensorFlow Lite z C++ wygląda następująco:

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

Więcej przykładowego kodu można znaleźć w minimal.cc i label_image.cc .

Załaduj i uruchom model w Pythonie

Platforma: Linux

Interfejs API Pythona do uruchamiania wnioskowania jest dostępny w module tf.lite . Z tego powodu do załadowania modelu i wyciągnięcia wniosków potrzebny jest najczęściej tylko tf.lite.Interpreter .

Poniższy przykład pokazuje, jak użyć interpretera języka Python do załadowania pliku .tflite i uruchomienia wnioskowania na podstawie losowych danych wejściowych:

Ten przykład jest zalecany w przypadku konwersji z SavedModel ze zdefiniowanym SignatureDef. Dostępne począwszy od 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'])

Inny przykład, jeśli w modelu nie zdefiniowano SignatureDefs.

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)

Alternatywnie do ładowania modelu jako wstępnie przekonwertowanego pliku .tflite , możesz połączyć swój kod z interfejsem API Pythona TensorFlow Lite Converter ( tf.lite.TFLiteConverter ), co umożliwi konwersję modelu Keras do formatu TensorFlow Lite, a następnie uruchom 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 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...

Więcej przykładowego kodu w języku Python można znaleźć w label_image.py .

Uruchom wnioskowanie z dynamicznym modelem kształtu

Jeśli chcesz uruchomić model z dynamicznym kształtem wejściowym, zmień rozmiar kształtu wejściowego przed uruchomieniem wnioskowania. W przeciwnym razie kształt None w modelach Tensorflow zostanie zastąpiony symbolem zastępczym 1 w modelach TFLite.

Poniższe przykłady pokazują, jak zmienić rozmiar kształtu wejściowego przed uruchomieniem wnioskowania w różnych językach. We wszystkich przykładach założono, że kształt wejściowy jest zdefiniowany jako [1/None, 10] i należy go zmienić na [3, 10] .

Przykład C++:

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

Przykład Pythona:

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