Ce document explique comment entraîner un modèle et exécuter des inférences à l'aide d'un microcontrôleur.
Exemple Hello World
La Bonjour est conçu pour illustrer les principes de base de l'utilisation de LiteRT. pour les microcontrôleurs. Nous entraînons et exécutons un modèle qui réplique une fonction sinus, Autrement dit, elle utilise un seul nombre en entrée et génère le nombre sine. Une fois déployé sur microcontrôleur, ses prédictions sont utilisées pour faire clignoter les LED ou pour contrôler de l'animation.
Le workflow de bout en bout comprend les étapes suivantes:
- Entraîner un modèle (en Python): fichier Python à entraîner, puis à convertir et optimiser un modèle pour une utilisation sur l'appareil.
- Exécuter une inférence (en C++ 17): test unitaire de bout en bout qui exécute des inférences sur le modèle à l'aide de la bibliothèque C++.
Obtenir un appareil compatible
L'exemple d'application que nous allons utiliser a été testé sur les appareils suivants:
- Arduino Nano 33 BLE Sense (avec l'IDE Arduino)
- SparkFun Edge (compilation directe de la source)
- Kit de découverte STM32F746 (avec Mbed)
- Adafruit EdgeBadge (avec Arduino) IDE)
- Kit Adafruit LiteRT pour microcontrôleurs (avec l'IDE Arduino)
- Circuit de fruits de mer des adafruits (avec l'IDE Arduino)
- Espressif ESP32-DevKitC (à l'aide d'ESP IDF)
- Espressif ESP-EYE (à l'aide d'ESP IDF)
Pour en savoir plus sur les plates-formes compatibles, consultez LiteRT for Microcontrollers
Entraîner un modèle
Utilisez train.py entraînement de modèle pour hello world pour la reconnaissance sinwave
Exécution: 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/
Exécuter une inférence
Pour exécuter le modèle sur votre appareil, suivez les instructions fournies dans l'
README.md
:
Les sections suivantes présentent les
evaluate_test.cc
test unitaire montrant comment exécuter une inférence avec LiteRT pour
Microcontrôleurs Elle charge le modèle et exécute des inférences plusieurs fois.
1. Inclure les en-têtes de bibliothèque
Pour utiliser la bibliothèque LiteRT for Microcontrollers, nous devons inclure le les fichiers d'en-tête suivants:
#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
fournit les opérations utilisées par l'interpréteur pour exécuter le modèle.micro_error_reporter.h
génère 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 modèle LiteRTFlatBuffer
.version.h
fournit des informations de gestion des versions pour le schéma LiteRT.
2. Inclure l'en-tête du modèle
L'interpréteur LiteRT for Microcontrollers s'attend à ce que le modèle soit
fourni en tant que tableau C++. Le modèle est défini dans les fichiers model.h
et model.cc
.
L'en-tête est inclus dans la ligne suivante:
#include "tensorflow/lite/micro/examples/hello_world/model.h"
3. Inclure l'en-tête du framework de test unitaire
Pour créer un test unitaire, nous incluons la fonction LiteRT pour Structure de tests unitaires des 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
Examinons maintenant le 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
à une instance tflite::MicroErrorReporter
:
tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = µ_error_reporter;
Cette variable sera transmise à l'interpréteur, qui lui permet d'écrire
journaux. Étant donné que les microcontrôleurs ont souvent une variété
de mécanismes pour la journalisation, le
l'implémentation de tflite::MicroErrorReporter
est conçue pour être personnalisée
sur votre appareil.
5. Charger un modèle
Dans le code suivant, le modèle est instancié à l'aide des données d'un tableau char
.
g_model
, qui est déclaré dans model.h
. Nous vérifions ensuite le modèle pour nous assurer
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. Instancier le résolveur d'opérations
A
MicroMutableOpResolver
est déclarée. L'interprète l'utilisera pour enregistrer et
accéder aux opérations utilisées par le modèle:
using HelloWorldOpResolver = tflite::MicroMutableOpResolver<1>;
TfLiteStatus RegisterOps(HelloWorldOpResolver& op_resolver) {
TF_LITE_ENSURE_STATUS(op_resolver.AddFullyConnected());
return kTfLiteOk;
L'élément MicroMutableOpResolver
nécessite un paramètre de modèle indiquant le nombre
d'opérations qui seront enregistrées. La fonction RegisterOps
enregistre les opérations
avec le résolveur.
HelloWorldOpResolver op_resolver;
TF_LITE_ENSURE_STATUS(RegisterOps(op_resolver));
7. Allouer de la mémoire
Nous devons préallouer une certaine quantité de mémoire
pour l'entrée, la sortie et
des tableaux intermédiaires. Il 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épend du modèle utilisé et devra peut-être être déterminée par des tests.
8. Instancier un interprète
Nous créons une instance tflite::MicroInterpreter
en transmettant les variables
créé précédemment:
tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
tensor_arena_size, error_reporter);
9. Allouer des Tensors
Nous indiquons à l'interpréteur d'allouer de la mémoire à partir de tensor_arena
pour le
les Tensors du modèle:
interpreter.AllocateTensors();
10. Valider la forme de saisie
L'instance MicroInterpreter
peut nous fournir un pointeur vers l'instance
Tensor d'entrée en appelant .input(0)
, où 0
représente le premier (et le seul)
Tensor d'entrée:
// Obtain a pointer to the model's input tensor
TfLiteTensor* input = interpreter.input(0);
Nous inspectons ensuite ce Tensor pour confirmer que sa forme et son type sont bien attendue:
// 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, et est défini dans
common.h
11. Saisir une valeur
Pour fournir une entrée au modèle, nous définissons le contenu du Tensor d'entrée, comme suit : ce qui suit:
input->data.f[0] = 0.;
Dans ce cas, nous saisissons une valeur à virgule flottante représentant 0
.
12. Exécuter le modèle
Pour exécuter le modèle, nous pouvons appeler Invoke()
sur notre tflite::MicroInterpreter
.
instance:
TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
TF_LITE_REPORT_ERROR(error_reporter, "Invoke failed\n");
}
Nous pouvons vérifier la valeur renvoyée, TfLiteStatus
, pour déterminer si l'exécution a été
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é
correctement exécutée.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);
13. Obtenir le résultat
Le Tensor de sortie du modèle peut être obtenu en appelant output(0)
sur la
tflite::MicroInterpreter
, où 0
représente la première (et la seule) sortie
Tensor.
Dans cet exemple, la sortie du modèle est une valeur unique à virgule flottante contenue dans un Tensor bidimensionnel:
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 Tensor de sortie et affirmer que c'est bien 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écuter à nouveau l'inférence
Le reste du code exécute l'inférence plusieurs fois de plus. Dans chaque cas, nous attribuons une valeur au Tensor d'entrée, nous appelons l'interpréteur, puis nous lisons résultat du Tensor 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);