Inferência do TensorFlow Lite

O termo inferência refere-se ao processo de execução de um modelo do TensorFlow Lite no dispositivo para fazer previsões com base nos dados de entrada. Para realizar uma inferência com um modelo do TensorFlow Lite, você deve executá-lo por meio de um intérprete . O interpretador TensorFlow Lite foi projetado para ser simples e rápido. O interpretador usa uma ordenação de gráfico estático e um alocador de memória personalizado (menos dinâmico) para garantir carga, inicialização e latência de execução mínimas.

Esta página descreve como acessar o interpretador do TensorFlow Lite e realizar uma inferência usando C++, Java e Python, além de links para outros recursos para cada plataforma compatível .

Conceitos importantes

A inferência do TensorFlow Lite normalmente segue as seguintes etapas:

  1. Carregando um modelo

    Você deve carregar o modelo .tflite na memória, que contém o gráfico de execução do modelo.

  2. Transformando dados

    Os dados brutos de entrada do modelo geralmente não correspondem ao formato dos dados de entrada esperado pelo modelo. Por exemplo, pode ser necessário redimensionar uma imagem ou alterar o formato da imagem para ser compatível com o modelo.

  3. Executando inferência

    Esta etapa envolve o uso da API TensorFlow Lite para executar o modelo. Envolve algumas etapas, como construir o interpretador e alocar tensores, conforme descrito nas seções a seguir.

  4. Interpretando a saída

    Ao receber resultados da inferência do modelo, você deve interpretar os tensores de uma forma significativa que seja útil em seu aplicativo.

    Por exemplo, um modelo pode retornar apenas uma lista de probabilidades. Cabe a você mapear as probabilidades para categorias relevantes e apresentá-las ao usuário final.

Plataformas suportadas

As APIs de inferência do TensorFlow são fornecidas para as plataformas móveis/incorporadas mais comuns, como Android , iOS e Linux , em diversas linguagens de programação.

Na maioria dos casos, o design da API reflete uma preferência pelo desempenho em vez da facilidade de uso. O TensorFlow Lite foi projetado para inferência rápida em dispositivos pequenos, portanto não deve ser surpresa que as APIs tentem evitar cópias desnecessárias em detrimento da conveniência. Da mesma forma, a consistência com as APIs do TensorFlow não era um objetivo explícito e é esperada alguma variação entre as linguagens.

Em todas as bibliotecas, a API TensorFlow Lite permite carregar modelos, alimentar entradas e recuperar saídas de inferência.

Plataforma Android

No Android, a inferência do TensorFlow Lite pode ser realizada usando APIs Java ou C++. As APIs Java oferecem conveniência e podem ser usadas diretamente nas classes de atividades 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++.

Veja abaixo detalhes sobre como usar C++ e Java ou siga o início rápido do Android para obter um tutorial e um exemplo de código.

Gerador de código wrapper para Android TensorFlow Lite

Para o modelo TensorFlow Lite aprimorado com metadados , os desenvolvedores podem usar o gerador de código wrapper Android do TensorFlow Lite para criar código wrapper específico da plataforma. O código wrapper elimina a necessidade de interagir diretamente com ByteBuffer no Android. Em vez disso, os desenvolvedores podem interagir com o modelo TensorFlow Lite com objetos digitados como Bitmap e Rect . Para obter mais informações, consulte o gerador de código wrapper Android do TensorFlow Lite .

Plataforma iOS

No iOS, o TensorFlow Lite está disponível com bibliotecas nativas do iOS escritas em Swift e Objective-C . Você também pode usar a API C diretamente em códigos Objective-C.

Veja abaixo detalhes sobre como usar Swift , Objective-C e API C ou siga o início rápido do iOS para obter um tutorial e código de exemplo.

Plataforma Linux

Em plataformas Linux (incluindo Raspberry Pi ), ​​você pode executar inferências usando APIs do TensorFlow Lite disponíveis em C++ e Python , conforme mostrado nas seções a seguir.

Executando um modelo

A execução de um modelo do TensorFlow Lite envolve algumas etapas simples:

  1. Carregue o modelo na memória.
  2. Construa um Interpreter baseado em um modelo existente.
  3. Defina valores de tensor de entrada. (Opcionalmente, redimensione os tensores de entrada se os tamanhos predefinidos não forem desejados.)
  4. Invoque inferência.
  5. Leia os valores do tensor de saída.

As seções a seguir descrevem como essas etapas podem ser executadas em cada idioma.

Carregar e executar um modelo em Java

Plataforma: Android

A API Java para executar uma inferência com o TensorFlow Lite foi projetada principalmente para uso com Android, portanto, está disponível como uma dependência da biblioteca Android: org.tensorflow:tensorflow-lite .

Em Java, você usará a classe Interpreter para carregar um modelo e conduzir a inferência de modelo. Em muitos casos, esta pode ser a única API necessária.

Você pode inicializar um Interpreter usando um arquivo .tflite :

public Interpreter(@NotNull File modelFile);

Ou com um MappedByteBuffer :

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

Em ambos os casos, você deve fornecer um modelo válido do TensorFlow Lite ou a API lançará IllegalArgumentException . Se você usar MappedByteBuffer para inicializar um Interpreter , ele deverá permanecer inalterado durante toda a vida útil do Interpreter .

A maneira preferida 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 leva 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 até os dados de saída.

  • Nome da assinatura [opcional]: Nome da assinatura (pode ser deixado em branco se o modelo tiver assinatura única).

Outra forma de executar uma inferência quando o modelo não possui assinaturas definidas. Basta chamar Interpreter.run() . Por 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 seu modelo tiver múltiplas entradas ou múltiplas saídas, use:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

