Esta página foi traduzida pela API Cloud Translation.
Switch to English

Inferência do TensorFlow Lite

O termo inferência se refere 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 TensorFlow Lite, você deve executá-lo por meio de um interpretador . 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 o mínimo de carga, inicialização e latência de execução.

Esta página descreve como acessar o interpretador 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 de entrada brutos para o modelo geralmente não correspondem ao formato de 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. Inferência de execução

    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. Produção de interpretação

    Ao receber os resultados da inferência do modelo, você deve interpretar os tensores de uma forma significativa que seja útil em sua aplicação.

    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 várias linguagens de programação.

Na maioria dos casos, o design da API reflete uma preferência por desempenho em relação à 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 tentam evitar cópias desnecessárias em detrimento da conveniência. Da mesma forma, a consistência com as APIs do TensorFlow não era uma meta explícita e algumas variações entre as linguagens são esperadas.

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

Operações com suporte

O TensorFlow Lite é compatível com um subconjunto de operações do TensorFlow com algumas limitações. Para uma lista completa de operações e limitações, consulte a página TF Lite Ops .

Android

No Android, a inferência do TensorFlow Lite pode ser realizada usando APIs Java ou C ++. As APIs Java fornecem conveniência e podem ser usadas diretamente em suas classes de atividade 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 os detalhes sobre como usar C ++ e Java ou siga o guia de início rápido do Android para ver um tutorial e um exemplo de código.

Gerador de código de wrapper TensorFlow Lite Android

Para o modelo TensorFlow Lite aprimorado com metadados , os desenvolvedores podem usar o gerador de código do wrapper TensorFlow Lite Android para criar o código do wrapper específico da plataforma. O código do 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 de wrapper do TensorFlow Lite Android .

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 os detalhes sobre como usar as APIs Swift, Objective-C e C, ou siga o guia de início rápido do iOS para um tutorial e código de exemplo.

Linux

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

Executando um modelo

Executar um modelo TensorFlow Lite envolve algumas etapas simples:

  1. Carregue o modelo na memória.
  2. Construa um Interpreter baseado em um modelo existente.
  3. Defina os valores do 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 realizadas em cada idioma.

Carregue e execute um modelo em Java

Plataforma: Android

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

Em Java, você usará a classe Interpreter para carregar um modelo e conduzir a inferência do modelo. Em muitos casos, essa pode ser a única API de que você precisa.

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ça IllegalArgumentException . Se você usar MappedByteBuffer para inicializar um Interpreter , ele deve permanecer inalterado por todo o tempo de vida do Interpreter .

Para então executar uma inferência com o modelo, basta chamar Interpreter.run() . Por exemplo:

try (Interpreter interpreter = new Interpreter(file_of_a_tensorflowlite_model)) {
  interpreter.run(input, output);
}

O método run() aceita apenas uma entrada e retorna apenas uma saída. Portanto, se seu modelo tem várias entradas ou saídas, use:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

Nesse caso, cada entrada nas 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 devem corresponder aos valores que você deu ao TensorFlow Lite Converter quando criou o modelo. Esteja ciente de que a ordem dos tensores na input deve corresponder à ordem fornecida 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 .

Também tome cuidado, pois o Interpreter possui recursos. Para evitar vazamento de memória, os recursos devem ser liberados após o uso por:

interpreter.close();

Para um projeto de exemplo com Java, consulte o exemplo de classificação de imagens 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

String tipos de String também são suportados, mas são codificados de maneira diferente dos tipos primitivos. Em particular, a forma de um tensor de corda dita o número e a disposição das cordas no tensor, com cada elemento sendo uma corda 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 único argumento ByteBuffer plano.

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 uma 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 deve 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 deve ser ByteOrder.nativeOrder() . Depois de ser usado para uma inferência de modelo, ele deve 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 dos tipos primitivos suportados ou um ByteBuffer do tamanho apropriado. Observe que alguns modelos têm 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 as extensões planejadas tornarão isso possível.

Carregue e execute um modelo em Swift

Plataforma: iOS

A API Swift está disponível em TensorFlowLiteSwift Pod de 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...
}

Carregue e execute um modelo em Objective-C

Plataforma: iOS

O API Objective-C é disponível em TensorFlowLiteObjC vagem de 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...

// Copy the input data to the input `TFLTensor`.
[interpreter copyData:inputData toInputTensorAtIndex:0 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:&amp;error];
if (error != nil) { /* Error handling... */ }

Usando C API em 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);

Carregue e execute 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 várias 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 do Interpreter são mostradas no trecho de código abaixo. Deve-se observar que:

  • Tensores são representados por inteiros, a fim de evitar comparações de strings (e qualquer dependência fixa em bibliotecas de strings).
  • Um intérprete não deve ser acessado de encadeamentos simultâneos.
  • A alocação de memória para tensores de entrada e saída deve ser disparada chamando AllocateTensors() logo após o redimensionamento dos 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 mais exemplo de código, consulte minimal.cc e label_image.cc .

Carregue e execute um modelo em Python

Plataforma: Linux

A API Python para executar uma inferência é fornecida no módulo tf.lite . A partir do qual, você só precisa principalmente de 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:

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 para carregar o modelo como um arquivo .tflite pré-convertido, você pode combinar seu código com a API TensorFlow Lite Converter Python ( tf.lite.TFLiteConverter ), permitindo converter seu modelo TensorFlow para o formato TensorFlow Lite e executar inferência:

import numpy as np
import tensorflow as tf

img = tf.placeholder(name="img", dtype=tf.float32, shape=(1, 64, 64, 3))
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
with tf.Session() as sess:
  converter = tf.lite.TFLiteConverter.from_session(sess, [img], [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 .

Dica: execute help(tf.lite.Interpreter) no terminal Python para obter documentação detalhada sobre o interpretador.

Escreva um operador personalizado

Todos os operadores do TensorFlow Lite (personalizados e integrados) são definidos usando uma interface C puro simples que consiste em quatro funções:

typedef struct {
  void* (*init)(TfLiteContext* context, const char* buffer, size_t length);
  void (*free)(TfLiteContext* context, void* buffer);
  TfLiteStatus (*prepare)(TfLiteContext* context, TfLiteNode* node);
  TfLiteStatus (*invoke)(TfLiteContext* context, TfLiteNode* node);
} TfLiteRegistration;

Consulte context.h para obter detalhes sobre TfLiteContext e TfLiteNode . O primeiro fornece recursos de relatório de erros e acesso a objetos globais, incluindo todos os tensores. O último permite que as implementações acessem suas entradas e saídas.

Quando o interpretador carrega um modelo, ele chama init() uma vez para cada nó no gráfico. Um determinado init() será chamado mais de uma vez se op for usado várias vezes no gráfico. Para operações customizadas, um buffer de configuração será fornecido, contendo um flexbuffer que mapeia nomes de parâmetros para seus valores. O buffer está vazio para operações integradas porque o interpretador já analisou os parâmetros de operação. As implementações do kernel que requerem estado devem inicializá-lo aqui e transferir a propriedade para o chamador. Para cada chamada init() , haverá uma chamada correspondente para free() , permitindo que as implementações eliminem o buffer que podem ter alocado em init() .

Sempre que os tensores de entrada são redimensionados, o interpretador irá percorrer o gráfico notificando as implementações sobre a mudança. Isso lhes dá a chance de redimensionar seu buffer interno, verificar a validade das formas e tipos de entrada e recalcular as formas de saída. Isso tudo é feito por meio de prepare() , e as implementações podem acessar seu estado usando node->user_data .

Finalmente, cada vez que a inferência é executada, o interpretador atravessa o gráfico chamando invoke() , e aqui também o estado está disponível como node->user_data .

As operações personalizadas podem ser implementadas exatamente da mesma maneira que as operações integradas, definindo essas quatro funções e uma função de registro global que geralmente se parece com isto:

namespace tflite {
namespace ops {
namespace custom {
  TfLiteRegistration* Register_MY_CUSTOM_OP() {
    static TfLiteRegistration r = {my_custom_op::Init,
                                   my_custom_op::Free,
                                   my_custom_op::Prepare,
                                   my_custom_op::Eval};
    return &r;
  }
}  // namespace custom
}  // namespace ops
}  // namespace tflite

Observe que o registro não é automático e uma chamada explícita para Register_MY_CUSTOM_OP deve ser feita em algum lugar. Enquanto o BuiltinOpResolver padrão (disponível no destino :builtin_ops ) cuida do registro dos builtins, as operações personalizadas terão que ser coletadas em bibliotecas personalizadas separadas.

Personalize a biblioteca do kernel

Nos bastidores, o interpretador carregará uma biblioteca de kernels que será atribuída para executar cada um dos operadores no modelo. Embora a biblioteca padrão contenha apenas kernels embutidos, é possível substituí-la por uma biblioteca personalizada.

O intérprete usa um OpResolver para traduzir códigos de operador e nomes em código real:

class OpResolver {
  virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
  virtual TfLiteRegistration* FindOp(const char* op) const = 0;
  virtual void AddOp(tflite::BuiltinOperator op, TfLiteRegistration* registration) = 0;
  virtual void AddOp(const char* op, TfLiteRegistration* registration) = 0;
};

O uso regular requer que você use o BuiltinOpResolver e escreva:

tflite::ops::builtin::BuiltinOpResolver resolver;

Você pode, opcionalmente, registrar operações personalizadas (antes de passar o resolvedor para o InterpreterBuilder ):

resolver.AddOp("MY_CUSTOM_OP", Register_MY_CUSTOM_OP());

Se o conjunto de ops embutidos for considerado muito grande, um novo OpResolver pode ser gerado por código com base em um determinado subconjunto de ops, possivelmente apenas aqueles contidos em um determinado modelo. Isso é o equivalente ao registro seletivo do TensorFlow (e uma versão simples dele está disponível no diretório de tools ).