TensorFlow Lite 推断

“推断”一词是指在设备上执行 TensorFlow Lite 模型以根据输入数据进行预测的过程。如需使用 TensorFlow Lite 模型执行推断,您必须通过解释器运行此模型。TensorFlow Lite 解释器旨在实现精简而快速。 该解释器使用静态图排序和自定义(非动态)内存分配器来确保将负载、初始化和执行延迟时间降到最低。

本页介绍了如何访问 TensorFlow Lite 解释器并使用 C++、Java 和 Python 执行推断,还提供了指向每个受支持平台的其他资源的链接。

重要概念

TensorFlow Lite 推断通常遵循以下步骤:

  1. 加载模型

    您必须将 .tflite 模型加载到包含该模型的执行图的内存中。

  2. 转换数据

    模型的原始输入数据通常与模型预期的输入数据格式不匹配。例如,您可能需要调整图片大小或更改图片格式,以便与模型兼容。

  3. 运行推理

    此步骤涉及使用 TensorFlow Lite API 执行模型。它涉及几个步骤,例如构建解释器和分配张量,如以下部分所述。

  4. 解读输出

    收到模型推断的结果时,您必须以对您的应用有意义的方式解释张量。

    例如,模型可能仅返回概率列表。您要自行将概率映射到相关类别,并将其呈现给最终用户。

支持的平台

TensorFlow 推理 API 以多种编程语言面向大多数常见的移动/嵌入式平台(例如 AndroidiOSLinux)提供。

在大多数情况下,API 设计反映出其对性能的偏爱而非易用性。TensorFlow Lite 专为在小型设备上进行快速推理而设计,因此 API 为方便起见会试图避免不必要的副本也不足为奇。同样,与 TensorFlow API 保持一致不是一个明确的目标,语言之间也会存在一些差异。

借助 TensorFlow Lite API,您可以通过所有库加载模型、Feed 输入并检索推理输出。

Android 平台

在 Android 上,可以使用 Java API 或 C++ API 执行 TensorFlow Lite 推断。Java API 提供了便利,可直接在 Android Activity 类中使用。C++ API 可提供更高的灵活性和速度,但可能需要编写 JNI 封装容器才能在 Java 层和 C++ 层之间移动数据。

如需详细了解如何使用 C++Java,请参阅下文;要获取教程和示例代码,请参阅 Android 快速入门

TensorFlow Lite Android 封装容器代码生成器

对于使用元数据增强的 TensorFlow Lite 模型,开发者可以使用 TensorFlow Lite Android 封装容器代码生成器创建平台专用封装容器代码。使用封装容器代码时,无需直接与 Android 上的 ByteBuffer 交互。开发者可以使用类型化对象(例如 BitmapRect)与 TensorFlow Lite 模型进行交互。如需了解详情,请参阅 TensorFlow Lite Android 封装容器代码生成器

iOS 平台

在 iOS 上,TensorFlow Lite 适用于以 SwiftObjective-C 编写的原生 iOS 库。您还可以直接在 Objective-C 代码中使用 C API

如需详细了解如何使用 SwiftObjective-CC API,请参阅 iOS 快速入门以获取教程和示例代码。

Linux 平台

在 Linux 平台(包括 Raspberry Pi)上,您可以使用 C++Python 中提供的 TensorFlow Lite API 运行推断,如以下部分所示。

运行模型

运行 TensorFlow Lite 模型只需几个简单的步骤:

  1. 将模型加载到内存中。
  2. 根据现有模型构建 Interpreter
  3. 设置输入张量值。(如果不需要预定义大小,可以选择调整输入张量的大小。)
  4. 调用推断。
  5. 读取输出张量值。

下面几部分介绍了如何针对每种语言完成这些步骤。

使用 Java 加载和运行模型

平台:Android

用于通过 TensorFlow Lite 运行推断的 Java API 主要为与 Android 配合使用,因此以 Android 库依赖项的形式提供:org.tensorflow:tensorflow-lite

在 Java 中,您将使用 Interpreter 类加载模型并驱动模型推断。在许多情况下,这可能是您需要的唯一 API。

您可以使用 .tflite 文件初始化 Interpreter

public Interpreter(@NotNull File modelFile);

或者,如果您使用的是 MappedByteBuffer

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

在这两种情况下,您都必须提供有效的 TensorFlow Lite 模型,否则 API 会抛出 IllegalArgumentException。如果您使用 MappedByteBuffer 初始化 Interpreter,则该属性在 Interpreter 的整个生命周期内必须保持不变。

在模型上运行推断的首选方法是使用签名 - 适用于从 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");
}

runSignature 方法采用三个参数:

  • 输入:将输入从签名中的输入名称映射到输入对象。

  • 输出:用于从签名中的输出名称到输出数据的输出映射。

  • 签名名称(选填):签名名称(如果模型只有一个签名,则可以留空)。

在模型未定义签名时运行推断的另一种方式。只需调用 Interpreter.run() 即可。例如:

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

run() 方法仅接受一个输入,并且仅返回一个输出。因此,如果您的模型具有多个输入或输出,请改用:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

