{ "version": "https://jsonfeed.org/version/1.1", "user_comment": "This feed allows you to read the posts from this site in any feed reader that supports the JSON Feed format. To add this feed to your reader, copy the following URL -- https://eloquentarduino.github.io/category/senza-categoria/feed/json/ -- and add it your reader.", "home_page_url": "https://eloquentarduino.github.io/category/senza-categoria/", "feed_url": "https://eloquentarduino.github.io/category/senza-categoria/feed/json/", "language": "en-US", "title": "Senza categoria – Eloquent Arduino Blog", "description": "Machine learning on Arduino, programming & electronics", "items": [ { "id": "https://eloquentarduino.github.io/?p=1365", "url": "https://eloquentarduino.github.io/2020/11/tinyml-on-arduino-and-stm32-cnn-convolutional-neural-network-example/", "title": "TinyML on Arduino and STM32: CNN (Convolutional Neural Network) example", "content_html": "
Painless TinyML Convolutional Neural Network on your Arduino and STM32 boards: the MNIST dataset example!
\nAre you fascinated by TinyML and Tensorflow for microcontrollers?
\nDo you want to run a CNN (Convolutional Neural Network) on your Arduino and STM32 boards?
\nDo you want to do it without pain?
\nEloquentTinyML is the library for you!
\n\n\n
EloquentTinyML, my library to easily run Tensorflow Lite neural networks on Arduino microcontrollers, is gaining some popularity so I think it's time for a good tutorial on the topic.
\nIf you're a seasoned follower of my blog, you may know that I don't really like Tensorflow on microcontrollers, because it is often "over-sized" for the project at hand and there are leaner, faster alternatives.
\nNonetheless, Tensorflow is gaining much popularity in the embedded world so I'll try to give my contribute too.
\nIn this tutorial, I'm going to show you step by step how to train a CNN in Tensorflow and deploy it to you board: I tested the code both on the Arduino Nano 33 BLE Sense and the STM32 Nucleus L432KC.
\nI'm not an expert either in Tensorflow nor Convolutional Neural Networks, so I kept the project as simple as possible. I used an image-like dataset to create a setup where CNN should perform well: the dataset is the MNIST handwritten digits one.
\n\nIt is composed by 8x8 images of handwritten digits, from 0 to 9 and can be easily imported via the scikit-learn
Python package.
Regarding the CNN topology, I wanted to stay as lean as possible: the goal of this tutorial is to teach you how to deploy your own network, not about achieving 100% accuracy.
\nLet's see step by step how to produce a usable model.
\nWe will need numpy
and Tensorflow
, of course, plus scikit-learn
to load the dataset and tinymlgen to port the CNN to plain C.
import numpy as np\nfrom sklearn.datasets import load_digits\nimport tensorflow as tf\nfrom tensorflow.keras import layers\nfrom tinymlgen import port
\nTo train the network, we need:
\ntraining data
: this is the data the network uses to learn its weightsvalidation data
: this is the data the network uses to understand if it's doing well during learningtest data
: this is the data we use to test the network accuracy once it's done learningdef get_data():\n np.random.seed(1337)\n x_values, y_values = load_digits(return_X_y=True)\n x_values /= x_values.max()\n # reshape to (8 x 8 x 1)\n x_values = x_values.reshape((len(x_values), 8, 8, 1))\n\n # split into train, validation, test\n TRAIN_SPLIT = int(0.6 * len(x_values))\n TEST_SPLIT = int(0.2 * len(x_values) + TRAIN_SPLIT)\n x_train, x_test, x_validate = np.split(x_values, [TRAIN_SPLIT, TEST_SPLIT])\n y_train, y_test, y_validate = np.split(y_values, [TRAIN_SPLIT, TEST_SPLIT])\n\n return x_train, x_test, x_validate, y_train, y_test, y_validate
\nNow we have to create our network topology.
\nAs I stated earlier, I wanted to keep this as simple as possible (also considering that we're using a toy dataset): I added a single convolution layer (without even max pooling) followed by the output layer.
\ndef get_model():\n x_train, x_test, x_validate, y_train, y_test, y_validate = get_data()\n\n # create a CNN\n model = tf.keras.Sequential()\n model.add(layers.Conv2D(8, (3, 3), activation='relu', input_shape=(8, 8, 1)))\n # model.add(layers.MaxPooling2D((2, 2)))\n model.add(layers.Flatten())\n model.add(layers.Dense(len(np.unique(y_train))))\n\n model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy'])\n model.fit(x_train, y_train, epochs=50, batch_size=16,\n validation_data=(x_validate, y_validate))\n return model, x_test, y_test
\nDo you think this topology is too simple to learn something useful in so few epochs?
\nThink again: it achieved 97% accuracy!
\nNot bad.
\ndef test_model(model, x_test, y_test):\n x_test = (x_test / x_test.max()).reshape((len(x_test), 8, 8, 1))\n y_pred = model.predict(x_test).argmax(axis=1)\n\n print('ACCURACY', (y_pred == y_test).sum() / len(y_test))
\nOnce we have a trained model that performs well, we want to deploy it to our microcontroller. Thanks to the tinymlgen
packages, is as easy as a one-liner.
if __name__ == '__main__':\n model, x_test, y_test = get_model()\n test_model(model, x_test, y_test)\n c_code = port(model, variable_name='digits_model', pretty_print=True)\n print(c_code)
\nOk, now we have the content we need to create an Arduino sketch to run the CNN on our microcontroller.
\nWe will use the EloquentTinyML
library to do this without pain.
This is a library to run TinyML models on your microcontroller without messing around with complex compilation procedures and esoteric errors.
\nYou must first install the library at its latest version (0.0.5 or 0.0.4 if not available), either via the Library Manager or directly from Github.
\n#include <EloquentTinyML.h>\n\n// copy the printed code from tinymlgen into this file\n#include "digits_model.h"\n\n#define NUMBER_OF_INPUTS 64\n#define NUMBER_OF_OUTPUTS 10\n#define TENSOR_ARENA_SIZE 8*1024\n\nEloquent::TinyML::TfLite<NUMBER_OF_INPUTS, NUMBER_OF_OUTPUTS, TENSOR_ARENA_SIZE> ml;\n\nvoid setup() {\n Serial.begin(115200);\n ml.begin(digits_model);\n}\n\nvoid loop() {\n // a random sample from the MNIST dataset (precisely the last one)\n float x_test[64] = { 0., 0. , 0.625 , 0.875 , 0.5 , 0.0625, 0. , 0. ,\n 0. , 0.125 , 1. , 0.875 , 0.375 , 0.0625, 0. , 0. ,\n 0. , 0. , 0.9375, 0.9375, 0.5 , 0.9375, 0. , 0. ,\n 0. , 0. , 0.3125, 1. , 1. , 0.625 , 0. , 0. ,\n 0. , 0. , 0.75 , 0.9375, 0.9375, 0.75 , 0. , 0. ,\n 0. , 0.25 , 1. , 0.375 , 0.25 , 1. , 0.375 , 0. ,\n 0. , 0.5 , 1. , 0.625 , 0.5 , 1. , 0.5 , 0. ,\n 0. , 0.0625, 0.5 , 0.75 , 0.875 , 0.75 , 0.0625, 0. };\n // the output vector for the model predictions\n float y_pred[10] = {0};\n // the actual class of the sample\n int y_test = 8;\n\n // let's see how long it takes to classify the sample\n uint32_t start = micros();\n\n ml.predict(x_test, y_pred);\n\n uint32_t timeit = micros() - start;\n\n Serial.print("It took ");\n Serial.print(timeit);\n Serial.println(" micros to run inference");\n\n // let's print the raw predictions for all the classes\n // these values are not directly interpretable as probabilities!\n Serial.print("Test output is: ");\n Serial.println(y_test);\n Serial.print("Predicted proba are: ");\n\n for (int i = 0; i < 10; i++) {\n Serial.print(y_pred[i]);\n Serial.print(i == 9 ? '\\n' : ',');\n }\n\n // let's print the "most probable" class\n // you can either use probaToClass() if you also want to use all the probabilities\n Serial.print("Predicted class is: ");\n Serial.println(ml.probaToClass(y_pred));\n // or you can skip the predict() method and call directly predictClass()\n Serial.print("Sanity check: ");\n Serial.println(ml.predictClass(x_test));\n\n delay(1000);\n}
\nThat's it: if everything went fine, you should see that the predicted class is 8
.
I'll report the figures I get for compiling and running this project on the two boards I used.
\nBoard | \nFlash | \nRAM | \nInference time | \n
---|---|---|---|
Nucleus L432KC | \n154560 | \nnot available* | \n7187 | \n
Arduino Nano 33 BLE Sense | \n197656 | \n56160 | \n9400 | \n
I used the Grumpyoldpizza compiler for the Nucleus, which doesn't report back the RAM usage
\nWere you able to deploy a CNN to your microcontroller thanks to this tutorial? Or are you having troubles?
\nLet me know in the comment and I will help you or share your experience with us.
\nYou can find the whole code on Github.
\nL'articolo TinyML on Arduino and STM32: CNN (Convolutional Neural Network) example proviene da Eloquent Arduino Blog.
\n", "content_text": "Painless TinyML Convolutional Neural Network on your Arduino and STM32 boards: the MNIST dataset example!\nAre you fascinated by TinyML and Tensorflow for microcontrollers? \nDo you want to run a CNN (Convolutional Neural Network) on your Arduino and STM32 boards? \nDo you want to do it without pain? \nEloquentTinyML is the library for you!\n\n\nEloquentTinyML, my library to easily run Tensorflow Lite neural networks on Arduino microcontrollers, is gaining some popularity so I think it's time for a good tutorial on the topic.\nIf you're a seasoned follower of my blog, you may know that I don't really like Tensorflow on microcontrollers, because it is often "over-sized" for the project at hand and there are leaner, faster alternatives.\nNonetheless, Tensorflow is gaining much popularity in the embedded world so I'll try to give my contribute too.\nIn this tutorial, I'm going to show you step by step how to train a CNN in Tensorflow and deploy it to you board: I tested the code both on the Arduino Nano 33 BLE Sense and the STM32 Nucleus L432KC.\nTable of contentsHow to train a CNN in TensorflowStep 1. Import the librariesStep 2. Generate train, validation and test dataStep 3. Create and train the modelStep 4. Testing the model accuracyStep 5. Exporting the modelHow to run a CNN on Arduino and STM32 boards with EloquentTinyMLCNN on Arduino and STM32 figuresAnd you?\nHow to train a CNN in Tensorflow\nI'm not an expert either in Tensorflow nor Convolutional Neural Networks, so I kept the project as simple as possible. I used an image-like dataset to create a setup where CNN should perform well: the dataset is the MNIST handwritten digits one.\n\nIt is composed by 8x8 images of handwritten digits, from 0 to 9 and can be easily imported via the scikit-learn Python package.\nRegarding the CNN topology, I wanted to stay as lean as possible: the goal of this tutorial is to teach you how to deploy your own network, not about achieving 100% accuracy.\nLet's see step by step how to produce a usable model.\nStep 1. Import the libraries\nWe will need numpy and Tensorflow, of course, plus scikit-learn to load the dataset and tinymlgen to port the CNN to plain C.\nimport numpy as np\nfrom sklearn.datasets import load_digits\nimport tensorflow as tf\nfrom tensorflow.keras import layers\nfrom tinymlgen import port\nStep 2. Generate train, validation and test data\nTo train the network, we need:\n\ntraining data: this is the data the network uses to learn its weights\nvalidation data: this is the data the network uses to understand if it's doing well during learning\ntest data: this is the data we use to test the network accuracy once it's done learning\n\ndef get_data():\n np.random.seed(1337)\n x_values, y_values = load_digits(return_X_y=True)\n x_values /= x_values.max()\n # reshape to (8 x 8 x 1)\n x_values = x_values.reshape((len(x_values), 8, 8, 1))\n\n # split into train, validation, test\n TRAIN_SPLIT = int(0.6 * len(x_values))\n TEST_SPLIT = int(0.2 * len(x_values) + TRAIN_SPLIT)\n x_train, x_test, x_validate = np.split(x_values, [TRAIN_SPLIT, TEST_SPLIT])\n y_train, y_test, y_validate = np.split(y_values, [TRAIN_SPLIT, TEST_SPLIT])\n\n return x_train, x_test, x_validate, y_train, y_test, y_validate\nStep 3. Create and train the model\nNow we have to create our network topology.\nAs I stated earlier, I wanted to keep this as simple as possible (also considering that we're using a toy dataset): I added a single convolution layer (without even max pooling) followed by the output layer.\ndef get_model():\n x_train, x_test, x_validate, y_train, y_test, y_validate = get_data()\n\n # create a CNN\n model = tf.keras.Sequential()\n model.add(layers.Conv2D(8, (3, 3), activation='relu', input_shape=(8, 8, 1)))\n # model.add(layers.MaxPooling2D((2, 2)))\n model.add(layers.Flatten())\n model.add(layers.Dense(len(np.unique(y_train))))\n\n model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy'])\n model.fit(x_train, y_train, epochs=50, batch_size=16,\n validation_data=(x_validate, y_validate))\n return model, x_test, y_test\nStep 4. Testing the model accuracy\nDo you think this topology is too simple to learn something useful in so few epochs?\nThink again: it achieved 97% accuracy!\nNot bad.\ndef test_model(model, x_test, y_test):\n x_test = (x_test / x_test.max()).reshape((len(x_test), 8, 8, 1))\n y_pred = model.predict(x_test).argmax(axis=1)\n\n print('ACCURACY', (y_pred == y_test).sum() / len(y_test))\nStep 5. Exporting the model\nOnce we have a trained model that performs well, we want to deploy it to our microcontroller. Thanks to the tinymlgen packages, is as easy as a one-liner.\nif __name__ == '__main__':\n model, x_test, y_test = get_model()\n test_model(model, x_test, y_test)\n c_code = port(model, variable_name='digits_model', pretty_print=True)\n print(c_code)\nHow to run a CNN on Arduino and STM32 boards with EloquentTinyML\nOk, now we have the content we need to create an Arduino sketch to run the CNN on our microcontroller.\nWe will use the EloquentTinyML library to do this without pain.\nThis is a library to run TinyML models on your microcontroller without messing around with complex compilation procedures and esoteric errors.\nYou must first install the library at its latest version (0.0.5 or 0.0.4 if not available), either via the Library Manager or directly from Github.\n#include <EloquentTinyML.h>\n\n// copy the printed code from tinymlgen into this file\n#include "digits_model.h"\n\n#define NUMBER_OF_INPUTS 64\n#define NUMBER_OF_OUTPUTS 10\n#define TENSOR_ARENA_SIZE 8*1024\n\nEloquent::TinyML::TfLite<NUMBER_OF_INPUTS, NUMBER_OF_OUTPUTS, TENSOR_ARENA_SIZE> ml;\n\nvoid setup() {\n Serial.begin(115200);\n ml.begin(digits_model);\n}\n\nvoid loop() {\n // a random sample from the MNIST dataset (precisely the last one)\n float x_test[64] = { 0., 0. , 0.625 , 0.875 , 0.5 , 0.0625, 0. , 0. ,\n 0. , 0.125 , 1. , 0.875 , 0.375 , 0.0625, 0. , 0. ,\n 0. , 0. , 0.9375, 0.9375, 0.5 , 0.9375, 0. , 0. ,\n 0. , 0. , 0.3125, 1. , 1. , 0.625 , 0. , 0. ,\n 0. , 0. , 0.75 , 0.9375, 0.9375, 0.75 , 0. , 0. ,\n 0. , 0.25 , 1. , 0.375 , 0.25 , 1. , 0.375 , 0. ,\n 0. , 0.5 , 1. , 0.625 , 0.5 , 1. , 0.5 , 0. ,\n 0. , 0.0625, 0.5 , 0.75 , 0.875 , 0.75 , 0.0625, 0. };\n // the output vector for the model predictions\n float y_pred[10] = {0};\n // the actual class of the sample\n int y_test = 8;\n\n // let's see how long it takes to classify the sample\n uint32_t start = micros();\n\n ml.predict(x_test, y_pred);\n\n uint32_t timeit = micros() - start;\n\n Serial.print("It took ");\n Serial.print(timeit);\n Serial.println(" micros to run inference");\n\n // let's print the raw predictions for all the classes\n // these values are not directly interpretable as probabilities!\n Serial.print("Test output is: ");\n Serial.println(y_test);\n Serial.print("Predicted proba are: ");\n\n for (int i = 0; i < 10; i++) {\n Serial.print(y_pred[i]);\n Serial.print(i == 9 ? '\\n' : ',');\n }\n\n // let's print the "most probable" class\n // you can either use probaToClass() if you also want to use all the probabilities\n Serial.print("Predicted class is: ");\n Serial.println(ml.probaToClass(y_pred));\n // or you can skip the predict() method and call directly predictClass()\n Serial.print("Sanity check: ");\n Serial.println(ml.predictClass(x_test));\n\n delay(1000);\n}\nThat's it: if everything went fine, you should see that the predicted class is 8.\nCNN on Arduino and STM32 figures\nI'll report the figures I get for compiling and running this project on the two boards I used.\n\n\n\nBoard\nFlash\nRAM\nInference time\n\n\n\n\nNucleus L432KC\n154560\nnot available*\n7187\n\n\nArduino Nano 33 BLE Sense\n197656\n56160\n9400\n\n\n\nI used the Grumpyoldpizza compiler for the Nucleus, which doesn't report back the RAM usage\nAnd you?\nWere you able to deploy a CNN to your microcontroller thanks to this tutorial? Or are you having troubles?\nLet me know in the comment and I will help you or share your experience with us.\n\nYou can find the whole code on Github.\nL'articolo TinyML on Arduino and STM32: CNN (Convolutional Neural Network) example proviene da Eloquent Arduino Blog.", "date_published": "2020-11-10T17:37:13+01:00", "date_modified": "2020-11-10T19:10:06+01:00", "authors": [ { "name": "simone", "url": "https://eloquentarduino.github.io/author/simone/", "avatar": "http://1.gravatar.com/avatar/d670eb91ca3b1135f213ffad83cb8de4?s=512&d=mm&r=g" } ], "author": { "name": "simone", "url": "https://eloquentarduino.github.io/author/simone/", "avatar": "http://1.gravatar.com/avatar/d670eb91ca3b1135f213ffad83cb8de4?s=512&d=mm&r=g" }, "tags": [ "Senza categoria" ] }, { "id": "https://eloquentarduino.github.io/?p=1237", "url": "https://eloquentarduino.com/projects/arduino-indoor-positioning", "title": "The Ultimate Guide to Wifi Indoor Positioning using Arduino and Machine Learning", "content_html": "This will be the most detailed, easy to follow tutorial over the Web on how to implement Wifi indoor positioning using an Arduino microcontroller and Machine Learning. It contains all the steps, tools and code from the start to the end of the project.
\n
\nri-elaborated from https://www.accuware.com/blog/ambient-signals-plus-video-images/
\n
My original post abot Wifi indoor positioning is one of my top-performing post of all time (after motion detection using ESP32 camera and the introductory post on Machine Learning for Arduino). This is why I settled to revamp it and add some more details, tools and scripts to create the most complete free guide on how to implement such a system, from the beginning to the end.
\nThis post will cover all the necessary steps and provide all the code you need: for an introduction to the topic, I point you to the original post.
\nThis part stays the same as the original post: we will use the RSSIs (signal strength) of the nearby Wifi hotspots to classifiy which location we're in.
\nEach location will "see" a certain number of networks, each with a RSSI that will stay mostly the same: the unique combination of these RSSIs will become a fingerprint to distinguish the locations from one another.
\nSince not all networks will be visible all the time, the shape of our data will be more likely a sparse matrix.
\nA sparse matrix is a matrix where most of the elements will be zero, meaning the absence of the given feature. Only the relevant elements will be non-zero and will represent the RSSI of the nth network.
The following example table should give you an idea of what our data will look like.
\nLocation | \nNet #1 | \nNet #2 | \nNet #3 | \nNet #4 | \nNet #5 | \nNet #6 | \nNet #7 | \n
---|---|---|---|---|---|---|---|
Kitchen/1 | \n50 | \n30 | \n60 | \n0 | \n0 | \n0 | \n0 | \n
Kitchen/2 | \n55 | \n30 | \n55 | \n0 | \n0 | \n5 | \n0 | \n
Kitchen/3 | \n50 | \n35 | \n65 | \n0 | \n0 | \n0 | \n5 | \n
Bedroom/1 | \n0 | \n80 | \n0 | \n80 | \n0 | \n40 | \n40 | \n
Bedroom/2 | \n0 | \n80 | \n0 | \n85 | \n10 | \n20 | \n20 | \n
Bedroom/3 | \n0 | \n70 | \n0 | \n85 | \n0 | \n30 | \n40 | \n
Bathroom/1 | \n0 | \n0 | \n30 | \n80 | \n80 | \n0 | \n0 | \n
Bathroom/2 | \n0 | \n0 | \n10 | \n90 | \n85 | \n0 | \n0 | \n
Bathroom/3 | \n0 | \n0 | \n30 | \n90 | \n90 | \n5 | \n0 | \n
Even though the numbers in this table are fake, you should recognize a pattern:
\nOur machine learning algorithm should be able to extract each location's fingerprint without being fooled by this inconsistent features.
\nNow that we know what our data should look like, we need to first get it.
\nIn the original post, this point was the one I'm unhappy with since it's not as straight-forward as I would have liked. The method I present you in this post, instead, is by far way simpler to follow.
\nFirst of all, you will need a Wifi equipped board. I will use an Arduino MKR WiFi 1010, but any ESP8266 / ESP32 or the like will work.
\nThe following sketch will do the job: it scans the visible networks at a regular interval and prints their RSSIs encoded in JSON format.
\n// file DataGathering.h\n\n#include "WiFi.h"\n\n#define print(string) Serial.print(string);\n#define quote(string) print('"'); print(string); print('"');\n\nString location = "";\n\n/**\n * \n */\nvoid setup() {\n Serial.begin(115200);\n delay(3000);\n WiFi.disconnect();\n}\n\n/**\n * \n */\nvoid loop() { \n // if location is set, scan networks\n if (location != "") {\n int numNetworks = WiFi.scanNetworks();\n\n // print location\n print('{');\n quote("__location");\n print(": ");\n quote(location);\n print(", ");\n\n // print each network SSID and RSSI\n for (int i = 0; i < numNetworks; i++) {\n quote(WiFi.SSID(i));\n print(": ");\n print(WiFi.RSSI(i));\n print(i == numNetworks - 1 ? "}\\n" : ", ");\n }\n\n delay(1000);\n }\n // else wait for user to enter the location\n else {\n String input;\n\n Serial.println("Enter 'scan {location}' to start the scanning");\n\n while (!Serial.available())\n delay(200);\n\n input = Serial.readStringUntil('\\n');\n\n if (input.indexOf("scan ") == 0) {\n input.replace("scan ", "");\n location = input;\n }\n else {\n location = "";\n }\n }\n}
\nUpload the sketch to your board and start mapping your house / office: go to the target location and type scan {location}
in the serial monitor, where {location}
is the name you want to give to the current location (so, for example, if you're mapping the kitchen, type scan kitchen
).
Move around the room a bit so you capture a few variations of the visible hotspots: this will lead to a more robust classification later on.
\nTo stop the recording just type stop
in the serial monitor.
Now repeat this process for each location you want to classify. At this point you should have ended with something similar to the following:
\n{"__location": "Kitchen", "N1": 100, "N2": 50}\n{"__location": "Bedroom", "N3": 100, "N2": 50}\n{"__location": "Bathroom", "N1": 100, "N4": 50}\n{"__location": "Bathroom", "N5": 100, "N4": 50}
\nIn your case, "N1", "N2"... will contain the name of the visible networks.
\nWhen you're happy with your training data, it's time to convert it to something useful.
\nGiven the data we have, we want to generate C code that can convert a Wifi scan result into a feature vector we can use for classification.
\nSince I'm a fan of code-generators, I wrote one specifically for this very project. And since I already have a code-generator library I use for Machine Learning code written in Python, I updated it with this new functionality.
\nStart by installing the library.
\n# be sure it installs version >= 1.1.8\npip install --upgrade micromlgen
\nNow create a script with the following code:
\nfrom micromlgen import port_wifi_indoor_positioning\n\nif __name__ == '__main__':\n samples = '''\n {"__location": "Kitchen", "N1": 100, "N2": 50}\n {"__location": "Bedroom", "N3": 100, "N2": 50}\n {"__location": "Bathroom", "N1": 100, "N4": 50}\n {"__location": "Bathroom", "N5": 100, "N4": 50}\n '''\n X, y, classmap, converter_code = port_wifi_indoor_positioning(samples)\n print(converter_code)
\nOf course you have to replace the samples
content with the output you got in the previous step.
In the console you should see a C++ class we will use later in the Arduino sketch. The class should be similar to the following example code.
\n// Save this code in your sketch as Converter.h\n\n#pragma once\nnamespace Eloquent {\n namespace Projects {\n class WifiIndoorPositioning {\n public:\n /**\n * Get feature vector\n */\n float* getFeatures() {\n static float features[5] = {0};\n uint8_t numNetworks = WiFi.scanNetworks();\n\n for (uint8_t i = 0; i < 5; i++) {\n features[i] = 0;\n }\n\n for (uint8_t i = 0; i < numNetworks; i++) {\n int featureIdx = ssidToFeatureIdx(WiFi.SSID(i));\n\n if (featureIdx >= 0) {\n features[featureIdx] = WiFi.RSSI(i);\n }\n }\n\n return features;\n }\n\n protected:\n /**\n * Convert SSID to featureIdx\n */\n int ssidToFeatureIdx(String ssid) {\n if (ssid.equals("N1"))\n return 0;\n\n if (ssid.equals("N2"))\n return 1;\n\n if (ssid.equals("N3"))\n return 2;\n\n if (ssid.equals("N4"))\n return 3;\n\n if (ssid.equals("N5"))\n return 4;\n\n return -1;\n }\n };\n }\n }
\nI will briefly explain what it does: when you call getFeatures()
, it runs a Wifi scan and for each network it finds, it fills the corresponding element in the feature vector (if the network is a known one).
At the end of the procedure, your feature vector will look something like [0, 10, 0, 0, 50, 0, 0]
, each element representing the RSSI of a given network.
To close the loop of the project, we need to be able to classify the features vector into one of the recorded location. Since we already have micromlgen
installed, it will be very easy to do so.
Let's update the Python code we already have: this time, instead of printing the converter code, we will print the classifier code.
\n# install ml package first\npip install scikit-learn
\nfrom sklearn.tree import DecisionTreeClassifier\nfrom micromlgen import port_wifi_indoor_positioning, port\n\nif __name__ == '__main__':\n samples = '''\n {"__location": "Kitchen", "N1": 100, "N2": 50}\n {"__location": "Bedroom", "N3": 100, "N2": 50}\n {"__location": "Bathroom", "N1": 100, "N4": 50}\n {"__location": "Bathroom", "N5": 100, "N4": 50}\n '''\n X, y, classmap, converter_code = port_wifi_indoor_positioning(samples)\n clf = DecisionTreeClassifier()\n clf.fit(X, y)\n print(port(clf, classmap=classmap))
\nHere I chose Decision tree because it is a very lightweight algorithm and should work fine for the kind of features we're working with.
\nIf you're not satisfied with the results, you can try to use SVM or Gaussian Naive Bayes, which are both supported by micromlgen
.
In the console you will see the generated code for the classifier you trained. In the case of DecisionTree
the code will look like the following.
// Save this code in your sketch as Classifier.h\n\n#pragma once\nnamespace Eloquent {\n namespace ML {\n namespace Port {\n class DecisionTree {\n public:\n /**\n * Predict class for features vector\n */\n int predict(float *x) {\n if (x[2] <= 25.0) {\n if (x[4] <= 50.0) {\n return 1;\n }\n\n else {\n return 2;\n }\n }\n\n else {\n return 0;\n }\n }\n\n /**\n * Convert class idx to readable name\n */\n const char* predictLabel(float *x) {\n switch (predict(x)) {\n case 0:\n return "Bathroom";\n case 1:\n return "Bedroom";\n case 2:\n return "Kitchen";\n default:\n return "Houston we have a problem";\n }\n }\n\n protected:\n };\n }\n }\n }
\nNow that we have all the pieces together, we only need to merge them to get a complete working example.
\n// file WifiIndoorPositioning.h\n\n#include "WiFi.h"\n#include "Converter.h"\n#include "Classifier.h"\n\nEloquent::Projects::WifiIndoorPositioning positioning;\nEloquent::ML::Port::DecisionTree classifier;\n\nvoid setup() {\n Serial.begin(115200);\n}\n\nvoid loop() {\n Serial.print("You're in ");\n Serial.println(classifier.predictLabel(positioning.getFeatures()));\n delay(3000);\n}
\nTo the bare minimum, the above code runs the scan and tells you which location you're in. That's it.
\nThis system should be pretty accurate and robust if you properly gather the data, though I can quantify how much accurate.
\nThis is not an indoor navigation system: it can't tell you "the coordinates" of where you are, it can only detect in which room you're in.
\nIf your location lack of nearby Wifi hotspots, an easy and cheap solution would be to spawn a bunch of ESP8266 / ESP32 boards around your house each acting as Access Point: with this simple trick you should be able to be as accurate as needed by just adding more boards.
\n\r\nWith this in-depth tutorial I hope I helped you going from start to end of setting up a Wifi indoor positioning system using cheap hardware as ESP8266 / ESP32 boards and the Arduino IDE.
\nAs you can see, Machine learning has not to be intimidating even for beginners: you just need the right tools to get the job done.
\nIf this guide excited you about Machine learning on microcontrollers, I invite you to read the many other posts I wrote on the topic and share them on the socials.
\nYou can find the whole project on Github. Don't forget to star the repo if you like it.
\nL'articolo The Ultimate Guide to Wifi Indoor Positioning using Arduino and Machine Learning proviene da Eloquent Arduino Blog.
\n", "content_text": "This will be the most detailed, easy to follow tutorial over the Web on how to implement Wifi indoor positioning using an Arduino microcontroller and Machine Learning. It contains all the steps, tools and code from the start to the end of the project.\n\nri-elaborated from https://www.accuware.com/blog/ambient-signals-plus-video-images/\n\nMy original post abot Wifi indoor positioning is one of my top-performing post of all time (after motion detection using ESP32 camera and the introductory post on Machine Learning for Arduino). This is why I settled to revamp it and add some more details, tools and scripts to create the most complete free guide on how to implement such a system, from the beginning to the end.\nThis post will cover all the necessary steps and provide all the code you need: for an introduction to the topic, I point you to the original post.\nTable of contentsFeatures definitionData gatheringGenerating the features converterGenerating the classifierWrapping it all togetherDisclaimer\nFeatures definition\nThis part stays the same as the original post: we will use the RSSIs (signal strength) of the nearby Wifi hotspots to classifiy which location we're in.\nEach location will "see" a certain number of networks, each with a RSSI that will stay mostly the same: the unique combination of these RSSIs will become a fingerprint to distinguish the locations from one another.\nSince not all networks will be visible all the time, the shape of our data will be more likely a sparse matrix.\nA sparse matrix is a matrix where most of the elements will be zero, meaning the absence of the given feature. Only the relevant elements will be non-zero and will represent the RSSI of the nth network.\nThe following example table should give you an idea of what our data will look like.\n\n\n\nLocation\nNet #1\nNet #2\nNet #3\nNet #4\nNet #5\nNet #6\nNet #7\n\n\n\n\nKitchen/1\n50\n30\n60\n0\n0\n0\n0\n\n\nKitchen/2\n55\n30\n55\n0\n0\n5\n0\n\n\nKitchen/3\n50\n35\n65\n0\n0\n0\n5\n\n\nBedroom/1\n0\n80\n0\n80\n0\n40\n40\n\n\nBedroom/2\n0\n80\n0\n85\n10\n20\n20\n\n\nBedroom/3\n0\n70\n0\n85\n0\n30\n40\n\n\nBathroom/1\n0\n0\n30\n80\n80\n0\n0\n\n\nBathroom/2\n0\n0\n10\n90\n85\n0\n0\n\n\nBathroom/3\n0\n0\n30\n90\n90\n5\n0\n\n\n\nEven though the numbers in this table are fake, you should recognize a pattern:\n\neach location is characterized by a certain combination of always-visible networks\nsome sample could be "noised" by weak networks (the 5 in the table)\n\nOur machine learning algorithm should be able to extract each location's fingerprint without being fooled by this inconsistent features.\nData gathering\nNow that we know what our data should look like, we need to first get it.\nIn the original post, this point was the one I'm unhappy with since it's not as straight-forward as I would have liked. The method I present you in this post, instead, is by far way simpler to follow.\nFirst of all, you will need a Wifi equipped board. I will use an Arduino MKR WiFi 1010, but any ESP8266 / ESP32 or the like will work.\nThe following sketch will do the job: it scans the visible networks at a regular interval and prints their RSSIs encoded in JSON format.\n// file DataGathering.h\n\n#include "WiFi.h"\n\n#define print(string) Serial.print(string);\n#define quote(string) print('"'); print(string); print('"');\n\nString location = "";\n\n/**\n * \n */\nvoid setup() {\n Serial.begin(115200);\n delay(3000);\n WiFi.disconnect();\n}\n\n/**\n * \n */\nvoid loop() { \n // if location is set, scan networks\n if (location != "") {\n int numNetworks = WiFi.scanNetworks();\n\n // print location\n print('{');\n quote("__location");\n print(": ");\n quote(location);\n print(", ");\n\n // print each network SSID and RSSI\n for (int i = 0; i < numNetworks; i++) {\n quote(WiFi.SSID(i));\n print(": ");\n print(WiFi.RSSI(i));\n print(i == numNetworks - 1 ? "}\\n" : ", ");\n }\n\n delay(1000);\n }\n // else wait for user to enter the location\n else {\n String input;\n\n Serial.println("Enter 'scan {location}' to start the scanning");\n\n while (!Serial.available())\n delay(200);\n\n input = Serial.readStringUntil('\\n');\n\n if (input.indexOf("scan ") == 0) {\n input.replace("scan ", "");\n location = input;\n }\n else {\n location = "";\n }\n }\n}\nUpload the sketch to your board and start mapping your house / office: go to the target location and type scan {location} in the serial monitor, where {location}is the name you want to give to the current location (so, for example, if you're mapping the kitchen, type scan kitchen).\nMove around the room a bit so you capture a few variations of the visible hotspots: this will lead to a more robust classification later on.\nTo stop the recording just type stop in the serial monitor.\nNow repeat this process for each location you want to classify. At this point you should have ended with something similar to the following:\n{"__location": "Kitchen", "N1": 100, "N2": 50}\n{"__location": "Bedroom", "N3": 100, "N2": 50}\n{"__location": "Bathroom", "N1": 100, "N4": 50}\n{"__location": "Bathroom", "N5": 100, "N4": 50}\nIn your case, "N1", "N2"... will contain the name of the visible networks.\nWhen you're happy with your training data, it's time to convert it to something useful.\nGenerating the features converter\nGiven the data we have, we want to generate C code that can convert a Wifi scan result into a feature vector we can use for classification.\nSince I'm a fan of code-generators, I wrote one specifically for this very project. And since I already have a code-generator library I use for Machine Learning code written in Python, I updated it with this new functionality.\nYou must have Python installed on your system\nStart by installing the library.\n# be sure it installs version >= 1.1.8\npip install --upgrade micromlgen\nNow create a script with the following code:\nfrom micromlgen import port_wifi_indoor_positioning\n\nif __name__ == '__main__':\n samples = '''\n {"__location": "Kitchen", "N1": 100, "N2": 50}\n {"__location": "Bedroom", "N3": 100, "N2": 50}\n {"__location": "Bathroom", "N1": 100, "N4": 50}\n {"__location": "Bathroom", "N5": 100, "N4": 50}\n '''\n X, y, classmap, converter_code = port_wifi_indoor_positioning(samples)\n print(converter_code)\nOf course you have to replace the samples content with the output you got in the previous step. \nIn the console you should see a C++ class we will use later in the Arduino sketch. The class should be similar to the following example code.\n// Save this code in your sketch as Converter.h\n\n#pragma once\nnamespace Eloquent {\n namespace Projects {\n class WifiIndoorPositioning {\n public:\n /**\n * Get feature vector\n */\n float* getFeatures() {\n static float features[5] = {0};\n uint8_t numNetworks = WiFi.scanNetworks();\n\n for (uint8_t i = 0; i < 5; i++) {\n features[i] = 0;\n }\n\n for (uint8_t i = 0; i < numNetworks; i++) {\n int featureIdx = ssidToFeatureIdx(WiFi.SSID(i));\n\n if (featureIdx >= 0) {\n features[featureIdx] = WiFi.RSSI(i);\n }\n }\n\n return features;\n }\n\n protected:\n /**\n * Convert SSID to featureIdx\n */\n int ssidToFeatureIdx(String ssid) {\n if (ssid.equals("N1"))\n return 0;\n\n if (ssid.equals("N2"))\n return 1;\n\n if (ssid.equals("N3"))\n return 2;\n\n if (ssid.equals("N4"))\n return 3;\n\n if (ssid.equals("N5"))\n return 4;\n\n return -1;\n }\n };\n }\n }\nI will briefly explain what it does: when you call getFeatures(), it runs a Wifi scan and for each network it finds, it fills the corresponding element in the feature vector (if the network is a known one).\nAt the end of the procedure, your feature vector will look something like [0, 10, 0, 0, 50, 0, 0], each element representing the RSSI of a given network.\n\r\n\r\n\r\n \r\n\tFinding this content useful?\r\n\r\n\t\r\n\r\n\t\r\n\t\t\r\n\t\t\r\n\t \r\n \r\n \r\n \r\n\r\n\r\n\r\n\nGenerating the classifier\nTo close the loop of the project, we need to be able to classify the features vector into one of the recorded location. Since we already have micromlgen installed, it will be very easy to do so.\nLet's update the Python code we already have: this time, instead of printing the converter code, we will print the classifier code.\n# install ml package first\npip install scikit-learn\nfrom sklearn.tree import DecisionTreeClassifier\nfrom micromlgen import port_wifi_indoor_positioning, port\n\nif __name__ == '__main__':\n samples = '''\n {"__location": "Kitchen", "N1": 100, "N2": 50}\n {"__location": "Bedroom", "N3": 100, "N2": 50}\n {"__location": "Bathroom", "N1": 100, "N4": 50}\n {"__location": "Bathroom", "N5": 100, "N4": 50}\n '''\n X, y, classmap, converter_code = port_wifi_indoor_positioning(samples)\n clf = DecisionTreeClassifier()\n clf.fit(X, y)\n print(port(clf, classmap=classmap))\nHere I chose Decision tree because it is a very lightweight algorithm and should work fine for the kind of features we're working with.\nIf you're not satisfied with the results, you can try to use SVM or Gaussian Naive Bayes, which are both supported by micromlgen.\nIn the console you will see the generated code for the classifier you trained. In the case of DecisionTree the code will look like the following.\n// Save this code in your sketch as Classifier.h\n\n#pragma once\nnamespace Eloquent {\n namespace ML {\n namespace Port {\n class DecisionTree {\n public:\n /**\n * Predict class for features vector\n */\n int predict(float *x) {\n if (x[2] <= 25.0) {\n if (x[4] <= 50.0) {\n return 1;\n }\n\n else {\n return 2;\n }\n }\n\n else {\n return 0;\n }\n }\n\n /**\n * Convert class idx to readable name\n */\n const char* predictLabel(float *x) {\n switch (predict(x)) {\n case 0:\n return "Bathroom";\n case 1:\n return "Bedroom";\n case 2:\n return "Kitchen";\n default:\n return "Houston we have a problem";\n }\n }\n\n protected:\n };\n }\n }\n }\nWrapping it all together\nNow that we have all the pieces together, we only need to merge them to get a complete working example.\n// file WifiIndoorPositioning.h\n\n#include "WiFi.h"\n#include "Converter.h"\n#include "Classifier.h"\n\nEloquent::Projects::WifiIndoorPositioning positioning;\nEloquent::ML::Port::DecisionTree classifier;\n\nvoid setup() {\n Serial.begin(115200);\n}\n\nvoid loop() {\n Serial.print("You're in ");\n Serial.println(classifier.predictLabel(positioning.getFeatures()));\n delay(3000);\n}\nTo the bare minimum, the above code runs the scan and tells you which location you're in. That's it.\nDisclaimer\nThis system should be pretty accurate and robust if you properly gather the data, though I can quantify how much accurate.\nThis is not an indoor navigation system: it can't tell you "the coordinates" of where you are, it can only detect in which room you're in.\nIf your location lack of nearby Wifi hotspots, an easy and cheap solution would be to spawn a bunch of ESP8266 / ESP32 boards around your house each acting as Access Point: with this simple trick you should be able to be as accurate as needed by just adding more boards.\n\r\n\r\n\r\n \r\n\tFinding this content useful?\r\n\r\n\t\r\n\r\n\t\r\n\t\t\r\n\t\t\r\n\t \r\n \r\n \r\n \r\n\r\n\r\n\r\n\n\nWith this in-depth tutorial I hope I helped you going from start to end of setting up a Wifi indoor positioning system using cheap hardware as ESP8266 / ESP32 boards and the Arduino IDE. \nAs you can see, Machine learning has not to be intimidating even for beginners: you just need the right tools to get the job done.\nIf this guide excited you about Machine learning on microcontrollers, I invite you to read the many other posts I wrote on the topic and share them on the socials.\nYou can find the whole project on Github. Don't forget to star the repo if you like it.\nL'articolo The Ultimate Guide to Wifi Indoor Positioning using Arduino and Machine Learning proviene da Eloquent Arduino Blog.", "date_published": "2020-08-08T15:21:25+02:00", "date_modified": "2020-08-09T16:19:32+02:00", "authors": [ { "name": "simone", "url": "https://eloquentarduino.github.io/author/simone/", "avatar": "http://1.gravatar.com/avatar/d670eb91ca3b1135f213ffad83cb8de4?s=512&d=mm&r=g" } ], "author": { "name": "simone", "url": "https://eloquentarduino.github.io/author/simone/", "avatar": "http://1.gravatar.com/avatar/d670eb91ca3b1135f213ffad83cb8de4?s=512&d=mm&r=g" }, "tags": [ "Senza categoria" ] } ] }