Skip to content

Getting Started on SML

Guilherme Íscaro edited this page Aug 27, 2015 · 19 revisions

Overview

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

Building

Start by cloning Soletta Machine Learning git repository:

$ git clone [email protected]:solettaproject/soletta-machine-learning.git

Install the dependencies: fuzzylite-5.0, fann-2.2.0 and soletta

fuzzylite is fuzzy logic control library written in C++:

FANN is a neural network library.

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.

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

Modeling the solution

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.

Coding

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

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.

Add variables

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. Although we have the possibility to define each term to describe your problem, for this example we will let the fuzzy engine do this for us. To improve the quality of the automatically created terms, we need to give fuzzy engine some hints about the variable being used. As both variables are boolean values, lets set the default term width to 0.5 and the range from 0 to 1. So we will have 2 terms, one for on and another for off.

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_set_default_term_width(sml, sensor, 0.5);

    light = sml_new_output(sml, "Light");
    sml_variable_set_range(sml, light, 0, 1);
    sml_fuzzy_variable_set_default_term_width(sml, light, 0.5);

Register read callback

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;
    }

Register change callback

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");
        }
    }

Call process()

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);
    }

Final code

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_set_default_term_width(sml, sensor, 0.5);
    
        light = sml_new_output(sml, "Light");
        sml_variable_set_range(sml, light, 0, 1);
        sml_fuzzy_variable_set_default_term_width(sml, light, 0.5);

        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;
    }

Output

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.

Fine tuning

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

Code samples

There is a directory inside soletta-machine-learning repository with a few code samples:

$ cd machine-learning/examples/

List: