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 包裝函式程式碼產生器

如果是以中繼資料強化 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 方法使用三個引數:

  • 「Inputs」:將簽名中的輸入名稱輸入內容對應至輸入物件。

  • 輸出:對簽名中的輸出名稱與輸出資料進行對應。

  • Signature Name (選填):簽章名稱 (如果模型有單一簽名,則可留空)。

在模型沒有已定義的簽名時,另一種執行推論的方式。只要呼叫 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 轉換工具的順序相符。

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 類型,但其編碼方式與原始類型不同。具體來說,字串的形狀決定了 Tensor 中的字串數量和排列方式,而每個元素本身都是變數長度字串。這表示無法單獨根據形狀和類型計算 Tensor 的 (位元組) 大小,因此也無法以單一固定 ByteBuffer 引數提供字串。

如果使用其他資料類型 (包括 IntegerFloat 等方塊類型),系統會擲回 IllegalArgumentException

輸入

每個輸入內容都應為支援原始類型的陣列或多維陣列,或是適當大小的原始 ByteBuffer。如果輸入內容是陣列或多維度陣列,相關聯的輸入張量會在推論期間間接調整為陣列的尺寸。如果輸入內容為 ByteBuffer,則呼叫端必須先透過 Interpreter.resizeInput() 手動調整關聯輸入張量,然後再執行推論。

使用 ByteBuffer 時,建議使用直接位元組緩衝區,因為這可讓 Interpreter 避免不必要的複製。如果 ByteBuffer 是直接位元組緩衝區,則順序必須為 ByteOrder.nativeOrder()。將模型用於模型推論後,在模型推論完成前,這個值必須保持不變。

輸出

每個輸出都應為支援基本類型的陣列或多維陣列,或是適當大小的 ByteBuffer。請注意,部分模型具有動態輸出,其中輸出張量的形狀可能因輸入而異。使用現有的 Java Inference 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.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

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

如果您想執行含有動態輸入形狀的模型,請在執行推論之前調整輸入形狀大小。否則,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()

支援作業

TensorFlow Lite 支援一部分的 TensorFlow 運算,但有一些限制。如需作業和限制的完整清單,請參閱 TF Lite 作業頁面