Introducción a los microcontroladores

Este documento explica cómo entrenar un modelo y ejecutar la inferencia usando un microcontrolador.

El ejemplo de Hola Mundo

El ejemplo de Hello World está diseñado para demostrar los conceptos básicos absolutos del uso de TensorFlow Lite para microcontroladores. Entrenamos y ejecutamos un modelo que replica una función de seno, es decir, toma un solo número como entrada y genera el valor del seno del número. Cuando se implementa en el microcontrolador, sus predicciones se utilizan para hacer parpadear los LED o controlar una animación.

El flujo de trabajo de extremo a extremo implica los siguientes pasos:

  1. Entrene un modelo (en Python): un cuaderno jupyter para entrenar, convertir y optimizar un modelo para uso en el dispositivo.
  2. Ejecutar inferencia (en C++ 11): una prueba unitaria integral que ejecuta la inferencia en el modelo mediante la biblioteca de C++ .

Obtener un dispositivo compatible

La aplicación de ejemplo que usaremos ha sido probada en los siguientes dispositivos:

Obtén más información sobre las plataformas admitidas en TensorFlow Lite para microcontroladores .

entrenar a un modelo

Utilice Google Colaboratory para entrenar su propio modelo . Para obtener más detalles, consulte el README.md :

Hello World Capacitación README.md

Ejecutar inferencia

Para ejecutar el modelo en su dispositivo, seguiremos las instrucciones en README.md :

Hola mundo README.md

Las siguientes secciones recorren el ejemplo hello_world_test.cc , prueba unitaria que demuestra cómo ejecutar la inferencia con TensorFlow Lite para microcontroladores. Carga el modelo y ejecuta la inferencia varias veces.

1. Incluya los encabezados de la biblioteca

Para usar la biblioteca TensorFlow Lite for Microcontrollers, debemos incluir los siguientes archivos de encabezado:

#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"

2. Incluya el encabezado del modelo

El intérprete de TensorFlow Lite para microcontroladores espera que el modelo se proporcione como una matriz de C++. El modelo se define en los archivos model.h y model.cc . El encabezado se incluye con la siguiente línea:

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

3. Incluya el encabezado del marco de prueba de unidad

Para crear una prueba de unidad, incluimos el marco de prueba de unidad TensorFlow Lite para microcontroladores al incluir la siguiente línea:

#include "tensorflow/lite/micro/testing/micro_test.h"

La prueba se define utilizando las siguientes macros:

TF_LITE_MICRO_TESTS_BEGIN

TF_LITE_MICRO_TEST(LoadModelAndPerformInference) {
  . // add code here
  .
}

TF_LITE_MICRO_TESTS_END

Ahora discutimos el código incluido en la macro anterior.

4. Configurar el registro

Para configurar el registro, se crea un puntero tflite::ErrorReporter ::ErrorReporter utilizando un puntero a una instancia de tflite::MicroErrorReporter :

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

Esta variable se pasará al intérprete, lo que le permite escribir registros. Dado que los microcontroladores a menudo tienen una variedad de mecanismos para el registro, la implementación de tflite::MicroErrorReporter está diseñada para personalizarse para su dispositivo en particular.

5. Cargue un modelo

En el siguiente código, se crea una instancia del modelo utilizando datos de una matriz char , g_model , que se declara en model.h . Luego verificamos el modelo para asegurarnos de que su versión de esquema sea compatible con la versión que estamos usando:

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. Instanciar operaciones de resolución

Se declara una instancia AllOpsResolver . Esto será utilizado por el intérprete para acceder a las operaciones que utiliza el modelo:

tflite::AllOpsResolver resolver;

AllOpsResolver carga todas las operaciones disponibles en TensorFlow Lite para microcontroladores, que usa mucha memoria. Dado que un modelo dado solo usará un subconjunto de estas operaciones, se recomienda que las aplicaciones del mundo real carguen solo las operaciones que se necesitan.

Esto se hace usando una clase diferente, MicroMutableOpResolver . Puede ver cómo usarlo en micro_speech_test.cc del ejemplo de Micro Speech.

7. Asignar memoria

Necesitamos preasignar una cierta cantidad de memoria para matrices de entrada, salida e intermedias. Esto se proporciona como una matriz uint8_t de tamaño tensor_arena_size :

const int tensor_arena_size = 2 * 1024;
uint8_t tensor_arena[tensor_arena_size];

El tamaño requerido dependerá del modelo que esté utilizando y es posible que deba determinarse mediante experimentación.

8. Instanciar intérprete

Creamos una instancia de tflite::MicroInterpreter , pasando las variables creadas anteriormente:

tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
                                     tensor_arena_size, error_reporter);

9. Asignar tensores

Le decimos al intérprete que asigne memoria de tensor_arena para los tensores del modelo:

interpreter.AllocateTensors();

10. Validar forma de entrada

La instancia MicroInterpreter puede proporcionarnos un puntero al tensor de entrada del modelo llamando a .input(0) , donde 0 representa el primer (y único) tensor de entrada:

  // Obtain a pointer to the model's input tensor
  TfLiteTensor* input = interpreter.input(0);

Luego inspeccionamos este tensor para confirmar que su forma y tipo son los que esperamos:

// 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);

El valor de enumeración kTfLiteFloat32 es una referencia a uno de los tipos de datos de TensorFlow Lite y se define en common.h .

11. Proporcione un valor de entrada

Para proporcionar una entrada al modelo, establecemos el contenido del tensor de entrada de la siguiente manera:

input->data.f[0] = 0.;

En este caso, ingresamos un valor de punto flotante que representa 0 .

12. Ejecutar el modelo

Para ejecutar el modelo, podemos llamar a Invoke() en nuestra instancia tflite::MicroInterpreter :

TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
  TF_LITE_REPORT_ERROR(error_reporter, "Invoke failed\n");
}

Podemos verificar el valor devuelto, un TfLiteStatus , para determinar si la ejecución fue exitosa. Los valores posibles de TfLiteStatus , definidos en common.h , son kTfLiteOk y kTfLiteError .

El siguiente código afirma que el valor es kTfLiteOk , lo que significa que la inferencia se ejecutó correctamente.

TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);

13. Obtenga la salida

El tensor de salida del modelo se puede obtener llamando a output(0) en tflite::MicroInterpreter , donde 0 representa el primer (y único) tensor de salida.

En el ejemplo, la salida del modelo es un único valor de punto flotante contenido dentro de un tensor 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);

Podemos leer el valor directamente del tensor de salida y afirmar que es lo que esperamos:

// 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. Vuelva a ejecutar la inferencia

El resto del código ejecuta la inferencia varias veces más. En cada caso, asignamos un valor al tensor de entrada, invocamos al intérprete y leemos el resultado del tensor de salida:

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. Lee el código de la aplicación

Una vez que haya realizado esta prueba unitaria, debería poder comprender el código de la aplicación del ejemplo, que se encuentra en main_functions.cc . Sigue un proceso similar, pero genera un valor de entrada basado en cuántas inferencias se han ejecutado y llama a una función específica del dispositivo que muestra la salida del modelo al usuario.