ابدأ مع ميكروكنترولر

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

مثال مرحبا العالم

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

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

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

احصل على جهاز مدعوم

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

تعرف على المزيد حول الأنظمة الأساسية المدعومة في TensorFlow Lite لوحدات التحكم الدقيقة .

تدريب نموذج

استخدم Train.py للتدريب على نموذج helloworld للتعرف على الموجات الخطية

تشغيل: 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"

2. قم بتضمين رأس النموذج

يتوقع مترجم TensorFlow Lite for Microcontrollers أن يتم توفير النموذج كمصفوفة 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);