این سند نحوه آموزش یک مدل و اجرای استنتاج با استفاده از یک میکروکنترلر را توضیح می دهد.
مثال سلام جهان
مثال Hello World برای نشان دادن اصول اولیه استفاده از LiteRT برای میکروکنترلرها طراحی شده است. ما مدلی را آموزش میدهیم و اجرا میکنیم که یک تابع سینوسی را تکرار میکند، یعنی یک عدد را به عنوان ورودی میگیرد و مقدار سینوسی عدد را خروجی میکند. هنگامی که روی میکروکنترلر مستقر می شود، پیش بینی های آن برای چشمک زدن LED ها یا کنترل یک انیمیشن استفاده می شود.
گردش کار انتها به انتها شامل مراحل زیر است:
- آموزش یک مدل (در پایتون): یک فایل پایتون برای آموزش، تبدیل و بهینه سازی یک مدل برای استفاده در دستگاه.
- اجرای استنتاج (در C++ 17): یک تست واحد سرتاسری که استنتاج را روی مدل با استفاده از کتابخانه C++ اجرا میکند.
یک دستگاه پشتیبانی شده دریافت کنید
برنامه نمونه ای که ما استفاده خواهیم کرد در دستگاه های زیر آزمایش شده است:
- Arduino Nano 33 BLE Sense (با استفاده از Arduino IDE)
- SparkFun Edge (ساخت مستقیم از منبع)
- کیت کشف STM32F746 (با استفاده از Mbed)
- Adafruit EdgeBadge (با استفاده از Arduino IDE)
- Adafruit LiteRT برای کیت میکروکنترلرها (با استفاده از Arduino IDE)
- Adafruit Circuit Playground Bluefruit (با استفاده از Arduino IDE)
- Espressif ESP32-DevKitC (با استفاده از ESP IDF)
- اسپرسف ESP-EYE (با استفاده از ESP IDF)
درباره پلتفرم های پشتیبانی شده در LiteRT برای میکروکنترلرها بیشتر بیاموزید.
یک مدل تربیت کنید
از train.py برای آموزش مدل hello world برای تشخیص امواج سینوسی استفاده کنید
اجرا: 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/
استنتاج را اجرا کنید
برای اجرای مدل بر روی دستگاه شما، دستورالعملهای موجود در README.md
را بررسی میکنیم:
بخشهای زیر از آزمون واحد evaluate_test.cc
این مثال استفاده میکنند که نحوه اجرای استنتاج با استفاده از LiteRT برای میکروکنترلرها را نشان میدهد. مدل را بارگذاری می کند و استنتاج را چندین بار اجرا می کند.
1. سرصفحه های کتابخانه را وارد کنید
برای استفاده از کتابخانه LiteRT برای میکروکنترلرها، باید فایلهای هدر زیر را اضافه کنیم:
#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
عملیات استفاده شده توسط مفسر برای اجرای مدل را فراهم می کند. -
micro_error_reporter.h
اطلاعات اشکال زدایی را خروجی می کند. -
micro_interpreter.h
حاوی کدی برای بارگیری و اجرای مدل ها است. -
schema_generated.h
شامل طرحی برای قالب فایل مدل LiteRTFlatBuffer
است. -
version.h
اطلاعات نسخهسازی را برای طرح LiteRT فراهم میکند.
2. سربرگ مدل را وارد کنید
مفسر LiteRT برای میکروکنترلرها انتظار دارد که مدل به صورت یک آرایه ++C ارائه شود. مدل در فایل های model.h
و model.cc
تعریف شده است. هدر با خط زیر همراه است:
#include "tensorflow/lite/micro/examples/hello_world/model.h"
3. سربرگ چارچوب تست واحد را وارد کنید
برای ایجاد یک تست واحد، چارچوب تست واحد LiteRT برای میکروکنترلرها را با گنجاندن خط زیر اضافه می کنیم:
#include "tensorflow/lite/micro/testing/micro_test.h"
تست با استفاده از ماکروهای زیر تعریف می شود:
TF_LITE_MICRO_TESTS_BEGIN
TF_LITE_MICRO_TEST(LoadModelAndPerformInference) {
. // add code here
.
}
TF_LITE_MICRO_TESTS_END
اکنون در مورد کد موجود در ماکرو بالا بحث می کنیم.
4. ورود به سیستم را تنظیم کنید
برای تنظیم گزارش، یک نشانگر tflite::ErrorReporter
با استفاده از یک اشاره گر به یک نمونه tflite::MicroErrorReporter
ایجاد می شود:
tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = µ_error_reporter;
این متغیر به مفسر ارسال می شود که به آن اجازه می دهد تا لاگ بنویسد. از آنجایی که میکروکنترلرها اغلب مکانیسمهای مختلفی برای ورود به سیستم دارند، پیادهسازی tflite::MicroErrorReporter
به گونهای طراحی شده است که برای دستگاه خاص شما سفارشی شود.
5. یک مدل را بارگذاری کنید
در کد زیر، مدل با استفاده از داده های آرایه char
، g_model
که در model.h
تعریف شده است، نمونه سازی شده است. سپس مدل را بررسی می کنیم تا مطمئن شویم که نسخه طرحواره آن با نسخه ای که استفاده می کنیم سازگار است:
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. حل کننده عملیات آنی
یک نمونه MicroMutableOpResolver
اعلام شده است. این توسط مفسر برای ثبت و دسترسی به عملیاتی که توسط مدل استفاده می شود استفاده می شود:
using HelloWorldOpResolver = tflite::MicroMutableOpResolver<1>;
TfLiteStatus RegisterOps(HelloWorldOpResolver& op_resolver) {
TF_LITE_ENSURE_STATUS(op_resolver.AddFullyConnected());
return kTfLiteOk;
MicroMutableOpResolver
به یک پارامتر الگو نیاز دارد که تعداد عملیات ثبت شده را نشان دهد. تابع RegisterOps
عملیات را با حل کننده ثبت می کند.
HelloWorldOpResolver op_resolver;
TF_LITE_ENSURE_STATUS(RegisterOps(op_resolver));
7. تخصیص حافظه
ما باید مقدار مشخصی از حافظه را برای ورودی، خروجی و آرایه های میانی تخصیص دهیم. این به عنوان یک آرایه uint8_t
با اندازه tensor_arena_size
ارائه شده است:
const int tensor_arena_size = 2 * 1024;
uint8_t tensor_arena[tensor_arena_size];
اندازه مورد نیاز به مدلی که استفاده می کنید بستگی دارد و ممکن است نیاز باشد با آزمایش تعیین شود.
8. مترجم فوری
ما یک نمونه tflite::MicroInterpreter
ایجاد می کنیم که متغیرهای ایجاد شده قبلی را منتقل می کند:
tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
tensor_arena_size, error_reporter);
9. تانسورها را اختصاص دهید
ما به مفسر می گوییم که حافظه را از tensor_arena
برای تانسورهای مدل اختصاص دهد:
interpreter.AllocateTensors();
10. شکل ورودی را اعتبارسنجی کنید
نمونه MicroInterpreter
می تواند با فراخوانی .input(0)
یک اشاره گر به تانسور ورودی مدل در اختیار ما قرار دهد، جایی که 0
نشان دهنده اولین (و تنها) تانسور ورودی است:
// Obtain a pointer to the model's input tensor
TfLiteTensor* input = interpreter.input(0);
سپس این تانسور را بررسی می کنیم تا تأیید کنیم که شکل و نوع آن همان چیزی است که انتظار داریم:
// 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);
مقدار enum kTfLiteFloat32
ارجاع به یکی از انواع داده LiteRT است و به صورت common.h
تعریف شده است.
11. یک مقدار ورودی ارائه دهید
برای ارائه ورودی به مدل، محتویات تانسور ورودی را به صورت زیر تنظیم می کنیم:
input->data.f[0] = 0.;
در این حالت، یک مقدار ممیز شناور به نمایندگی از 0
وارد می کنیم.
12. مدل را اجرا کنید
برای اجرای مدل، میتوانیم Invoke()
در نمونه tflite::MicroInterpreter
فراخوانی کنیم:
TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
TF_LITE_REPORT_ERROR(error_reporter, "Invoke failed\n");
}
ما میتوانیم مقدار بازگشتی، یک TfLiteStatus
را بررسی کنیم تا مشخص کنیم که آیا اجرا با موفقیت انجام شده است یا خیر. مقادیر ممکن TfLiteStatus
که در common.h
تعریف شده اند kTfLiteOk
و kTfLiteError
هستند.
کد زیر بیان می کند که مقدار kTfLiteOk
است، به این معنی که استنتاج با موفقیت اجرا شد.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);
13. خروجی را بدست آورید
تانسور خروجی مدل را می توان با فراخوانی output(0)
در tflite::MicroInterpreter
به دست آورد، جایی که 0
نشان دهنده اولین (و تنها) تانسور خروجی است.
در مثال، خروجی مدل یک مقدار ممیز شناور منفرد است که در یک تانسور دو بعدی قرار دارد:
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);
میتوانیم مقدار را مستقیماً از تانسور خروجی بخوانیم و ادعا کنیم که همان چیزی است که انتظار داریم:
// 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. استنتاج را دوباره اجرا کنید
بقیه کد چندین بار استنتاج را اجرا می کند. در هر نمونه، یک مقدار به تانسور ورودی اختصاص می دهیم، مفسر را فراخوانی می کنیم و نتیجه را از تانسور خروجی می خوانیم:
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);