توضح هذه الوثيقة كيفية تطبيق نموذج وتنفيذ الاستنتاج باستخدام وحدة التحكم الدقيقة.
مثال Hello World
تشير رسالة الأشكال البيانية مرحبًا بالعالم لتوضيح الأساسيات المطلقة لاستخدام LiteRT لوحدات التحكم الدقيقة. نُدرّب نموذجًا ونشغّله ينسخ دالة جيب الزاوية، أي أنه يستخدم رقمًا واحدًا كمدخله، ويقوم بإخراج حساب sin. عند نشرها في وحدة التحكم الدقيقة، تستخدم تنبؤاتها إما لوميض مصابيح LED أو للتحكم في الرسوم المتحركة.
يتضمن سير العمل الشامل الخطوات التالية:
- تدريب نموذج (في بايثون): ملف بايثون لتدريبه وتحويله وتحسين نموذج للاستخدام على الجهاز فقط
- تنفيذ الاستنتاج (في لغة C++ 17): اختبار شامل للوحدة ويجري الاستنتاج على النموذج باستخدام مكتبة C++.
الحصول على جهاز متوافق
لقد تم اختبار مثال التطبيق الذي سنستخدمه على الأجهزة التالية:
- Arduino Nano 33 BLE Sense (باستخدام Arduino IDE)
- SparkFun Edge (الإنشاء مباشرةً من المصدر)
- مجموعة أدوات STM32F746 Discovery (باستخدام Mbed)
- Adafruit EdgeBadge (باستخدام Arduino بيئة تطوير متكاملة (IDE)
- Adafruit LiteRT لمجموعة وحدات التحكّم الدقيقة (باستخدام Arduino IDE)
- حديقة مطار Adafruit Bluefruit (باستخدام Arduino IDE)
- Espressif ESP32-DevKitC (باستخدام ESP IDF)
- Espressif ESP-EYE (باستخدام ESP IDF)
مزيد من المعلومات حول الأنظمة الأساسية المتوافقة في LiteRT لأدوات التحكّم الدقيقة
تدريب نموذج
استخدام train.py للحصول على تدريب النموذج العالمي للتعرّف على الموجات السينية (sinwave)
تشغيل: 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
يحتوي على المخطط الخاص بـ LiteRT تنسيق ملف النموذجFlatBuffer
: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);
تشير قيمة التعداد 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);