Вывод TensorFlow Lite

Термин вывод относится к процессу выполнения модели TensorFlow Lite на устройстве, чтобы делать прогнозы на основе входных данных. Чтобы выполнить вывод с помощью модели TensorFlow Lite, вы должны запустить ее через интерпретатор . Интерпретатор TensorFlow Lite разработан, чтобы быть компактным и быстрым. Интерпретатор использует статический порядок графа и настраиваемый (менее динамичный) распределитель памяти, чтобы обеспечить минимальную задержку загрузки, инициализации и выполнения.

На этой странице описывается, как получить доступ к интерпретатору TensorFlow Lite и выполнить вывод с помощью C++, Java и Python, а также ссылки на другие ресурсы для каждой поддерживаемой платформы .

Важные концепции

Вывод TensorFlow Lite обычно следует следующим шагам:

  1. Загрузка модели

    Вы должны загрузить модель .tflite в память, которая содержит график выполнения модели.

  2. Преобразование данных

    Необработанные входные данные для модели обычно не соответствуют формату входных данных, ожидаемому моделью. Например, вам может потребоваться изменить размер изображения или изменить формат изображения, чтобы оно было совместимо с моделью.

  3. Вывод

    Этот шаг включает использование API TensorFlow Lite для выполнения модели. Он включает в себя несколько шагов, таких как создание интерпретатора и выделение тензоров, как описано в следующих разделах.

  4. Интерпретация вывода

    Когда вы получаете результаты вывода модели, вы должны осмысленно интерпретировать тензоры, которые будут полезны в вашем приложении.

    Например, модель может возвращать только список вероятностей. Вы должны сопоставить вероятности с соответствующими категориями и представить их конечному пользователю.

Поддерживаемые платформы

API вывода TensorFlow предоставляются для наиболее распространенных мобильных/встраиваемых платформ, таких как Android , iOS и Linux , на нескольких языках программирования.

В большинстве случаев дизайн API отражает предпочтение производительности, а не простоты использования. TensorFlow Lite предназначен для быстрого логического вывода на небольших устройствах, поэтому неудивительно, что API стараются избегать ненужных копий в ущерб удобству. Точно так же согласованность с API-интерфейсами TensorFlow не была явной целью, и следует ожидать некоторых различий между языками.

Во всех библиотеках API TensorFlow Lite позволяет загружать модели, подавать входные данные и извлекать выходные данные.

Платформа Android

На Android вывод TensorFlow Lite можно выполнять с помощью API Java или C++. API-интерфейсы Java обеспечивают удобство и могут использоваться непосредственно в классах Android Activity. API-интерфейсы C++ обеспечивают большую гибкость и скорость, но могут потребовать написания оболочек JNI для перемещения данных между уровнями Java и C++.

Подробнее об использовании C++ и Java см. ниже или следуйте инструкциям и примерам кода в кратком руководстве по Android .

Генератор кода оболочки TensorFlow Lite для Android

Для модели TensorFlow Lite, дополненной метаданными , разработчики могут использовать генератор кода оболочки TensorFlow Lite Android для создания кода оболочки для конкретной платформы. Код-оболочка избавляет от необходимости напрямую взаимодействовать с ByteBuffer на Android. Вместо этого разработчики могут взаимодействовать с моделью TensorFlow Lite с типизированными объектами, такими как Bitmap и Rect . Дополнительные сведения см. в генераторе кода-оболочки TensorFlow Lite для Android .

Платформа iOS

На iOS TensorFlow Lite доступен с нативными библиотеками iOS, написанными на Swift и Objective-C . Вы также можете использовать C API непосредственно в кодах Objective-C.

Подробнее об использовании Swift , Objective-C и C API см. ниже или следуйте краткому руководству по iOS , чтобы получить руководство и пример кода.

Платформа Linux

На платформах Linux (включая Raspberry Pi ) вы можете выполнять выводы с помощью API-интерфейсов TensorFlow Lite, доступных на C++ и Python , как показано в следующих разделах.

Запуск модели

Запуск модели TensorFlow Lite включает в себя несколько простых шагов:

  1. Загрузите модель в память.
  2. Создайте Interpreter на основе существующей модели.
  3. Установите значения входного тензора. (При желании измените размер входных тензоров, если предопределенные размеры не нужны.)
  4. Вызовите умозаключение.
  5. Считайте выходные значения тензора.

В следующих разделах описывается, как эти шаги можно выполнить на каждом языке.

Загрузите и запустите модель в Java

Платформа: Android

API Java для выполнения вывода с помощью TensorFlow Lite в первую очередь предназначен для использования с Android, поэтому он доступен в виде зависимости от библиотеки Android: org.tensorflow:tensorflow-lite .

В Java вы будете использовать класс Interpreter для загрузки модели и вывода модели. Во многих случаях это может быть единственный API, который вам нужен.

Вы можете инициализировать Interpreter с помощью файла .tflite :

public Interpreter(@NotNull File modelFile);

Или с MappedByteBuffer :

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

В обоих случаях необходимо предоставить допустимую модель TensorFlow Lite, иначе API IllegalArgumentException . Если вы используете MappedByteBuffer для инициализации Interpreter , он должен оставаться неизменным на протяжении всего времени существования Interpreter .

Предпочтительный способ вывода модели — использовать подписи. Доступно для моделей, преобразованных начиная с 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");
}

Метод runSignature принимает три аргумента:

  • Входы : сопоставление входных данных от имени ввода в подписи к входному объекту.

  • Выходы : карта для сопоставления выходных данных с выходным именем в подписи на выходные данные.

  • Имя подписи [необязательно]: имя подписи (можно оставить пустым, если модель имеет одну подпись).

Другой способ выполнить вывод, когда модель не имеет определенных сигнатур. Просто вызовите Interpreter.run() . Например:

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

