Questo documento spiega come addestrare un modello ed eseguire l'inferenza utilizzando un microcontroller.
Esempio di Hello World
L'esempio Hello World è progettato per dimostrare le basi assolute dell'utilizzo di TensorFlow Lite per microcontroller. Addestriamo ed eseguiamo un modello che replica una funzione seno, ovvero prende un singolo numero come input e restituisce il valore sino del numero. Quando viene distribuito sul microcontroller, 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 file Python per addestrare, convertire e ottimizzare un modello per l'uso sul dispositivo.
- Esegui inferenza (in C++ 17): un test delle unità end-to-end che esegue l'inferenza sul modello utilizzando la libreria C++.
Acquista un dispositivo supportato
L'applicazione di esempio che utilizzeremo è stata testata sui seguenti dispositivi:
- Arduino Nano 33 BLE Sense (con Arduino IDE)
- SparkFun Edge (creando direttamente dalla fonte)
- Kit Discovery STM32F746 (utilizzando Mbed)
- Adafruit EdgeBadge (utilizzando l'IDE Arduino)
- Kit Adafruit TensorFlow Lite per microcontroller (utilizzando l'IDE Arduino)
- Adafruit Circuit Playground Bluefruit (utilizzando l'IDE Arduino)
- Espressif ESP32-DevKitC (utilizzando ESP IDF)
- Espressif ESP-EYE (utilizzando ESP IDF)
Scopri di più sulle piattaforme supportate in TensorFlow Lite per microcontroller.
Addestra un modello
Utilizza train.py per l'addestramento del modello hello world per il riconoscimento sinwave
Esegui: 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, analizzeremo le istruzioni nel
README.md
:
Le sezioni seguenti illustrano il test delle unità di evaluate_test.cc
dell'esempio, che mostra come eseguire l'inferenza utilizzando TensorFlow Lite per i microcontroller. Il modello viene caricato ed eseguito diverse volte l'inferenza.
1. Includi le intestazioni della libreria
Per utilizzare la libreria TensorFlow Lite for Microcontrollers, dobbiamo includere 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
genera informazioni di debug.micro_interpreter.h
contiene codice per caricare ed eseguire i modelli.schema_generated.h
contiene lo schema per il formato di file del modelloFlatBuffer
di TensorFlow Lite.version.h
fornisce informazioni sul controllo delle versioni per lo schema di TensorFlow Lite.
2. Includi l'intestazione del modello
L'interprete di TensorFlow Lite per microcontroller si aspetta che il modello venga fornito come array C++. Il modello viene definito nei file model.h
e model.cc
.
L'intestazione è inclusa nella 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 framework di test delle unità TensorFlow Lite per Microcontrollers 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 parleremo del codice incluso nella macro precedente.
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, che permette di scrivere
log. Poiché i microcontroller spesso hanno una serie di meccanismi per il logging, l'implementazione di tflite::MicroErrorReporter
è progettata per essere personalizzata per il tuo 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
. Dopodiché verifichiamo 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. Crea un'istanza del resolver operazioni
È stata dichiarata un'istanza
MicroMutableOpResolver
. che verrà utilizzato dall'interprete per registrare e accedere 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 di 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 gli array di input, di output e 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 utilizzato e potrebbe dover essere determinata mediante sperimentazione.
8. Crea un'istanza dell'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. Alloca i tensori
Chiediamo all'interprete di allocare la memoria da tensor_arena
per i tensori del modello:
interpreter.AllocateTensors();
10. Convalida 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 siano 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 di TensorFlow Lite ed è definito in common.h
.
11. Specifica un valore di input
Per fornire un input al modello, impostiamo i contenuti del tensore di input come segue:
input->data.f[0] = 0.;
In questo caso, inserisci un valore in virgola mobile che rappresenta 0
.
12. esegui il modello
Per eseguire il modello, possiamo richiamare 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 è riuscita. I valori possibili 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)
in 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 in 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ò 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. Esegui di nuovo l'inferenza
La parte rimanente del codice esegue l'inferenza diverse volte. In ogni istanza assegniamo un valore al tensore di input, richiamiamo 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);