Suy luận TensorFlow Lite

Thuật ngữ suy luận đề cập đến quá trình thực thi mô hình TensorFlow Lite trên thiết bị để đưa ra dự đoán dựa trên dữ liệu đầu vào. Để thực hiện suy luận với mô hình TensorFlow Lite, bạn phải chạy nó thông qua trình thông dịch . Trình thông dịch TensorFlow Lite được thiết kế gọn gàng và nhanh chóng. Trình thông dịch sử dụng thứ tự biểu đồ tĩnh và bộ cấp phát bộ nhớ tùy chỉnh (ít động hơn) để đảm bảo độ trễ tải, khởi tạo và thực thi ở mức tối thiểu.

Trang này mô tả cách truy cập trình thông dịch TensorFlow Lite và thực hiện suy luận bằng C++, Java và Python, cùng với các liên kết đến các tài nguyên khác cho từng nền tảng được hỗ trợ .

khái niệm quan trọng

Suy luận TensorFlow Lite thường tuân theo các bước sau:

  1. Đang tải một mô hình

    Bạn phải tải mô hình .tflite vào bộ nhớ chứa biểu đồ thực thi của mô hình.

  2. Chuyển đổi dữ liệu

    Dữ liệu đầu vào thô cho mô hình thường không khớp với định dạng dữ liệu đầu vào mà mô hình mong đợi. Ví dụ: bạn có thể cần thay đổi kích thước hình ảnh hoặc thay đổi định dạng hình ảnh để tương thích với mô hình.

  3. Chạy suy luận

    Bước này liên quan đến việc sử dụng API TensorFlow Lite để thực thi mô hình. Nó bao gồm một số bước như xây dựng trình thông dịch và phân bổ các tensor, như được mô tả trong các phần sau.

  4. Đầu ra phiên dịch

    Khi nhận được kết quả từ suy luận mô hình, bạn phải diễn giải các tensor theo cách có ý nghĩa và hữu ích trong ứng dụng của bạn.

    Ví dụ: một mô hình có thể chỉ trả về một danh sách các xác suất. Bạn có quyền sắp xếp các xác suất cho các danh mục có liên quan và trình bày nó cho người dùng cuối của mình.

Nền tảng được hỗ trợ

API suy luận TensorFlow được cung cấp cho hầu hết các nền tảng di động/nhúng phổ biến như Android , iOSLinux , bằng nhiều ngôn ngữ lập trình.

Trong hầu hết các trường hợp, thiết kế API phản ánh ưu tiên về hiệu suất hơn là tính dễ sử dụng. TensorFlow Lite được thiết kế để suy luận nhanh trên các thiết bị nhỏ, do đó không có gì ngạc nhiên khi các API cố gắng tránh các bản sao không cần thiết nhưng lại gây bất lợi cho sự tiện lợi. Tương tự, tính nhất quán với API TensorFlow không phải là mục tiêu rõ ràng và có thể sẽ có một số khác biệt giữa các ngôn ngữ.

Trên tất cả các thư viện, API TensorFlow Lite cho phép bạn tải mô hình, nguồn cấp dữ liệu đầu vào và truy xuất kết quả suy luận.

Nền tảng Android

Trên Android, suy luận TensorFlow Lite có thể được thực hiện bằng cách sử dụng API Java hoặc C++. API Java mang lại sự tiện lợi và có thể được sử dụng trực tiếp trong các lớp Hoạt động Android của bạn. API C++ cung cấp tính linh hoạt và tốc độ cao hơn nhưng có thể yêu cầu viết trình bao bọc JNI để di chuyển dữ liệu giữa các lớp Java và C++.

Xem bên dưới để biết chi tiết về cách sử dụng C++Java hoặc làm theo hướng dẫn bắt đầu nhanh của Android để biết hướng dẫn và mã ví dụ.

Trình tạo mã trình bao bọc Android TensorFlow Lite

