TinyML Classification: Wine Dataset

This post is a step-by-step tutorial on how to train, export, and run a Tensorflow Neural Network on an Arduino-compatible microcontroller for the classification task: in particular, we will classify the Wine data set.

Many newcomers to Tensorflow for TinyML and Microcontrollers are still struggling to start neural networks for classification tasks, so we decided to write this post as a step-by-step guide to fill this gap.

This content will include:

  1. A Tensorflow Neural network training in Python to classify the Iris dataset.
  2. Transferring Neural Network to Arduino-compliant C++
  3. Running the Neural Network on a microcontroller

We will consider all the steps so that you can use this post as a reference for other projects that need to classify any dataset you may have: most (if not all) steps will be the same.

Training a Neural Network in Python

First of all, we need to create and train a neural network in Python using the Tensorflow framework.We will use the Keras API because it is quite easy and common to use.

import numpy as npfrom tensorflow.keras import Sequential, layers
from tensorflow.keras.utils import to_categorical
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split

# train, validate, test load, and split the dataset
X, y = load_wine(return_X_y=True)
y = to_categorical(y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.3)

input_dim = X_train.shape[1:]
output_dim = y.shape[1]

print('input_dim', input_dim)
print('output_dim', output_dim)

# create and train network# you can customize the layers as youprefer
nn = Sequential()
nn.add(layers. Dense(units=50, activation='relu', input_shape=input_dim))
nn.add(layers. Dense(units=50, activation='relu'))
nn.add(layers. Dense(output_dim, activation='softmax'))

# use categorical_crossentropy for multi-class classification
nn.compile(loss='categorical_crossentropy',optimizer='man',metrics=['accuracy'])
nn.fit(X_train, y_train, validation_data=(X_valid, y_valid), epochs=100, verbose=0)

print('Accuracy: %.1f' % nn.evaluate(X_test, y_test)[1])


In this configuration we have achieved the best accuracy of 89%: results may vary depending on your dataset and layers.

Transfer to C++

We will use the tinymlgen package to transfer the neural network to the C data array.

from tinymlgen import port

print(port(nn, variable_name='wine_model', pretty_print=True, optimized =False))

You will receive something like:

#ifdef __has_attribute#if HAVE_ATTRIBUTE(aligned) #endif#define HAVE_ATTRIBUTE(x) 0 __has_attribute(x)#else#define HAVE_ATTRIBUTE(x) || (defined(__GNUC__) &&!defined(__clang__)) #define DATA_ALIGN_ATTRIBUTE __attribute__((aligned(4)))#else #define DATA_ALIGN_ATTRIBUTE#endifconst unsigned char wine_model[] DATA_ALIGN_ATTRIBUTE = {
    0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00 , 0x12,  0x00, 
    0x1c, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00 , 0x10, 0x00 ,  0x14,  0x00,
    .......
};
const int iris_model_len = 15228;











Copy this text andInside your Arduino project, paste it into a file named wine_model.h.

Running the Neural Network in a Microcontroller

We will use the EloquentTinyML library to easily operate the neural network in our microcontroller. You can easily install it through the Arduino IDE library manager.

#include <EloquentTinyML.h></EloquentTinyML.h>  #include "wine_model.h"#define NUMBER_OF_INPUTS 13#define NUMBER_OF_OUTPUTS 3#define TENSOR_ARENA_SIZE 16*1024

Eloquent::TinyML::TfLite<NUMBER_OF_INPUTS, number_of_outputs,="" tensor_arena_size=""> nn;

