Bắt đầu với vi điều khiển

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ụ Hello World

Ví dụ Hello World được thiết kế để thể hiện những điều cơ bản tuyệt đối về 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 hàm sin, tức là lấy một số duy nhất làm đầu vào và xuất ra giá trị sin của số đó. Khi được triển khai vào bộ vi điều khiển, các dự đoán của nó sẽ đượ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 từ đầu đến cuối bao gồm các bước sau:

  1. Huấn luyện mô hình (bằng Python): Tệp python để huấn luyện, chuyển đổi và tối ưu hóa mô hình để sử dụng trên thiết bị.
  2. Chạy suy luận (trong C++ 17): Kiểm tra đơn vị từ đầu đến cuối chạy suy luận trên mô hình bằ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:

Tìm hiểu thêm về các nền tảng được hỗ trợ trong TensorFlow Lite dành cho Bộ vi điều khiển .

Đào tạo một người mẫu

Sử dụng train.py để đào tạo mô hình hello world nhằm 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 suy luận

Để chạy mô hình trên thiết bị của bạn, chúng tôi sẽ xem hướng dẫn trong README.md :

Xin chào thế giới README.md

Các phần sau đây sẽ hướng dẫn các bài kiểm tra đơn vị evaluate_test.cc của ví dụ này minh họa cách chạy suy luận bằng cách sử dụng TensorFlow Lite dành cho Bộ 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 cho Vi điều khiển, chúng ta phải bao gồm 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"

2. Bao gồm tiêu đề mô hình

Trình thông dịch TensorFlow Lite dành cho Bộ vi điều khiển dự kiến ​​mô hình sẽ được cung cấp dưới dạng mảng C++. Mô hình được xác định trong tệp model.hmodel.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 bao gồm khung kiểm tra đơn vị TensorFlow Lite dành 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 phiên bản tflite::MicroErrorReporter :

tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = &micro_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ế ghi nhật ký khác nhau nên 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 đ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 lược đồ 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 phiên bản MicroMutableOpResolver được khai báo. Điều này sẽ được trình thông dịch sử dụng để đăng ký và truy cập các hoạt động được 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 yêu cầu tham số mẫu cho biết số lượng hoạt động sẽ được đăng ký. Hàm RegisterOps đăng ký ops với trình phân giải.

HelloWorldOpResolver op_resolver;
TF_LITE_ENSURE_STATUS(RegisterOps(op_resolver));

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 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ả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ẽ tùy 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 nhanh

Chúng tôi tạo một phiên bản tflite::MicroInterpreter , chuyển vào các biến được tạo trước đó:

tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
                                     tensor_arena_size, error_reporter);

9. Phân bổ tensor

Chúng tôi yêu cầu trình thông dịch phân bổ 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 bản MicroInterpreter có thể cung cấp cho chúng ta 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 tensor này để xác nhận rằng hình dạng và loại của nó đúng như những gì chúng tôi 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 xác định trong common.h .

11. Cung cấp giá trị đầu vào

Để 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 tôi nhập 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 phiên bản tflite::MicroInterpreter của mình:

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 quá trình chạy có thành công hay không. Các giá trị có thể có của TfLiteStatus , được xác định trong common.h , là kTfLiteOkkTfLiteError .

Đoạn mã sau xác nhận rằng giá trị là kTfLiteOk , nghĩa là suy luận đã được chạy thành công.

TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);

13. Lấy đầ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ụ này, đầu ra của mô hình là một giá trị dấu phẩy động duy nhất có trong một tenxơ 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 giá trị trực tiếp từ tensor đầu ra và khẳng định rằng đó là những gì chúng ta 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 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ả 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);