Wnioskowanie TensorFlow Lite

Termin wnioskowanie odnosi się do procesu wykonywania modelu TensorFlow Lite na urządzeniu w celu dokonania prognoz na podstawie danych wejściowych. Aby przeprowadzić wnioskowanie za pomocą modelu TensorFlow Lite, należy uruchomić go za pomocą interpretera . Interpreter TensorFlow Lite został zaprojektowany tak, aby był oszczędny i szybki. Interpreter używa statycznego porządkowania grafów i niestandardowego (mniej dynamicznego) alokatora pamięci, aby zapewnić minimalne obciążenie, inicjalizację i opóźnienie 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 linki do innych zasobów dla każdej obsługiwanej platformy .

Ważne pojęcia

Wnioskowanie TensorFlow Lite zwykle obejmuje następujące kroki:

  1. Ładowanie modelu

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

  2. Transformacja danych

    Surowe dane wejściowe dla modelu generalnie nie pasują do formatu danych wejściowych oczekiwanego 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 obejmuje użycie 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. Wynik tłumaczenia

    Po otrzymaniu wyników z wnioskowania o modelu należy interpretować tensory w zrozumiały sposób, który jest przydatny w aplikacji.

    Na przykład model może zwrócić tylko listę prawdopodobieństw. Do Ciebie należy mapowanie prawdopodobieństw do odpowiednich kategorii i przedstawienie ich użytkownikowi końcowemu.

Obsługiwane platformy

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

W większości przypadków projekt interfejsu API odzwierciedla preferencje dotyczące wydajności, a nie łatwości użytkowania. TensorFlow Lite jest przeznaczony do szybkiego wnioskowania na małych urządzeniach, więc nie powinno dziwić, ż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 różnic między językami.

We wszystkich bibliotekach interfejs API TensorFlow Lite umożliwia ładowanie modeli, wprowadzanie 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 Java lub C++. Interfejsy API języka Java zapewniają wygodę i mogą być używane bezpośrednio w klasach aktywności systemu Android. Interfejsy API C++ oferują większą elastyczność i szybkość, ale mogą wymagać napisania opakowań JNI w celu przenoszenia danych między warstwami Java i C++.

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

TensorFlow Lite Generator kodu opakowania dla 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 opakowujący eliminuje potrzebę bezpośredniej interakcji z ByteBuffer na Androidzie. Zamiast tego programiści mogą wchodzić w interakcje z modelem TensorFlow Lite za pomocą wpisywanych obiektów, takich jak Bitmap i Rect . Aby uzyskać więcej informacji, zapoznaj się z generatorem kodów opakowania TensorFlow Lite Android .

Platforma iOS

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

Zobacz poniżej, aby uzyskać szczegółowe informacje na temat korzystania z Swift , Objective-C i C API lub postępuj zgodnie z przewodnikiem Szybki start dla systemu iOS , aby uzyskać samouczek i przykładowy kod.

Platforma Linuksowa

Na platformach Linux (w tym Raspberry Pi ) można uruchamiać wnioskowania 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 predefiniowane rozmiary nie są pożądane).
  4. Wywołaj wnioskowanie.
  5. Odczytaj wartości tensora wyjściowego.

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

Załaduj i uruchom model w Javie

Platforma: Android

Interfejs API języka Java do uruchamiania wnioskowania za pomocą TensorFlow Lite jest przeznaczony przede wszystkim do użytku z systemem Android, więc 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 prowadzenia wnioskowania o modelu. W wielu przypadkach może to być jedyne potrzebne API.

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żyjesz MappedByteBuffer do zainicjowania 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ść od nazwy wejścia w sygnaturze do obiektu wejściowego.

  • Wyjścia : mapa do mapowania danych wyjściowych z nazwy wyjściowej w sygnaturze do danych wyjściowych.

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

Inny sposób uruchomienia wnioskowania, gdy model nie ma zdefiniowanych sygnatur. 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() przyjmuje tylko jedno wejście i zwraca tylko jedno wyjście. Więc jeśli 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 wskaźniki tensorów powinny odpowiadać wartościom nadanym konwerterowi TensorFlow Lite podczas tworzenia modelu. Należy pamiętać, że kolejność tensorów na input musi być zgodna z kolejnością nadaną konwerterowi TensorFlow Lite.

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

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

interpreter.close();

Aby zapoznać się z przykładowym projektem z Javą, zobacz przykładową klasyfikację obrazu 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 struny Tensor 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 rozmiar (bajtowy) Tensor nie może być obliczony na podstawie samego kształtu i typu, w związku z czym łańcuchy znaków nie mogą być dostarczane jako pojedynczy, płaski argument ByteBuffer . Możesz zobaczyć kilka przykładów na tej stronie .

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