Đối với mô hình TensorFlow Lite được cải tiến bằng siêu dữ liệu , nhà phát triển có thể sử dụng trình tạo mã trình bao bọc Android TensorFlow Lite để tạo mã trình bao bọc dành riêng cho nền tảng. Mã trình bao bọc loại bỏ nhu cầu tương tác trực tiếp với ByteBuffer trên Android. Thay vào đó, các nhà phát triển có thể tương tác với mô hình TensorFlow Lite với các đối tượng được nhập như BitmapRect . Để biết thêm thông tin, vui lòng tham khảo Trình tạo mã trình bao bọc Android TensorFlow Lite .

Nền tảng iOS

Trên iOS, TensorFlow Lite có sẵn các thư viện iOS gốc được viết bằng SwiftObjective-C . Bạn cũng có thể sử dụng API C trực tiếp trong mã Objective-C.

Xem bên dưới để biết chi tiết về cách sử dụng Swift , Objective-CAPI C hoặc làm theo hướng dẫn bắt đầu nhanh trên iOS để biết hướng dẫn và mã ví dụ.

Nền tảng Linux

Trên nền tảng Linux (bao gồm Raspberry Pi ), ​​bạn có thể chạy suy luận bằng API TensorFlow Lite có sẵn trong C++Python , như được hiển thị trong các phần sau.

Chạy mô hình

Chạy mô hình TensorFlow Lite bao gồm một số bước đơn giản:

  1. Tải mô hình vào bộ nhớ.
  2. Xây dựng Interpreter dựa trên mô hình hiện có.
  3. Đặt giá trị tensor đầu vào. (Tùy chọn thay đổi kích thước các tensor đầu vào nếu không muốn kích thước được xác định trước.)
  4. Gọi suy luận.
  5. Đọc giá trị tensor đầu ra.

Các phần sau đây mô tả cách thực hiện các bước này bằng từng ngôn ngữ.

Tải và chạy mô hình trong Java

Nền tảng: Android

API Java để chạy suy luận với TensorFlow Lite được thiết kế chủ yếu để sử dụng với Android, do đó, nó có sẵn dưới dạng phần phụ thuộc thư viện Android: org.tensorflow:tensorflow-lite .

Trong Java, bạn sẽ sử dụng lớp Interpreter để tải mô hình và điều khiển suy luận mô hình. Trong nhiều trường hợp, đây có thể là API duy nhất bạn cần.

Bạn có thể khởi tạo Interpreter bằng tệp .tflite :

public Interpreter(@NotNull File modelFile);

Hoặc với MappedByteBuffer :

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

Trong cả hai trường hợp, bạn phải cung cấp mô hình TensorFlow Lite hợp lệ hoặc API sẽ ném IllegalArgumentException . Nếu bạn sử dụng MappedByteBuffer để khởi tạo một Interpreter thì nó phải không thay đổi trong suốt thời gian tồn tại của Interpreter .

Cách ưa thích để chạy suy luận trên một mô hình là sử dụng chữ ký - Có sẵn cho các mô hình được chuyển đổi bắt đầu từ 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");
}

Phương thức runSignature nhận ba đối số:

  • Đầu vào : ánh xạ đầu vào từ tên đầu vào trong chữ ký tới đối tượng đầu vào.

  • Đầu ra : ánh xạ để ánh xạ đầu ra từ tên đầu ra trong chữ ký đến dữ liệu đầu ra.

  • Tên chữ ký [tùy chọn]: Tên chữ ký (Có thể để trống nếu model có chữ ký đơn).

Một cách khác để chạy suy luận khi mô hình không có chữ ký xác định. Đơn giản chỉ cần gọi Interpreter.run() . Ví dụ:

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

Phương thức run() chỉ nhận một đầu vào và chỉ trả về một đầu ra. Vì vậy, nếu mô hình của bạn có nhiều đầu vào hoặc nhiều đầu ra, thay vào đó hãy sử dụng:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

