Introduction

In this project, I will show an easy way to get started with TinyML: implementing a Machine Learning model on an Arduino board while creating something cool: a gesture recognition system based on an accelerometer.

To make the experiment simpler, the system is designed to recognize only two gestures: a punch and a flex movement (in the data science field, a binary classification).

punch.gif

flex.gif

The biggest challenge of this experiment is trying to run the prediction model on a very tiny device: an 8-bit microcontroller. To achieve this, you can use Neuton

Neuton is a TinyML framework. It allows to automatically build neural networks without any coding and with a little machine learning experience and embed them into small computing devices. It supports 8, 16, and 32-bit microcontrollers.

The experiment is divided into three steps:

flow.jpeg

Setup

The gesture recognition system is composed by:

GY-521 is powered with the 5V and GND pins of the Arduino Mega power section, while for data communication the I2C pins are used (Pin 20 and Pin 21).The remaining pins are optional and not useful for this application.

To verify if the GY-521 module is correctly supplied, connect the USB cable of the Arduino board and check if the LED mounted on the sensor board is turning on.

imu.png

After verifying the sensor power supply, check if the I2C communication is working properly by downloading the Adafruit MPU6050 Arduino library and opening the “plotter” example.

Upload the example sketch on the Arduino board, open the “Serial Plotter” within the Tools menu, set 115200 in the baud drop-down menu, and “shake” the sensor board. The expected result will be the following:

plotter.gif

Now, the system is ready to collect accelerometer and gyroscope data.

1. Capture training data

The first step to building the predictive model is to collect enough motion measurements.This set of measures is called training dataset and it will be used to train the Neuton neural network builder.

The easiest way to achieve this is to repeat several times the same two motions (punch and flex), by capturing acceleration and gyroscope measurements and storing the result in a file.To do this, you create an Arduino sketch dedicated to sensor data acquisition. The program will acquire the measurements of each motion and will print the sensor measurements output on the serial port console.

You will perform at least 60 motions: 30 for the first movement (punch) and 30 for the second one (flex). For each motion, you will acquire 50 acceleration and 50 gyroscope measures in a 1 second time window (Sampling time: 20ms —50Hz). In this experiment, 60 motions are enough. By increasing the number of motion measurements, you can improve the predictive power of the model. However, a large dataset can lead to an over-fitted model. There is no “correct” dataset size, but a “trial and error” approach is recommended.

The serial port output of the Arduino sketch will be formatted according to Neutontraining dataset requirements.

Below, Arduino program for dataset creation:

#define NUM_SAMPLES 50

Adafruit_MPU6050 mpu;

void setup() {

  // init serial port

  Serial.begin(115200);

  while (!Serial) {

    delay(10);

  }

  // init IMU sensor

  if (!mpu.begin()) {

    while (1) {

      delay(10);

    }

  }

  // configure IMU sensor

  // [...]

  // print the CSV header (ax0,ay0,az0,...,gx49,gy49,gz49,target)

  for (int i=0; i<NUM_SAMPLES; i++) {

    Serial.print("aX");

    Serial.print(i);

    Serial.print(",aY");

    Serial.print(i);

    Serial.print(",aZ");

    Serial.print(i);

    Serial.print(",gX");

    Serial.print(i);

    Serial.print(",gY");

    Serial.print(i);

    Serial.print(",gZ");

    Serial.print(i);

    Serial.print(",");

  }

  Serial.println("target");

}

 Firstly, run the above sketch with the serial monitor opened and GESTURE_TARGET set to GESTURE_0. Then, run with GESTURE_TARGET set to GESTURE_1. For each execution, perform the same motion 30 times, ensuring, as far as possible, that the motion is performed in the same way.

Copy the serial monitor output of the two motions in a text file and rename it to “trainingdata.csv”.

2. Train the model with Neuton TinyML

Neuton performs training automatically and without any user interaction.Train a Neural Network with Neuton is quick and easy and is divided into three phases:

2.1. Dataset: Upload and validation

image.png

image.png

image.png

image.png

image.png

image.png

2.2. Training: Auto ML

Now, let’s get to the heart of training!

image.png

image.png

image.png

image.png

image.png

image.png

image.png

2.3. Prediction: Result analysis and model download

image.png

After the training procedure is completed, you will be redirected to the «Prediction» section. In this experiment, the model has reached an accuracy of 98>#/b###. It means that from 100 predicted records, 98 had been assigned to the correct class… that’s impressive!

Moreover, the size of the model to embed is less than 3KB. This is a very small size, considering that the Arduino board in use is 256KB memory size and the typical memory size for an 8-bit microcontroller is 64KB÷256KB.

image.png

To download the model archive, click on the “Download” button.

image.png

3. Deploy the model on Arduino

The model archive downloaded from Neuton includes the following files and folders:

First, you modify the user_app.c file adding functions to initialize model and run inference.

/*

 * Function: model_init

 * ----------------------------

 *

 *   returns: result of initialization (bool)

 */

uint8_t model_init() {

   uint8_t res;


   res = CalculatorInit(&neuralNet, NULL);

   return (ERR_NO_ERROR == res);

}

/*

 * Function: model_run_inference

 * ----------------------------

 *

 *   sample: input array to make prediction

 *   size_in: size of input array

 *   size_out: size of result array

 *

 *   returns: result of prediction

 */

float* model_run_inference(float* sample, 

                           uint32_t size_in, 

                           uint32_t *size_out) {

   if (!sample || !size_out)

      return NULL;

   if (size_in != neuralNet.inputsDim)

      return NULL;

   *size_out = neuralNet.outputsDim;

   return CalculatorRunInference(&neuralNet, sample);

}

 After that, you create the user_app.h header file to allow the main application using the user functions.

uint8_t model_init();

float*  model_run_inference(float* sample, 

                            uint32_t size_in, 

                            uint32_t* size_out);

 Below, the Arduino sketch of main application:

Model in action!

/neuton_gesturerecognition

 |- /src

 | |- /Gesture Recognition_v1

 |   |- /model

 |   |- /neuton

 |   |- user_app.c

 |   |- user_app.h

 |- neuton_gesturerecognition.ino

Now, it’s time to see the predictive model in action!

For each detected motion, the model will try to guess what type of movement is (0-punch or 1-flex) and how accurate the prediction is. If the accuracy of the prediction is low (0.5), the model does not make a decision.

Below, an example of model inference execution:

image.png


And.. that's all!