Эта страница была переведа с помощью Cloud Translation API.
Switch to English

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

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

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

Android

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

См. Ниже подробные сведения об использовании C ++ и Java или следуйте краткому руководству по Android, чтобы получить руководство и примеры кода.

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

Для модели TensorFlow Lite, дополненной метаданными , разработчики могут использовать генератор кода оболочки Android TensorFlow Lite для создания кода оболочки для конкретной платформы. Код оболочки устраняет необходимость прямого взаимодействия с 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 .

Чтобы затем выполнить логический вывод с моделью, просто вызовите 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 Converter.

Класс 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 типы, но они кодируются иначе, чем примитивные типы. В частности, форма тензора строки определяет количество и расположение строк в тензоре, при этом каждый элемент сам по себе является строкой переменной длины. В этом смысле (байтовый) размер Tensor не может быть вычислен только на основе формы и типа, и, следовательно, строки не могут быть предоставлены как единственный плоский аргумент ByteBuffer .

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

Входы

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

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

Выходы

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

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

Платформа: iOS

Swift API доступен в TensorFlowLiteSwift Pod от 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 Pod от 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...

// Copy the input data to the input `TFLTensor`.
[interpreter copyData:inputData toInputTensorAtIndex:0 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:&amp;error];
if (error != nil) { /* Error handling... */ }

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

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

Загрузить и запустить модель на 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 .

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

  • Тензоры представлены целыми числами, чтобы избежать сравнения строк (и любой фиксированной зависимости от строковых библиотек).
  • К интерпретатору нельзя обращаться из параллельных потоков.
  • Выделение памяти для тензоров ввода и вывода должно запускаться вызовом 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

Платформа: Linux

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

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

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-интерфейсом Python для преобразователя tf.lite.TFLiteConverter Lite ( 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 .

Совет: запустите help(tf.lite.Interpreter) в терминале Python, чтобы получить подробную документацию по интерпретатору.

Напишите собственный оператор

Все операторы TensorFlow Lite (как пользовательские, так и встроенные) определены с использованием простого интерфейса на чистом C, который состоит из четырех функций:

typedef struct {
  void* (*init)(TfLiteContext* context, const char* buffer, size_t length);
  void (*free)(TfLiteContext* context, void* buffer);
  TfLiteStatus (*prepare)(TfLiteContext* context, TfLiteNode* node);
  TfLiteStatus (*invoke)(TfLiteContext* context, TfLiteNode* node);
} TfLiteRegistration;

См. context.h для получения подробной информации о TfLiteContext и TfLiteNode . Первый предоставляет средства сообщения об ошибках и доступ к глобальным объектам, включая все тензоры. Последний позволяет реализациям получать доступ к своим входам и выходам.

Когда интерпретатор загружает модель, он вызывает init() один раз для каждого узла в графе. Данная init() будет вызываться более одного раза, если операция используется в графе несколько раз. Для настраиваемых операций будет предоставлен буфер конфигурации, содержащий гибкий буфер, который сопоставляет имена параметров с их значениями. Буфер пуст для встроенных операций, потому что интерпретатор уже проанализировал параметры операции. Реализации ядра, которым требуется состояние, должны инициализировать его здесь и передать право собственности вызывающей стороне. Для каждого вызова init() будет соответствующий вызов free() , позволяющий реализациям избавиться от буфера, который они могли выделить в init() .

Каждый раз, когда размер входных тензоров изменяется, интерпретатор просматривает график, уведомляя реализации об изменении. Это дает им возможность изменить размер своего внутреннего буфера, проверить правильность входных форм и типов и пересчитать выходные формы. Все это делается через prepare() , а реализации могут получить доступ к своему состоянию с помощью node->user_data .

Наконец, каждый раз, когда выполняется вывод, интерпретатор проходит по графу, вызывая invoke() , и здесь состояние также доступно как node->user_data .

Пользовательские операции могут быть реализованы точно так же, как встроенные операции, путем определения этих четырех функций и функции глобальной регистрации, которая обычно выглядит следующим образом:

namespace tflite {
namespace ops {
namespace custom {
  TfLiteRegistration* Register_MY_CUSTOM_OP() {
    static TfLiteRegistration r = {my_custom_op::Init,
                                   my_custom_op::Free,
                                   my_custom_op::Prepare,
                                   my_custom_op::Eval};
    return &r;
  }
}  // namespace custom
}  // namespace ops
}  // namespace tflite

Обратите внимание, что регистрация не является автоматической и где-то должен быть сделан явный вызов Register_MY_CUSTOM_OP . В то время как стандартный BuiltinOpResolver (доступный по BuiltinOpResolver :builtin_ops target) заботится о регистрации встроенных команд, пользовательские операции должны быть собраны в отдельных пользовательских библиотеках.

Настроить библиотеку ядра

За кулисами интерпретатор загрузит библиотеку ядер, которая будет назначена для выполнения каждого из операторов в модели. Хотя библиотека по умолчанию содержит только встроенные ядра, ее можно заменить специальной библиотекой.

Интерпретатор использует OpResolver для преобразования кодов и имен операторов в реальный код:

class OpResolver {
  virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
  virtual TfLiteRegistration* FindOp(const char* op) const = 0;
  virtual void AddOp(tflite::BuiltinOperator op, TfLiteRegistration* registration) = 0;
  virtual void AddOp(const char* op, TfLiteRegistration* registration) = 0;
};

При регулярном использовании необходимо использовать BuiltinOpResolver и писать:

tflite::ops::builtin::BuiltinOpResolver resolver;

При желании вы можете зарегистрировать пользовательские операции (перед тем, как передать преобразователь в InterpreterBuilder ):

resolver.AddOp("MY_CUSTOM_OP", Register_MY_CUSTOM_OP());

Если набор встроенных операций считается слишком большим, новый OpResolver может быть сгенерирован кодом на основе заданного подмножества операций, возможно, только тех, которые содержатся в данной модели. Это эквивалент выборочной регистрации TensorFlow (простая версия доступна в каталоге tools ).