wnioskowanie TensorFlow Lite

Termin wnioskowanie oznacza proces wykonywania modelu TensorFlow Lite na urządzeniu w celu generowania prognoz na podstawie danych wejściowych. Aby przeprowadzić wnioskowanie z użyciem modelu TensorFlow Lite, musisz go uruchomić za pomocą interpretatora. Tłumacz języka TensorFlow Lite został zaprojektowany z myślą o sprawności i szybkości działania. Tłumacz wykorzystuje statyczne porządkowanie wykresów i niestandardowy (mniej dynamiczny) przydział pamięci, aby zminimalizować obciążenie, inicjowanie i czas oczekiwania na wykonanie.

Na tej stronie dowiesz się, jak uzyskać dostęp do interpretera TensorFlow Lite i wykonać wnioskowanie za pomocą C++, Javy i Pythona. Dowiesz się też, jak uzyskać dostęp do innych zasobów dla każdej obsługiwanej platformy.

Ważne pojęcia

Działanie wnioskowania w TensorFlow Lite przebiega zazwyczaj w ten sposób:

  1. Wczytywanie modelu

    Musisz wczytać do pamięci model .tflite zawierający wykres wykonywania modelu.

  2. Przekształcanie danych

    Nieprzetworzone dane wejściowe modelu zwykle nie odpowiadają formatowi danych wejściowych, którego oczekuje model. Na przykład może być konieczna zmiana rozmiaru obrazu lub jego formatu, aby był zgodny z modelem.

  3. Przeprowadzanie wnioskowania

    Ten krok obejmuje użycie interfejsu TensorFlow Lite API do uruchomienia modelu. Obejmuje on kilka kroków, takich jak utworzenie tłumaczenia rozmowy i przydzielanie intensywności, jak opisano w poniższych sekcjach.

  4. Interpretowanie danych wyjściowych

    Po otrzymaniu wyników wnioskowania z modelu musisz interpretować generatory w sposób przydatny w Twojej aplikacji.

    Na przykład model może zwracać tylko listę prawdopodobieństw. To Ty przypisujesz prawdopodobieństwo do odpowiednich kategorii i przedstawiasz je użytkownikowi.

Obsługiwane platformy

Interfejsy TensorFlow wnioskowania z interfejsów API są udostępniane w wielu językach programowania na większości popularnych platform mobilnych lub osadzonych, takich jak Android, iOS i Linux.

W większości przypadków konstrukcja interfejsu API odzwierciedla raczej wydajność, a nie łatwość obsługi. TensorFlow Lite zaprojektowano z myślą o szybkim wnioskowaniu na małych urządzeniach, więc nic dziwnego, że interfejsy API starają się uniknąć zbędnych kopii przy jednoczesnym zapewnianiu wygody. Podobnie spójność z interfejsami API TensorFlow nie była jawnym celem i należy się spodziewać pewnych różnic między językami.

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

Platforma

Na Androidzie wnioskowanie TensorFlow Lite można wykonywać za pomocą interfejsów API C++ i Javy. Interfejsy Java API zapewniają wygodę i można ich używać bezpośrednio w klasach aktywności na Androidzie. Interfejsy API C++ zapewniają większą elastyczność i szybkość, ale mogą wymagać pisania opakowań JNI w celu przenoszenia danych między warstwami Javy i C++.

Poniżej znajdziesz szczegółowe informacje o korzystaniu z C++ i Java. Znajdziesz tam też samouczek i przykładowy kod z krótkiego wprowadzenia do Androida.

Generator kodu opakowań na Androida TensorFlow Lite

W przypadku modelu TensorFlow Lite ulepszonego za pomocą metadanych programiści mogą użyć generatora kodów opakowań na Androida TensorFlow Lite, aby utworzyć kod otoki specyficzny dla platformy. Kod towarzyszący eliminuje konieczność bezpośredniej interakcji z ByteBuffer na Androidzie. Zamiast tego programiści mogą wchodzić w interakcje z modelem TensorFlow Lite z wpisanymi obiektami, takimi jak Bitmap i Rect. Więcej informacji znajdziesz w generatorze kodów opakowań TensorFlow Lite na Androida.

Platforma iOS

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

