Tài liệu này giải thích cách huấn luyện mô hình và chạy suy luận bằng cách sử dụng vi điều khiển.
Ví dụ về Hello World
Chiến lược phát hành đĩa đơn Xin chào mọi người được thiết kế để thể hiện kiến thức cơ bản tuyệt đối về việc sử dụng LiteRT cho Vi điều khiển. Chúng tôi huấn luyện và chạy một mô hình sao chép hàm sin, tức là hàm lấy một số duy nhất làm đầu vào và cho ra kết quả của số đó sin. Khi được triển khai cho vi điều khiển, các dự đoán của bộ vi điều khiển được dùng để nhấp nháy đèn LED hoặc điều khiển ảnh động.
Quy trình làm việc toàn diện bao gồm các bước sau:
- Đào tạo một mô hình (bằng Python): Tệp python để huấn luyện, chuyển đổi và tối ưu hoá một mô hình để sử dụng trên thiết bị.
- Chạy suy luận (trong C++ 17): Kiểm thử đơn vị toàn diện chạy suy luận trên mô hình bằng cách sử dụng thư viện C++.
Mua một thiết bị được hỗ trợ
Ứng dụng mẫu chúng ta sẽ sử dụng đã được thử nghiệm trên các thiết bị sau:
- Arduino Nano 33 BLE Sense (sử dụng Arduino IDE)
- SparkFun Edge (tạo trực tiếp từ nguồn)
- Bộ công cụ Khám phá STM32F746 (sử dụng Mbed)
- Adafruit EdgeHuy hiệu (sử dụng Arduino IDE)
- Bộ công cụ Adafruit LiteRT cho bộ vi điều khiển (sử dụng Arduino IDE)
- Khu vui chơi giải trí Adafruit Circuit Playground (sử dụng Arduino IDE)
- Espressif ESP32-DevKitC (sử dụng IDF ESP)
- Espressif ESP-EYE (sử dụng IDF ESP)
Tìm hiểu thêm về các nền tảng được hỗ trợ trong LiteRT cho vi điều khiển.
Huấn luyện người mẫu
Sử dụng train.py để huấn luyện mô hình Hello world (mô hình xin chào thế giới) về nhận dạng sinwave
Chạy: bazel build tensorflow/lite/micro/examples/hello_world:train
bazel-bin/tensorflow/lite/micro/examples/hello_world/train --save_tf_model
--save_dir=/tmp/model_created/
Chạy dự đoán
Để chạy mô hình trên thiết bị của bạn, chúng tôi sẽ xem xét hướng dẫn trong
README.md
:
Chào bạn README.md trên thế giới
Các phần sau đây sẽ hướng dẫn bạn
evaluate_test.cc
!
phương thức kiểm thử đơn vị minh hoạ cách chạy dự đoán bằng LiteRT cho
Vi điều khiển. Trình này tải mô hình và chạy dự đoán nhiều lần.
1. Thêm tiêu đề thư viện
Để sử dụng thư viện LiteRT cho Vi điều khiển, chúng ta phải đưa vào các tệp tiêu đề sau:
#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/micro/micro_error_reporter.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
micro_mutable_op_resolver.h
cung cấp các thao tác mà trình phiên dịch sử dụng để chạy mô hình.micro_error_reporter.h
xuất ra thông tin gỡ lỗi.micro_interpreter.h
chứa mã để tải và chạy mô hình.schema_generated.h
chứa giản đồ cho LiteRTFlatBuffer
.version.h
cung cấp thông tin về phiên bản cho giản đồ LiteRT.
2. Thêm tiêu đề mô hình
Trình thông dịch LiteRT cho bộ vi điều khiển dự kiến rằng mô hình sẽ
được cung cấp dưới dạng mảng C++. Mô hình này được xác định trong các tệp model.h
và model.cc
.
Tiêu đề này được bao gồm với dòng sau:
#include "tensorflow/lite/micro/examples/hello_world/model.h"
3. Thêm tiêu đề khung kiểm thử đơn vị
Để tạo một kiểm thử đơn vị, chúng tôi đưa LiteRT vào cho Khung kiểm thử đơn vị bộ vi điều khiển bằng cách thêm dòng sau:
#include "tensorflow/lite/micro/testing/micro_test.h"
Chương trình kiểm thử này được xác định bằng các macro sau:
TF_LITE_MICRO_TESTS_BEGIN
TF_LITE_MICRO_TEST(LoadModelAndPerformInference) {
. // add code here
.
}
TF_LITE_MICRO_TESTS_END
Bây giờ, chúng ta sẽ thảo luận về mã được đưa vào macro ở trên.
4. Thiết lập tính năng ghi nhật ký
Để thiết lập tính năng ghi nhật ký, con trỏ tflite::ErrorReporter
sẽ được tạo bằng con trỏ
đến một thực thể tflite::MicroErrorReporter
:
tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = µ_error_reporter;
Biến này sẽ được chuyển vào trình phiên dịch, cho phép biến viết
nhật ký. Vì bộ vi điều khiển thường có nhiều cơ chế để ghi nhật ký,
Việc triển khai tflite::MicroErrorReporter
được thiết kế để tùy chỉnh cho
thiết bị cụ thể của bạn.
5. Tải mô hình
Trong mã sau, mô hình được tạo thực thể bằng cách sử dụng dữ liệu từ mảng char
,
g_model
được khai báo trong model.h
. Sau đó, chúng tôi sẽ kiểm tra mô hình để đảm bảo
phiên bản giản đồ tương thích với phiên bản chúng tôi đang sử dụng:
const tflite::Model* model = ::tflite::GetModel(g_model);
if (model->version() != TFLITE_SCHEMA_VERSION) {
TF_LITE_REPORT_ERROR(error_reporter,
"Model provided is schema version %d not equal "
"to supported version %d.\n",
model->version(), TFLITE_SCHEMA_VERSION);
}
6. Tạo thực thể trình phân giải hoạt động
Đáp
MicroMutableOpResolver
thực thể được khai báo. Thông tin này sẽ được phiên dịch viên sử dụng để đăng ký và
truy cập vào các toán tử mà mô hình sử dụng:
using HelloWorldOpResolver = tflite::MicroMutableOpResolver<1>;
TfLiteStatus RegisterOps(HelloWorldOpResolver& op_resolver) {
TF_LITE_ENSURE_STATUS(op_resolver.AddFullyConnected());
return kTfLiteOk;
MicroMutableOpResolver
cần có một tham số mẫu cho biết số
hoạt động sẽ được đăng ký. Hàm RegisterOps
đăng ký hoạt động
với trình phân giải.
HelloWorldOpResolver op_resolver;
TF_LITE_ENSURE_STATUS(RegisterOps(op_resolver));
7. Phân bổ bộ nhớ
Chúng ta cần phân bổ trước một mức bộ nhớ nhất định cho dữ liệu đầu vào, đầu ra và
mảng trung gian. Giá trị này được cung cấp dưới dạng mảng kích thước uint8_t
tensor_arena_size
:
const int tensor_arena_size = 2 * 1024;
uint8_t tensor_arena[tensor_arena_size];
Kích thước bắt buộc sẽ phụ thuộc vào mô hình bạn đang sử dụng và có thể cần phải được xác định bằng thử nghiệm.
8. Tạo thực thể phiên dịch
Chúng ta tạo một thực thể tflite::MicroInterpreter
, truyền các biến vào
được tạo trước đó:
tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
tensor_arena_size, error_reporter);
9. Phân bổ tensor
Chúng ta yêu cầu trình thông dịch phân bổ bộ nhớ từ tensor_arena
cho
tensor của mô hình:
interpreter.AllocateTensors();
10. Xác thực hình dạng nhập
Thực thể MicroInterpreter
có thể cung cấp cho chúng ta con trỏ đến
đầu vào tensor bằng cách gọi .input(0)
, trong đó 0
đại diện cho tensor đầu tiên (và duy nhất)
tensor đầu vào:
// Obtain a pointer to the model's input tensor
TfLiteTensor* input = interpreter.input(0);
Sau đó, chúng ta kiểm tra tensor này để xác nhận rằng hình dạng và kiểu của tensor này đúng như bản chất của chúng ta đang mong đợi:
// Make sure the input has the properties we expect
TF_LITE_MICRO_EXPECT_NE(nullptr, input);
// The property "dims" tells us the tensor's shape. It has one element for
// each dimension. Our input is a 2D tensor containing 1 element, so "dims"
// should have size 2.
TF_LITE_MICRO_EXPECT_EQ(2, input->dims->size);
// The value of each element gives the length of the corresponding tensor.
// We should expect two single element tensors (one is contained within the
// other).
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->data[0]);
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->data[1]);
// The input is a 32 bit floating point value
TF_LITE_MICRO_EXPECT_EQ(kTfLiteFloat32, input->type);
Giá trị enum kTfLiteFloat32
là tham chiếu đến một trong các LiteRT
và được xác định trong
common.h
.
11. Cung cấp giá trị nhập
Để cung cấp đầu vào cho mô hình, chúng ta đặt nội dung của tensor đầu vào như sau:
input->data.f[0] = 0.;
Trong trường hợp này, chúng ta sẽ nhập một giá trị dấu phẩy động đại diện cho 0
.
12. Chạy mô hình
Để chạy mô hình này, chúng ta có thể gọi Invoke()
trên tflite::MicroInterpreter
thực thể:
TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
TF_LITE_REPORT_ERROR(error_reporter, "Invoke failed\n");
}
Chúng ta có thể kiểm tra giá trị trả về (TfLiteStatus
) để xác định xem lần chạy có
thành công. Các giá trị có thể có của TfLiteStatus
, được xác định trong
common.h
,
là kTfLiteOk
và kTfLiteError
.
Mã sau đây khẳng định rằng giá trị là kTfLiteOk
, nghĩa là suy luận
đã chạy thành công.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);
13. Nhận kết quả
Bạn có thể thu được tensor đầu ra của mô hình bằng cách gọi output(0)
trên
tflite::MicroInterpreter
, trong đó 0
đại diện cho đầu ra đầu tiên (và duy nhất)
tensor.
Trong ví dụ này, đầu ra của mô hình là một giá trị dấu phẩy động duy nhất được chứa trong tensor 2D:
TfLiteTensor* output = interpreter.output(0);
TF_LITE_MICRO_EXPECT_EQ(2, output->dims->size);
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->data[0]);
TF_LITE_MICRO_EXPECT_EQ(1, input->dims->data[1]);
TF_LITE_MICRO_EXPECT_EQ(kTfLiteFloat32, output->type);
Chúng ta có thể đọc trực tiếp giá trị từ tensor đầu ra và xác nhận rằng đó là giá trị chúng tôi mong đợi:
// Obtain the output value from the tensor
float value = output->data.f[0];
// Check that the output value is within 0.05 of the expected value
TF_LITE_MICRO_EXPECT_NEAR(0., value, 0.05);
14. Chạy lại suy luận
Phần còn lại của mã sẽ chạy suy luận thêm vài lần nữa. Trong mỗi trường hợp, chúng ta gán một giá trị cho tensor đầu vào, gọi trình thông dịch và đọc kết quả của tensor đầu ra:
input->data.f[0] = 1.;
interpreter.Invoke();
value = output->data.f[0];
TF_LITE_MICRO_EXPECT_NEAR(0.841, value, 0.05);
input->data.f[0] = 3.;
interpreter.Invoke();
value = output->data.f[0];
TF_LITE_MICRO_EXPECT_NEAR(0.141, value, 0.05);
input->data.f[0] = 5.;
interpreter.Invoke();
value = output->data.f[0];
TF_LITE_MICRO_EXPECT_NEAR(-0.959, value, 0.05);