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:
- Modell trainieren (in Python): Eine Python-Datei zum Trainieren und Konvertieren und optimieren Sie ein Modell für die Nutzung auf dem Gerät.
- 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:
- Arduino Nano 33 BLE Sense (mit Arduino-IDE)
- SparkFun Edge (direkte Erstellung aus der Quelle)
- STM32F746 Discovery-Kit (mit MB)
- Adafruit EdgeBadge (mit Arduino) IDE)
- Adafruit LiteRT für Mikrocontroller-Kit (mit Arduino-IDE)
- Adafruit Circuit Playground Bluefruit (mit Arduino-IDE)
- Espressif ESP32-DevKitC (mit ESP IDF)
- Espressif ESP-EYE (mit ESP IDF)
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
:
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"
micro_mutable_op_resolver.h
stellt die Vorgänge bereit, die vom Interpreter zum Ausführen des Modells verwendet werden.micro_error_reporter.h
gibt Debug-Informationen aus.micro_interpreter.h
enthält Code zum Laden und Ausführen von Modellen.schema_generated.h
enthält das Schema für die LiteRT-FlatBuffer
-Modelldateiformat.version.h
stellt Versionsinformationen für das LiteRT-Schema bereit.
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 = µ_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);