Votre première application Machine Learning avec Tensorflowjs et React

Commencez l'aventure ‘machine learning’ avec Tensorflowjs et React, tutoriel avec démo et sources

Objectifs

  • Utiliser l’api de base de Tensorflowjs

Une démo de l’application et les sources sont disponibles sur github.

Contexte

React est une des librairies javascript la plus utilisée pour construire une interface utilisateur riche et performante. Tensorflowjs est l’implémentation de Tensorflow pour NodeJS et pour permettre aux développeurs d’utiliser côté client des modèles de machine learning créés côté serveur.

Préparation de l’environnement

Node.js doit être installé pour générer le projet et gérer les dépendances.

L’application est développée grâce à la librairie React, nous allons utiliser Create React App pour générer le projet rapidement avec une configuration de base.

Création du Projet React

npx create-react-app tensorflowjs-app && cd tensorflowjs-app

Les fichiers non nécessaires sont supprimés, l’application se présente ainsi :

Ajout de Tensorflowjs au projet :

npm i @tensorflow/tfjs

Développement de l’application

Avant de se lancer dans le code, un petit résumé de ce que doit pouvoir faire l’utilisateur :

  • Visualiser les données d’entrainement du modèle

Génération des données

Pour simplifier, nous utilisons des données linéaires, liées par une relation connue à l’avance. Cette relation est arbitraire, prenons par exemple une fonction y = 2 * x + 5

On part avec les données suivantes :

Pas besoin de machine learning pour prédire une valeur inconnue à partir de ces données.

Si on veut déterminer la valeur de y pour x = 100 on obtient y = 100 * 2 + 5 = 205

L’exemple est volontairement simple ce n’est pas le résultat qui compte mais comment on va entrainer notre modèle pour prédire ce résultat. Notre application ignore le lien entre x et y, c’est ce lien que l’on va recréer en utilisant le machine learning.

Le fichier tensorflow.js contiendra les fonctions liées à la logique de notre application.

La première fonction nous retourne nos valeurs de départ sous forme de tensors. Les tensors sont des éléments utilisés par Tensorflow pour travailler sur les données.

// tensorflow.js
export function generateData() {
// data may come from an external resource : CSV file, API, database...
const input = tf.tensor([0, 2, 4, 7, 10, 20, 50], [7, 1]);
const label = tf.tensor([5, 9, 13, 19, 25, 45, 105], [7, 1]);
return [input, label];
}

On créé deux tensors, un pour les données “x” que l’on nomme input par convention, et l’autre pour les données “y” nommées label.

Du côté de l’interface, on créé un composant React TensorflowApp.js, qui sera directement rendu par notre index.js.

// TensorflowApp.js
function TensorflowApp() {
return (
<div>
<h1>Tensorflowjs in React</h1>
</div>
);
}

On récupère les tensors générés.

// TensorflowApp.js
const [input, label] = generateData();

Tensorflowjs nous met à disposition des fonctions pour accéder aux données contenues dans les tensor. On utilise tensor.arraySync() pour transformer les données en tableaux javascript. On affiche les valeurs qu’ils contiennent sous forme de tableau html.

Création du modèle

Pour créer le modèle, on peut faire varier plusieurs paramètres, que nous allons variabiliser pour permettre à l’utilisateur de modifier ces paramètres via l’interface React.

// tensorflow.js
export function createModel({
units = 1,
learningRate = 0.01,
optimizer = "adam",
}) {
const selectOptimizer = (optimizer) => {
return OPTIMIZERS[optimizer].fn(learningRate);
};

const model = tf.sequential();
model.add(tf.layers.dense({ units, inputShape: [1] }));
model.compile({
optimizer: selectOptimizer(optimizer),
loss: "meanSquaredError",
});
return model;
}

Un des paramètres important est l’ optimizer qui est l’algorithme qui permettra au modèle de s’affiner pendant son entrainement. Ce paramètre est une chaine de caractères, qui retourne une fonction mise à disposition par Tensorflowjs.

