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 都能讓您載入模型、饋送輸入並擷取推論輸出。

Android 平台

在 Android 上,可以使用 Java 或 C++ API 執行 TensorFlow Lite 推論。Java API 提供便利性,可以直接在您的 Android Activity 類別中使用。C++ API 提供更高的彈性和速度,但可能需要編寫 JNI 包裝函式,才能在 Java 和 C++ 層之間移動資料。

如需使用 C++Java 的詳細資訊,請參閱下文,或依照 Android 快速入門指南取得教學課程和範例程式碼。

TensorFlow Lite Android 包裝函式程式碼產生器

對於以metadata 增強的 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

Swift API 可在 Cocoapods 的 TensorFlowLiteSwift Pod 中取得。

首先,您需要匯入 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

Objective-C API 可在 Cocoapods 的 TensorFlowLiteObjC Pod 中取得。

首先,您需要匯入 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 模組中提供。從中,您主要只需要 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'])

如果模型未定義 SignatureDef,則為另一個範例。

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 (tf.lite.TFLiteConverter) 結合使用,讓您可以將 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

使用動態形狀模型執行推論

如果您想使用動態輸入形狀執行模型,請在執行推論之前*調整輸入形狀大小*。否則,Tensorflow 模型中的 None 形狀將被 TFLite 模型中的預留位置 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()