-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started on SML
If you're new to Soletta Machine Learning (SML), here are a few steps to create a simple software using its C API.
See Soletta Machine Learning for a introduction to the project and learn a bit about machine learning.
For instructions on how to use machine learning on flows written with Soletta, see How to add machine learning to flows
Start by cloning Soletta Machine Learning git repository:
$ git clone [email protected]:solettaproject/soletta-machine-learning.git
Install the dependencies: fuzzylite, fann and soletta
fuzzylite is fuzzy logic control library written in C++:
- Web site: http://www.fuzzylite.com/
- github: https://github.com/fuzzylite/fuzzylite.git
- you may need to tweak fuzzylite.pc.in a bit (change Cflags: -I${includedir}/fl for Cflags: -I${includedir})
- you may find packages for your distro as well
FANN is a neural network library.
- Web site: http://leenissen.dk/fann/wp/
- github: https://github.com/libfann/fann
- some distros like Fedora, has Fann in its rpm repository.
Soletta is a framework for making IoT devices. Some Soletta's data types are used by SML. Also SML provides a module for Soletta that depends on it.
- Web site: http://solettaproject.org/
- github: https://github.com/solettaproject/soletta
CMake building system is used to build SML, so it's required to install it.
After dependencies are properly installed, it's time to build SML:
$ cd soletta-machine-learning/
$ mkdir build/
$ cd build/
$ cmake ..
$ make
Before start coding, it's a good idea to think about the problem and try to answer a few questions:
- What is it trying to learn?
- What would be the input and output variables?
- What engine is better to this specific problem? See this section to learn about their differences.
- (For fuzzy only) How can these variables change? Take on consideration the type of variables, ranges, how should they be separated on terms.
Let's consider a very simple scenario, a product composed by a presence sensor, a switch and a light bulb. So when the user gets in the her room presence sensor reads "on", the user press the switch turning the light on. When the user is leaving the room she disable the switch, turning the lights out, and the presence sensor reads "off".
The SML main flow consist in the following steps:
- Choose an engine (ANN or Fuzzy)
- Create the inputs and outputs variables
- Register the read callback
- Register the change callback
- Read the variable value, set it in SML and call process()
Let's use fuzzy engine for demonstration purposes, but both engines should be do fine for such scenario.
Start creating a file hello_world.c including general purpose header sml.h and sml_fuzzy specific for the chosen engine. sml_ann.h would be included if neural network would be used.
Also, create the main() function. For now, just create a sml instance and delete it later. This instance will be used to add variables, receive values, and process this data making it possible to predict output.
#include <sml.h>
#include <sml_fuzzy.h>
#include <stdio.h>
int
main(int argc, char **argv) {
Sml *sml;
sml = sml_fuzzy_new();
// TODO: everything else
sml_free(sml);
return 0;
}
So after compiling it:
$ gcc hello_world.c -o hello_world `pkg-config --cflags --libs sml`
You should be able to run it:
$ ./hello_world
but it won't do anything useful yet.
In this scenario, there are only two variables:
- one input variable, the presence sensor, that may be 0 or 1
- one output variable, the light state, that also can be 0 or 1 (representing on / off)
So let's declare these variables:
Sml_Variable *sensor, *light;
And add them to our example, after the sml instance creation and before its deletion - where was marked with TODO. The range (from 0 to 1), and terms must to be defined.
Terms are a way to split the values in the range in meaningful parts. Fuzzy supports some functions to describe them, as ramps, triangles and others. Since they're binary variables, using two ramps, one starting on 0 and other ending on 1, with a small overlap between them, seems good enough. This way, it will try to predict the output for any possible value being more certain about it on extremities.
To learn more about terms, see https://github.com/solettaproject/soletta/wiki/Soletta-Machine-Learning#fuzzy-terms.
We don't need to explicitly delete this variables or terms later, since it'll be done when sml_free() is called.
sensor = sml_new_input(sml, "PresenceSensor");
sml_variable_set_range(sml, sensor, 0, 1);
sml_fuzzy_variable_add_term_ramp(sml, sensor, "Off", 0.6, 0, 1);
sml_fuzzy_variable_add_term_ramp(sml, sensor, "On", 0.4, 1.0, 1);
light = sml_new_output(sml, "Light");
sml_variable_set_range(sml, light, 0, 1);
sml_fuzzy_variable_add_term_ramp(sml, light, "Off", 0.6, 0, 1);
sml_fuzzy_variable_add_term_ramp(sml, light, "On", 0.4, 1.0, 1);
It's required to set a read callback. This function will be called at each processing cycle to update variables values. Inside main(), after creating sml instance.
sml_set_read_callback(sml, read_cb, NULL);
Third argument is a pointer for any data that may be needed by this function.
So in this function the values of presence sensor and light must to be fetched. It could be done via GPIO, OIC, or any other way, depending in your product. It may be synchronous or asynchronous. But it's out of scope for this tutorial.
To keep it simple, yet illustrative, we're going to simulate the values in a function read_cb() Variable sensor_state represents presense sensor reading, switch_state represents light state. This variable will be global since its going to be used by the callback of state changes (you could use a struct with this variable and pass them to callbacks).
static int switch_state = 0;
To simulate a daily based used, a day would be a cycle of 15 readings, user will be present on last 5 readings of each "day". When she leaves she remembers to turn lights off... most of the time. This is simulated by off_count . When she forgets lights on and leaves, machine learning should suggest to turn lights off.
After states are "fetch", they're set on sml variables (input and output).
static bool
read_cb(Sml *sml, void *data)
{
Sml_Variables_List *inputs, *outputs;
Sml_Variable *sensor, *light;
static int sensor_state = 0;
static int off_count = 0;
static unsigned int count = 0;
// user is present after 10 reads
if (count == 10) {
printf("User got in the room.\n");
sensor_state = 1;
switch_state = 1;
count++;
}
// and stay there for 5 more reads
else if (count == 15) {
printf("User left the room.\n");
off_count++;
sensor_state = 0;
// most of times she remembers to swith off lights when she leaves
if (off_count % 4 == 0) {
printf("Oops! User forgot to turn lights off.\n");
} else {
switch_state = 0;
}
count = 0;
}
// ... forever on this cycle
else {
count++;
}
inputs = sml_get_input_list(sml);
sensor = sml_variables_list_index(sml, inputs, 0);
sml_variable_set_value(sml, sensor, sensor_state);
outputs = sml_get_output_list(sml);
light = sml_variables_list_index(sml, outputs, 0);
sml_variable_set_value(sml, light, switch_state);
return true;
}
To fetch predicted output values, it's required to set a callback function using sml_set_change_state_callback()
This callback will be called when SML makes a prediction for at least one output variable.
So in main() function:
sml_set_change_state_callback(sml, change_state_cb, NULL);
When SML fails to predict the value of an output variable, it sets this value as NaN (not a number). To handle that, we should use math library.
#include <math.h>
Change state callback only will be called on output value changes. But most of the time, this prediction should matches current state of output variable. On this case no action must to be taken. Also, sometimes SML doesn't have enough information to make a prediction, setting output variable value to NaN.
So in change state callback we're going to check first if it was able to predict a value, then check if we need to act, in case prediction and current light state diverges.
In this example we'll just print a message informing the light state should be changed.
static void
change_state_cb(Sml *sml, Sml_Variables_List *changed, void *data)
{
Sml_Variable *light;
double prediction;
light = sml_variables_list_index(sml, changed, 0);
prediction = sml_variable_get_value(sml, light);
// When SML can't predict a value, it'll be set to NaN
if (isnan(prediction)) {
printf("Sorry, can't predict light state.\n");
return;
}
// prediction is equal to current state
if ((prediction > 0.5) == switch_state) {
return;
}
if (prediction > 0.5) {
printf("Light should be turned ON.\n");
} else {
printf("Light should be turned OFF.\n");
}
}
Everything is set up, it's time to trigger the processing. It will be done in a simple loop, making 150 sml_process() calls, but more elaborated ways to call process() can be implemented, using mainloops.
for (int i = 0; i < 150; i++)
{
sml_process(sml);
}
After all these steps, code should look like this:
#include <math.h>
#include <sml.h>
#include <sml_fuzzy.h>
#include <stdio.h>
static int switch_state = 0;
static bool
read_cb(Sml *sml, void *data)
{
Sml_Variables_List *inputs, *outputs;
Sml_Variable *sensor, *light;
static int sensor_state = 0;
static int off_count = 0;
static unsigned int count = 0;
// user is present after 10 reads
if (count == 10) {
printf("User got in the room.\n");
sensor_state = 1;
switch_state = 1;
count++;
}
// and stay there for 5 more reads
else if (count == 15) {
printf("User left the room.\n");
off_count++;
sensor_state = 0;
// most of times she remembers to swith off lights when she leaves
if (off_count % 4 == 0) {
printf("Oops! User forgot to turn lights off.\n");
} else {
switch_state = 0;
}
count = 0;
}
// ... forever on this cycle
else {
count++;
}
inputs = sml_get_input_list(sml);
sensor = sml_variables_list_index(sml, inputs, 0);
sml_variable_set_value(sml, sensor, sensor_state);
outputs = sml_get_output_list(sml);
light = sml_variables_list_index(sml, outputs, 0);
sml_variable_set_value(sml, light, switch_state);
return true;
}
static void
change_state_cb(Sml *sml, Sml_Variables_List *changed, void *data)
{
Sml_Variable *light;
double prediction;
light = sml_variables_list_index(sml, changed, 0);
prediction = sml_variable_get_value(sml, light);
// When SML can't predict a value, it'll be set to NaN
if (isnan(prediction)) {
printf("Sorry, can't predict light state.\n");
return;
}
// prediction is equal to current state
if ((prediction > 0.5) == switch_state) {
return;
}
if (prediction > 0.5) {
printf("Light should be turned ON.\n");
} else {
printf("Light should be turned OFF.\n");
}
}
int
main(int argc, char *argv[])
{
Sml *sml;
Sml_Variable *sensor, *light;
sml = sml_fuzzy_new();
sensor = sml_new_input(sml, "PresenceSensor");
sml_variable_set_range(sml, sensor, 0, 1);
sml_fuzzy_variable_add_term_ramp(sml, sensor, "Off", 0.6, 0, 1);
sml_fuzzy_variable_add_term_ramp(sml, sensor, "On", 0.4, 1.0, 1);
light = sml_new_output(sml, "Light");
sml_variable_set_range(sml, light, 0, 1);
sml_fuzzy_variable_add_term_ramp(sml, light, "Off", 0.6, 0, 1);
sml_fuzzy_variable_add_term_ramp(sml, light, "On", 0.4, 1.0, 1);
sml_set_read_callback(sml, read_cb, NULL);
sml_set_change_state_callback(sml, change_state_cb, NULL);
for (int i = 0; i < 150; i++)
{
sml_process(sml);
}
sml_free(sml);
return 0;
}
Running this sample, some messages will be written on terminal.
User got in the room.
and
User left the room.
just display the simulation state.
Message:
Oops! User forgot to turn lights off.
Indicates user failed to act. Since the program was trained to learn to keep lights off when user isn't in the room (indicated by presence sensor), the following message is expected to be displayed:
Light should be turned OFF.
In a real product it could be used to turn lights off, send user a message or any other desired behavior.
At the first times you try to retrieve SML may return NaN as result while it doesn't have enough information about current scenario. In this example, you may see a message indicating such situation in the beginning of the simulation:
Sorry, can't predict light state.
SML supports a lot of customization, even some options specific for each engine.
To learn more about it, check the project's online API
If you prefer, it's possible to build it online
$ cd machine-learning/build/
$ make doc
It depends on Doxygen
There is a directory inside soletta-machine-learning repository with a few code samples:
$ cd machine-learning/examples/
List: