In diesem Dokument wird erläutert, wie Sie ein Modell trainieren und Inferenzen mithilfe eines Mikrocontrollers ausführen.
Hello World-Beispiel
Das Beispiel Hello World soll die absoluten Grundlagen der Verwendung von TensorFlow Lite für Mikrocontroller demonstrieren. Wir trainieren und führen ein Modell aus, das eine Sinusfunktion repliziert. Das heißt, es verwendet eine einzelne Zahl als Eingabe und gibt den Sinuswert der Zahl aus. Bei der Bereitstellung auf dem Mikrocontroller werden die Vorhersagen entweder dazu verwendet, LEDs zu blinken oder eine Animation zu steuern.
Der End-to-End-Workflow umfasst die folgenden Schritte:
- Modell trainieren (in Python): Eine Python-Datei zum Trainieren, Konvertieren und Optimieren eines Modells für die Verwendung auf dem Gerät.
- Inferenz ausführen (in C++ 17): Ein End-to-End-Unittest, der mithilfe der C++-Bibliothek eine Inferenz für das Modell ausführt.
Unterstütztes Gerät kaufen
Die Beispielanwendung, die wir verwenden werden, wurde auf den folgenden Geräten getestet:
- Arduino Nano 33 BLE Sense (mit Arduino-IDE)
- SparkFun Edge (direkt aus der Quelle erstellen)
- STM32F746 Discovery-Kit (mit Mbed)
- Adafruit EdgeBadge (mit Arduino IDE)
- Adafruit TensorFlow Lite for Microcontrollers Kit (mit Arduino-IDE)
- Adafruit Circuit Playground Bluefruit (mit der Arduino-IDE)
- Espressif ESP32-DevKitC (mit ESP IDF)
- Espressif ESP-EYE (mit ESP IDF)
Weitere Informationen zu unterstützten Plattformen finden Sie unter TensorFlow Lite für Mikrocontroller.
Modell trainieren
Verwenden Sie train.py für das Hello World-Modelltraining für die Sinwave-Erkennung
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 deinem Gerät auszuführen, folgen wir der Anleitung in der README.md
:
In den folgenden Abschnitten wird der Einheitentest evaluate_test.cc
aus diesem Beispiel beschrieben, der zeigt, wie Inferenzen mit TensorFlow Lite für Mikrocontroller ausgeführt werden. Das Modell wird geladen und die Inferenz wird mehrmals ausgeführt.
1. Bibliotheks-Header einschließen
Zur Verwendung der TensorFlow Lite for Microcontrollers-Bibliothek müssen die folgenden Header-Dateien hinzugefügt werden:
#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 der Interpreter zum Ausführen des Modells verwendet.micro_error_reporter.h
gibt Informationen zur Fehlerbehebung aus.micro_interpreter.h
enthält Code zum Laden und Ausführen von Modellen.schema_generated.h
enthält das Schema für das Dateiformat des TensorFlow Lite-FlatBuffer
-Modells.version.h
stellt Versionierungsinformationen für das TensorFlow Lite-Schema bereit.
2. Modellheader einschließen
Der Interpreter für TensorFlow Lite for Microcontrollers erwartet, dass das Modell als C++-Array bereitgestellt wird. 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. Framework-Header für Einheitentest einfügen
Um einen Einheitentest zu erstellen, fügen wir das Framework für TensorFlow Lite for Microcontrollers-Einheitentests hinzu, indem wir 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
Wir besprechen nun den Code, der im obigen Makro enthalten ist.
4. Logging einrichten
Zum Einrichten von Logging wird ein tflite::ErrorReporter
-Zeiger mit einem Zeiger auf eine tflite::MicroErrorReporter
-Instanz erstellt:
tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = µ_error_reporter;
Diese Variable wird an den Interpreter übergeben, der ihm das Schreiben von Logs ermöglicht. Da Mikrocontroller oft eine Vielzahl von Mechanismen für das Logging haben, ist die Implementierung von tflite::MicroErrorReporter
so konzipiert, dass sie für Ihr jeweiliges Gerät angepasst wird.
5. Modell laden
Im folgenden Code wird das Modell mit Daten aus dem char
-Array g_model
instanziiert, das in model.h
deklariert ist. Anschließend prüfen wir, ob die Schemaversion des Modells mit der von uns verwendeten Version kompatibel ist:
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. Vorgangs-Resolver instanziieren
Es wird eine MicroMutableOpResolver
-Instanz deklariert. Diese wird vom Interpreter verwendet, um die vom Modell verwendeten Vorgänge zu registrieren und darauf zuzugreifen:
using HelloWorldOpResolver = tflite::MicroMutableOpResolver<1>;
TfLiteStatus RegisterOps(HelloWorldOpResolver& op_resolver) {
TF_LITE_ENSURE_STATUS(op_resolver.AddFullyConnected());
return kTfLiteOk;
Für MicroMutableOpResolver
ist ein Vorlagenparameter erforderlich, der die Anzahl der zu registrierenden Vorgänge angibt. Die Funktion RegisterOps
registriert die Vorgänge beim Resolver.
HelloWorldOpResolver op_resolver;
TF_LITE_ENSURE_STATUS(RegisterOps(op_resolver));
7. Arbeitsspeicher zuweisen
Wir müssen eine bestimmte Menge an Speicher für Eingabe-, Ausgabe- und Zwischenarrays vorab zuweisen. Sie wird als uint8_t
-Array der Größe tensor_arena_size
bereitgestellt:
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 möglicherweise durch Experimente bestimmt werden.
8. Dolmetscher instanziieren
Wir erstellen eine tflite::MicroInterpreter
-Instanz und übergeben die zuvor erstellten Variablen:
tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
tensor_arena_size, error_reporter);
9. Tensoren zuweisen
Wir weisen den Interpreter an, Speicher aus dem tensor_arena
für die Tensoren des Modells zuzuweisen:
interpreter.AllocateTensors();
10. Eingabeform validieren
Die MicroInterpreter
-Instanz kann durch Aufrufen von .input(0)
einen Zeiger auf den Eingabetensor des Modells bereitstellen, wobei 0
den ersten (und einzigen) Eingabetensor darstellt:
// Obtain a pointer to the model's input tensor
TfLiteTensor* input = interpreter.input(0);
Anschließend prüfen wir, ob Form und Typ des Tensors den Erwartungen entsprechen:
// 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 TensorFlow Lite-Datentypen und ist in common.h
definiert.
11. Eingabewert angeben
Um eine Eingabe für das Modell bereitzustellen, legen wir den Inhalt des Eingabetensors so fest:
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 Sie Invoke()
in der tflite::MicroInterpreter
-Instanz aufrufen:
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 in common.h
definierten Werte für TfLiteStatus
sind kTfLiteOk
und kTfLiteError
.
Der folgende Code bestätigt, dass der Wert kTfLiteOk
ist, was bedeutet, dass die Inferenz erfolgreich ausgeführt wurde.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);
13. Ausgabe abrufen
Sie können den Ausgabetensor des Modells abrufen, indem Sie output(0)
für tflite::MicroInterpreter
aufrufen, wobei 0
den ersten (und einzigen) Ausgabetensor darstellt.
In diesem Beispiel ist die Ausgabe des Modells ein einzelner Gleitkommawert, der in einem 2D-Tensor enthalten ist:
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, dass er unseren Erwartungen entspricht:
// 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
Im Rest des Codes werden mehrere Male Inferenzen ausgeführt. In jeder Instanz weisen wir dem Eingabetensor einen Wert zu, rufen den Interpreter auf und lesen das Ergebnis aus dem Ausgabetensor:
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);