Comece a usar o LiteRT

Este guia apresenta o processo de execução de um modelo do LiteRT (abreviação de Lite Runtime) no dispositivo para fazer previsões com base nos dados de entrada. Isso é alcançado com o intérprete LiteRT, que usa uma ordenação de gráfico estática e uma personalizado (menos dinâmico) para garantir carga, inicialização, e latência de execução.

A inferência do LiteRT geralmente segue estas etapas:

  1. Carregar um modelo: carregue o modelo .tflite na memória, que contém o gráfico de execução do modelo.

  2. 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 ao formato de dados de entrada esperado pelo modelo. Por exemplo, talvez seja necessário redimensionar imagem ou alterar o formato da imagem para ser compatível com o modelo.

  3. Execução de inferência: execute o modelo LiteRT para fazer previsões. Esta etapa envolve o uso da API LiteRT para executar o modelo. Isso envolve algumas etapas, como criar o interpretador e alocar tensores.

  4. Interpretação da saída: interprete os tensores de saída de uma maneira significativa que seja útil no seu app. 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 interpretador 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 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 por desempenho em vez de facilidade de uso. 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++. As APIs Java são convenientes e podem ser usadas diretamente nas classes de atividade do Android. 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 nas bibliotecas Swift e Objective-C 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, é possível executar inferências usando APIs LiteRT disponíveis em C++.

Carregar e executar um modelo

Carregar e executar um modelo LiteRT envolve as seguintes etapas:

  1. Carregar o modelo na memória.
  2. Criar um Interpreter com base em um modelo atual.
  3. Definir valores de tensor de entrada.
  4. Como invocar inferências.
  5. Geração de valores de 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 direcionar a inferência do modelo. Em muitos casos, essa pode ser a única API necessária.

É possível 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, ele precisará permanecer inalterado durante toda a vida útil 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.

  • 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() 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 os dados de saída correspondentes.

Em ambos os casos, os índices de tensor precisam corresponder aos valores fornecidos ao Conversor LiteRT ao criar o modelo. Atenção que a ordem dos tensores em input precisa corresponder à ordem informada 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, Interpreter é o proprietário dos recursos. Para evitar vazamentos de memória, os recursos precisam ser liberados após o uso:

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 calculado apenas pela forma e pelo tipo, e, consequentemente, as strings não podem ser fornecidas como um único argumento ByteBuffer plano.

Se outros tipos de dados, incluindo tipos encapsulados como Integer e Float, forem usados, 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 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 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 bytes diretos, já que isso permite que Interpreter evite cópias desnecessárias. Se o ByteBuffer for um byte 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 de 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 a forma dos tensores de saída pode variar dependendo da 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 API Objective-C está disponível no pod LiteRTObjC do 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 a inferência com o LiteRT é compatível com plataformas Android, iOS e 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 abaixo. É 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 após o redimensionamento dos 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 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 LiteRT Interpreter

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:

Esse exemplo é recomendado se você estiver convertendo de SavedModel com uma SignatureDef definida.

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 LiteRT Compiler, permitindo que você converta seu modelo Keras no formato LiteRT e execute 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 de 1 nos modelos do 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 é definida como [1/None, 10] e precisa ser redimensionada 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()