Это руководство знакомит вас с процессом запуска модели LiteRT (сокращение от Lite Runtime) на устройстве для построения прогнозов на основе входных данных. Это достигается с помощью интерпретатора LiteRT, который использует статическое упорядочивание графа и собственный (менее динамичный) распределитель памяти для обеспечения минимальной нагрузки, инициализации и задержки выполнения.
Вывод LiteRT обычно выполняется следующим образом:
Загрузка модели : загрузка модели
.tfliteв память, которая содержит график выполнения модели.Преобразование данных : преобразование входных данных в ожидаемый формат и размеры. Необработанные входные данные для модели, как правило, не соответствуют формату, ожидаемому моделью. Например, может потребоваться изменить размер изображения или его формат для совместимости с моделью.
Выполнение вывода : запуск модели LiteRT для построения прогнозов. Этот шаг включает использование API LiteRT для выполнения модели. Он включает в себя несколько этапов, таких как создание интерпретатора и выделение тензоров.
Интерпретация выходных данных : интерпретируйте выходные тензоры осмысленным образом, полезным для вашего приложения. Например, модель может возвращать только список вероятностей. Вы можете самостоятельно сопоставить вероятности с соответствующими категориями и отформатировать выходные данные.
В этом руководстве описывается, как получить доступ к интерпретатору LiteRT и выполнить вывод с использованием C++, Java и Python.
Поддерживаемые платформы
API-интерфейсы вывода TensorFlow предоставляются для большинства распространенных мобильных и встраиваемых платформ, таких как Android , iOS и Linux , на нескольких языках программирования .
В большинстве случаев при проектировании API производительность ставится выше простоты использования. LiteRT разработан для быстрого вывода данных на небольших устройствах, поэтому API избегают ненужного копирования в ущерб удобству.
Во всех библиотеках API LiteRT позволяет загружать модели, вводить входные данные и получать выходные данные вывода.
Платформа Android
На Android вывод LiteRT может осуществляться с помощью API Java или C++. API Java удобны и могут использоваться непосредственно в классах Activity Android. API C++ обеспечивают большую гибкость и скорость, но могут потребовать написания JNI-обёрток для передачи данных между уровнями Java и C++.
Дополнительную информацию см. в разделах C++ и Java или следуйте краткому руководству по Android .
Платформа iOS
На iOS LiteRT доступен в библиотеках Swift и Objective-C . Вы также можете использовать C API непосредственно в коде Objective-C.
См. разделы Swift , Objective-C и C API или следуйте краткому руководству по iOS .
Платформа Linux
На платформах Linux вы можете выполнять выводы с использованием API LiteRT, доступных в C++ .
Загрузите и запустите модель
Загрузка и запуск модели LiteRT включает следующие шаги:
- Загрузка модели в память.
- Создание
Interpreterна основе существующей модели. - Установка входных значений тензора.
- Вызывание умозаключений.
- Вывод значений тензора.
Android (Java)
API Java для выполнения выводов с помощью LiteRT в первую очередь разработан для использования с Android, поэтому он доступен как зависимость библиотеки Android: com.google.ai.edge.litert .
В Java вы будете использовать класс Interpreter для загрузки модели и управления её выводом. Во многих случаях это может быть единственный API, который вам понадобится.
Вы можете инициализировать Interpreter , используя файл FlatBuffers ( .tflite ):
public Interpreter(@NotNull File modelFile);
Или с MappedByteBuffer :
public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);
В обоих случаях необходимо предоставить допустимую модель LiteRT, иначе 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 сопоставляет индексы выходных тензоров с соответствующими выходными данными.
В обоих случаях индексы тензоров должны соответствовать значениям, заданным в LiteRT Converter при создании модели. Обратите внимание, что порядок тензоров на input должен соответствовать порядку, заданному в LiteRT Converter.
Класс Interpreter также предоставляет удобные функции для получения индекса любого входа или выхода модели с использованием имени операции:
public int getInputIndex(String opName);
public int getOutputIndex(String opName);
Если opName не является допустимой операцией в модели, выдается исключение IllegalArgumentException .
Также имейте в виду, что ресурсы принадлежат Interpreter . Во избежание утечки памяти ресурсы должны быть освобождены после использования:
interpreter.close();
Пример проекта на Java см. в примере приложения обнаружения объектов Android .
Поддерживаемые типы данных
Для использования LiteRT типы данных входных и выходных тензоров должны быть одним из следующих примитивных типов:
-
float -
int -
long -
byte
String типы также поддерживаются, но они кодируются иначе, чем примитивные типы. В частности, форма строкового тензора определяет количество и расположение строк в нём, при этом каждый элемент представляет собой строку переменной длины. В этом смысле размер тензора (в байтах) не может быть вычислен только по форме и типу, и, следовательно, строки не могут быть представлены как один плоский аргумент ByteBuffer .
Если используются другие типы данных, включая упакованные типы, такие как Integer и Float , будет выдано исключение IllegalArgumentException .
Входы
Каждый входной параметр должен быть массивом или многомерным массивом поддерживаемых примитивных типов, либо необработанным байтовым ByteBuffer подходящего размера. Если входной параметр — массив или многомерный массив, соответствующий входной тензор будет неявно изменён в соответствии с размерами массива во время вывода. Если входной параметр — байтовый буфер (ByteBuffer), вызывающий должен сначала вручную изменить размер соответствующего входного тензора (с помощью Interpreter.resizeInput() ) перед выполнением вывода.
При использовании ByteBuffer предпочтительнее использовать прямые байтовые буферы, так как это позволяет Interpreter избежать ненужного копирования. Если ByteBuffer — это прямой байтовый буфер, его порядок должен быть ByteOrder.nativeOrder() . После использования для вывода модели он должен оставаться неизменным до завершения вывода модели.
Выходы
Каждый выходной сигнал должен представлять собой массив или многомерный массив поддерживаемых примитивных типов, либо байтовый буфер соответствующего размера. Обратите внимание, что некоторые модели имеют динамические выходные данные, где форма выходных тензоров может меняться в зависимости от входных данных. В существующем API вывода Java нет простого способа решения этой проблемы, но планируемые расширения сделают это возможным.
iOS (Swift)
API Swift доступен в 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...
}
iOS (Objective-C)
API Objective-C доступен в LiteRTObjC Pod от Cocoapods.
Сначала вам необходимо импортировать модуль TensorFlowLiteObjC .
@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, необходимо напрямую вызывать базовый 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);
С++
API C++ для выполнения вывода с помощью LiteRT совместим с платформами Android, iOS и Linux. API C++ на iOS доступен только при использовании Bazel.
В C++ модель хранится в классе FlatBufferModel . Он инкапсулирует модель LiteRT, и её можно построить несколькими способами, в зависимости от того, где хранится модель:
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()сразу после изменения размера тензоров.
Простейшее использование LiteRT с 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 needed.
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 .
Питон
API Python для выполнения выводов использует Interpreter для загрузки модели и выполнения выводов.
Установите пакет LiteRT:
$ python3 -m pip install ai-edge-litert
Импортируйте интерпретатор LiteRT
from ai_edge_litert.interpreter import Interpreter
Interpreter = Interpreter(model_path=args.model.file)
В следующем примере показано, как использовать интерпретатор Python для загрузки файла FlatBuffers ( .tflite ) и выполнения вывода со случайными входными данными:
Этот пример рекомендуется использовать, если вы конвертируете из SavedModel с определенным SignatureDef.
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 LiteRT model in LiteRT Interpreter
from ai_edge_litert.interpreter import Interpreter
interpreter = 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 LiteRT model and allocate tensors.
from ai_edge_litert.interpreter import Interpreter
interpreter = Interpreter(TFLITE_FILE_PATH)
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 вы можете объединить свой код с компилятором LiteRT , что позволит вам преобразовать вашу модель Keras в формат LiteRT, а затем запустить вывод:
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 LiteRT format
converter = tf.lite.TFLiteConverter.from_keras_model(tf.keras.models.Model(inputs=[img], outputs=[out]))
tflite_model = converter.convert()
# Load the LiteRT model and allocate tensors.
from ai_edge_litert.interpreter import Interpreter
interpreter = Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
# Continue to get tensors and so forth, as shown above...
Дополнительный пример кода Python см. в label_image.py .
Выполнение вывода с использованием динамической модели формы
Если вы хотите запустить модель с динамической формой входных данных, измените размер формы входных данных перед запуском вывода. В противном случае форма None в моделях Tensorflow будет заменена заполнителем 1 в моделях LiteRT.
В следующих примерах показано, как изменить размер входной формы перед запуском вывода на разных языках. Во всех примерах предполагается, что входная форма определена как [1/None, 10] и её размер необходимо изменить до [3, 10] .
Пример на С++:
// Resize input tensors before allocate tensors
interpreter->ResizeInputTensor(/*tensor_index=*/0, std::vector<int>{3,10});
interpreter->AllocateTensors();
Пример на Python:
# Load the LiteRT model in LiteRT Interpreter
from ai_edge_litert.interpreter import Interpreter
interpreter = 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()