Questo documento spiega come addestrare un modello ed eseguire l'inferenza utilizzando un un microcontrollore.
Esempio di Hello World
La Hello World è progettato per dimostrare le nozioni di base assolute sull'utilizzo di LiteRT per i microcontroller. Addestriamo ed eseguiamo un modello che replica una funzione sinusoidale, cioè prende un singolo numero come input e restituisce la sine. Una volta eseguito il deployment microcontroller, le sue previsioni vengono utilizzate per far lampeggiare i LED o per controllare l'animazione.
Il flusso di lavoro end-to-end prevede i seguenti passaggi:
- Addestra un modello (in Python): un file Python da addestrare, convertire e ottimizzare un modello per l'uso sul dispositivo.
- Esegui l'inferenza (in C++ 17): un test delle unità end-to-end che esegue l'inferenza sul modello utilizzando la libreria C++.
Ottieni un dispositivo supportato
L'applicazione di esempio che utilizzeremo è stata testata sui seguenti dispositivi:
- Arduino Nano 33 BLE Sense (con l'IDE di Arduino)
- SparkFun Edge (costruire direttamente dall'origine)
- Kit Discovery STM32F746 (con Mbed)
- Adafruit EdgeBadge (con Arduino IDE).
- Kit Adafruit LiteRT per microcontroller (con l'IDE di Arduino)
- Parco giochi del circuito Adafruit Bluefruit (con l'IDE di Arduino)
- Espressif ESP32-DevKitC (utilizzando ESP IDF)
- Espressif ESP-EYE (utilizzando ESP IDF)
Scopri di più sulle piattaforme supportate in LiteRT per microcontroller.
Addestra un modello
Utilizza le funzionalità di train.py per l'addestramento di modelli hello world per il riconoscimento sinwave
Corsa: 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/
Esegui inferenza
Per eseguire il modello sul tuo dispositivo, seguiremo le istruzioni fornite nella
README.md
:
Ciao README.md a livello mondiale
Le seguenti sezioni descrivono nel dettaglio la configurazione
evaluate_test.cc
,
un test unitario che dimostra come eseguire l'inferenza utilizzando LiteRT
Microcontrollori Carica il modello ed esegue l'inferenza più volte.
1. Includi le intestazioni della libreria
Per utilizzare la libreria LiteRT for Microcontrollers, dobbiamo includere il parametro i seguenti file di intestazione:
#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
fornisce le operazioni utilizzate dall'interprete per eseguire il modello.micro_error_reporter.h
restituisce le informazioni di debug.micro_interpreter.h
contiene il codice per caricare ed eseguire i modelli.schema_generated.h
contiene lo schema per LiteRT Formato file del modelloFlatBuffer
.version.h
fornisce informazioni sul controllo delle versioni per lo schema LiteRT.
2. Includi l'intestazione del modello
L'interprete di LiteRT per microcontroller si aspetta che il modello sia
come array C++. Il modello è definito nei file model.h
e model.cc
.
L'intestazione è inclusa con la seguente riga:
#include "tensorflow/lite/micro/examples/hello_world/model.h"
3. Includi l'intestazione del framework di test delle unità
Per creare un test delle unità, includiamo il valore LiteRT per Framework per il test delle unità dei microcontroller includendo la seguente riga:
#include "tensorflow/lite/micro/testing/micro_test.h"
Il test viene definito utilizzando le seguenti macro:
TF_LITE_MICRO_TESTS_BEGIN
TF_LITE_MICRO_TEST(LoadModelAndPerformInference) {
. // add code here
.
}
TF_LITE_MICRO_TESTS_END
Ora esaminiamo il codice incluso nella macro riportata sopra.
4. Configura il logging
Per configurare il logging, viene creato un puntatore tflite::ErrorReporter
utilizzando un puntatore
a un'istanza tflite::MicroErrorReporter
:
tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = µ_error_reporter;
Questa variabile verrà passata all'interprete, il che gli consente di scrivere
logaritmi. Poiché i microcontroller spesso hanno diversi meccanismi per la registrazione,
di implementazione di tflite::MicroErrorReporter
è progettata per essere personalizzata
dispositivo specifico.
5. Carica un modello
Nel codice seguente, viene creata un'istanza del modello utilizzando i dati di un array char
,
g_model
, dichiarato in model.h
. Quindi controlliamo il modello per assicurarci che
dello schema è compatibile con la versione che stiamo utilizzando:
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. Creare un'istanza resolver delle operazioni
R
MicroMutableOpResolver
viene dichiarata l'istanza principale. Verrà utilizzato dall'interprete per registrarsi e
alle operazioni utilizzate dal modello:
using HelloWorldOpResolver = tflite::MicroMutableOpResolver<1>;
TfLiteStatus RegisterOps(HelloWorldOpResolver& op_resolver) {
TF_LITE_ENSURE_STATUS(op_resolver.AddFullyConnected());
return kTfLiteOk;
MicroMutableOpResolver
richiede un parametro del modello che indichi il numero
di operazioni che verranno registrate. La funzione RegisterOps
registra le operazioni
con il resolver.
HelloWorldOpResolver op_resolver;
TF_LITE_ENSURE_STATUS(RegisterOps(op_resolver));
7. Alloca memoria
Dobbiamo preallocare una certa quantità di memoria per l'input,
array intermedi. Viene fornito come un array uint8_t
di dimensioni
tensor_arena_size
:
const int tensor_arena_size = 2 * 1024;
uint8_t tensor_arena[tensor_arena_size];
Le dimensioni richieste dipendono dal modello in uso e potrebbero essere necessarie determinato dalla sperimentazione.
8. Crea un'istanza per l'interprete
Creiamo un'istanza tflite::MicroInterpreter
, passando le variabili
creati in precedenza:
tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
tensor_arena_size, error_reporter);
9. Alloca i tensori
Chiediamo all'interprete di allocare la memoria da tensor_arena
per
tensori del modello:
interpreter.AllocateTensors();
10. Convalida forma di input
L'istanza MicroInterpreter
può fornirci un puntatore alla riga del modello
tensore di input chiamando .input(0)
, dove 0
rappresenta il primo (e solo)
tensore di input:
// Obtain a pointer to the model's input tensor
TfLiteTensor* input = interpreter.input(0);
Ispezioniamo quindi questo tensore per confermare che la sua forma e il suo tipo corrispondono a quelli che stiamo in attesa:
// 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);
Il valore enum kTfLiteFloat32
fa riferimento a uno dei valori LiteRT
tipi di dati ed è definito in
common.h
11. Specifica un valore di input
Per fornire un input al modello, impostiamo il contenuto del tensore di input, come che segue:
input->data.f[0] = 0.;
In questo caso, inseriamo un valore con virgola mobile che rappresenta 0
.
12. Esegui il modello
Per eseguire il modello, possiamo chiamare Invoke()
sul nostro tflite::MicroInterpreter
istanza:
TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
TF_LITE_REPORT_ERROR(error_reporter, "Invoke failed\n");
}
Possiamo controllare il valore restituito, TfLiteStatus
, per determinare se l'esecuzione è stata
riuscito. I possibili valori di TfLiteStatus
, definiti in
common.h
,
sono kTfLiteOk
e kTfLiteError
.
Il seguente codice asserisce che il valore è kTfLiteOk
, il che significa che l'inferenza è
dell'esecuzione.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);
13. Ottenere l'output
Il tensore di output del modello può essere ottenuto chiamando output(0)
sul
tflite::MicroInterpreter
, dove 0
rappresenta il primo (e solo) output
tensore.
Nell'esempio, l'output del modello è un singolo valore in virgola mobile contenuto all'interno di un tensore 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);
Possiamo leggere il valore direttamente dal tensore di output e affermare che è ciò prevediamo:
// 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. Esegui di nuovo l'inferenza
La parte restante del codice esegue l'inferenza più volte. In ogni caso, assegniamo un valore al tensore di input, richiamiamo l'interprete e leggiamo il risultato del tensore di output:
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);