Inizia con i microcontrollori

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 nozioni di base assolute sull'utilizzo di TensorFlow Lite per microcontrollori. Addestriamo ed eseguiamo un modello che replica una funzione seno, ovvero prende un singolo numero come input e restituisce in output il valore seno del numero. Quando implementate 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:

  1. Addestra un modello (in Python): un file Python per addestrare, convertire e ottimizzare un modello per l'uso sul dispositivo.
  2. Esegui inferenza (in C++ 17): uno unit test 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:

Scopri di più sulle piattaforme supportate in TensorFlow Lite per microcontrollori .

Addestra un modello

Utilizza train.py per l'addestramento del modello Hello World per il riconoscimento delle onde sinusoidali

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 l'inferenza

Per eseguire il modello sul tuo dispositivo, seguiremo le istruzioni nel README.md :

Ciao mondo README.md

Le sezioni seguenti illustrano evaluate_test.cc dell'esempio, unit test che dimostra 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 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"

2. Includere l'intestazione del modello

L'interprete TensorFlow Lite for Microcontrollers prevede che il modello venga fornito come array C++. Il modello è definito nei file model.h e model.cc . L'intestazione è inclusa nella seguente riga:

#include "tensorflow/lite/micro/examples/hello_world/model.h"

3. Includere l'intestazione del framework del test unitario

Per creare uno unit test, includiamo il framework di unit test TensorFlow Lite for 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

Parliamo ora del codice incluso nella macro sopra.

4. Configura la registrazione

Per impostare la registrazione, viene creato un puntatore tflite::ErrorReporter utilizzando un puntatore a un'istanza tflite::MicroErrorReporter :

tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = &micro_error_reporter;

Questa variabile verrà passata all'interprete, che gli consentirà 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 un array char , g_model , dichiarato in model.h . Quindi controlliamo il modello per garantire 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 il risolutore di operazioni

Viene dichiarata un'istanza MicroMutableOpResolver . Questo verrà utilizzato dall'interprete per registrarsi 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 indica il numero di operazioni che verranno registrate. La funzione RegisterOps registra le operazioni con il risolutore.

HelloWorldOpResolver op_resolver;
TF_LITE_ENSURE_STATUS(RegisterOps(op_resolver));

7. Allocare memoria

Dobbiamo preallocare una certa quantità di memoria per gli array di input, output e intermedi. Questo viene fornito come array uint8_t di dimensione 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 essere necessario determinarla mediante 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. Assegnare i tensori

Diciamo all'interprete di allocare memoria da tensor_arena per i tensori del modello:

interpreter.AllocateTensors();

10. Convalidare la forma dell'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, immettiamo 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, a TfLiteStatus , per determinare se l'esecuzione ha avuto successo. I possibili valori di TfLiteStatus , definiti in common.h , sono kTfLiteOk e kTfLiteError .

Il codice seguente asserisce che il valore è kTfLiteOk , il che significa che l'inferenza è stata eseguita correttamente.

TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);

13. Ottieni 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 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 è 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);