Trong trường hợp này, mỗi mục nhập trong inputs tương ứng với một tensor đầu vào và map_of_indices_to_outputs ánh xạ các chỉ số của tensor đầu ra với dữ liệu đầu ra tương ứng.

Trong cả hai trường hợp, các chỉ số tensor phải tương ứng với các giá trị bạn đã cung cấp cho TensorFlow Lite Converter khi tạo mô hình. Xin lưu ý rằng thứ tự của các tensor trong input phải khớp với thứ tự được cung cấp cho Bộ chuyển đổi TensorFlow Lite.

Lớp Interpreter cũng cung cấp các chức năng thuận tiện để bạn lấy chỉ mục của bất kỳ đầu vào hoặc đầu ra mô hình nào bằng cách sử dụng tên thao tác:

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

Nếu opName không phải là một thao tác hợp lệ trong mô hình, nó sẽ ném ra IllegalArgumentException .

Cũng hãy lưu ý rằng Interpreter sở hữu tài nguyên. Để tránh rò rỉ bộ nhớ, tài nguyên phải được giải phóng sau khi sử dụng bởi:

interpreter.close();

Để biết dự án mẫu với Java, hãy xem mẫu phân loại hình ảnh của Android .

Các kiểu dữ liệu được hỗ trợ (trong Java)

Để sử dụng TensorFlow Lite, kiểu dữ liệu của tensor đầu vào và đầu ra phải là một trong các kiểu nguyên thủy sau:

  • float
  • int
  • long
  • byte

Các kiểu String cũng được hỗ trợ, nhưng chúng được mã hóa khác với các kiểu nguyên thủy. Cụ thể, hình dạng của Tensor chuỗi quyết định số lượng và cách sắp xếp các chuỗi trong Tensor, với mỗi phần tử là một chuỗi có độ dài thay đổi. Theo nghĩa này, kích thước (byte) của Tensor không thể được tính toán chỉ từ hình dạng và loại, và do đó, các chuỗi không thể được cung cấp dưới dạng một đối số ByteBuffer phẳng, đơn lẻ. Bạn có thể xem một số ví dụ trong trang này.

Nếu sử dụng các loại dữ liệu khác, bao gồm các loại được đóng hộp như IntegerFloat , thì IllegalArgumentException sẽ được đưa ra.

Đầu vào

Mỗi đầu vào phải là một mảng hoặc mảng đa chiều thuộc các kiểu nguyên thủy được hỗ trợ hoặc ByteBuffer thô có kích thước phù hợp. Nếu đầu vào là một mảng hoặc mảng nhiều chiều, tenxơ đầu vào liên quan sẽ được thay đổi kích thước hoàn toàn theo kích thước của mảng tại thời điểm suy luận. Nếu đầu vào là ByteBuffer, thì trước tiên, trình gọi phải thay đổi kích thước tenxơ đầu vào liên quan theo cách thủ công (thông qua Interpreter.resizeInput() ) trước khi chạy suy luận.

Khi sử dụng ByteBuffer , hãy ưu tiên sử dụng bộ đệm byte trực tiếp vì điều này cho phép Interpreter tránh các bản sao không cần thiết. Nếu ByteBuffer là bộ đệm byte trực tiếp thì thứ tự của nó phải là ByteOrder.nativeOrder() . Sau khi được sử dụng cho suy luận mô hình, nó phải không thay đổi cho đến khi quá trình suy luận mô hình kết thúc.

đầu ra

Mỗi đầu ra phải là một mảng hoặc mảng đa chiều thuộc các kiểu nguyên thủy được hỗ trợ hoặc ByteBuffer có kích thước phù hợp. Lưu ý rằng một số kiểu máy có đầu ra động, trong đó hình dạng của tensor đầu ra có thể khác nhau tùy thuộc vào đầu vào. Không có cách nào đơn giản để xử lý vấn đề này bằng API suy luận Java hiện có, nhưng các phần mở rộng được lên kế hoạch sẽ giúp điều này trở nên khả thi.

