Ce document explique comment entraîner un modèle et exécuter une inférence à l'aide d'un microcontrôleur.
L'exemple Hello World
L'exemple Hello World est conçu pour démontrer les bases absolues de l'utilisation de TensorFlow Lite pour les microcontrôleurs. Nous entraînons et exécutons un modèle qui reproduit une fonction sinusoïdale, c'est-à-dire qu'il prend un seul nombre en entrée et produit la valeur sinusoïdale du nombre. Lorsqu'elles sont déployées sur le microcontrôleur, ses prédictions sont utilisées pour faire clignoter des LED ou contrôler une animation.
Le workflow de bout en bout comprend les étapes suivantes :
- Entraîner un modèle (en Python) : un bloc-notes jupyter pour entraîner, convertir et optimiser un modèle pour une utilisation sur l'appareil.
- Exécuter l'inférence (en C++ 11) : test unitaire de bout en bout qui exécute l'inférence sur le modèle à l'aide de la bibliothèque C++ .
Obtenir un appareil pris en charge
L'exemple d'application que nous allons utiliser a été testé sur les appareils suivants :
- Arduino Nano 33 BLE Sense (avec Arduino IDE)
- SparkFun Edge (construire directement à partir de la source)
- Kit de découverte STM32F746 (avec Mbed)
- Adafruit EdgeBadge (utilisant l'IDE Arduino)
- Kit Adafruit TensorFlow Lite pour microcontrôleurs (utilisant Arduino IDE)
- Adafruit Circuit Playground Bluefruit (avec Arduino IDE)
- Espressif ESP32-DevKitC (avec ESP IDF)
- Espressif ESP-EYE (avec ESP IDF)
En savoir plus sur les plates-formes prises en charge dans TensorFlow Lite pour microcontrôleurs .
Former un modèle
Utilisez Google Colaboratory pour entraîner votre propre modèle . Pour plus de détails, reportez-vous au README.md
:
Formation Hello World README.md
Exécuter l'inférence
Pour exécuter le modèle sur votre appareil, nous allons parcourir les instructions du README.md
:
Les sections suivantes décrivent le test unitaire hello_world_test.cc
de l'exemple qui montre comment exécuter l'inférence à l'aide de TensorFlow Lite pour les microcontrôleurs. Il charge le modèle et exécute l'inférence plusieurs fois.
1. Inclure les en-têtes de bibliothèque
Pour utiliser la bibliothèque TensorFlow Lite for Microcontrollers, nous devons inclure les fichiers d'en-tête suivants :
#include "tensorflow/lite/micro/all_ops_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"
-
all_ops_resolver.h
fournit les opérations utilisées par l'interpréteur pour exécuter le modèle. -
micro_error_reporter.h
des informations de débogage. -
micro_interpreter.h
contient du code pour charger et exécuter des modèles. -
schema_generated.h
contient le schéma du format de fichier de modèle TensorFlow LiteFlatBuffer
. -
version.h
fournit des informations de version pour le schéma TensorFlow Lite.
2. Inclure l'en-tête du modèle
L'interpréteur TensorFlow Lite pour microcontrôleurs s'attend à ce que le modèle soit fourni sous la forme d'un tableau C++. Le modèle est défini dans les fichiers model.h
et model.cc
. L'en-tête est inclus avec la ligne suivante :
#include "tensorflow/lite/micro/examples/hello_world/model.h"
3. Inclure l'en-tête du framework de test unitaire
Afin de créer un test unitaire, nous incluons le framework de test unitaire TensorFlow Lite pour microcontrôleurs en incluant la ligne suivante :
#include "tensorflow/lite/micro/testing/micro_test.h"
Le test est défini à l'aide des macros suivantes :
TF_LITE_MICRO_TESTS_BEGIN
TF_LITE_MICRO_TEST(LoadModelAndPerformInference) {
. // add code here
.
}
TF_LITE_MICRO_TESTS_END
Nous discutons maintenant du code inclus dans la macro ci-dessus.
4. Configurer la journalisation
Pour configurer la journalisation, un pointeur tflite::ErrorReporter
est créé à l'aide d'un pointeur vers une instance tflite::MicroErrorReporter
:
tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = µ_error_reporter;
Cette variable sera transmise à l'interpréteur, ce qui lui permet d'écrire des journaux. Étant donné que les microcontrôleurs ont souvent une variété de mécanismes de journalisation, l'implémentation de tflite::MicroErrorReporter
est conçue pour être personnalisée pour votre appareil particulier.
5. Charger un modèle
Dans le code suivant, le modèle est instancié à l'aide des données d'un tableau de char
, g_model
, qui est déclaré dans model.h
. Nous vérifions ensuite le modèle pour nous assurer que sa version de schéma est compatible avec la version que nous utilisons :
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. Résolveur d'opérations d'instanciation
Une instance AllOpsResolver
est déclarée. Il sera utilisé par l'interpréteur pour accéder aux opérations utilisées par le modèle :
tflite::AllOpsResolver resolver;
Le AllOpsResolver
charge toutes les opérations disponibles dans TensorFlow Lite pour microcontrôleurs, qui utilise beaucoup de mémoire. Étant donné qu'un modèle donné n'utilisera qu'un sous-ensemble de ces opérations, il est recommandé que les applications du monde réel ne chargent que les opérations nécessaires.
Ceci est fait en utilisant une classe différente, MicroMutableOpResolver
. Vous pouvez voir comment l'utiliser dans micro_speech_test.cc
de l'exemple Micro speech .
7. Allouer de la mémoire
Nous devons préallouer une certaine quantité de mémoire pour les tableaux d'entrée, de sortie et intermédiaires. Ceci est fourni sous la forme d'un tableau uint8_t
de taille tensor_arena_size
:
const int tensor_arena_size = 2 * 1024;
uint8_t tensor_arena[tensor_arena_size];
La taille requise dépendra du modèle que vous utilisez et devra peut-être être déterminée par expérimentation.
8. Interprète instancié
Nous créons une instance tflite::MicroInterpreter
, en passant les variables créées précédemment :
tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
tensor_arena_size, error_reporter);
9. Attribuer des tenseurs
Nous disons à l'interpréteur d'allouer de la mémoire depuis le tensor_arena
pour les tenseurs du modèle :
interpreter.AllocateTensors();
10. Valider la forme d'entrée
L'instance MicroInterpreter
peut nous fournir un pointeur vers le tenseur d'entrée du modèle en appelant .input(0)
, où 0
représente le premier (et unique) tenseur d'entrée :
// Obtain a pointer to the model's input tensor
TfLiteTensor* input = interpreter.input(0);
Nous inspectons ensuite ce tenseur pour confirmer que sa forme et son type correspondent à ce que nous attendons :
// 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);
La valeur d'énumération kTfLiteFloat32
est une référence à l'un des types de données TensorFlow Lite et est définie dans common.h
.
11. Fournissez une valeur d'entrée
Pour fournir une entrée au modèle, nous définissons le contenu du tenseur d'entrée, comme suit :
input->data.f[0] = 0.;
Dans ce cas, nous saisissons une valeur à virgule flottante représentant 0
.
12. Exécutez le modèle
Pour exécuter le modèle, nous pouvons appeler Invoke()
sur notre instance tflite::MicroInterpreter
:
TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
TF_LITE_REPORT_ERROR(error_reporter, "Invoke failed\n");
}
Nous pouvons vérifier la valeur de retour, un TfLiteStatus
, pour déterminer si l'exécution a réussi. Les valeurs possibles de TfLiteStatus
, définies dans common.h
, sont kTfLiteOk
et kTfLiteError
.
Le code suivant affirme que la valeur est kTfLiteOk
, ce qui signifie que l'inférence a été exécutée avec succès.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);
13. Obtenir la sortie
Le tenseur de sortie du modèle peut être obtenu en appelant output(0)
sur le tflite::MicroInterpreter
, où 0
représente le premier (et le seul) tenseur de sortie.
Dans l'exemple, la sortie du modèle est une seule valeur à virgule flottante contenue dans un tenseur 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);
Nous pouvons lire la valeur directement à partir du tenseur de sortie et affirmer que c'est ce que nous attendons :
// 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. Exécutez à nouveau l'inférence
Le reste du code exécute l'inférence plusieurs fois. Dans chaque cas, nous attribuons une valeur au tenseur d'entrée, invoquons l'interpréteur et lisons le résultat du tenseur de sortie :
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);
15. Lisez le code de l'application
Une fois que vous avez parcouru ce test unitaire, vous devriez être en mesure de comprendre le code d'application de l'exemple, situé dans main_functions.cc
. Il suit un processus similaire, mais génère une valeur d'entrée basée sur le nombre d'inférences exécutées et appelle une fonction spécifique à l'appareil qui affiche la sortie du modèle à l'utilisateur.