Filloni me mikrokontrolluesit

Ky dokument shpjegon se si të trajnoni një model dhe të ekzekutoni konkluzionet duke përdorur një mikrokontrollues.

Shembulli Hello World

Shembulli Hello World është krijuar për të demonstruar bazat absolute të përdorimit të LiteRT për mikrokontrolluesit. Ne trajnojmë dhe ekzekutojmë një model që përsërit një funksion sinus, dmth, merr një numër të vetëm si hyrje të tij dhe nxjerr vlerën e sinusit të numrit. Kur vendoset te mikrokontrolluesi, parashikimet e tij përdoren ose për të ndezur LED ose për të kontrolluar një animacion.

Rrjedha e punës nga fundi në fund përfshin hapat e mëposhtëm:

  1. Trajnimi i një modeli (në Python): Një skedar python për të trajnuar, konvertuar dhe optimizuar një model për përdorim në pajisje.
  2. Ekzekutimi i konkluzionit (në C++ 17): Një test njësie nga fundi në fund që ekzekuton konkluzionet në model duke përdorur bibliotekën C++ .

Merrni një pajisje të mbështetur

Shembulli i aplikacionit që do të përdorim është testuar në pajisjet e mëposhtme:

Mësoni më shumë rreth platformave të mbështetura në LiteRT për mikrokontrolluesit .

Trajnoni një model

Përdor train.py për trajnimin e modelit hello world për njohjen e valëve mëkate

Run: 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/

Ekzekutoni konkluzionet

Për të ekzekutuar modelin në pajisjen tuaj, ne do të kalojmë nëpër udhëzimet në README.md :

Përshëndetje Bota README.md

Seksionet e mëposhtme kalojnë në shembullin e evaluate_test.cc , testi i njësisë i cili demonstron se si të ekzekutoni konkluzionet duke përdorur LiteRT për mikrokontrolluesit. Ai ngarkon modelin dhe ekzekuton konkluzionet disa herë.

1. Përfshini titujt e bibliotekës

Për të përdorur bibliotekën LiteRT për Mikrokontrolluesit, duhet të përfshijmë skedarët e mëposhtëm të kokës:

#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. Përfshi kokën e modelit

Përkthyesi LiteRT për Mikrokontrolluesit pret që modeli të ofrohet si një grup C++. Modeli përcaktohet në skedarët model.h dhe model.cc . Titulli përfshihet me rreshtin e mëposhtëm:

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

3. Përfshi kokën e kornizës së testit të njësisë

Për të krijuar një test njësie, ne përfshijmë kornizën e testimit të njësisë LiteRT për Mikrokontrollues duke përfshirë linjën e mëposhtme:

#include "tensorflow/lite/micro/testing/micro_test.h"

Testi përcaktohet duke përdorur makrot e mëposhtme:

TF_LITE_MICRO_TESTS_BEGIN

TF_LITE_MICRO_TEST(LoadModelAndPerformInference) {
  . // add code here
  .
}

TF_LITE_MICRO_TESTS_END

Tani diskutojmë kodin e përfshirë në makro më lart.

4. Vendosni regjistrimin

Për të konfiguruar regjistrimin, krijohet një tregues tflite::ErrorReporter duke përdorur një tregues në një shembull tflite::MicroErrorReporter :

tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = &micro_error_reporter;

Kjo variabël do të kalojë në interpretues, i cili e lejon atë të shkruajë regjistrat. Meqenëse mikrokontrolluesit shpesh kanë një sërë mekanizmash për regjistrim, zbatimi i tflite::MicroErrorReporter është krijuar për t'u përshtatur për pajisjen tuaj të veçantë.

5. Ngarko një model

Në kodin e mëposhtëm, modeli instantohet duke përdorur të dhëna nga një grup char , g_model , i cili është deklaruar në model.h . Më pas kontrollojmë modelin për t'u siguruar që versioni i skemës së tij është i pajtueshëm me versionin që po përdorim:

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. Zgjidhës i menjëhershëm i operacioneve

