Hướng dẫn này giới thiệu cho bạn quy trình chạy mô hình LiteRT (viết tắt của Lite Runtime) trên thiết bị để đưa ra dự đoán dựa trên dữ liệu đầu vào. Điều này đạt được nhờ trình thông dịch LiteRT, sử dụng một thứ tự đồ thị tĩnh và một trình phân bổ bộ nhớ tuỳ chỉnh (ít động hơn) để đảm bảo độ trễ tải, khởi tạo và thực thi tối thiểu.
Suy luận LiteRT thường tuân theo các bước sau:
Tải một mô hình: tải mô hình
.tflitevào bộ nhớ, chứa biểu đồ thực thi của mô hình.Chuyển đổi dữ liệu: Chuyển đổi dữ liệu đầu vào thành định dạng và kích thước dự kiến. 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 dự kiến. 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.
Chạy suy luận: Thực thi mô hình LiteRT để đưa ra dự đoán. Bước này liên quan đến việc sử dụng API LiteRT để thực thi mô hình. Quá trình này bao gồm một số bước như tạo trình thông dịch và phân bổ các tensor.
Diễn giải đầu ra: Diễn giải các tensor đầu ra 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ề danh sách xác suất. Bạn có thể tự do liên kết các xác suất với các danh mục có liên quan và định dạng đầu ra.
Hướng dẫn này mô tả cách truy cập vào trình thông dịch LiteRT và thực hiện suy luận bằng C++, Java và Python.
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 và nền tảng nhúng phổ biến như Android, iOS và Linux, 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 sự ưu tiên về hiệu suất hơn là tính dễ sử dụng. LiteRT được thiết kế để suy luận nhanh trên các thiết bị nhỏ, vì vậy, các API sẽ tránh những bản sao không cần thiết để đổi lấy sự thuận tiện.
Trên tất cả các thư viện, LiteRT API cho phép bạn tải các mô hình, nguồn cấp dữ liệu đầu vào và truy xuất đầu ra suy luận.
Nền tảng Android
Trên Android, bạn có thể thực hiện suy luận LiteRT 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 của Android. Các API C++ mang lại tính linh hoạt và tốc độ cao hơn, nhưng có thể yêu cầu bạn viết trình bao bọc JNI để di chuyển dữ liệu giữa các lớp Java và C++.
Hãy xem các phần C++ và Java để biết thêm thông tin hoặc làm theo hướng dẫn bắt đầu nhanh về Android.
Nền tảng iOS
Trên iOS, LiteRT có trong các thư viện Swift và Objective-C của iOS. Bạn cũng có thể sử dụng trực tiếp C API trong mã Objective-C.
Hãy xem các phần Swift, Objective-C và C API hoặc làm theo hướng dẫn bắt đầu nhanh trên iOS.
Nền tảng Linux
Trên các nền tảng Linux, bạn có thể chạy các suy luận bằng cách sử dụng API LiteRT có trong C++.
Tải và chạy một mô hình
Quá trình tải và chạy một mô hình LiteRT bao gồm các bước sau:
- Tải mô hình vào bộ nhớ.
- Xây dựng
Interpreterdựa trên một mô hình hiện có. - Đặt giá trị của tensor đầu vào.
- Đưa ra suy luận.
- Xuất giá trị tensor.
Android (Java)
API Java để chạy các suy luận bằng LiteRT 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 một phần phụ thuộc của thư viện Android: com.google.ai.edge.litert.
Trong Java, bạn sẽ dùng lớp Interpreter để tải một 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 một Interpreter bằng tệp FlatBuffers (.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ột mô hình LiteRT hợp lệ hoặc API sẽ gửi IllegalArgumentException. Nếu bạn dùng MappedByteBuffer để khởi chạy một Interpreter, thì giá trị này phải 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 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 3 đối số:
Đầu vào : ánh xạ cho đầu vào từ tên đầu vào trong chữ ký đến một đối tượng đầu vào.
Đầu ra : bản đồ để liên kết đầu ra từ tên đầu ra trong chữ ký đến dữ liệu đầu ra.
Tên chữ ký (không bắt buộc): Tên chữ ký (Bạn có thể để trống nếu mô hình có một chữ ký).
Một cách khác để chạy suy luận khi mô hình không có chữ ký xác định.
Bạn chỉ cần gọi đến số 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 dữ liệu đầu vào và chỉ trả về một dữ liệu đầ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 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 LiteRT 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 Trình chuyển đổi LiteRT.
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 mọi đầu vào hoặc đầu ra mô hình 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, thì thao tác này sẽ gửi một IllegalArgumentException.
Ngoài ra, hãy lưu ý rằng Interpreter sở hữu các tài nguyên. Để tránh rò rỉ bộ nhớ, bạn phải giải phóng tài nguyên sau khi sử dụng bằng cách:
interpreter.close();
Để xem một dự án mẫu bằng Java, hãy xem ứng dụng mẫu phát hiện đối tượng trên Android.
Loại dữ liệu được hỗ trợ
Để sử dụng LiteRT, các 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 thuỷ sau:
floatintlongbyte
Các loại String cũng được hỗ trợ, nhưng chúng được mã hoá khác với các loại nguyên thuỷ. Cụ thể, hình dạng của một Tensor chuỗi sẽ quyết định 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 nghĩa này, bạn không thể tính toán kích thước (byte) của Tensor chỉ dựa vào hình dạng và loại. Do đó, bạn không thể cung cấp các chuỗi dưới dạng một đối số ByteBuffer phẳng duy nhất.
Nếu các kiểu dữ liệu khác, bao gồm cả các kiểu đóng hộp như Integer và Float, được dùng, thì IllegalArgumentException sẽ được truyền.
Thông tin đầ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 thuỷ được hỗ trợ, hoặc một ByteBuffer thô có kích thước phù hợp. Nếu đầu vào là một mảng hoặc mảng đa chiều, thì tensor đầu vào được liên kết sẽ được đổi kích thước ngầm định thành kích thước của mảng tại thời gian suy luận. Nếu đầu vào là ByteBuffer, trước tiên, người gọi phải tự điều chỉnh kích thước của tensor đầu vào được liên kết (thông qua Interpreter.resizeInput()) trước khi chạy quy trình suy luận.
Khi sử dụng ByteBuffer, bạn nê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à một 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 cho một quy trình suy luận mô hình, giá trị này phải giữ nguyên cho đến khi quy trình suy luậ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 thuộc các kiểu nguyên thuỷ được hỗ trợ hoặc một ByteBuffer có kích thước phù hợp. Xin lưu ý rằng một số mô hình có đầu ra động, trong đó hình dạng của các 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ý việc này bằng API suy luận Java hiện có, nhưng các tiện ích dự kiến sẽ giúp bạn làm được việc này.
iOS (Swift)
Swift API có trong Pod 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...
}
iOS (Objective-C)
API Objective-C có trong Pod LiteRTObjC của Cocoapods.
Trước tiên, bạn cần nhập mô-đun TensorFlowLiteObjC.
@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... */ }
C API trong mã Objective-C
API Objective-C không hỗ trợ các uỷ quyền. Để sử dụng các uỷ quyền 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);
C++
API C++ để chạy suy luận bằng LiteRT tương thích với các nền tảng Android, iOS và Linux. API C++ trên iOS chỉ có khi bạn sử dụng bazel.
Trong C++, mô hình được lưu trữ trong lớp FlatBufferModel.
Nó đóng gói một mô hình LiteRT và bạn có thể tạo mô hình này theo một số 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 một đối tượng FlatBufferModel, bạn có thể thực thi mô hình đó bằng một Interpreter.
Nhiều Interpreter có thể dùng cùng một FlatBufferModel.
Các phần quan trọng của API Interpreter xuất hiện trong đoạn mã bên dưới. Bạn cần lưu ý rằng:
- Các tensor được biểu thị bằng số nguyên để tránh so sánh chuỗi (và mọi sự phụ thuộc cố định vào thư viện chuỗi).
- Bạn không được truy cập vào trình thông dịch từ các luồng đồng thời.
- Bạn phải kích hoạt việc phân bổ bộ nhớ cho các 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 LiteRT đơn giản nhất với C++ trô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 needed.
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 mã ví dụ, hãy xem minimal.cc và label_image.cc.
Python
Python API để chạy suy luận sử dụng Interpreter để tải một mô hình và chạy suy luận.
Cài đặt gói LiteRT:
$ python3 -m pip install ai-edge-litert
Nhập Trình thông dịch LiteRT
from ai_edge_litert.interpreter import Interpreter
Interpreter = Interpreter(model_path=args.model.file)
Ví dụ sau đây cho biết cách sử dụng trình thông dịch Python để tải tệp FlatBuffers (.tflite) và chạy suy luận bằng dữ liệu đầu vào ngẫu nhiên:
Bạn nên dùng ví dụ này nếu đang chuyển đổi từ SavedModel có SignatureDef được xác định.
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 LiteRT model in LiteRT Interpreter
from ai_edge_litert.interpreter import Interpreter
interpreter = 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ó SignatureDefs được xác định.
import numpy as np
import tensorflow as tf
# Load the LiteRT model and allocate tensors.
from ai_edge_litert.interpreter import Interpreter
interpreter = Interpreter(TFLITE_FILE_PATH)
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)
Ngoài cách tải mô hình dưới dạng tệp .tflite đã chuyển đổi trước, bạn có thể kết hợp mã của mình với Trình biên dịch LiteRT, cho phép bạn chuyển đổi mô hình Keras thành định dạng LiteRT rồi chạy quy trình 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 LiteRT format
converter = tf.lite.TFLiteConverter.from_keras_model(tf.keras.models.Model(inputs=[img], outputs=[out]))
tflite_model = converter.convert()
# Load the LiteRT model and allocate tensors.
from ai_edge_litert.interpreter import Interpreter
interpreter = 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 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 đầu vào động, hãy đổ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ỗ 1 trong các mô hình LiteRT.
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 chạy suy luận bằng nhiều ngôn ngữ. 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 đổ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 LiteRT model in LiteRT Interpreter
from ai_edge_litert.interpreter import Interpreter
interpreter = 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()