Suy luận về TensorFlow Lite

Thuật ngữ dự đoá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 dự đoán bằng mô hình TensorFlow Lite, bạn phải chạy mô hình đó thông qua một trình diễn giải. Trình phiên dịch TensorFlow Lite có thiết kế đơn giản và nhanh chóng. Trình thông dịch sử dụng thứ tự biểu đồ tĩnh và trình phân bổ bộ nhớ tuỳ chỉnh (ít động) để đảm bảo độ trễ tải, khởi chạy và thực thi ở mức tối thiểu.

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

Các khái niệm quan trọng

Hoạt động dự đoán của TensorFlow Lite thường tuân theo các bước sau:

  1. 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 này thường không khớp với định dạng dữ liệu đầu vào mà mô hình dự kiến sử dụng. Ví dụ: bạn có thể cần đổ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 bao gồm việc sử dụng API TensorFlow Lite để thực thi mô hình. Quá trình này bao gồm một số bước như xây dựng trình thông dịch và phân bổ tensor, như mô tả trong các phần sau.

  4. Diễn giải kết quả

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

    Ví dụ: một mô hình có thể chỉ trả về danh sách các xác suất. Bạn có thể tuỳ ý liên kết xác suất với các danh mục có liên quan và hiển thị cho người dùng cuối.

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

API suy luận của TensorFlow được cung cấp cho hầu hết các nền tảng di động/được nhúng phổ biến (chẳng hạ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 thể hiện sự ư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ỏ, vì vậy, không có gì đáng ngạc nhiên khi các API cố gắng tránh các bản sao không cần thiết với chi phí thuận tiện. 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à một số khác biệt giữa các ngôn ngữ là điều cần thiết.

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

Nền tảng Android

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

Hãy xem phần bên dưới để biết thông tin chi tiết về cách sử dụng C++Java, hoặc làm theo Hướng dẫn bắt đầu nhanh dành cho Android để xem hướng dẫn và mã mẫu.

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

Đối với mô hình TensorFlow Lite được tăng cường bằng siêu dữ liệu, nhà phát triển có thể sử dụng trình tạo mã bao bọc Android TensorFlow Lite cho Android để tạo mã bao bọc dành riêng cho nền tảng. Mã bao bọc giúp bạn không cần phải tương tác trực tiếp với ByteBuffer trên Android. Thay vào đó, 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 đã nhập như BitmapRect. Để biết thêm thông tin, hãy tham khảo Trình tạo mã bao bọc Android TensorFlow Lite.

Nền tảng iOS

Trên iOS, TensorFlow Lite hoạt động với 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.

Hãy xem nội dung bên dưới để biết thông tin chi tiết về cách sử dụng Swift, Objective-CAPI C, hoặc làm theo phần Hướng dẫn nhanh dành cho iOS để nắm được hướng dẫn và mã ví dụ.

Nền tảng Linux

Trên các nền tảng Linux (bao gồm cả Rspberry Pi), bạn có thể chạy dự đoán bằng cách sử dụng API TensorFlow Lite có trong C++Python, như minh hoạ trong các phần sau.

Chạy mô hình

Quy trì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. Tạo Interpreter dựa trên mô hình hiện có.
  3. Đặt giá trị tensor đầu vào. (Không bắt buộc đổi kích thước tensor đầu vào nếu không muốn dùng kích thước xác định trước.)
  4. Gọi suy luận.
  5. Đọc cá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 dự đoán với TensorFlow Lite chủ yếu được thiết kế để sử dụng với Android, vì vậy, API này 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ẽ dùng lớp Interpreter để tải mô hình và thúc đẩy dự đoán mô hình. Trong nhiều trường hợp, đây có thể là API duy nhất mà 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 bằng một 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ệ, nếu không API sẽ gửi IllegalArgumentException. Nếu bạn sử dụng MappedByteBuffer để khởi chạy Interpreter, thì thuộc tính này phải được giữ nguyên trong toàn bộ thời gian hoạt động của Interpreter.

Cách ưu tiên để 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 kể 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 3 đối số:

  • Đầu vào : ánh xạ các mục nhập từ tên đầu vào trong chữ ký đến một đố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.

  • Chữ ký (không bắt buộc): Tên chữ ký (Có thể để trống nếu mô hình có một chữ ký).

Một cách khác để chạy dự đoán khi mô hình không có chữ ký xác định. 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, 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ỉ mục 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ỉ mục tensor phải tương ứng với các giá trị mà bạn đã cung cấp cho Trình chuyển đổi TensorFlow Lite khi tạo mô hình. Lưu ý rằng thứ tự của tensor trong input phải khớp với thứ tự được đặt cho Trình chuyển đổi TensorFlow Lite.

Lớp Interpreter cũng cung cấp các hàm thuận tiện để bạn lấy chỉ mục của bất kỳ đầu vào hoặc đầu ra nào của mô hình thông qua 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 hoạt động hợp lệ trong mô hình, thì ứng dụng sẽ gửi một IllegalArgumentException.

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

interpreter.close();

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

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

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

  • float
  • int
  • long
  • byte

Các loại String cũng được hỗ trợ, nhưng được mã hoá khác với các loại nguyên gốc. Cụ thể, hình dạng của Tensor chuỗi cho biết số lượng và cách sắp xếp các chuỗi trong Tensor, trong đó mỗi phần tử là một chuỗi có độ dài thay đổi. Theo đó, kích thước (byte) của Tensor không thể được tính chỉ dựa trên hình dạng và kiểu, do đó, các chuỗi không thể được cung cấp dưới dạng một đối số ByteBuffer phẳng.

Nếu bạn sử dụng các kiểu dữ liệu khác, bao gồm cả các loại đóng hộp như IntegerFloat, thì hệ thống sẽ gửi IllegalArgumentException.

Thông tin đầu vào

Mỗi dữ liệu đầu vào phải là một mảng hoặc mảng đa chiều của các loại nguyên gốc được hỗ trợ, hoặc một ByteBuffer thô có kích thước phù hợp. Nếu dữ liệu đầu vào là một mảng hoặc mảng đa chiều, thì tensor đầu vào liên kết sẽ được đổi kích thước ngầm thành các kích thước của mảng tại thời điểm dự đoán. Nếu dữ liệu đầu vào là ByteBuffer, trước tiên, phương thức gọi phải đổi kích thước tensor đầu vào được liên kết (thông qua Interpreter.resizeInput()) theo cách thủ công trước khi chạy dự đoán.

Khi sử dụng ByteBuffer, hãy ưu tiên sử dụng vùng đệ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à vùng đệm byte trực tiếp, thì thứ tự của vùng đệm phải là ByteOrder.nativeOrder(). Sau khi được dùng để dự đoán mô hình, dữ liệu đó phải không thay đổi cho đến khi quá trình dự đoán mô hình kết thúc.

Kết quả đầu ra

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

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

Nền tảng: iOS

API Swift có trong nhóm TensorFlowLiteSwift của CocoaPods.

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ột mô hình trong Objective-C

Nền tảng: iOS

API Objective-C có trong TensorFlowLiteObjC Nhóm của CocoaPods.

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 C API trong mã Objective-C

Hiện tại, Objective-C API không hỗ trợ người được uỷ quyền. Để sử dụng uỷ quyền bằng 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. Thư viện này đóng gói mô hình TensorFlow Lite và bạn có thể xây dựng theo một vài cách, tuỳ thuộc vào vị trí 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);
};

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

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

  • Tensor được biểu thị bằng số nguyên để tránh so sánh chuỗi (và mọi phần phụ thuộc cố định trên 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.
  • Bạn phải kích hoạt quá trình phân bổ bộ nhớ cho tensor đầu vào và đầu ra bằng cách gọi AllocateTensors() ngay sau khi đổi kích thước tensor.

Cách sử dụng TensorFlow Lite với C++ đơn giản nhất sẽ có dạng như sau:

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

Để xem thêm đoạn mã ví dụ, hãy xem minimal.cclabel_image.cc.

Tải và chạy một mô hình trong Python

Nền tảng: Linux

API Python để chạy một dự đoán hầu như chỉ cần tf.lite.Interpreter để tải mô hình và chạy dự đoá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 dự đoán với dữ liệu đầu vào ngẫu nhiên:

Bạn nên sử dụng ví dụ này nếu đang chuyển đổi từ savedModel với một SignatureDef đã xác định. 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 vì tải mô hình dưới dạng tệp .tflite đã được chuyển đổi trước, bạn có thể kết hợp mã với TensorFlow Lite Converter Python API để chuyển đổi mô hình Keras thành định dạng TensorFlow Lite rồi 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...

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

Chạy suy luận bằng 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 dữ liệu đầu vào động, hãy đổi kích thước hình dạng dữ liệu đầ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 một phần giữ chỗ là 1 trong các mô hình TFLite.

Các ví dụ sau đây cho thấy cách đổi kích thước hình dạng đầu vào trước khi đưa ra suy luận bằng nhiều ngôn ngữ. Tất 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 đổ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()

Thao tác được hỗ trợ

TensorFlow Lite hỗ trợ một tập hợp con các thao tác của TensorFlow có một số điểm hạn chế. Để biết danh sách đầy đủ các thao tác và các giới hạn, hãy tham khảo trang TF Lite Ops.