Empiece a utilizar microcontroladores

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

El ejemplo de Hello World

El Hello World ejemplo está diseñado para demostrar los fundamentos absolutos del uso de TensorFlow Lite para microcontroladores. Nosotros entrenamos y ejecutar un modelo que reproduce una función seno, es decir, se necesita un solo número como su entrada y salidas del número sinusoidal de valor. 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 un extremo a otro incluye los siguientes pasos:

  1. Entrenar un modelo (en Python): Un cuaderno jupyter de tren, convertir y optimizar un modelo para su uso en el dispositivo.
  2. Inferencia de ejecución (en C ++ 11): Una unidad de prueba de extremo a extremo que se ejecuta la inferencia en el modelo usando el biblioteca de C ++ .

Obtén un dispositivo compatible

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

Más información sobre las plataformas soportadas en TensorFlow Lite para microcontroladores .

Entrena un modelo

Uso Google Colaboratorio a entrenar a su propio modelo . Para más detalles, consulte la README.md :

Entrenamiento de Hello World README.md

Ejecutar inferencia

Para ejecutar el modelo de su dispositivo, vamos a caminar a través de las instrucciones del README.md :

Hola mundo README.md

En las siguientes secciones a pie a través del ejemplo hello_world_test.cc prueba, unidad que muestra cómo ejecutar la inferencia utilizando 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 para microcontroladores, 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 model.h y model.cc archivos. 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 pruebas unitarias.

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

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

La prueba se define mediante 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, un tflite::ErrorReporter puntero se crea utilizando un puntero a una tflite::MicroErrorReporter ejemplo:

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. Desde los microcontroladores a menudo tienen una variedad de mecanismos para la explotación forestal, la implementación de tflite::MicroErrorReporter está diseñado para ser personalizado para su dispositivo en particular.

5. Cargar un modelo

En el siguiente código, el modelo se instancia utilizando datos de un char array, 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. Instancia de resolución de operaciones

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

tflite::AllOpsResolver resolver;

Los AllOpsResolver cargas todas las operaciones disponibles en TensorFlow Lite para microcontroladores, que utiliza una gran cantidad de memoria. Dado que un modelo determinado solo usará un subconjunto de estas operaciones, se recomienda que las aplicaciones del mundo real carguen solo las operaciones necesarias.

Esto se hace usando una clase diferente, MicroMutableOpResolver . Se puede ver cómo usarlo en el ejemplo de discurso Micro micro_speech_test.cc .

7. Asignar memoria

Necesitamos preasignar una cierta cantidad de memoria para matrices de entrada, salida e intermedias. Esto se proporciona como un uint8_t matriz 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. Instancia de intérprete

Creamos un tflite::MicroInterpreter ejemplo, pasando de las variables creadas anteriormente:

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

9. Asignar tensores

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

interpreter.AllocateTensors();

10. Validar la forma de entrada

El MicroInterpreter ejemplo nos puede proporcionar un puntero al tensor de la entrada del modelo llamando .input(0) , donde 0 representa la primera (y única) de entrada de tensor:

  // 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 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, tenemos una entrada de valor de punto flotante que representa a 0 .

12. Ejecute el modelo

Para ejecutar el modelo, podemos llamar a Invoke() en nuestro tflite::MicroInterpreter ejemplo:

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

Podemos comprobar el valor de retorno, un TfLiteStatus , para determinar si la carrera se ha realizado correctamente. Los valores posibles de TfLiteStatus , definidas en common.h , son kTfLiteOk y kTfLiteError .

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

TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);

13. Obtenga el resultado

Tensor de salida del modelo puede ser obtenida llamando output(0) en el tflite::MicroInterpreter , donde 0 representa la primera (y única) 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. Ejecute inferencia nuevamente

El resto del código ejecuta 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. Lea el código de la aplicación

Una vez que haya caminado a través de esta unidad de prueba, debe ser capaz de entender el código de aplicación del ejemplo, 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.