4. Getting started on browser (WASM/Emscripten)

emlearn models work anywhere there is a C99 compiler available. This includes running client-side in a web browser, by using a compiler like Emscription to target WebAssembly (WASM).

emlearn is primarily designed for very compute and size constrained microcontrollers. This makes it good for making very small models, which is a benefit also in the browser, in order to minimize download time. Furthermore using a browser can be very useful when developing embedded systems, as it enables making demonstrations and tools that are very accessible, while re-using the same code that is deployed on-device.

4.1. Prerequisites

You need to have installed Python (version 3.6+), and the Emscripten toolchain.

This guide assumes that you have basic familiarity with web development.

Note

We will be showing some shell commands below. These will assume that a bash-compatible shell is used. For cmd.exe, Powershell et.c the commands may need to be adjusted somewhat.

4.2. Install scikit-learn

In this example, scikit-learn is used to train the models.

pip install scikit-learn

4.3. Install emlearn

emlearn will be used to convert the scikit-learn models to C code.

pip install emlearn

4.4. Create model in Python

We will train a simple model to learn the XOR function. The same steps will be used for model of any complexity. Copy and save this as file xor_train.py.

 1import emlearn
 2import numpy
 3from sklearn.ensemble import RandomForestClassifier
 4from sklearn.metrics import get_scorer
 5
 6# Generate simple dataset
 7def make_xor(lower=0.0, upper=1.0, threshold=0.5, samples=100, seed=42):
 8    rng = numpy.random.RandomState(seed)
 9    X = rng.uniform(lower, upper, size=(samples, 2))
10    y = numpy.logical_xor(X[:, 0] > threshold, X[:, 1] > threshold)
11    return X, y
12
13X, y = make_xor()
14
15# Train a model
16estimator = RandomForestClassifier(n_estimators=3, max_depth=3, max_features=2, random_state=1)
17estimator.fit(X, y)
18score = get_scorer('f1')(estimator, X, y)
19assert score > 0.90, score # verify that we learned the function
20
21# Convert model using emlearn
22path = 'xor_model.h'
23cmodel = emlearn.convert(estimator, method='inline')
24cmodel.save(file=path, name='xor_model')
25print('Wrote model to', path)

Run the script

python xor_train.py

It will generate a file xor_model.h containing the C code for our model.

4.5. Use in C code for WASM module

To run C code in a browser, we need to build it as a WebAssembly (WASM) module. For that we will use Emscripten.

Copy and save this as file xor_browser.c.

 1#include "xor_model.h" // emlearn generated model
 2
 3#include <stdio.h>
 4#include <stdlib.h>
 5
 6// Function that will be exposed to JavaScript using emscripten
 7int
 8run_xor_model(const float *features, int length)
 9{
10    int out = -EmlSizeMismatch;
11    float a = -1.0;
12    float b = -1.0;
13    if (length == 2) {
14        a = features[0];
15        b = features[1];
16        out = xor_model_predict(features, length); // Alternative A: "inline"
17        out = eml_trees_predict(&xor_model, features, length); // Alternative B: "loadable"
18    }
19
20    printf("run_xor_model n_features=%d inputs=(%.2f, %.2f) out=%d\n", length, a, b, out);
21    return out; 
22}

Compile the C code to WASM using Emscripten.

export EMLEARN_INCLUDE_DIR=`python -c 'import emlearn; print(emlearn.includedir)'`
emcc xor_browser.c -o xor_browser.js \
    -sALLOW_MEMORY_GROWTH=1 \
    -sEXPORTED_FUNCTIONS=_run_xor_model,_malloc,_free \
    -I${EMLEARN_INCLUDE_DIR}

The setting ALLOW_MEMORY_GROWTH is needed to support malloc, which is used to allocate space for the input data. And all C functions which we want to call needs to be added to EXPORTED_FUNCTIONS (with an underscore in front).

4.6. Load WASM module in webpage

Now we will set up a small webpage where we will use the model.

Copy and save this as file xor_browser.html.

 1<html>
 2   <head>
 3      <!-- Load WebAssembly module -->
 4      <script type="text/javascript" src="xor_browser.js"></script>
 5   </head>
 6   <body>
 7
 8      <!-- Some UI elements for input/output -->
 9      <div style="width: 300px">
10          <h3>emlearn Hello World: XOR</h3>
11          <input id="input1" type="range" min"0" max="1.0" value="0.5" step="0.1" style="width:200px; height:20px" oninput="inputChanged(this)">
12          <output id="input1val" style="float: right;">0.5</output>
13          <input id="input2" type="range" min="0" max="1.0" value="0.5" step="0.1" style="width:200px; height:20px" oninput="inputChanged(this)">
14          <output id="input2val" style="float: right;">0.5</output>
15          <p><small>Drag sliders to update</small></p>
16          <span id='out' style="width: 200px">Out:</span><span id="answer" style="width: 60px; float: right; text-align: right">Unknown</span>
17      </div>
18
19      <script>
20        var runModel = function(features)
21        {
22            // Convert JavaScript array to WASM/C float array
23            var values = new Float32Array(features);
24            var heapSpace = Module._malloc(values.length * values.BYTES_PER_ELEMENT);
25            Module.HEAPF32.set(values, heapSpace>>2); // float has 4 bytes
26
27            // Run the WASM/C code
28            const result = Module._run_xor_model( heapSpace, values.length );
29
30            // Free allocated memory for input
31            Module._free(heapSpace);
32            return result;
33        }
34
35        var inputChanged = function()
36        {
37            input1val.value = input1.value;
38            input2val.value = input2.value;
39            var features = [ input1.value, input2.value ];
40            const result = runModel(features);
41            answer.innerHTML = result;
42            const color = (result) ? "green" : "red";
43            document.getElementById('answer').style.backgroundColor= color;
44        }
45      </script>
46   </body>
47</html>

4.7. Try it out

Run a web server, which will serve the xor_browser.html file

python -m http.server

Open browser and navigate to localhost:8000/xor_browser.html. You should see a webpage with two sliders. When changing the input values using the slides, the JavaScript will call the xor_browser.c via WASM. This runs the model built with emlearn, which classifies the data. The result is written to the “Out” element.

In our training data input values above 0.5 is considered “true”. So for the XOR function, if one and only one of the values is above 0.5, should get class 1 as output - else class 0.

Browser running emlearn XOR model. Output=1

4.8. Next

Now you have the emlearn setup, that can build browser-based applications.

You may be interested in trying it out on a hardware device. See for example Getting started on hardware (Arduino).