Retour au blog

JavaScript et Machine Learning : TensorFlow.js Democratise l'IA

Salut HaWkers, imaginez entrainer un modele de Machine Learning et l'executer directement dans le navigateur, sans backend Python, sans serveurs lourds. Ca semble futuriste ? C'est la realite avec TensorFlow.js.

Vous, developpeur JavaScript, avez maintenant acces a des capacites d'IA qui etaient autrefois reservees aux data scientists. Explorons comment ca fonctionne, des cas d'usage reels et du code pratique que vous pouvez executer aujourd'hui.

Pourquoi le Machine Learning dans le Navigateur ?

Avantages de TensorFlow.js :

  1. Confidentialite : Les donnees ne quittent jamais l'appareil de l'utilisateur
  2. Latence zero : Pas d'aller-retour serveur
  3. Cout : Traitement distribue sur les clients (pas votre serveur)
  4. Accessibilite : N'importe quel dev JavaScript peut commencer
  5. Multi-plateforme : Browser, Node.js, React Native, Electron

Cas d'usage reels en 2025 :

  • Filtres camera en temps reel (Instagram, Snapchat)
  • Transcription audio offline (Zoom, Meet)
  • Detection de fraude dans les paiements
  • Recommandations personnalisees sans envoyer de donnees
  • Accessibilite (sous-titres, traduction en temps reel)

Configuration de Base : Votre Premier Modele

Creons un detecteur de sentiment de texte — classifie si une phrase est positive ou negative.

Installation :

npm install @tensorflow/tfjs
# Ou via CDN :
# <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>

Modele pre-entraine simple :

import * as tf from '@tensorflow/tfjs';

// 1. Creer un modele sequentiel simple
const model = tf.sequential({
  layers: [
    // Input : Texte converti en nombres (embedding)
    tf.layers.dense({ inputShape: [100], units: 16, activation: 'relu' }),

    // Hidden layer
    tf.layers.dense({ units: 8, activation: 'relu' }),

    // Output : 2 classes (positif/negatif)
    tf.layers.dense({ units: 2, activation: 'softmax' })
  ]
});

// 2. Compiler le modele
model.compile({
  optimizer: tf.train.adam(0.001),
  loss: 'categoricalCrossentropy',
  metrics: ['accuracy']
});

// 3. Entrainer avec des donnees
async function trainModel(reviews, labels) {
  // reviews : array de textes
  // labels : array de [0, 1] ou [1, 0]

  const xs = tf.tensor2d(reviews);
  const ys = tf.tensor2d(labels);

  await model.fit(xs, ys, {
    epochs: 50,
    batchSize: 32,
    validationSplit: 0.2,
    callbacks: {
      onEpochEnd: (epoch, logs) => {
        console.log(`Epoque ${epoch}: loss = ${logs.loss.toFixed(4)}`);
      }
    }
  });

  console.log('✓ Modele entraine !');
}

// 4. Faire des predictions
function predict(text) {
  // Convertir le texte en vecteur numerique (simplifie)
  const vector = textToVector(text);
  const input = tf.tensor2d([vector]);

  const prediction = model.predict(input);
  const scores = prediction.dataSync();

  return {
    positive: scores[0],
    negative: scores[1],
    sentiment: scores[0] > scores[1] ? 'Positif' : 'Negatif'
  };
}

// Exemple d'utilisation
const result = predict('J\'adore ce produit, tres bon !');
console.log(result);
// { positive: 0.89, negative: 0.11, sentiment: 'Positif' }

AI Technology

Cas Pratique : Reconnaissance d'Images en Temps Reel

Utilisons un modele pre-entraine (MobileNet) pour classifier les images de la webcam.

import * as tf from '@tensorflow/tfjs';
import * as mobilenet from '@tensorflow-models/mobilenet';

class ImageClassifier {
  constructor() {
    this.model = null;
    this.video = null;
  }

  async init() {
    // 1. Charger le modele (telechargement automatique)
    console.log('Chargement de MobileNet...');
    this.model = await mobilenet.load();
    console.log('✓ Modele charge !');

    // 2. Configurer la webcam
    this.video = document.getElementById('webcam');

    const stream = await navigator.mediaDevices.getUserMedia({
      video: { width: 640, height: 480 }
    });

    this.video.srcObject = stream;
    await new Promise(resolve => {
      this.video.onloadedmetadata = resolve;
    });

    this.video.play();
  }

  async classify() {
    if (!this.model || !this.video) return;

    // 3. Faire une prediction en temps reel
    const predictions = await this.model.classify(this.video);

    return predictions.map(p => ({
      class: p.className,
      probability: (p.probability * 100).toFixed(2) + '%'
    }));
  }