Poniżej znajdziesz szczegółowe informacje o używaniu Swift, Objective-C i interfejsu C API. Samouczek i przykładowy kod znajdziesz w krótkim wprowadzeniu do iOS.

Platforma Linux

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

Uruchamianie modelu

Uruchomienie modelu TensorFlow Lite wymaga kilku prostych kroków:

  1. Wczytaj model do pamięci.
  2. Utwórz Interpreter na podstawie istniejącego modelu.
  3. Ustaw wartości tensorów wejściowych. (Jeśli zdefiniowane wstępnie rozmiary nie są potrzebne, opcjonalnie zmień rozmiar tensorów wejściowych).
  4. Wywołaj wnioskowanie.
  5. Odczytaj wyjściowe wartości tensorów.

Kolejne sekcje zawierają opis czynności, które można wykonać w poszczególnych językach.

Wczytywanie i uruchamianie modelu w Javie

Platforma: Android

Interfejs Java API do uruchamiania wnioskowania za pomocą TensorFlow Lite jest przeznaczony przede wszystkim do użytku na Androidzie, dlatego jest dostępny jako zależność biblioteki Androida: org.tensorflow:tensorflow-lite.

W Javie będziesz używać klasy Interpreter do wczytywania modelu i wspomagania wnioskowania na podstawie modelu. W wielu przypadkach może to być jedyny interfejs API, którego potrzebujesz.

Możesz zainicjować Interpreter przy użyciu pliku .tflite:

public Interpreter(@NotNull File modelFile);

Lub za pomocą MappedByteBuffer:

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

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

Preferowanym sposobem wnioskowania na model jest użycie podpisów. Dostępne w przypadku modeli skonwertowanych 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 3 argumenty:

  • Dane wejściowe : mapuje dane wejściowe z nazwy danych wejściowych w podpisie na obiekt wejściowy.

  • Dane wyjściowe : zmapowane dane wyjściowe z nazwy wyjściowej w podpisie na dane wyjściowe.

  • Nazwa podpisu (opcjonalna): nazwa podpisu (może zostać pusta, jeśli model ma pojedynczy podpis).

Inny sposób uruchomienia wnioskowania, gdy model nie ma zdefiniowanych podpisów. Zadzwoń do firmy 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 wynik. Jeśli więc model ma wiele danych wejściowych lub wyjściowych, zastosuj polecenie:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

W tym przypadku każda pozycja w tabeli inputs odpowiada tensorowi wejściowemu, a map_of_indices_to_outputs mapuje indeksy tensorów wyjściowych na odpowiednie dane wyjściowe.

W obu przypadkach indeksy tensorów powinny odpowiadać wartościom podanym podczas tworzenia modelu TensorFlow Lite Converter. Pamiętaj, że kolejność tensorów w funkcji input musi być taka sama jak kolejność podana dla konwertera TensorFlow Lite.

Klasa Interpreter udostępnia też wygodne funkcje umożliwiające pobieranie indeksu wszystkich danych wejściowych lub wyjściowych modelu za pomocą 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.

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

interpreter.close();

Przykładowy projekt w Javie znajdziesz w tym artykule.

Obsługiwane typy danych (w Javie)

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

  • float
  • int
  • long
  • byte

Typy String również są obsługiwane, ale są kodowane inaczej niż typy podstawowe. Kształt ciągu znaków Tensor określa liczbę i układ ciągów tekstowych w Tensorzie, a każdy element może być ciągiem o zmiennej długości. W tym sensie wielkość (w bajtach) Tensor nie może zostać obliczona na podstawie samego kształtu i typu, dlatego ciągi znaków nie mogą być przekazywane jako pojedynczy, płaski argument ByteBuffer.

Jeśli używane są inne typy danych, w tym typy z pudełkami, takie jak Integer i Float, funkcja IllegalArgumentException jest zwracana.

Dane wejściowe

Wszystkie dane wejściowe powinny być tablicą lub wielowymiarową tablicą obsługiwanych typów podstawowych albo nieprzetworzonym obiektem ByteBuffer o odpowiednim rozmiarze. Jeśli dane wejściowe to tablica lub wielowymiarowa tablica, w momencie wnioskowania rozmiar powiązanego tensora wejściowego zostanie domyślnie zmieniony na wymiary tablicy. Jeśli dane wejściowe to ByteBuffer, element wywołujący powinien najpierw ręcznie zmienić rozmiar powiązanego tensora wejściowego (za pomocą Interpreter.resizeInput()), zanim przeprowadzi wnioskowanie.