Wejścia

Każde wejście powinno być tablicą lub tablicą wielowymiarową obsługiwanych typów pierwotnych lub nieprzetworzonym buforem ByteBuffer o odpowiednim rozmiarze. Jeśli dane wejściowe są tablicą lub tablicą wielowymiarową, powiązany tensor wejściowy zostanie niejawnie przeskalowany do wymiarów tablicy w czasie wnioskowania. Jeśli dane wejściowe to ByteBuffer, wywołujący powinien najpierw ręcznie zmienić rozmiar powiązanego tensora wejściowego (poprzez Interpreter.resizeInput() ) przed uruchomieniem wnioskowania.

Używając ByteBuffer , preferuj używanie bezpośrednich buforów bajtów, 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 do wnioskowania o modelu musi pozostać niezmieniony do momentu zakończenia wnioskowania o modelu.

Wyjścia

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

Załaduj i uruchom model w Swift

Platforma: iOS

Swift API jest dostępny w TensorFlowLiteSwift Pod 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

API Objective-C jest dostępne w TensorFlowLiteObjC Pod firmy 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 Objective-C nie obsługuje delegatów. Aby używać delegatów z kodem Objective-C, musisz bezpośrednio wywoływać podstawowy interfejs API języka 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 tego, gdzie jest przechowywany 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);
};

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 .

W poniższym fragmencie kodu przedstawiono ważne części interfejsu API Interpreter . Należy zauważyć że:

  • Tensory są reprezentowane przez liczby całkowite, aby uniknąć porównań łańcuchów (i wszelkich stałych zależności od bibliotek ciągów).
  • Nie należy uzyskiwać dostępu do interpretera z współbieżnych wątków.
  • Alokacja pamięci dla tensorów wejściowych i wyjściowych musi zostać wywołana przez wywołanie 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);

Aby uzyskać więcej przykładowego kodu, zobacz minimal.cc i label_image.cc .

Załaduj i uruchom model w Pythonie

Platforma: Linuks

Interfejs API języka Python do uruchamiania wnioskowania jest dostępny w module tf.lite . Z tego najczęściej potrzebujesz tylko tf.lite.Interpreter , aby załadować model i uruchomić wnioskowanie.

Poniższy przykład pokazuje, jak użyć interpretera języka Python do załadowania pliku .tflite i uruchomienia wnioskowania z losowymi danymi wejściowymi:

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 model nie ma zdefiniowanych 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)

Jako alternatywę dla ładowania modelu jako wstępnie przekonwertowanego pliku .tflite , możesz połączyć swój kod z interfejsem API TensorFlow Lite Converter Python ( tf.lite.TFLiteConverter ), co pozwoli Ci przekonwertować model TensorFlow do formatu TensorFlow Lite, a następnie uruchom wnioskowanie:

import numpy as np
import tensorflow as tf

img = tf.placeholder(name="img", dtype=tf.float32, shape=(1, 64, 64, 3))
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
with tf.Session() as sess:
  converter = tf.lite.TFLiteConverter.from_session(sess, [img], [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...

Aby uzyskać więcej przykładowego kodu języka Python, zobacz 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 przyjęto założenie, że kształt wejściowy jest zdefiniowany jako [1/None, 10] i należy zmienić jego rozmiar na [3, 10] .

###### C++ {.new-tab} ```c++ // Zmiana rozmiaru wejściowych tensorów przed przydzieleniem tensorów interpreter->ResizeInputTensor(/*tensor_index=*/0, std::vector {3,10}); interpreter->Przydziel Tensory(); ``` ###### Python {.new-tab} ```python # Załaduj model TFLite do TFLite Interpreter interpreter = tf.lite.Interpreter(ścieżka_modelu=TFLITE_FILE_PATH) # Zmień rozmiar wejściowego kształtu dla dynamicznego modelu kształtu i przydziel tensor interpreter.resize_tensor_input(interpreter.get_input_details()[0]['index'], [3, 10]) interpreter.allocate_tensor() # Pobierz tensory wejścia i wyjścia. szczegóły_wejściowe = interpreter.get_input_details() szczegóły_wyjściowe = interpreter.get_output_details() ```

Obsługiwane operacje

TensorFlow Lite obsługuje podzbiór operacji TensorFlow z pewnymi ograniczeniami. Pełna lista operacji i ograniczeń znajduje się na stronie TF Lite Ops .