在本例中,inputs 中的每个条目对应一个输入张量,map_of_indices_to_outputs 会将输出张量的索引映射到相应的输出数据。

在这两种情况下,张量索引都应与您在创建模型时为 TensorFlow Lite Converter 提供的值相对应。请注意,input 中的张量顺序必须与提供给 TensorFlow Lite Converter 的顺序一致。

Interpreter 类还提供了便捷的函数,供您使用操作名称获取任何模型输入或输出的索引:

public int getInputIndex(String opName);
public int getOutputIndex(String opName);

如果 opName 不是模型中的有效操作,则会抛出 IllegalArgumentException

另请注意,Interpreter 拥有资源。为避免内存泄漏,必须在使用后通过以下方式释放资源:

interpreter.close();

如需查看使用 Java 的示例项目,请参阅 Android 图片分类示例

支持的数据类型(在 Java 中)

如需使用 TensorFlow Lite,输入和输出张量的数据类型必须是以下基元类型之一:

  • float
  • int
  • long
  • byte

String 类型也受支持,但其编码与基元类型不同。特别是,字符串张量的形状决定了张量中字符串的数量和排列方式,其中每个元素本身都是一个可变长度的字符串。从这个意义上讲,无法仅根据形状和类型计算张量的(字节)大小,因此字符串不能作为单个扁平的 ByteBuffer 参数提供。

如果使用了其他数据类型(包括 IntegerFloat 等盒装类型),系统会抛出 IllegalArgumentException

输入

每个输入应该是受支持基元类型的数组或多维数组,或者是适当大小的原始 ByteBuffer。如果输入是数组或多维数组,则关联的输入张量会在推理时隐式调整为数组的维度。如果输入是 ByteBuffer,调用方应首先手动调整关联输入张量的大小(通过 Interpreter.resizeInput()),然后再运行推理。

使用 ByteBuffer 时,请优先使用直接字节缓冲区,因为这样可让 Interpreter 避免不必要的复制。如果 ByteBuffer 是直接字节缓冲区,则其顺序必须为 ByteOrder.nativeOrder()。将该模型用于模型推理后,在模型推理完成之前必须保持不变。

输出

每个输出都应是受支持基元类型的数组或多维数组,或者大小适当的 ByteBuffer。请注意,某些模型具有动态输出,其中输出张量的形状可能因输入而异。使用现有的 Java 推断 API 没有直接处理此问题的方法,但计划的扩展可以让这成为可能。

在 Swift 中加载并运行模型

平台:iOS

Cocoapods 中的 TensorFlowLiteSwift Pod 提供了 Swift API

首先,您需要导入 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...
}

在 Objective-C 中加载和运行模型

平台:iOS

Cocoapods 中的 TensorFlowLiteObjC Pod 提供了 Objective-C API

首先,您需要导入 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... */ }

在 Objective-C 代码中使用 C API

目前,Objective-C API 不支持委托。为了在 Objective-C 代码中使用委托,您需要直接调用底层 C 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++ 模型

平台:Android、iOS 和 Linux

在 C++ 中,模型存储在 FlatBufferModel 类中。它封装了 TensorFlow Lite 模型,您可以通过几种不同的方式构建该模型,具体取决于模型的存储位置:

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);
};

现在,您已将模型作为 FlatBufferModel 对象,可以使用 Interpreter 执行该模型。一个 FlatBufferModel 可以由多个 Interpreter 同时使用。

以下代码段显示了 Interpreter API 的重要部分。需要注意的是:

  • 张量用整数表示,以避免字符串比较(以及对字符串库的任何固定依赖)。
  • 不得从并发线程访问解释器。
  • 必须在调整张量大小后立即调用 AllocateTensors() 来触发输入和输出张量的内存分配。

TensorFlow Lite 与 C++ 的最简单用法如下所示:

// 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);

如需查看更多示例代码,请参阅 minimal.cclabel_image.cc

在 Python 中加载和运行模型

平台:Linux

用于运行推理的 Python API 大多只需要 tf.lite.Interpreter 来加载模型并运行推理。

以下示例展示了如何使用 Python 解释器加载 .tflite 文件并使用随机输入数据进行推理:

如果您要从具有已定义的 SignatureDef 的 SavedModel 进行转换,则推荐使用此示例。从 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'])

另一个例子是模型未定义 SignatureDefs。

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)

除了将模型加载为预转换的 .tflite 文件外,您还可以将代码与 TensorFlow Lite Converter Python API 结合使用,以便将 Keras 模型转换为 TensorFlow Lite 格式,然后进行推理:

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

如需查看更多 Python 示例代码,请参阅 label_image.py

使用动态形状模型进行推断

如果要运行具有动态输入形状的模型,请在运行推理之前调整输入形状的大小。否则,在 TFLite 模型中,Tensorflow 模型中的 None 形状将被替换为 1 的占位符。

以下示例展示了如何在使用其他语言运行推断之前调整输入形状的大小。所有示例都假定输入形状定义为 [1/None, 10],并且需要调整为 [3, 10]

C++ 示例:

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

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()

支持的操作

TensorFlow Lite 支持部分 TensorFlow 操作,但存在一些限制。如需查看操作和限制的完整列表,请参阅 TF Lite 操作页面