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

توضح هذه الوثيقة كيفية تطبيق نموذج وتنفيذ الاستنتاج باستخدام وحدة التحكم الدقيقة.

مثال Hello World

تشير رسالة الأشكال البيانية مرحبًا بالعالم لتوضيح الأساسيات المطلقة لاستخدام LiteRT لوحدات التحكم الدقيقة. نُدرّب نموذجًا ونشغّله ينسخ دالة جيب الزاوية، أي أنه يستخدم رقمًا واحدًا كمدخله، ويقوم بإخراج حساب sin. عند نشرها في وحدة التحكم الدقيقة، تستخدم تنبؤاتها إما لوميض مصابيح LED أو للتحكم في الرسوم المتحركة.

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

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

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

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

مزيد من المعلومات حول الأنظمة الأساسية المتوافقة في 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:

مرحبًا برنامج 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"

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 = &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 إلى أحد قيم 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);