Gdy używasz ByteBuffer, preferuj korzystanie z bezpośrednich buforów bajtów, ponieważ dzięki temu Interpreter unika niepotrzebnych kopii. Jeśli ByteBuffer jest bezpośrednim buforem bajtów, jego kolejność musi wynosić ByteOrder.nativeOrder(). Po użyciu do wnioskowania modelu musi pozostać niezmieniony, dopóki wnioskowanie przez model nie zostanie zakończone.

Wyjście

Dane wyjściowe powinny być tablicą lub wielowymiarową tablicą obsługiwanych typów podstawowych albo obiektem ByteBuffer o odpowiednim rozmiarze. Pamiętaj, że niektóre modele mają dynamiczne dane wyjściowe, gdzie kształt tensorów wyjściowych może się różnić w zależności od danych wejściowych. Nie ma prostego sposobu na radzenie sobie z tym za pomocą istniejącego interfejsu Java Inference API, ale planowane rozszerzenia będą to umożliwiać.

Wczytywanie i uruchamianie modelu w Swift

Platforma: iOS

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

Wczytywanie i uruchamianie modelu w Objective-C

Platforma: iOS

Interfejs Objective-C API jest dostępny w TensorFlowLiteObjC podzie 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 interfejsu C API w kodzie Objective-C

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

Wczytywanie i uruchamianie modelu w C++

Platformy: Android, iOS i Linux

W C++ model jest przechowywany w klasie FlatBufferModel. Obejmuje on model TensorFlow Lite i można go utworzyć na kilka różnych sposobów w zależności od tego, gdzie model 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);
};

Gdy model będzie już obiektem FlatBufferModel, możesz go wykonać za pomocą Interpreter. Jeden typ FlatBufferModel może być używany jednocześnie przez więcej niż 1 element Interpreter.

Poniżej znajdziesz ważne elementy interfejsu Interpreter API. Warto zauważyć, że:

  • Tensory są reprezentowane przez liczby całkowite, co pozwala uniknąć porównywania ciągów znaków (oraz jakiejkolwiek stałej zależności od bibliotek ciągów znaków).
  • Nie można korzystać z tłumacza w równoczesnych wątkach.
  • Alokacja pamięci na potrzeby tensorów wejściowych i wyjściowych musi być aktywowana przez wywołanie AllocateTensors() zaraz po zmianie rozmiaru tensorów.

Najprostsze wykorzystanie TensorFlow Lite w 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);

Więcej przykładowego kodu znajdziesz w sekcji minimal.cc i label_image.cc.

Wczytywanie i uruchamianie modelu w Pythonie

Platforma: Linux

Interfejs Python API do uruchamiania wnioskowania potrzebuje tylko tf.lite.Interpreter, aby wczytać model i uruchomić wnioskowanie.

Ten przykład pokazuje, jak użyć interpretera Pythona do wczytania pliku .tflite i uruchomienia wnioskowania na podstawie losowych danych wejściowych:

Ten przykład jest zalecany, jeśli dokonujesz konwersji z SavedModel ze zdefiniowaną definicją 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 sytuacji, w której model nie ma zdefiniowanych elementów 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)

Zamiast wczytywać model jako wstępnie przekonwertowany plik .tflite, możesz połączyć swój kod z interfejsem TensorFlow Lite Converter Python API, co pozwoli Ci przekonwertować model Keras na format TensorFlow Lite, a następnie uruchomić 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 Pythonie znajdziesz w sekcji label_image.py.

Przeprowadź wnioskowanie z użyciem modelu dynamicznego kształtu

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

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 kształt danych wejściowych jest zdefiniowany jako [1/None, 10] i trzeba 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 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()

Obsługiwane operacje

TensorFlow Lite obsługuje podzbiór operacji TensorFlow z pewnymi ograniczeniami. Pełną listę operacji i ograniczeń znajdziesz na stronie operacji TF Lite.