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 vi điều khiển.
Ví dụ về Hello World
Ví dụ Hello World được thiết kế để chứng minh những điều cơ bản tuyệt đối của việc sử dụng TensorFlow Lite cho Vi điều khiển. Chúng tôi đào tạo và chạy một mô hình sao chép một hàm sin, tức là, nó lấy một số duy nhất làm đầu vào của nó và xuất ra giá trị sin của số đó. Khi được triển khai cho bộ vi điều khiển, các dự đoán của nó được sử dụng để nhấp nháy đèn LED hoặc điều khiển hoạt ảnh.
Quy trình làm việc end-to-end bao gồm các bước sau:
- Đào tạo một mô hình (bằng Python): Một sổ ghi chép jupyter để đào tạo, chuyển đổi và tối ưu hóa một mô hình để sử dụng trên thiết bị.
- Chạy suy luận (trong C ++ 11): Một bài kiểm tra đơn vị end-to-end chạy suy luận trên mô hình bằng cách sử dụng thư viện C ++ .
Nhận một thiết bị được hỗ trợ
Ứng dụng mẫu mà chúng tôi 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 (xây dựng trực tiếp từ nguồn)
- Bộ khám phá STM32F746 (sử dụng Mbed)
- Adafruit EdgeBadge (sử dụng Arduino IDE)
- Adafruit TensorFlow Lite cho Bộ vi điều khiển (sử dụng Arduino IDE)
- Adafruit Circuit Playground Bluefruit (sử dụng Arduino IDE)
- Espressif ESP32-DevKitC (sử dụng ESP IDF)
- Espressif ESP-EYE (sử dụng ESP IDF)
Tìm hiểu thêm về các nền tảng được hỗ trợ trong TensorFlow Lite dành cho Vi điều khiển .
Đào tạo một người mẫu
Sử dụng Google Colaboratory để đào tạo mô hình của riêng bạn . Để biết thêm chi tiết, hãy tham khảo README.md
:
Hello World Training README.md
Chạy suy luận
Để chạy mô hình trên thiết bị của bạn, chúng tôi sẽ xem qua các hướng dẫn trong README.md
:
Các phần sau đây hướng dẫn về hello_world_test.cc
, bài kiểm tra đơn vị của ví dụ, trình bày cách chạy suy luận bằng TensorFlow Lite dành cho Vi điều khiển. Nó tải mô hình và chạy suy luận nhiều lần.
1. Bao gồm các tiêu đề thư viện
Để sử dụng thư viện TensorFlow Lite for Microcontrollers, chúng tôi phải bao gồm các tệp tiêu đề sau:
#include "tensorflow/lite/micro/all_ops_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"
-
all_ops_resolver.h
cung cấp các hoạt động được trình thông 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 các mô hình. -
schema_generated.h
chứa giản đồ cho định dạng tệp mô hình TensorFlow LiteFlatBuffer
. -
version.h
cung cấp thông tin lập phiên bản cho lược đồ TensorFlow Lite.
2. Bao gồm tiêu đề mô hình
Trình thông dịch TensorFlow Lite dành cho Bộ điều khiển mong đợi mô hình được cung cấp dưới dạng một mảng C ++. Mô hình được định nghĩa trong các tệp model.h
và model.cc
. Tiêu đề được bao gồm với dòng sau:
#include "tensorflow/lite/micro/examples/hello_world/model.h"
3. Bao gồm tiêu đề khung kiểm tra đơn vị
Để tạo một bài kiểm tra đơn vị, chúng tôi đưa vào khung kiểm tra đơn vị TensorFlow Lite cho Bộ vi điều khiển bằng cách bao gồm dòng sau:
#include "tensorflow/lite/micro/testing/micro_test.h"
Kiểm tra được xác định bằng cách sử dụ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 thảo luận về mã có trong macro ở trên.
4. Thiết lập ghi nhật ký
Để thiết lập ghi nhật ký, một con trỏ tflite::ErrorReporter
được tạo bằng cách sử dụng một con trỏ tới một 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 thông dịch, cho phép nó ghi nhật ký. Vì bộ vi điều khiển thường có nhiều cơ chế khác nhau để 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ột mô hình
Trong đoạn mã sau, mô hình được khởi tạo 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 kiểm tra mô hình để đảm bảo phiên bản giản đồ của 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. Khởi tạo trình giải quyết hoạt động
Một cá thể AllOpsResolver
được khai báo. Điều này sẽ được trình thông dịch sử dụng để truy cập các hoạt động được sử dụng bởi mô hình:
tflite::AllOpsResolver resolver;
AllOpsResolver
tải tất cả các hoạt động có sẵn trong TensorFlow Lite dành cho Vi điều khiển, sử dụng nhiều bộ nhớ. Vì một mô hình nhất định sẽ chỉ sử dụng một tập hợp con của các hoạt động này, nên các ứng dụng trong thế giới thực chỉ tải các hoạt động cần thiết.
Điều này được thực hiện bằng cách sử dụng một lớp khác, MicroMutableOpResolver
. Bạn có thể xem cách sử dụng nó trong micro_speech_test.cc
của ví dụ về giọng nói Micro .
7. Cấp phát bộ nhớ
Chúng ta cần phân bổ trước một lượng bộ nhớ nhất định cho các mảng đầu vào, đầu ra và mảng trung gian. Điều này được cung cấp dưới dạng một mảng uint8_t
có kích thước tensor_arena_size
:
const int tensor_arena_size = 2 * 1024;
uint8_t tensor_arena[tensor_arena_size];
Kích thước yêu cầu sẽ phụ thuộc vào kiểu máy bạn đang sử dụng và có thể cần được xác định bằng thử nghiệm.
8. Phiên dịch tức thời
Chúng tôi tạo một cá thể tflite::MicroInterpreter
, truyền vào các biến đã tạo trước đó:
tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
tensor_arena_size, error_reporter);
9. Phân bổ các bộ căng
Chúng tôi yêu cầu trình thông dịch cấp phát bộ nhớ từ tensor_arena
cho các tensor của mô hình:
interpreter.AllocateTensors();
10. Xác thực hình dạng đầu vào
Phiên MicroInterpreter
có thể cung cấp cho chúng tôi một con trỏ tới tensor đầu vào của mô hình bằng cách gọi .input(0)
, trong đó 0
đại diện cho tensor đầu vào đầu tiên (và duy nhất):
// Obtain a pointer to the model's input tensor
TfLiteTensor* input = interpreter.input(0);
Sau đó, chúng tôi kiểm tra bộ căng này để xác nhận rằng hình dạng và kiểu của nó là những gì chúng tôi đ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 kiểu dữ liệu TensorFlow Lite và được định nghĩa common.h
.
11. Cung cấp giá trị đầu vào
Để cung cấp đầu vào cho mô hình, chúng tôi đặ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 tôi 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, chúng ta có thể gọi Invoke()
trên cá thể tflite::MicroInterpreter
:
TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
TF_LITE_REPORT_ERROR(error_reporter, "Invoke failed\n");
}
Chúng tôi có thể kiểm tra giá trị trả về, TfLiteStatus
, để xác định xem quá trình chạy có thành công hay không. Các giá trị có thể có của TfLiteStatus
, được định nghĩa trong common.h
, là kTfLiteOk
và kTfLiteError
.
Đoạn mã sau khẳng định rằng giá trị là kTfLiteOk
, có nghĩa là suy luận đã được chạy thành công.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);
13. Đạt được đầu ra
Có thể lấy 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 tensor đầu ra đầu tiên (và duy nhất).
Trong ví dụ, đầu ra của mô hình là một giá trị dấu phẩy động duy nhất được chứa trong một 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 tôi có thể đọc giá trị trực tiếp từ tensor đầu ra và khẳng định rằng đó là những gì 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ã chạy suy luận nhiều lần nữa. Trong mỗi trường hợp, chúng tôi gán một giá trị cho tensor đầu vào, gọi trình thông dịch và đọc kết quả từ 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);
15. Đọc mã ứng dụng
Khi bạn đã hoàn thành bài kiểm tra đơn vị này, bạn sẽ có thể hiểu mã ứng dụng của ví dụ, nằm trong main_functions.cc
. Nó tuân theo một quy trình tương tự, nhưng tạo ra một giá trị đầu vào dựa trên số lượng suy luận đã được chạy và gọi một chức năng dành riêng cho thiết bị để hiển thị đầu ra của mô hình cho người dùng.