  async classifyLoop() {
    const resultsDiv = document.getElementById('results');

    setInterval(async () => {
      const predictions = await this.classify();

      resultsDiv.innerHTML = predictions
        .map(p => `<p>${p.class}: ${p.probability}</p>`)
        .join('');
    }, 1000); // Classifie toutes les 1 seconde
  }
}

// HTML correspondant
/*
<video id="webcam" autoplay></video>
<div id="results"></div>

<script>
  const classifier = new ImageClassifier();
  classifier.init().then(() => {
    classifier.classifyLoop();
  });
</script>
*/

Resultat : L'application identifie les objets dans la camera sans envoyer de donnees au serveur !

Transfer Learning : Entrainer Votre Propre Classificateur

Et si vous voulez classifier des choses specifiques ? Utilisez le Transfer Learning — prenez un modele pre-entraine et ajustez-le pour votre cas.

Exemple : Classificateur de poses (exercices physiques)

import * as tf from '@tensorflow/tfjs';
import * as mobilenet from '@tensorflow-models/mobilenet';

class PoseClassifier {
  constructor() {
    this.baseModel = null;
    this.model = null;
    this.classes = ['squat', 'pushup', 'plank', 'jump'];
  }

  async loadBaseModel() {
    // Charger MobileNet sans la derniere couche
    const mobilenet = await tf.loadLayersModel(
      'https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_0.25_224/model.json'
    );

    // Retirer la derniere couche (classification generique)
    const layer = mobilenet.getLayer('conv_pw_13_relu');
    this.baseModel = tf.model({
      inputs: mobilenet.inputs,
      outputs: layer.output
    });
  }

  createCustomModel() {
    // Ajouter de nouvelles couches pour vos classes
    const model = tf.sequential({
      layers: [
        tf.layers.flatten({
          inputShape: this.baseModel.outputs[0].shape.slice(1)
        }),
        tf.layers.dense({
          units: 128,
          activation: 'relu',
          kernelInitializer: 'varianceScaling'
        }),
        tf.layers.dropout({ rate: 0.5 }),
        tf.layers.dense({
          units: this.classes.length,
          activation: 'softmax'
        })
      ]
    });

    model.compile({
      optimizer: tf.train.adam(0.0001),
      loss: 'categoricalCrossentropy',
      metrics: ['accuracy']
    });

    this.model = model;
  }

  async train(images, labels) {
    // images : array d'elements <img>
    // labels : array d'indices [0, 1, 2, 3]

    // Extraire les features avec le modele de base
    const features = tf.tidy(() => {
      const imageTensors = images.map(img => {
        return tf.browser.fromPixels(img)
          .resizeBilinear([224, 224])
          .toFloat()
          .div(127.5)
          .sub(1);
      });

      const batched = tf.stack(imageTensors);
      return this.baseModel.predict(batched);
    });

    // Convertir les labels en one-hot
    const ys = tf.oneHot(tf.tensor1d(labels, 'int32'), this.classes.length);

    // Entrainer uniquement les nouvelles couches
    await this.model.fit(features, ys, {
      epochs: 20,
      batchSize: 32,
      validationSplit: 0.2,
      callbacks: {
        onEpochEnd: (epoch, logs) => {
          console.log(
            `Epoque ${epoch + 1}: ` +
            `loss = ${logs.loss.toFixed(4)}, ` +
            `accuracy = ${logs.acc.toFixed(4)}`
          );
        }
      }
    });

    features.dispose();
    ys.dispose();
  }

  async predict(imageElement) {
    const processed = tf.tidy(() => {
      const img = tf.browser.fromPixels(imageElement)
        .resizeBilinear([224, 224])
        .toFloat()
        .div(127.5)
        .sub(1)
        .expandDims(0);

      const features = this.baseModel.predict(img);
      return this.model.predict(features);
    });

    const probabilities = await processed.data();
    const classIndex = processed.argMax(-1).dataSync()[0];

    processed.dispose();

    return {
      class: this.classes[classIndex],
      confidence: (probabilities[classIndex] * 100).toFixed(2) + '%',
      allProbabilities: this.classes.map((name, i) => ({
        name,
        probability: (probabilities[i] * 100).toFixed(2) + '%'
      }))
    };
  }
}

// Utilisation
const classifier = new PoseClassifier();

await classifier.loadBaseModel();
classifier.createCustomModel();

// Collecter des donnees (prendre des photos de chaque exercice)
const squatImgs = [img1, img2, img3]; // 3 exemples
const pushupImgs = [img4, img5, img6];
// ... plus d'exemples

const allImages = [...squatImgs, ...pushupImgs, ...];
const labels = [0, 0, 0, 1, 1, 1, ...]; // Indices des classes

await classifier.train(allImages, labels);