Il nous faut une structure contenant les fonctions d’optimisation fournies par Tensorflowjs, nous allons créer un objet qui aura ce rôle, dans un fichier séparé pour permettre de le réutiliser éventuellement plus tard :

// optimizers.js
export const OPTIMIZERS = {
sgd: { libelle: "sgd", fn: (lr) => tf.train.sgd(lr) },
adam: { libelle: "adam", fn: (lr) => tf.train.adam(lr) },
adagrad: { libelle: "adagrad", fn: (lr) => tf.train.adagrad(lr) },
adadelta: { libelle: "adadelta", fn: (lr) => tf.train.adadelta(lr) },
momentum: { libelle: "momentum", fn: (lr) => tf.train.momentum(lr, 1) },
rmsprop: { libelle: "rmsprop", fn: (lr) => tf.train.rmsprop(lr) },
};

Nous allons ensuite mettre en place la gestion du modèle et ses paramètres. Pour cela nous utilisons le hook useReducer fourni par React.

// TensorflowApp.js
const initialModelOptions = {
optimizer: "adam",
learningRate: 0.01,
epochs: 50,
};

function modelReducer(state, action) {
switch (action.type) {
case "SET_OPTIMIZER":
return { ...state, optimizer: action.value };
case "SET_LEARNINGRATE":
return { ...state, learningRate: parseFloat(action.value) };
case "SET_EPOCHS":
return { ...state, epochs: parseInt(action.value) };
default:
return state;
}
}

Tous les paramètres seront facilement mis à jour grâce à une seule fonction qui s’occupe d’update le state avec la nouvelle valeur. Cela évite d’avoir autant de fonction “handleChange()” que l’on a de paramètres.

// TensorflowApp.js
const handleChangeModelOptions = (action) => (e) => {
dispatch({ type: action, value: e.target.value });
};

Il ne reste plus qu’à coder les champs correspondants aux paramètres (la fonction d’optimisation et le learning rate).

Entrainement du modèle

Il faut ensuite entrainer notre modèle sur les données, pour cela on créé une nouvelle fonction. Cette fonction prendra en paramètres un modèle, des données d’entrées (ou “x” ou “input”), des données de sortie (ou “y” ou “label”), et les epochs.

Les epochs sont le nombre de cycles pendant lesquels le modèle va être entrainé. Plus ce nombre est important, plus le modèle est censé être affiné et ses prévisions exactes.

// tensorflow.js
export async function trainModel(model, input, label, epochs = 150) {
await model.fit(input, label, { epochs });
}

Le modèle est créé, il faut à présent l’entrainer sur les données. La fonction suivante va entrainer le modèle, c’est une opération qui peut prendre un certain temps et doit donc être réalisée de façon asynchrone. Une fois le modèle créé on met à jour le state du composant avec un booléen qui gère le statut de notre modèle.

// TensorflowApp.js
const handleTrain = () => {
setModelReady(false);
trainModel(model, input, label, modelOptions.epochs).then(() =>
setModelReady(true)
);
};

Il nous reste à permettre à l’utilisateur de régler le nombre de cycles d’apprentissage, les epochs.

Utilisation pour prédire une valeur

On met à disposition à l’utilisateur un champ de saisie pour lui permettre de saisir une valeur d’entrée.

Résultats et perspectives

Un modèle est créé avec des paramètres arbitraires au lancement de l’application. Si on entraine ce modèle sans rien modifier et qu’on lance une prédiction, le résultat est très peu satisfaisant. Si on augmente le nombre d’ epochs et qu’on relance l’entrainement du modèle, les prédictions sont tout de suite plus justes !

Pour compléter l’application, on pourrait afficher les valeurs fournies par Tensorflowjs sur la précision du modèle, ou bien ajouter la visualisation des données grâce à la librairie d3js.

Vous pouvez tester la démo de l’application, et voir les sources.

Originally published at https://romainamichaud.com.

French independant developer.