بدء استخدام وحدات التحكّم الدقيقة

يشرح هذا المستند كيفية تدريب نموذج وإجراء الاستنتاج باستخدام وحدة تحكُّم دقيقة.

مثال Hello World

تم تصميم مثال Hello World لتوضيح الأساسيات المطلقة لاستخدام TensorFlow Lite لوحدات التحكم الدقيقة. نُدرِب ونشغل نموذجًا يكرر دالة sin هذه، أي يأخذ رقمًا واحدًا كمدخلات له، ويخرج قيمة sin هذه للرقم. وعند نشرها على وحدة التحكم الدقيقة، يتم استخدام تنبؤاتها إما لوميض مصابيح LED أو التحكم في إحدى الرسوم المتحركة.

يتضمن سير العمل المتكامل الخطوات التالية:

  1. تدريب نموذج (في بايثون): ملف python لتدريب نموذج وتحويله وتحسينه للاستخدام على الجهاز فقط.
  2. إجراء الاستدلال (في C++ 17): اختبار شامل يجري استنتاجًا بشأن النموذج باستخدام مكتبة C++.

الحصول على جهاز متوافق

تم اختبار نموذج التطبيق الذي سنستخدمه على الأجهزة التالية:

يمكنك الاطّلاع على مزيد من المعلومات حول المنصات المتوافقة في TensorFlow Lite لوحدات التحكّم الدقيقة.

تدريب نموذج

استخدم train.py لتدريب النموذج العالمي مرحبًا للتعرف على الموجات الجيبية

تنفيذ: 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:

مرحبًا بالعالم README.md

ترشدك الأقسام التالية في المثال على اختبار الوحدة evaluate_test.cc الذي يوضّح كيفية إجراء الاستنتاج باستخدام TensorFlow Lite لوحدات التحكّم الدقيقة. تُحمّل النموذج وتنفذ الاستنتاج عدة مرات.

1- تضمين عناوين المكتبة

لاستخدام مكتبة TensorFlow Lite عن وحدات التحكّم الصغيرة، يجب تضمين ملفات العناوين التالية:

#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 تحتوي على مخطط تنسيق ملف نموذج TensorFlow Lite FlatBuffer.
  • توفِّر version.h معلومات عن الإصدارات لمخطط TensorFlow Lite.

2. تضمين عنوان النموذج

يتوقع مترجم TensorFlow Lite لوحدات التحكم الدقيقة تقديم النموذج كمصفوفة C++. يتم تحديد النموذج في model.h وmodel.cc ملف. يتم تضمين العنوان مع السطر التالي:

#include "tensorflow/lite/micro/examples/hello_world/model.h"

3- تضمين عنوان إطار عمل اختبار الوحدة

من أجل إنشاء اختبار وحدة، نُدرج إطار عمل اختبار وحدات TensorFlow Lite لوحدات التحكم الدقيقة من خلال تضمين السطر التالي:

#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 = &micro_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 إلى أحد أنواع بيانات TensorFlow Lite، وهي معرَّفة في 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);