Questo documento spiega come addestrare un modello ed eseguire l'inferenza utilizzando un microcontrollore.
L'esempio di Hello World
L'esempio Hello World è progettato per dimostrare le basi assolute dell'utilizzo di TensorFlow Lite per microcontrollori. Formiamo ed eseguiamo un modello che replica una funzione seno, cioè prende un singolo numero come input e restituisce il valore seno del numero. Quando viene distribuito al microcontrollore, le sue previsioni vengono utilizzate per far lampeggiare i LED o controllare un'animazione.
Il flusso di lavoro end-to-end prevede i seguenti passaggi:
- Addestra un modello (in Python): un notebook jupyter per addestrare, convertire e ottimizzare un modello per l'uso sul dispositivo.
- Esegui inferenza (in C++ 11): uno unit test end-to-end che esegue l'inferenza sul modello usando la libreria C++ .
Ottieni un dispositivo supportato
L'applicazione di esempio che utilizzeremo è stata testata sui seguenti dispositivi:
- Arduino Nano 33 BLE Sense (utilizzando Arduino IDE)
- SparkFun Edge (costruzione direttamente dalla fonte)
- STM32F746 Kit di rilevamento (utilizzando Mbed)
- Adafruit EdgeBadge (utilizzando Arduino IDE)
- Kit Adafruit TensorFlow Lite per microcontrollori (utilizzando Arduino IDE)
- Adafruit Circuit Playground Bluefruit (utilizzando Arduino IDE)
- Espressif ESP32-DevKitC (usando ESP IDF)
- Espressif ESP-EYE (usando ESP IDF)
Scopri di più sulle piattaforme supportate in TensorFlow Lite per microcontrollori .
Addestra un modello
Usa Google Colaboratory per addestrare il tuo modello . Per maggiori dettagli, fare riferimento a README.md
:
Hello World Training README.md
Esegui l'inferenza
Per eseguire il modello sul tuo dispositivo, analizzeremo le istruzioni nel README.md
:
Le sezioni seguenti illustrano l'esempio hello_world_test.cc
, unit test che mostra come eseguire l'inferenza utilizzando TensorFlow Lite per microcontrollori. Carica il modello ed esegue l'inferenza più volte.
1. Includere le intestazioni della libreria
Per utilizzare la libreria TensorFlow Lite per microcontrollori, è necessario includere i seguenti file di intestazione:
#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
fornisce le operazioni utilizzate dall'interprete per eseguire il modello. -
micro_error_reporter.h
restituisce informazioni di debug. -
micro_interpreter.h
contiene il codice per caricare ed eseguire i modelli. -
schema_generated.h
contiene lo schema per il formato file del modello TensorFlow LiteFlatBuffer
. -
version.h
fornisce informazioni sul controllo delle versioni per lo schema TensorFlow Lite.
2. Includere l'intestazione del modello
L'interprete TensorFlow Lite per microcontrollori prevede che il modello venga fornito come array C++. Il modello è definito nei file model.h
e model.cc
. L'intestazione è inclusa nella riga seguente:
#include "tensorflow/lite/micro/examples/hello_world/model.h"
3. Includere l'intestazione del framework di unit test
Per creare uno unit test, includiamo il framework di unit test TensorFlow Lite per microcontrollori 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
Discutiamo ora del codice incluso nella macro precedente.
4. Configurare la registrazione
Per impostare la registrazione, viene creato un puntatore tflite::ErrorReporter
::ErrorReporter utilizzando un puntatore a tflite::MicroErrorReporter
:
tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = µ_error_reporter;
Questa variabile verrà passata all'interprete, che gli consente di scrivere i log. Poiché i microcontrollori hanno spesso una varietà di meccanismi per la registrazione, l'implementazione di tflite::MicroErrorReporter
è progettata per essere personalizzata per il tuo particolare dispositivo.
5. Caricare un modello
Nel codice seguente, viene creata un'istanza del modello utilizzando i dati di una matrice char
, g_model
, dichiarato in model.h
. Quindi controlliamo il modello per assicurarci che la sua versione dello schema sia 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. Istanziare operazioni risolutore
Viene dichiarata un'istanza AllOpsResolver
. Questo sarà utilizzato dall'interprete per accedere alle operazioni utilizzate dal modello:
tflite::AllOpsResolver resolver;
AllOpsResolver
carica tutte le operazioni disponibili in TensorFlow Lite per microcontrollori, che utilizza molta memoria. Poiché un determinato modello utilizzerà solo un sottoinsieme di queste operazioni, è consigliabile che le applicazioni del mondo reale carichino solo le operazioni necessarie.
Questo viene fatto utilizzando una classe diversa, MicroMutableOpResolver
. Puoi vedere come usarlo nell'esempio di Micro discorso micro_speech_test.cc
.
7. Assegna memoria
È necessario preallocare una certa quantità di memoria per gli array di input, output e intermedi. Questo è 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];
La dimensione richiesta dipenderà dal modello che stai utilizzando e potrebbe dover essere determinata dalla sperimentazione.
8. Istanziare l'interprete
Creiamo un'istanza tflite::MicroInterpreter
, passando le variabili create in precedenza:
tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
tensor_arena_size, error_reporter);
9. Assegna i tensori
Diciamo all'interprete di allocare memoria da tensor_arena
per i tensori del modello:
interpreter.AllocateTensors();
10. Convalida la forma di input
L'istanza MicroInterpreter
può fornirci un puntatore al tensore di input del modello chiamando .input(0)
, dove 0
rappresenta il primo (e unico) tensore di input:
// Obtain a pointer to the model's input tensor
TfLiteTensor* input = interpreter.input(0);
Quindi ispezioniamo questo tensore per confermare che la sua forma e il suo tipo sono quelli che ci aspettiamo:
// 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
è un riferimento a uno dei tipi di dati TensorFlow Lite ed è definito in common.h
.
11. Fornire un valore di input
Per fornire un input al modello, impostiamo il contenuto del tensore di input, come segue:
input->data.f[0] = 0.;
In questo caso, inseriamo un valore in virgola mobile che rappresenta 0
.
12. Eseguire il modello
Per eseguire il modello, possiamo chiamare Invoke()
sulla nostra istanza tflite::MicroInterpreter
:
TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
TF_LITE_REPORT_ERROR(error_reporter, "Invoke failed\n");
}
Possiamo controllare il valore restituito, un TfLiteStatus
, per determinare se l'esecuzione è andata a buon fine. I possibili valori di TfLiteStatus
, definiti in common.h
, sono kTfLiteOk
e kTfLiteError
.
Il codice seguente afferma che il valore è kTfLiteOk
, il che significa che l'inferenza è stata eseguita correttamente.
TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);
13. Ottenere l'output
Il tensore di output del modello può essere ottenuto chiamando output(0)
su tflite::MicroInterpreter
, dove 0
rappresenta il primo (e unico) tensore di output.
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 è quello che ci aspettiamo:
// 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. Eseguire nuovamente l'inferenza
Il resto del codice esegue l'inferenza più volte. In ogni caso, assegniamo un valore al tensore di input, invochiamo l'interprete e leggiamo il risultato dal 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);
15. Leggere il codice dell'applicazione
Dopo aver esaminato questo unit test, dovresti essere in grado di comprendere il codice dell'applicazione dell'esempio, che si trova in main_functions.cc
. Segue un processo simile, ma genera un valore di input basato su quante inferenze sono state eseguite e chiama una funzione specifica del dispositivo che mostra l'output del modello all'utente.