Wnioskowanie TensorFlow Lite

Termin wnioskowanie odnosi się do procesu wykonywania modelu TensorFlow Lite na urządzeniu w celu dokonywania prognoz na podstawie danych wejściowych. Aby przeprowadzić wnioskowanie z modelem TensorFlow Lite, musisz uruchomić go przez interpreter . Interpreter TensorFlow Lite został zaprojektowany tak, aby był oszczędny i szybki. Interpreter używa statycznej kolejności wykresów i niestandardowego (mniej dynamicznego) alokatora pamięci, aby zapewnić minimalne obciążenie, inicjalizację i opóźnienie wykonania.

Ta strona opisuje, jak uzyskać dostęp do interpretera TensorFlow Lite i przeprowadzić wnioskowanie przy użyciu C++, Java i Python, a także linki do innych zasobów dla każdej obsługiwanej platformy .

Ważne koncepcje

Wnioskowanie TensorFlow Lite zazwyczaj przebiega według następujących kroków:

  1. Ładowanie modelu

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

  2. Przekształcanie danych

    Surowe dane wejściowe dla modelu zazwyczaj nie są zgodne z formatem danych wejściowych oczekiwanym 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. Interpretacja wyników

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

    Na przykład model może zwrócić tylko listę prawdopodobieństw. Od Ciebie zależy, czy przypiszesz prawdopodobieństwa do odpowiednich kategorii i przedstawisz je 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 prostotą 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 się spodziewać pewnych różnic między językami.

We wszystkich bibliotekach interfejs API TensorFlow Lite umożliwia ładowanie modeli, wprowadzanie danych wejściowych i pobieranie danych wyjściowych wnioskowania.

Platforma Android

W systemie Android wnioskowanie TensorFlow Lite można przeprowadzić przy użyciu interfejsów API Java lub C++. Interfejsy Java API zapewniają wygodę i mogą być używane bezpośrednio w klasach Android Activity. 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++.

Zobacz poniżej, aby uzyskać szczegółowe informacje na temat używania C++ i Java , lub postępuj zgodnie z szybkim wprowadzeniem systemu Android , aby uzyskać samouczek i przykładowy kod.

Generator kodu wrappera dla systemu Android TensorFlow Lite

W przypadku modelu TensorFlow Lite wzbogaconego o metadane programiści mogą używać generatora kodu opakowania TensorFlow Lite Android do tworzenia kodu opakowania specyficznego dla platformy. Kod opakowania eliminuje potrzebę bezpośredniej interakcji z ByteBuffer w systemie Android. Zamiast tego programiści mogą wchodzić w interakcje z modelem TensorFlow Lite z typowanymi obiektami, takimi jak Bitmap i Rect . Aby uzyskać więcej informacji, zapoznaj się z generatorem kodu opakowania TensorFlow Lite Android .

Platforma iOS

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

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

Platforma Linux

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

Prowadzenie modelki

Uruchomienie modelu TensorFlow Lite obejmuje kilka prostych kroków:

  1. Załaduj model do pamięci.
  2. Zbuduj Interpreter na podstawie istniejącego modelu.
  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. Odczytuj wartości tensora wyjściowego.

Poniższe sekcje opisują, jak te kroki można wykonać 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 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 użyjesz klasy Interpreter do załadowania modelu i prowadzenia wnioskowania o modelu. W wielu przypadkach może to być jedyne potrzebne Ci API.

Interpreter można zainicjować za pomocą pliku .tflite :

public Interpreter(@NotNull File modelFile);

Lub z MappedByteBuffer :

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

W obu przypadkach musisz podać poprawny model TensorFlow Lite lub interfejs API zgłosi IllegalArgumentException . Jeśli używasz MappedByteBuffer do inicjowania 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:

  • Inputs : mapowanie danych wejściowych z nazwy wejściowej w sygnaturze do obiektu wejściowego.

  • Outputs : mapa mapowania wyjścia z nazwy wyjścia w sygnaturze na dane wyjściowe.

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

Inny sposób na uruchomienie 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() pobiera 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 indeksy tensorów powinny odpowiadać wartościom, które podałeś konwerterowi 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 zapewnia 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 IllegalArgumentException .

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

interpreter.close();

Aby zapoznać się z przykładowym projektem z językiem Java, zobacz przykład klasyfikacji obrazu systemu Android .

Obsługiwane typy danych (w Javie)

Aby używać TensorFlow Lite, typy danych tensorów wejściowych i wyjściowych muszą być jednym z następujących typów podstawowych:

  • 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 układ struny w tensorze, przy czym każdy element sam w sobie jest struną o zmiennej długości. W tym sensie rozmiar (bajtowy) Tensora nie może być obliczony na podstawie samego kształtu i typu, a w konsekwencji łańcuchy nie mogą być dostarczone jako pojedynczy, płaski argument ByteBuffer .

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

Wejścia

Każde dane wejściowe powinny być tablicą lub wielowymiarową tablicą obsługiwanych typów pierwotnych lub nieprzetworzonym ByteBuffer o odpowiednim rozmiarze. Jeśli dane wejściowe są tablicą lub tablicą wielowymiarową, skojarzony tensor wejściowy zostanie niejawnie zmieniony na wymiary tablicy w czasie wnioskowania. Jeśli dane wejściowe to ByteBuffer, obiekt wywołujący powinien najpierw ręcznie zmienić rozmiar skojarzonego tensora wejściowego (za pośrednictwem Interpreter.resizeInput() ) przed uruchomieniem wnioskowania.

Korzystając z 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 do wnioskowania modelu musi pozostać niezmieniony do momentu zakończenia wnioskowania modelu.

Wyjścia

Każde dane wyjściowe powinny być tablicą lub wielowymiarową tablicą obsługiwanych typów pierwotnych lub ByteBuffer o odpowiednim rozmiarze. Należy zauważyć, że niektóre modele mają dynamiczne wyjścia, w 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 obsługi tego za pomocą istniejącego interfejsu Java inference API, ale planowane rozszerzenia umożliwią to.

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

Interfejs API Objective-C jest dostępny 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 celu-C nie obsługuje delegatów. Aby używać delegatów z kodem celu w języku C, musisz bezpośrednio wywołać bazowy 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 on 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ż jeden Interpreter .

Ważne części interfejsu API Interpreter są pokazane w poniższym fragmencie kodu. Należy zauważyć że:

  • Tensory są reprezentowane przez liczby całkowite, aby uniknąć porównań ciągów (i jakiejkolwiek stałej zależności od bibliotek ciągów).
  • Interpreter nie może być dostępny ze współbieżnych wątków.
  • Przydział pamięci dla tensorów wejściowych i wyjściowych musi być wyzwalany przez wywołanie AllocateTensors() zaraz po zmianie rozmiaru tensorów.

Najprostsze użycie TensorFlow Lite z 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 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 label_image.cc minimal.cc

Załaduj i uruchom model w Pythonie

Platforma: Linux

Interfejs API Pythona do uruchamiania wnioskowania znajduje się w module tf.lite . Z którego do załadowania modelu i uruchomienia wnioskowania potrzebujesz głównie tylko tf.lite.Interpreter .

Poniższy przykład pokazuje, jak używać interpretera Pythona do ładowania pliku .tflite i uruchamiania wnioskowania z losowymi danymi wejściowymi:

Ten przykład jest zalecany, jeśli konwertujesz z SavedModel ze zdefiniowanym SignatureDef. Dostępne 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 zdefiniowanego 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 TensorFlow Lite Converter Python API ( tf.lite.TFLiteConverter ), co pozwala na konwersję modelu 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 Pythona, zobacz label_image.py .

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 .