// Faire une prediction sur une nouvelle image
const result = await classifier.predict(newImageElement);
console.log(result);
// {
//   class: 'squat',
//   confidence: '94.32%',
//   allProbabilities: [...]
// }

Performance : Acceleration GPU

TensorFlow.js utilise WebGL pour le calcul sur GPU — crucial pour les grands modeles.

Optimisations importantes :

// 1. Utiliser tf.tidy() pour gerer la memoire
function processData(input) {
  return tf.tidy(() => {
    // Tous les tensors crees ici sont liberes automatiquement
    const normalized = input.div(255);
    const reshaped = normalized.reshape([1, 224, 224, 3]);
    return model.predict(reshaped);
  });
}

// 2. Batch processing pour plusieurs images
async function classifyMultiple(images) {
  const batch = tf.tidy(() => {
    const tensors = images.map(img =>
      tf.browser.fromPixels(img).resizeBilinear([224, 224])
    );
    return tf.stack(tensors);
  });

  const predictions = await model.predict(batch).array();

  batch.dispose();

  return predictions;
}

// 3. Quantification pour des modeles plus petits
async function loadQuantizedModel() {
  // Modele avec 4x moins de poids
  const model = await tf.loadGraphModel(
    'https://example.com/model_quantized/model.json'
  );

  return model;
}

// 4. Web Workers pour ne pas bloquer l'UI
// worker.js
importScripts('https://cdn.jsdelivr.net/npm/@tensorflow/tfjs');

self.onmessage = async (e) => {
  const { imageData } = e.data;

  const tensor = tf.browser.fromPixels(imageData);
  const prediction = await model.predict(tensor);

  self.postMessage({ prediction: await prediction.array() });

  tensor.dispose();
  prediction.dispose();
};

Cas d'Usage Avances

1. Detection de Poses (PoseNet) :

import * as posenet from '@tensorflow-models/posenet';

const net = await posenet.load({
  architecture: 'MobileNetV1',
  outputStride: 16,
  inputResolution: { width: 640, height: 480 },
  multiplier: 0.75
});

const pose = await net.estimateSinglePose(video);

// pose.keypoints contient 17 points (nez, yeux, epaules, etc)
const nose = pose.keypoints.find(kp => kp.part === 'nose');
console.log(`Nez a : x=${nose.position.x}, y=${nose.position.y}`);

2. Segmentation de Personnes (BodyPix) :

import * as bodyPix from '@tensorflow-models/body-pix';

const net = await bodyPix.load();

const segmentation = await net.segmentPerson(video);

// Appliquer un flou sur le fond (type Zoom)
const foregroundColor = { r: 0, g: 0, b: 0, a: 0 };
const backgroundColor = { r: 0, g: 0, b: 0, a: 255 };
const backgroundBlurAmount = 15;

const backgroundBlur = await bodyPix.blurBodyPart(
  canvas,
  video,
  segmentation,
  backgroundBlurAmount,
  foregroundColor,
  backgroundColor
);

3. Reconnaissance Vocale :

import * as speechCommands from '@tensorflow-models/speech-commands';

const recognizer = speechCommands.create('BROWSER_FFT');

await recognizer.ensureModelLoaded();

// Ecouter les commandes
recognizer.listen(result => {
  const scores = result.scores;
  const maxScore = Math.max(...scores);
  const command = recognizer.wordLabels()[scores.indexOf(maxScore)];

  console.log(`Commande detectee : ${command} (${(maxScore * 100).toFixed(2)}%)`);
}, {
  includeSpectrogram: true,
  probabilityThreshold: 0.75
});

Limitations et Considerations

Ce pour quoi TensorFlow.js N'est PAS ideal :

  • Entrainement de modeles gigantesques (GPT, DALL-E)
  • Traitement batch massif (millions d'images)
  • Recherche ML de pointe

Ideal pour :

  • Inference en temps reel sur le client
  • Modeles petits/moyens (<50MB)
  • Prototypes rapides
  • Applications necessitant de la confidentialite

Trade-offs :

  • Performance : ~2-5x plus lent que Python/C++ natif
  • Taille : Les modeles doivent etre legers pour le web
  • Compatibilite : Pas toutes les APIs Python sont disponibles

L'Avenir du ML en JavaScript

Tendances 2025 :

  • WebGPU pour une performance encore meilleure
  • Modeles de plus en plus petits (techniques de compression)
  • Edge computing (ML sur appareils IoT avec JS)
  • Integration avec les frameworks (composants React, Vue avec ML)

Si vous voulez explorer davantage comment JavaScript s'etend a des domaines innovants, consultez JavaScript et le Monde de l'IoT : Integrer le Web au Monde Physique.

C'est parti !

Commentaires (0)

Cet article n'a pas encore de commentaires. Soyez le premier!

Ajouter des commentaires