Tải và chạy mô hình trong Swift

Nền tảng: iOS

API Swift có sẵn trong TensorFlowLiteSwift Pod từ Cocoapod.

Trước tiên, bạn cần nhập mô-đun 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...
}

Tải và chạy mô hình trong Objective-C

Nền tảng: iOS

API Objective-C có sẵn trong TensorFlowLiteObjC Pod từ Cocoapod.

Trước tiên, bạn cần nhập mô-đun 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... */ }

Sử dụng API C trong mã Objective-C

Hiện tại API Objective-C không hỗ trợ đại biểu. Để sử dụng các đại biểu với mã Objective-C, bạn cần gọi trực tiếp API C cơ bản.

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

Tải và chạy mô hình trong C++

Nền tảng: Android, iOS và Linux

Trong C++, mô hình được lưu trữ trong lớp FlatBufferModel . Nó đóng gói một mô hình TensorFlow Lite và bạn có thể xây dựng nó theo một số cách khác nhau, tùy thuộc vào nơi lưu trữ mô hình:

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

Bây giờ bạn đã có mô hình dưới dạng đối tượng FlatBufferModel , bạn có thể thực thi nó bằng Interpreter . Một FlatBufferModel có thể được sử dụng đồng thời bởi nhiều hơn một Interpreter .

Các phần quan trọng của API Interpreter được hiển thị trong đoạn mã bên dưới. Cần lưu ý rằng:

  • Các tensor được biểu diễn bằng số nguyên để tránh so sánh chuỗi (và bất kỳ sự phụ thuộc cố định nào vào thư viện chuỗi).
  • Không được truy cập trình thông dịch từ các luồng đồng thời.
  • Việc phân bổ bộ nhớ cho các tensor đầu vào và đầu ra phải được kích hoạt bằng cách gọi AllocateTensors() ngay sau khi thay đổi kích thước các tensor.

Cách sử dụng TensorFlow Lite đơn giản nhất với C++ trông như thế này:

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

Để biết thêm mã ví dụ, hãy xem ở minimal.cclabel_image.cc .

Tải và chạy mô hình bằng Python

Nền tảng: Linux

API Python để chạy suy luận được cung cấp trong mô-đun tf.lite . Từ đó, bạn hầu như chỉ cần tf.lite.Interpreter để tải mô hình và chạy suy luận.

Ví dụ sau đây cho thấy cách sử dụng trình thông dịch Python để tải tệp .tflite và chạy suy luận với dữ liệu đầu vào ngẫu nhiên:

Ví dụ này được khuyên dùng nếu bạn đang chuyển đổi từ SavingModel có SignatureDef được xác định. Có sẵn bắt đầu từ 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'])

Một ví dụ khác nếu mô hình không được xác định 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)

Để thay thế cho việc tải mô hình dưới dạng .tflite được chuyển đổi trước, bạn có thể kết hợp mã của mình với API Python của TensorFlow Lite Converter ( tf.lite.TFLiteConverter ), cho phép bạn chuyển đổi mô hình Keras của mình sang định dạng TensorFlow Lite, sau đó chạy suy luận:

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

Để biết thêm mã mẫu Python, hãy xem label_image.py .

Chạy suy luận với mô hình hình dạng động

Nếu bạn muốn chạy một mô hình có hình dạng đầu vào động, hãy thay đổi kích thước hình dạng đầu vào trước khi chạy suy luận. Nếu không, hình dạng None trong các mô hình Tensorflow sẽ được thay thế bằng phần giữ chỗ là 1 trong các mô hình TFLite.

Các ví dụ sau đây cho thấy cách thay đổi kích thước hình dạng đầu vào trước khi chạy suy luận bằng các ngôn ngữ khác nhau. Tất cả các ví dụ đều giả định rằng hình dạng đầu vào được xác định là [1/None, 10] và cần được thay đổi kích thước thành [3, 10] .

Ví dụ về C++:

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

Ví dụ về 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()