Метод run() принимает только один ввод и возвращает только один вывод. Поэтому, если ваша модель имеет несколько входов или несколько выходов, вместо этого используйте:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

В этом случае каждая запись во inputs соответствует входному тензору, а map_of_indices_to_outputs отображает индексы выходных тензоров в соответствующие выходные данные.

В обоих случаях тензорные индексы должны соответствовать значениям, которые вы передали конвертеру TensorFlow Lite при создании модели. Имейте в виду, что порядок тензоров во input должен соответствовать порядку, указанному в конвертере TensorFlow Lite.

Класс Interpreter также предоставляет удобные функции для получения индекса любого ввода или вывода модели с использованием имени операции:

public int getInputIndex(String opName);
public int getOutputIndex(String opName);

Если opName не является допустимой операцией в модели, выдается исключение IllegalArgumentException .

Также имейте в виду, что Interpreter владеет ресурсами. Чтобы избежать утечки памяти, ресурсы должны быть освобождены после использования:

interpreter.close();

Пример проекта с Java см. в примере классификации изображений Android .

Поддерживаемые типы данных (в Java)

Чтобы использовать TensorFlow Lite, типы данных входных и выходных тензоров должны быть одним из следующих примитивных типов:

  • float
  • int
  • long
  • byte

Также поддерживаются String типы, но они кодируются иначе, чем примитивные типы. В частности, форма строкового тензора диктует количество и расположение строк в тензоре, причем каждый элемент сам по себе является строкой переменной длины. В этом смысле размер (в байтах) тензора не может быть вычислен только из формы и типа, и, следовательно, строки не могут быть предоставлены в виде одного плоского аргумента ByteBuffer .

Если используются другие типы данных, в том числе упакованные типы, такие как Integer и Float , будет IllegalArgumentException .

Входы

Каждый вход должен быть массивом или многомерным массивом поддерживаемых примитивных типов или необработанным ByteBuffer соответствующего размера. Если входные данные представляют собой массив или многомерный массив, связанный входной тензор будет неявно изменен в соответствии с размерами массива во время вывода. Если ввод представляет собой ByteBuffer, вызывающая сторона должна сначала вручную изменить размер связанного входного тензора (через Interpreter.resizeInput() ), прежде чем запускать вывод.

При использовании ByteBuffer использовать прямые байтовые буферы, так как это позволяет Interpreter избежать ненужных копий. Если ByteBuffer является прямым байтовым буфером, его порядок должен быть ByteOrder.nativeOrder() . После того, как он используется для вывода модели, он должен оставаться неизменным до завершения вывода модели.

Выходы

Каждый вывод должен быть массивом или многомерным массивом поддерживаемых примитивных типов или ByteBuffer соответствующего размера. Обратите внимание, что некоторые модели имеют динамические выходные данные, где форма выходных тензоров может варьироваться в зависимости от входных данных. Нет простого способа справиться с этим с помощью существующего API вывода Java, но запланированные расширения сделают это возможным.

Загрузите и запустите модель в Swift

Платформа: iOS

API Swift доступен в модуле TensorFlowLiteSwift от Cocoapods.

Во-первых, вам нужно импортировать модуль 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...
}

Загрузите и запустите модель в Objective-C

Платформа: iOS

API Objective-C доступен в модуле TensorFlowLiteObjC от Cocoapods.

Во-первых, вам нужно импортировать модуль 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... */ }

Использование C API в коде Objective-C

В настоящее время API Objective-C не поддерживает делегатов. Чтобы использовать делегаты с кодом Objective-C, вам необходимо напрямую вызвать базовый 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++

Платформы: Android, iOS и Linux

В C++ модель хранится в классе FlatBufferModel . Он инкапсулирует модель TensorFlow Lite, и вы можете построить ее несколькими способами, в зависимости от того, где хранится модель:

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

Теперь, когда у вас есть модель в виде объекта FlatBufferModel , вы можете выполнить ее с помощью Interpreter . Одна FlatBufferModel может использоваться одновременно несколькими Interpreter .

Важные части API Interpreter показаны во фрагменте кода ниже. Необходимо отметить, что:

  • Тензоры представлены целыми числами, чтобы избежать сравнения строк (и любой фиксированной зависимости от строковых библиотек).
  • Доступ к интерпретатору не должен осуществляться из параллельных потоков.
  • Выделение памяти для входных и выходных тензоров должно запускаться вызовом AllocateTensors() сразу после изменения размера тензоров.

Простейшее использование TensorFlow Lite с C++ выглядит так:

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

Дополнительные примеры кода см. minimal.cc и label_image.cc .

Загрузите и запустите модель в Python

Платформа: линукс

API Python для выполнения логического вывода предоставляется в модуле tf.lite . Из которых вам в основном нужен только tf.lite.Interpreter для загрузки модели и запуска вывода.

В следующем примере показано, как использовать интерпретатор Python для загрузки файла .tflite и выполнения вывода со случайными входными данными:

Этот пример рекомендуется, если вы выполняете преобразование из SavedModel с определенным SignatureDef. Доступно начиная с 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'])

Другой пример, если в модели не определены 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)

В качестве альтернативы загрузке модели в виде предварительно преобразованного файла .tflite вы можете объединить свой код с API-интерфейсом TensorFlow Lite Converter Python ( tf.lite.TFLiteConverter ), что позволит вам преобразовать вашу модель TensorFlow в формат TensorFlow Lite, а затем выполнить вывод:

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...

Дополнительные примеры кода Python см. label_image.py .

Поддерживаемые операции

TensorFlow Lite поддерживает подмножество операций TensorFlow с некоторыми ограничениями. Полный список операций и ограничений см. на странице TF Lite Ops .