Neste caso, cada entrada nas 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 do tensor devem corresponder aos valores fornecidos ao TensorFlow Lite Converter ao criar o modelo. Esteja ciente de que a ordem dos tensores na input deve corresponder à ordem dada ao TensorFlow Lite Converter.

A classe Interpreter também fornece funções convenientes para você obter o índice 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, ele lançará uma IllegalArgumentException .

Tenha também em atenção que Interpreter possui recursos. Para evitar vazamento de memória, os recursos devem ser liberados após o uso:

interpreter.close();

Para ver um exemplo de projeto com Java, consulte o exemplo de classificação de imagens do Android .

Tipos de dados suportados (em Java)

Para usar o TensorFlow Lite, os tipos de dados dos tensores de entrada e saída devem ser um dos seguintes tipos primitivos:

  • float
  • int
  • long
  • byte

Os tipos String também são suportados, mas são codificados de forma diferente dos tipos primitivos. Em particular, a forma de um Tensor de string determina o número e a disposição das strings no Tensor, com cada elemento sendo uma string de comprimento variável. Nesse sentido, o tamanho (byte) do Tensor não pode ser calculado apenas a partir da forma e do tipo e, consequentemente, as strings não podem ser fornecidas como um argumento ByteBuffer único e plano. Você pode ver alguns exemplos nesta página .

Se outros tipos de dados, incluindo tipos em caixa como Integer e Float , forem usados, uma IllegalArgumentException será lançada.

Entradas

Cada entrada deve ser uma matriz ou matriz multidimensional dos tipos primitivos suportados 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 chamador deverá primeiro redimensionar manualmente o tensor de entrada associado (via Interpreter.resizeInput() ) antes de executar a inferência.

Ao usar ByteBuffer , prefira usar buffers de bytes diretos, pois isso permite que o Interpreter evite cópias desnecessárias. Se o ByteBuffer for um buffer de bytes direto, sua ordem deverá ser ByteOrder.nativeOrder() . Após ser usado para uma inferência de modelo, ele deve permanecer inalterado até que a inferência de modelo seja concluída.

Resultados

Cada saída deve ser uma matriz ou matriz multidimensional dos tipos primitivos suportados ou um ByteBuffer do tamanho apropriado. Observe que alguns modelos possuem saídas dinâmicas, onde a forma dos tensores de saída pode variar dependendo da entrada. Não há uma maneira direta de lidar com isso com a API de inferência Java existente, mas extensões planejadas tornarão isso possível.

Carregar e executar um modelo em Swift

Plataforma: iOS

A API Swift 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...
}

Carregar e executar um modelo em Objective-C

Plataforma: iOS

A API Objective-C está disponível no pod TensorFlowLiteObjC da Cocoapods.

Primeiro, você precisa importar o módulo TensorFlowLite .

@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... */ }

Usando API C no código Objective-C

Atualmente a API Objective-C não oferece suporte a delegados. Para usar delegados com código Objective-C, você precisa 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);

Carregar e executar um modelo em C++

Plataformas: Android, iOS e Linux

Em C++, o modelo é armazenado na classe FlatBufferModel . Ele encapsula um modelo do TensorFlow Lite e você pode construí-lo de duas 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 , pode 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 trecho de código abaixo. Deve-se notar 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 deve ser acessado a partir de threads simultâneos.
  • A alocação de memória para tensores de entrada e saída deve ser acionada chamando AllocateTensors() logo após redimensionar os tensores.

O uso mais simples do TensorFlow Lite 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 desired.
interpreter->AllocateTensors();

float* input = interpreter->typed_input_tensor<float>(0);
// Fill `input`.

interpreter->Invoke();

float* output = interpreter->typed_output_tensor<float>(0);

Para obter mais exemplos de código, consulte minimal.cc e label_image.cc .

Carregar e executar um modelo em Python

Plataforma: Linux

A API Python para executar uma inferência é fornecida no módulo tf.lite . A partir disso, você precisa principalmente apenas tf.lite.Interpreter para carregar um modelo e executar uma inferência.

O exemplo a seguir mostra como usar o interpretador Python para carregar um arquivo .tflite e executar inferência com dados de entrada aleatórios:

Este exemplo é recomendado se você estiver convertendo de SavedModel com um SignatureDef definido. Disponível a partir do TensorFlow 2.5

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 TFLite model in TFLite Interpreter
interpreter = tf.lite.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 TFLite model and allocate tensors.
interpreter = tf.lite.Interpreter(model_path="converted_model.tflite")
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 a API Python do TensorFlow Lite Converter ( tf.lite.TFLiteConverter ), permitindo converter seu modelo Keras no formato TensorFlow Lite e, em seguida, 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 TF Lite format
converter = tf.lite.TFLiteConverter.from_keras_model(tf.keras.models.Model(inputs=[img], outputs=[out]))
tflite_model = converter.convert()

# Load the TFLite model and allocate tensors.
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()

# Continue to get tensors and so forth, as shown above...

Para obter mais exemplos de código Python, consulte label_image.py .

Execute inferência com modelo de forma dinâmica

Se 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 Tensorflow será substituída por um espaço reservado de 1 nos modelos TFLite.

Os exemplos a seguir mostram como redimensionar a forma de entrada antes de executar a inferência em diferentes idiomas. Todos os exemplos assumem que a forma de entrada é definida como [1/None, 10] e precisa ser redimensionada para [3, 10] .

Exemplo C++:

// Resize input tensors before allocate tensors
interpreter->ResizeInputTensor(/*tensor_index=*/0, std::vector<int>{3,10});
interpreter->AllocateTensors();

Exemplo de Python:

# Load the TFLite model in TFLite Interpreter
interpreter = tf.lite.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()