Është deklaruar një shembull MicroMutableOpResolver . Kjo do të përdoret nga përkthyesi për të regjistruar dhe aksesuar operacionet që përdoren nga modeli:

using HelloWorldOpResolver = tflite::MicroMutableOpResolver<1>;

TfLiteStatus RegisterOps(HelloWorldOpResolver& op_resolver) {
  TF_LITE_ENSURE_STATUS(op_resolver.AddFullyConnected());
  return kTfLiteOk;

MicroMutableOpResolver kërkon një parametër shabllon që tregon numrin e operacioneve që do të regjistrohen. Funksioni RegisterOps regjistron funksionet me zgjidhësin.

HelloWorldOpResolver op_resolver;
TF_LITE_ENSURE_STATUS(RegisterOps(op_resolver));

7. Alokoni memorien

Ne duhet të paracaktojmë një sasi të caktuar memorie për vargjet hyrëse, dalëse dhe të ndërmjetme. Kjo ofrohet si një grup uint8_t me madhësi tensor_arena_size :

const int tensor_arena_size = 2 * 1024;
uint8_t tensor_arena[tensor_arena_size];

Madhësia e kërkuar do të varet nga modeli që po përdorni dhe mund të duhet të përcaktohet me eksperiment.

8. Përkthyes i çastit

Ne krijojmë një shembull tflite::MicroInterpreter , duke kaluar në variablat e krijuar më parë:

tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
                                     tensor_arena_size, error_reporter);

9. Alokoni tensorët

Ne i themi interpretuesit të ndajë memorie nga tensor_arena për tensorët e modelit:

interpreter.AllocateTensors();

10. Vërtetoni formën e hyrjes

Shembulli MicroInterpreter mund të na japë një tregues për tensorin hyrës të modelit duke thirrur .input(0) , ku 0 përfaqëson tensorin e parë (dhe të vetëm) hyrës:

  // Obtain a pointer to the model's input tensor
  TfLiteTensor* input = interpreter.input(0);

Më pas e inspektojmë këtë tensor për të konfirmuar që forma dhe lloji i tij janë ato që presim:

// 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);

Vlera e numrit kTfLiteFloat32 është një referencë për një nga llojet e të dhënave LiteRT dhe është përcaktuar në common.h .

11. Jepni një vlerë hyrëse

Për të siguruar një hyrje në model, ne vendosim përmbajtjen e tensorit të hyrjes, si më poshtë:

input->data.f[0] = 0.;

Në këtë rast, ne futim një vlerë me pikë lundruese që përfaqëson 0 .

12. Drejtoni modelin

Për të ekzekutuar modelin, ne mund të thërrasim Invoke() në shembullin tonë tflite::MicroInterpreter :

TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
  TF_LITE_REPORT_ERROR(error_reporter, "Invoke failed\n");
}

Ne mund të kontrollojmë vlerën e kthimit, një TfLiteStatus , për të përcaktuar nëse ekzekutimi ishte i suksesshëm. Vlerat e mundshme të TfLiteStatus , të përcaktuara në common.h , janë kTfLiteOk dhe kTfLiteError .

Kodi i mëposhtëm pohon se vlera është kTfLiteOk , që do të thotë se përfundimi u ekzekutua me sukses.

TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);

13. Merrni rezultatin

Tenzori i daljes së modelit mund të merret duke thirrur output(0)tflite::MicroInterpreter , ku 0 përfaqëson tensorin e parë (dhe të vetëm) të daljes.

Në shembull, dalja e modelit është një vlerë e vetme me pikë lundruese e përfshirë brenda një tensor 2D:

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);

Ne mund ta lexojmë vlerën drejtpërdrejt nga tensori i daljes dhe të pohojmë se është ajo që presim:

// 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. Drejtoni përsëri përfundimin

Pjesa e mbetur e kodit ekzekuton konkluzionet disa herë të tjera. Në çdo rast, ne i caktojmë një vlerë tensorit të hyrjes, thërrasim interpretuesin dhe lexojmë rezultatin nga tensori i daljes:

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);