Comece a usar o LiteRT

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:

  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 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.

  3. 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.

  4. 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:

  1. Carregar o modelo na memória.
  2. Criação de um Interpreter com base em um modelo atual.
  3. Configurar valores do tensor de entrada.
  4. Invocar inferências.
  5. 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()