Storing and retrieving models with ML.js

ML.js is a javascript library developed as a part of the MLweb project and aimed at providing machine learning capabilities for web applications.

This short tutorial will show how to build a small and simple example application that saves a predictive model for futher use at next visits of the page.

The web page will

See the live demo (you might also want to look at the source of this demo page while reading this tutorial).

HTML part

First, we need to load the machine learning library, typically in the head:
<html>
<head>
	<meta charset="UTF-8">
	<script src="http://mlweb.loria.fr/ml.js"> </script>
</head>
Then, the document body merely contains a div to plot the data and another one to display some text:
<body onload="init();">

	<h1>Storing and retrieving data and models with ML.js</h1>

	<div style="font-size: 80%;" title="LALOLib output" id="LALOLibOutput"></div>

	<div id="textdiv"></div>

</body>
</html>

Javascript code

There are a number of ways to store data persistently in JavaScript (webWSQL, IndexedDB,... ). Here, we will use the most simple one: localStorage.

The javascript part is divided into two functions triggered by body.onload depending on whether this is the first visit of the page or not.

function init() {
	visit = localStorage.getItem("visit"); // retrieve the number of visits
	if ( visit == null ) {
		visit = 1;
		textdiv.innerHTML = "Welcome! This is your first time here!";
		
		firstVisit();				
	}
	else {
		textdiv.innerHTML = "Welcome again! This is your visit #" + visit;
		
		nextVisits();
	}	
	visit++; 
	localStorage.setItem("visit", visit); // update number of visits
}
The function firstVisit() generates a simple data set with points in 2D distributed according to a mixture of two Gaussians:
	// Generate a random data set 
	X1 = add(3, randn(50,2)); 	// points of class 1 distributed around [3,3]
	X2 = add(-3, randn(50,2)); 	// points of class -1 distributed around [-3,-3]
	X = mat([X1, X2], true);	// concatenate the two sets
	Y = ones(100);			// make the label vector with +1 and -1
	set(Y, range(50,100), -1);
	
	plot(X1,".b", X2,".r");	// plot the data
Then, we train the classifier on this data set with ML.js:
	mysvm = new Classifier(SVM);	// Create a classifier
	mysvm.train(X,Y);		// and train it on the data set
The result in mysvm is a complex Object of type Classifier that cannot be directly stored. First, we need to create a "storable" version of this object in the form of a String. This is done in two steps: we use getObjectWithoutFunc to prepare a version without its member functions and formatted so that these functions can be later recovered, and then we apply JSON.stringify to make the String. This can be written in one line as:
	mysvmReadyForStorage = JSON.stringify( getObjectWithoutFunc(mysvm) );
The result can then easily be stored in the localStorage with:
	localStorage.setItem("svm", mysvmReadyForStorage); 

The nextVisits() function first generates a new random data set as in firstVisit() but without the labels Y, which should be now predicted by the SVM classifier. This classifier is loaded from the localStorage with

	mysvmAsStoredLastTime = localStorage.getItem("svm");
More precisely, this gives a String which should first be parsed by JSON.parse to recover an Object. And in order to obtain an Object of the correct type (here, Classifier) with all its member functions, we also need to pass the result of JSON.parse to renewObject() as in:
	mysvm = renewObject( JSON.parse(mysvmAsStoredLastTime) );
This effectivelly recreates the SVM Object, that can now be used to predict the labels in a standard manner:
	Y = mysvm.predict(X); 

Summary

To store an ML.js Object (Classifier, Regression, Matrix, ... ) x, use
	localStorage.setItem("x", JSON.stringify( getObjectWithoutFunc( x ) ) );
To retrieve this object, use
	x = renewObject( JSON.parse( localStorage.getItem("x") ) );

Storing and retrieving models using Files

To store the stringified object (mysvmReadyForStorage above) into a file and create a download link, we need to create a Blob from the String and then create an URL that points to this Blob. In JavaScript language, this is done with

	var blob = new Blob ( [ mysvmReadyForStorage ] );
	var url = URL.createObjectURL( blob );
Then, a download link can be instered in the textdiv with something like
	textdiv.innerHTML += "<a href='" + url + "' download> my download link </a>";

To load the classifier from a file, we need a "Browse..." button in the HTML part of the page:

	<input id="fileBtn" type="file" onchange="loadfromfile();">
When the user selects a file or drag-and-drop a file on this button, this triggers loadfromfile(), which applies the same steps to recreate the Classifier from the String as in nextVisists(): JSON.parse and renewObject. The only difference is that the String has to be read from a file when it is loaded instead of the localStorage:
function loadfromfile() {
	var reader = new FileReader();
	reader.onload = function () {
		mysvmFromFile = renewObject( JSON.parse( reader.result ) );
		
		// Use the loaded SVM to predict the labels
		Y_fromFile = mysvmFromFile.predict(X); 	
		
		... do something with Y_fromFile...
	}
	reader.readAsText( fileBtn.files[0] );	
}

Summary

Any ML.js Object (Classifier, Regression, Matrix, ... ) x can be transformed into a storable String with
	var x_string = JSON.stringify( getObjectWithoutFunc( x ) ) ;
and retrieved as an ML.js Object with
	x = renewObject( JSON.parse( x_string ) );
There are various ways to actually store this String in the system and more information on these can easily be found elsewhere on the web.