Neste guia, apresentamos o processo de execução de um modelo LiteRT (abreviação de Lite Runtime) no dispositivo para fazer previsões com base nos dados de entrada. Isso é conseguido com o interpretador LiteRT, que usa uma ordenação de gráficos estáticos e um alocador de memória personalizado (menos dinâmico) para garantir latência mínima de carga, inicialização e execução.
A inferência do LiteRT geralmente segue estas etapas:
Carregar um modelo: carregue o modelo
.tflitena memória, que contém o gráfico de execução do modelo.Transformar dados: transforme os dados de entrada no formato e nas dimensões esperados. Os dados de entrada brutos para o modelo geralmente não correspondem ao formato de dados de entrada esperado pelo modelo. Por exemplo, talvez seja necessário redimensionar uma imagem ou mudar o formato dela para que seja compatível com o modelo.
Executar inferência: execute o modelo LiteRT para fazer previsões. Esta etapa envolve o uso da API LiteRT para executar o modelo. Ele envolve algumas etapas, como a criação do intérprete e a alocação de tensores.
Interpretação da saída: interprete os tensores de saída de uma maneira significativa que seja útil no seu aplicativo. Por exemplo, um modelo pode retornar apenas uma lista de probabilidades. É sua responsabilidade mapear as probabilidades para categorias relevantes e formatar a saída.
Este guia descreve como acessar o intérprete do LiteRT e realizar uma inferência usando C++, Java e Python.
Plataformas compatíveis
As APIs de inferência do TensorFlow são fornecidas para as plataformas móveis e incorporadas mais comuns, como Android, iOS e Linux, em várias linguagens de programação.
Na maioria dos casos, o design da API reflete uma preferência por desempenho em vez de facilidade de uso. O LiteRT foi projetado para inferência rápida em dispositivos pequenos. Por isso, as APIs evitam cópias desnecessárias à custa da conveniência.
Em todas as bibliotecas, a API LiteRT permite carregar modelos, inserir entradas e recuperar saídas de inferência.
Plataforma Android
No Android, a inferência do LiteRT pode ser realizada usando APIs Java ou C++. As APIs Java oferecem conveniência e podem ser usadas diretamente nas classes Activity do Android. As APIs C++ oferecem mais flexibilidade e velocidade, mas podem exigir a gravação de wrappers JNI para mover dados entre as camadas Java e C++.
Consulte as seções C++ e Java para mais informações ou siga o guia de início rápido do Android.
Plataforma iOS
No iOS, o LiteRT está disponível nas bibliotecas Swift e Objective-C do iOS. Você também pode usar a API C diretamente no código Objective-C.
Consulte as seções Swift, Objective-C e API C ou siga o guia de início rápido do iOS.
Plataforma Linux
Em plataformas Linux, é possível executar inferências usando as APIs LiteRT disponíveis em C++.
Carregar e executar um modelo
Carregar e executar um modelo do LiteRT envolve as seguintes etapas:
- Carregar o modelo na memória.
- Criar um
Interpretercom base em um modelo atual. - Definir valores de tensor de entrada.
- Invocando inferências.
- Gerar valores de tensor.
Android (Java)
A API Java para executar inferências com o LiteRT foi projetada principalmente para uso
com o Android. Por isso, ela está disponível como uma dependência de biblioteca do Android:
com.google.ai.edge.litert.
Em Java, você vai usar a classe Interpreter para carregar um modelo e gerar inferências. Em muitos casos, essa pode ser a única API de que você precisa.
É possível inicializar um Interpreter usando um arquivo FlatBuffers (.tflite):
public Interpreter(@NotNull File modelFile);
Ou com um MappedByteBuffer:
public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);
Em ambos os casos, você precisa fornecer um modelo LiteRT válido ou a API vai gerar
IllegalArgumentException. Se você usar MappedByteBuffer para inicializar um
Interpreter, ele vai precisar permanecer inalterado durante todo o ciclo de vida do
Interpreter.
A maneira preferencial de executar inferência em um modelo é usar assinaturas. Disponível para modelos convertidos a partir do 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");
}
O método runSignature usa três argumentos:
Entradas : mapa para entradas do nome de entrada na assinatura para um objeto de entrada.
Saídas : mapa para mapeamento de saída do nome de saída na assinatura para dados de saída.
Nome da assinatura (opcional): nome da assinatura. Pode ficar em branco se o modelo tiver uma única assinatura.
Outra maneira de executar inferências quando o modelo não tem assinaturas definidas.
Basta chamar Interpreter.run(). Exemplo:
try (Interpreter interpreter = new Interpreter(file_of_a_tensorflowlite_model)) {
interpreter.run(input, output);
}
O método run() recebe apenas uma entrada e retorna apenas uma saída. Portanto, se o modelo tiver várias entradas ou saídas, use:
interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);
Nesse caso, cada entrada em inputs corresponde a um tensor de entrada, e map_of_indices_to_outputs mapeia os índices dos tensores de saída para os dados de saída correspondentes.
Em ambos os casos, os índices de tensor precisam corresponder aos valores atribuídos ao LiteRT Converter ao criar o modelo. A ordem dos tensores em input precisa corresponder à ordem dada ao LiteRT Converter.
A classe Interpreter também oferece funções convenientes para você receber o índice de qualquer entrada ou saída do modelo usando um nome de operação:
public int getInputIndex(String opName);
public int getOutputIndex(String opName);
Se opName não for uma operação válida no modelo, uma
IllegalArgumentException será gerada.
Além disso, lembre-se de que Interpreter é proprietário de recursos. Para evitar vazamento de memória, os recursos precisam ser liberados após o uso por:
interpreter.close();
Para um exemplo de projeto com Java, consulte o app de exemplo de detecção de objetos do Android.
Tipos de dados compatíveis
Para usar o LiteRT, os tipos de dados dos tensores de entrada e saída precisam ser um dos seguintes tipos primitivos:
floatintlongbyte
Os tipos String também são aceitos, mas são codificados de maneira diferente dos tipos primitivos. Em particular, o formato de um tensor de string determina o número e a organização das strings no tensor, sendo cada elemento uma string de comprimento variável. Nesse sentido, o tamanho (em bytes) do tensor não pode ser calculado apenas com base na forma e no tipo. Consequentemente, as strings não podem ser fornecidas como um único argumento ByteBuffer simples.
Se outros tipos de dados, incluindo tipos de caixa como Integer e Float, forem usados, uma IllegalArgumentException será gerada.
Entradas
Cada entrada precisa ser uma matriz ou uma matriz multidimensional dos tipos primitivos compatíveis ou um ByteBuffer bruto do tamanho adequado. Se a entrada for uma matriz ou uma matriz multidimensional, o tensor de entrada associado será redimensionado implicitamente para as dimensões da matriz no momento da inferência. Se a entrada for um ByteBuffer, o caller precisará primeiro redimensionar manualmente o tensor de entrada associado (via Interpreter.resizeInput()) antes de executar a inferência.
Ao usar ByteBuffer, prefira buffers de bytes diretos, porque isso permite que o
Interpreter evite cópias desnecessárias. Se o ByteBuffer for um buffer de bytes direto, a ordem precisa ser ByteOrder.nativeOrder(). Depois de ser usado para uma inferência de modelo, ele precisa permanecer inalterado até que a inferência seja concluída.
Saídas
Cada saída precisa ser uma matriz ou uma matriz multidimensional dos tipos primitivos compatíveis ou um ByteBuffer do tamanho adequado. Alguns modelos têm saídas dinâmicas, em que o formato dos tensores de saída pode variar dependendo da entrada. Não há uma maneira simples de lidar com isso usando a API de inferência Java atual, mas as extensões planejadas vão tornar isso possível.
iOS (Swift)
A API
Swift
está disponível no pod TensorFlowLiteSwift do Cocoapods.
Primeiro, importe o módulo 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)
A API Objective-C está disponível no pod LiteRTObjC do Cocoapods.
Primeiro, importe o módulo 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... */ }
API C em código Objective-C
A API Objective-C não é compatível com delegados. Para usar delegados com código Objective-C, é necessário chamar diretamente a API C subjacente.
#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++
A API C++ para executar inferências com o LiteRT é compatível com as plataformas Android, iOS e Linux. A API C++ no iOS só está disponível ao usar o bazel.
Em C++, o modelo é armazenado na classe
FlatBufferModel.
Ele encapsula um modelo LiteRT e pode ser criado de algumas maneiras diferentes, dependendo de onde o modelo está armazenado:
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);
};
Agora que você tem o modelo como um objeto FlatBufferModel, é possível executá-lo com um Interpreter.
Um único FlatBufferModel pode ser usado simultaneamente por mais de um Interpreter.
As partes importantes da API Interpreter são mostradas no snippet de código abaixo. É importante lembrar que:
- Os tensores são representados por números inteiros para evitar comparações de strings (e qualquer dependência fixa de bibliotecas de strings).
- Um intérprete não pode ser acessado de linhas de execução simultâneas.
- A alocação de memória para tensores de entrada e saída precisa ser acionada chamando
AllocateTensors()logo após redimensionar os tensores.
O uso mais simples do LiteRT com C++ é assim:
// 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);
Para mais exemplos de código, consulte
minimal.cc
e
label_image.cc.
Python
A API Python para executar inferências usa o
Interpreter para carregar um modelo e
executar inferências.
Instale o pacote LiteRT:
$ python3 -m pip install ai-edge-litert
Importar o interpretador do LiteRT
from ai_edge_litert.interpreter import Interpreter
Interpreter = Interpreter(model_path=args.model.file)
O exemplo a seguir mostra como usar o interpretador Python para carregar um arquivo FlatBuffers (.tflite) e executar a inferência com dados de entrada aleatórios:
Esse exemplo é recomendado se você estiver convertendo de um SavedModel com um SignatureDef definido.
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'])
Outro exemplo se o modelo não tiver SignatureDefs definido.
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)
Como alternativa ao carregamento do modelo como um arquivo .tflite pré-convertido, você pode combinar seu código com o compilador LiteRT, permitindo converter seu modelo do Keras para o formato LiteRT e executar a inferência:
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...
Para mais exemplos de código em Python, consulte
label_image.py.
Executar inferência com um modelo de forma dinâmica
Se você quiser executar um modelo com forma de entrada dinâmica, redimensione a forma de entrada
antes de executar a inferência. Caso contrário, a forma None nos modelos do TensorFlow será substituída por um marcador de posição 1 nos modelos do LiteRT.
Os exemplos a seguir mostram como redimensionar o formato de entrada antes de executar a inferência em diferentes linguagens. Todos os exemplos pressupõem que o formato de entrada seja definido como [1/None, 10] e precise ser redimensionado para [3, 10].
Exemplo em C++:
// Resize input tensors before allocate tensors
interpreter->ResizeInputTensor(/*tensor_index=*/0, std::vector<int>{3,10});
interpreter->AllocateTensors();
Exemplo em 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()