Este guia apresenta o processo de execução de um modelo LiteRT no dispositivo para fazer previsões com base nos dados de entrada. Isso é conseguido com a biblioteca LiteRT de codificador-decodificador, que usa uma ordenação de gráfico estática e um padrão (menos dinâmico) alocador de memória para garantir latência mínima de carga, inicialização e execução.
A inferência do LiteRT normalmente segue estas etapas:
Carregar um modelo: carregue o modelo
.tflite
na memória, que contém o gráfico de execução do modelo.Transformação de dados: transformar os dados de entrada no formato esperado e dimensões. Os dados brutos de entrada do modelo geralmente não correspondem aos dados de entrada formato de dados esperado pelo modelo. Por exemplo, talvez seja necessário redimensionar imagem ou alterar o formato da imagem para ser compatível com o modelo.
Executar inferência: execute o modelo LiteRT para fazer previsões. Isso essa etapa envolve o uso da API LiteRT para executar o modelo. Ele envolve alguns em etapas, como criação do intérprete e alocação de tensores.
Interpretar a saída: interprete os tensores de saída de maneira significativa. úteis para seu aplicativo. Por exemplo, um modelo pode retornar somente lista de probabilidades. Cabe a você mapear as probabilidades de acordo e formatar a saída.
Este guia descreve como acessar o intérprete da 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 os dispositivos móveis e incorporados plataformas como Android, iOS e Linux, no várias linguagens de programação.
Na maioria dos casos, o design da API reflete uma preferência pelo desempenho em vez da facilidade de usar. A LiteRT foi projetada para inferência rápida em dispositivos pequenos para que as APIs evitem cópias desnecessárias em detrimento da conveniência.
Em todas as bibliotecas, a API LiteRT permite carregar modelos, entradas de feed e e recuperar saídas de inferência.
Plataforma Android
No Android, a inferência do LiteRT pode ser realizada usando as APIs Java ou C++. A As APIs Java proporcionam praticidade e podem ser usadas diretamente no ambiente Classes de atividades. As APIs C++ oferecem mais flexibilidade e velocidade, mas podem exigir escrever 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 em Swift e Objective-C Bibliotecas do iOS. Também é possível usar C API diretamente no código Objective-C.
Consulte as APIs Swift, Objective-C e C ou siga o Guia de início rápido do iOS.
Plataforma Linux
Em plataformas Linux, você pode executar inferências usando as APIs LiteRT disponíveis em C++
Carregar e executar um modelo
Carregar e executar um modelo LiteRT envolve as seguintes etapas:
- Carregar o modelo na memória.
- Criação de um
Interpreter
com base em um modelo atual. - Configurar valores do tensor de entrada.
- Invocar inferências.
- Emitindo valores do tensor.
Android (Java)
A API Java para executar inferências com o LiteRT foi projetada principalmente para uso
com Android, por isso 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 orientar
a inferência. Em muitos casos, essa pode ser a única API de que você precisa.
Você pode inicializar um Interpreter
usando um arquivo FlatBuffers (.tflite
):
public Interpreter(@NotNull File modelFile);
Ou com uma MappedByteBuffer
:
public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);
Em ambos os casos, é preciso fornecer um modelo LiteRT válido ou a API gera
IllegalArgumentException
: Se você usar MappedByteBuffer
para inicializar um
Interpreter
, ela precisa permanecer inalterada durante todo o ciclo de vida do
Interpreter
.
A maneira preferencial de executar a 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 : mapeie as entradas do nome da entrada na assinatura para uma entrada. objeto.
Saídas : mapa para o mapeamento da saída do nome da saída na assinatura para a saída. dados.
Signature Name (opcional): nome da assinatura (pode ser deixado em branco se o modelo tem 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()
usa apenas uma entrada e retorna apenas uma saída. Portanto, se
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 índices de tensores de saída para o
dados de saída.
Em ambos os casos, os índices de tensores devem corresponder aos valores que você forneceu para
o LiteRT Converter (em inglês) quando você criou o modelo. Atenção
que a ordem dos tensores em input
precisa corresponder à ordem fornecida ao LiteRT
Conversor.
A classe Interpreter
também oferece funções convenientes para você receber
de qualquer entrada ou saída de 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
.
Além disso, saiba que o Interpreter
é proprietário de recursos. Para evitar vazamento de memória,
os recursos precisam ser liberados após o uso por:
interpreter.close();
Para ver um exemplo de projeto com Java, consulte o Exemplo de detecção de objetos do Android app.
Tipos de dados com suporte
Para usar o LiteRT, os tipos de dados dos tensores de entrada e saída precisam ser um dos seguintes tipos primitivos:
float
int
long
byte
Os tipos String
também são suportados, mas são codificados de maneira diferente da
tipos primitivos. Em particular, o formato de um tensor de string determina o número
e o arranjo de strings no tensor, com cada elemento sendo um
string de comprimento variável. Nesse sentido, o tamanho (byte) do tensor não pode ser
computados apenas com base na forma e no tipo. Consequentemente, as strings não podem ser
fornecido como um único argumento ByteBuffer
simples.
Se outros tipos de dados forem usados, incluindo tipos de caixa, como Integer
e Float
,
uma IllegalArgumentException
será gerada.
Entradas
Cada entrada deve ser uma matriz ou uma matriz multidimensional das
tipos primitivos ou um ByteBuffer
bruto do tamanho apropriado. Se a entrada for
uma matriz ou uma matriz multidimensional, o tensor de entrada associado será
redimensionadas implicitamente para as dimensões da matriz no momento da inferência. Se a entrada for
um ByteBuffer, o autor da chamada precisa primeiro redimensionar manualmente a entrada associada
(via Interpreter.resizeInput()
) antes de executar a inferência.
Ao usar ByteBuffer
, prefira usar buffers de byte diretos, já que isso permite que o
Interpreter
para evitar cópias desnecessárias. Se o ByteBuffer
for um byte direto
, a ordem precisa ser ByteOrder.nativeOrder()
. Depois de ser usado por um
a inferência de modelo, ela precisa permanecer inalterada até que a inferência do modelo seja concluída.
Saídas
Cada saída deve ser uma matriz ou uma matriz multidimensional das tipos primitivos ou um ByteBuffer do tamanho apropriado. Alguns modelos têm saídas dinâmicas, em que o formato dos tensores de saída pode variar dependendo a entrada. Não há uma maneira direta de lidar com isso com os modelos API de inferência Java, mas as extensões planejadas tornarão isso possível.
iOS (Swift)
O banco de dados Swift
API
está disponível no pod TensorFlowLiteSwift
da Cocoapods.
Primeiro, você precisa importar 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 documentação do Objective-C
API
está disponível no pod LiteRTObjC
da Cocoapods.
Primeiro, você precisa importar 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 no código Objective-C
A API Objective-C não suporta delegados. Para usar delegados com código Objective-C, você precisa chamar diretamente o C subjacente 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++
A API C++ para executar inferência com o LiteRT é compatível com Android, iOS, e nas plataformas Linux. A API C++ no iOS só está disponível com o uso do Bazel.
Em C++, o modelo é armazenado em
classe FlatBufferModel
.
Ela encapsula um modelo LiteRT que pode ser construído em 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
, ele pode ser executado
com um
Interpreter
.
Uma única FlatBufferModel
pode ser usada simultaneamente por mais de uma
Interpreter
.
As partes importantes da API Interpreter
são mostradas no snippet de código
a seguir. É importante observar que:
- Os tensores são representados por números inteiros para evitar comparações de strings (e qualquer dependência fixa em bibliotecas de strings).
- Um intérprete não pode ser acessado em 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 depois de redimensionar os tensores.
O uso mais simples da LiteRT com C++ é semelhante ao seguinte:
// 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 conferir 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
e executar inferências.
Instale o pacote LiteRT:
$ python3 -m pip install ai-edge-litert
Importar o intérprete 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 de Python para carregar uma
FlatBuffers (.tflite
) e executar inferência com dados de entrada aleatórios:
Este exemplo é recomendado se você estiver convertendo do SavedModel com um 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'])
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ê
você pode combinar seu código com a LiteRT
Compilador
, o que permite converter o modelo Keras no formato LiteRT e executar
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 o modelo de forma dinâmico
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ído por um marcador de posição de 1
nos modelos LiteRT.
Os exemplos a seguir mostram como redimensionar a forma de entrada antes da execução
e inferência em linguagens diferentes. Todos os exemplos presumem que a forma de entrada
é definido como [1/None, 10]
e precisa ser redimensionado para [3, 10]
.
Exemplo de C++:
// Resize input tensors before allocate tensors
interpreter->ResizeInputTensor(/*tensor_index=*/0, std::vector<int>{3,10});
interpreter->AllocateTensors();
Exemplo de 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()