Erste Schritte mit Mikrocontrollern

In diesem Dokument wird erläutert, wie Sie ein Modell trainieren und eine Inferenz mit einem Mikrocontroller.

Das Hello World-Beispiel

Die Hallo Welt soll die absoluten Grundlagen der Verwendung von LiteRT aufzeigen. für Mikrocontroller. Wir trainieren und führen ein Modell aus, das eine Sinusfunktion repliziert, d.h., sie verwendet eine einzelne Zahl als Eingabe und gibt die sine-Wert. Bei der Bereitstellung im verwendet werden, werden anhand seiner Vorhersagen entweder LEDs blinken oder ein Animation.

Der End-to-End-Workflow umfasst die folgenden Schritte:

  1. Modell trainieren (in Python): Eine Python-Datei zum Trainieren und Konvertieren und optimieren Sie ein Modell für die Nutzung auf dem Gerät.
  2. Inferenz ausführen (in C++ 17): Ein End-to-End-Einheitentest, der führt mit der C++-Bibliothek eine Inferenz auf das Modell aus.

Unterstütztes Gerät kaufen

Die von uns verwendete Beispielanwendung wurde auf den folgenden Geräten getestet:

Weitere Informationen zu unterstützten Plattformen finden Sie unter LiteRT für Mikrocontroller

Modell trainieren

Verwenden Sie train.py für das Hello World-Modelltraining für die Sinwellenerkennung

Ausführen: 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/

Inferenz ausführen

Um das Modell auf Ihrem Gerät auszuführen, folgen Sie der Anleitung in der README.md:

Hallo World README.md

In den folgenden Abschnitten werden die evaluate_test.cc, Einheitentest, der zeigt, wie Inferenzen mit LiteRT Mikrocontroller. Er lädt das Modell und führt die Inferenz mehrmals aus.

1. Bibliotheksheader einschließen

Zur Verwendung der LiteRT-Bibliothek für Mikrocontroller müssen wir den Parameter folgende Headerdateien:

#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. Modellheader einschließen

Der Interpreter für LiteRT für Mikrocontroller erwartet, dass das Modell als C++ -Array angegeben. Das Modell ist in den Dateien model.h und model.cc definiert. Der Header ist in der folgenden Zeile enthalten:

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

3. Header des Unittest-Frameworks hinzufügen

Um einen Unittest zu erstellen, fügen wir das LiteRT für Mikrocontroller-Einheitentest-Framework, indem Sie die folgende Zeile einfügen:

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

Der Test wird mithilfe der folgenden Makros definiert:

TF_LITE_MICRO_TESTS_BEGIN

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

TF_LITE_MICRO_TESTS_END

Nun wird der Code im obigen Makro erörtert.

4. Logging einrichten

Zum Einrichten des Loggings wird mit einem Zeiger ein tflite::ErrorReporter-Zeiger erstellt in eine tflite::MicroErrorReporter-Instanz:

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

Diese Variable wird an den Interpreter übergeben, der es ermöglicht, Logs. Da Mikrocontroller oft über verschiedene Mechanismen für die Protokollierung verfügen, sind die Die Implementierung von tflite::MicroErrorReporter ist auf die an dein jeweiliges Gerät.

5. Modell laden

Im folgenden Code wird das Modell mit Daten aus einem char-Array instanziiert. g_model, die in model.h deklariert ist. Dann überprüfen wir das Modell, um sicherzustellen, schema-Version ist mit der von uns verwendeten Version kompatibel:

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. Resolver für Vorgänge instanziieren

A MicroMutableOpResolver -Instanz deklariert ist. Diese Information wird vom Dolmetscher verwendet, um sich zu registrieren und Auf die Vorgänge zugreifen, die vom Modell verwendet werden:

using HelloWorldOpResolver = tflite::MicroMutableOpResolver<1>;

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

MicroMutableOpResolver erfordert einen Vorlagenparameter, der die Nummer angibt der Operationen, die registriert werden. Die Funktion RegisterOps registriert die Vorgänge. mit dem Resolver.

HelloWorldOpResolver op_resolver;
TF_LITE_ENSURE_STATUS(RegisterOps(op_resolver));

7. Arbeitsspeicher zuweisen

Wir müssen eine bestimmte Arbeitsspeichermenge für Ein- und Ausgabe Zwischenarrays. Wird als uint8_t-Array der Größe bereitgestellt tensor_arena_size:

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

Die erforderliche Größe hängt vom verwendeten Modell ab und muss unter Umständen durch Experimente ermittelt.

8. Interpreter instanziieren

Wir erstellen eine tflite::MicroInterpreter-Instanz und übergeben dabei die Variablen zuvor erstellt:

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

9. Tensoren zuweisen

Wir weisen den Interpreter an, Arbeitsspeicher aus tensor_arena für den Modell-Tensoren:

interpreter.AllocateTensors();

10. Eingabeform validieren

Die Instanz MicroInterpreter kann einen Verweis auf die Modell- Eingabetensor durch Aufrufen von .input(0), wobei 0 den ersten (und einzigen) Eingabetensor:

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

Wir prüfen dann diesen Tensor, um zu bestätigen, dass seine Form und sein Typ unsere erwartet:

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

Der ENUM-Wert kTfLiteFloat32 verweist auf einen der LiteRT- Datentypen und wird in common.h

11. Geben Sie einen Wert ein

Um eine Eingabe für das Modell zu liefern, legen wir den Inhalt des Eingabetensors so fest: folgt:

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

In diesem Fall geben wir einen Gleitkommawert ein, der 0 darstellt.

12. Modell ausführen

Zum Ausführen des Modells können wir Invoke() in unserem tflite::MicroInterpreter aufrufen. Instanz:

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

Wir können den Rückgabewert TfLiteStatus prüfen, um festzustellen, ob die Ausführung erfolgreich war. Die möglichen Werte von TfLiteStatus, definiert in common.h, sind kTfLiteOk und kTfLiteError.

Der folgende Code bestätigt, dass der Wert kTfLiteOk ist, was bedeutet, dass die Inferenz war erfolgreich ausgeführt wird.

TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);

13. Ausgabe abrufen

Um den Ausgabetensor des Modells zu erhalten, rufen Sie output(0) für die tflite::MicroInterpreter, wobei 0 die erste (und einzige) Ausgabe darstellt Tensor.

In diesem Beispiel ist die Ausgabe des Modells ein einzelner Gleitkommawert, der innerhalb eines 2D-Tensors:

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

Wir können den Wert direkt aus dem Ausgabetensor lesen und bestätigen, erwarten wir:

// 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. Inferenz noch einmal ausführen

Der Rest des Codes führt mehrmals die Inferenz aus. In jedem Fall weisen wir dem Eingabetensor einen Wert zu, rufen den Interpreter auf und Ergebnis des Ausgabetensors:

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