Float X_test[20][13] = {
    {1,340e+01, 4,600e+00, 2,860e+00, 2,500e+01, 1,120e+02, 1,980e+00,
    9.600e-01, 2.700e-01, 1.110e+00, 8.500e+00, 6.700e-01, 1.920e+00, 6.300e+02},
    {1,285e+01, 3,270e+00, 2,580e+00, 2,200e+01, 1,060e+02, 1,650e+00,
    6.000e-01, 6.000e-01, 9.600e-01, 5.580e+00, 8.700e-01, 2.110e+00, 5.700e+02},
    {1,334e+01, 9,400e-01, 2,360e+00, 1,700e+01, 1,100e+02, 2,530e+00,
    1,300e+00, 5,500e-01, 4,200e-01, 3,170e+00, 1,020e+00, 1,930e+00, 7,500e+02},
    {1,423e+01, 1,710e+00, 2,430e+00, 1,560e+01, 1,270e+02, 2,800e+00,
    3.060e+00, 2.800e-01, 2.290e+00, 5.640e+00, 1.040e+00, 3.920e+00, 1.065e+03},
    {1,483e+01, 1,640e+00, 2,170e+00, 1,400e+01, 9,700e+01, 2,800e+00,
    2.980e+00, 2.900e-01, 1.980e+00, 5.200e+00, 1.080e+00, 2.850e+00, 1.045e+03},
    {1.245e+01, 3.030e+00, 2.640e+00, 2.700e+01, 9.700e+01, 1.900e+00,
    5.800e-01, 6.300e-01, 1.140e+00, 7.500e+00, 6.700e-01, 1.730e+00, 8.800e+02},
    {1,430e+01, 1,920e+00, 2,720e+00, 2,000e+01, 1,200e+02, 2,800e+00,
    3.140e+00, 3.300e-01, 1.970e+00, 6.200e+00, 1.070e+00, 2.650e+00, 1.280e+03},
    {1,390e+01, 1,680e+00, 2,120e+00, 1,600e+01, 1,010e+02, 3,100e+00,
    3.390e+00, 2.100e-01, 2.140e+00, 6.100e+00, 9.100e-01, 3.330e+00, 9.850e+02},
    {1.165e+01, 1.670e+00, 2.620e+00, 2.600e+01, 8.800e+01, 1.920e+00,
    1.610e+00, 4.000e-01, 1.340e+00, 2.600e+00, 1.360e+00, 3.210e+00, 5.620e+02},
    {1,386e+01, 1,510e+00, 2,670e+00, 2,500e+01, 8,600e+01, 2,950e+00,
    2.860e+00, 2.100e-01, 1.870e+00, 3.380e+00, 1.360e+00, 3.160e+00, 4.100e+02},
    {1,377e+01, 1,900e+00, 2,680e+00, 1,710e+01, 1,150e+02, 3,000e+00,
    2.790e+00, 3.900e-01, 1.680e+00, 6.300e+00, 1.130e+00, 2.930e+00, 1.375e+03},
    {1,296e+01, 3,450e+00, 2,350e+00, 1,850e+01, 1,060e+02, 1,390e+00,
    7,000e-01, 4,000e-01, 9,400e-01, 5,280e+00, 6,800e-01, 1,750e+00, 6,750e+02},
    {1.305e+01, 5.800e+00, 2.130e+00, 2.150e+01, 8.600e+01, 2.620e+00,
    2.650e+00, 3.000e-01, 2.010e+00, 2.600e+00, 7.300e-01, 3.100e+00, 3.800e+02},
    {1.182e+01, 1.470e+00, 1.990e+00, 2.080e+01, 8.600e+01, 1.980e+00,
    1,600e+00, 3,000e-01, 1,530e+00, 1,950e+00, 9,500e-01, 3,330e+00, 4,950e+02},
    {1.164e+01, 2.060e+00, 2.460e+00, 2.160e+01, 8.400e+01, 1.950e+00,
    1,690e+00, 4,800e-01, 1,350e+00, 2,800e+00, 1,000e+00, 2,750e+00, 6,800e+02},
    {1,303e+01, 9,000e-01, 1,710e+00, 1,600e+01, 8,600e+01, 1,950e+00,
    2.030e+00, 2.400e-01, 1.460e+00, 4.600e+00, 1.190e+00, 2.480e+00, 3.920e+02},
    {1,176e+01, 2,680e+00, 2,920e+00, 2,000e+01, 1,030e+02, 1,750e+00,
    2.030e+00, 6.000e-01, 1.050e+00, 3.800e+00, 1.230e+00, 2.500e+00, 6.070e+02},
    {1.439e+01, 1.870e+00, 2.450e+00, 1.460e+01, 9.600e+01, 2.500e+00,
    2.520e+00, 3.000e-01, 1.980e+00, 5.250e+00, 1.020e+00, 3.580e+00, 1.290e+03},
    {1,420e+01, 1,760e+00, 2,450e+00, 1,520e+01, 1,120e+02, 3,270e+00,
    3.390e+00, 3.400e-01, 1.970e+00, 6.750e+00, 1.050e+00, 2.850e+00, 1.450e+03},
    {1.368e+01, 1.830e+00, 2.360e+00, 1.720e+01, 1.040e+02, 2.420e+00,
    2.690e+00, 4.200e-01, 1.970e+00, 3.840e+00, 1.230e+00, 2.870e+00, 9.900e+02}
};

uint8_t y_test[20] = {2, 2, 1, 0, 0, 2, 0, 1, 1, 0, 2, 1, 1, 1 , 1, 1 , 1, 1, 0, 0, 0}; 

void setup() {
    Serial.begin(115200);
    nn.begin(wine_model);
}

void loop() {
    for (uint8_t i = 0; i <  10; i++) {
        Serial.print("Example #");
        Serial.print(i + 1);
        Serial.print(": ");
        Serial.print("guess ");
        Serial.print(nn.predictClass(X_test[i]););
        Serial.print(" vs ");
        Serial.print(y_test[i]);
        Serial.println("real value");
    }

delay(10000);
}</NUMBER_OF_INPUTS,>





Compile, load draft, and turn on serial monitor: you should see estimated classes that match the target classes.