Retour au blog

TensorFlow.js : Machine Learning Directement Dans le Navigateur Avec JavaScript

Salut HaWkers, et si vous pouviez exécuter des modèles de machine learning directement dans le navigateur de l'utilisateur, sans avoir besoin de serveur ? En 2025, ce n'est plus le futur - c'est la réalité, et TensorFlow.js est l'outil qui rend cela possible.

Dans ce guide, nous allons explorer comment utiliser TensorFlow.js pour créer des applications intelligentes qui tournent à 100% côté client.

Pourquoi le Machine Learning Dans le Navigateur

Exécuter du ML dans le navigateur offre des avantages uniques que beaucoup de développeurs n'explorent pas encore.

Avantages

Vie privée :

  • Les données ne quittent jamais l'appareil de l'utilisateur
  • Pas de préoccupations RGPD pour le traitement
  • Idéal pour les applications sensibles

Performance :

  • Pas de latence réseau pour l'inférence
  • Utilise le GPU de l'appareil via WebGL
  • Réponses en temps réel

Coût :

  • Zéro coût serveur pour l'inférence
  • Scale illimité sans augmenter l'infrastructure
  • Démocratise l'accès au ML

Expérience :

  • Fonctionne hors ligne
  • Interactivité en temps réel
  • Ne dépend pas de la connexion

🧠 Contexte : TensorFlow.js est utilisé par des millions de développeurs et des entreprises comme Google, Spotify et Airbnb pour le ML dans le navigateur.

Premiers Pas Avec TensorFlow.js

Configurons un projet et comprenons les concepts de base.

Installation

# Via NPM
npm install @tensorflow/tfjs

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

Concepts Fondamentaux

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

// Tensors : Arrays multidimensionnels
const tensor1D = tf.tensor([1, 2, 3, 4]);
const tensor2D = tf.tensor([[1, 2], [3, 4]]);

// Shapes : Dimensions du tensor
console.log(tensor2D.shape); // [2, 2]

// Opérations basiques
const a = tf.tensor([1, 2, 3]);
const b = tf.tensor([4, 5, 6]);

const sum = a.add(b);      // [5, 7, 9]
const product = a.mul(b);  // [4, 10, 18]
const mean = a.mean();     // 2

// IMPORTANT : Nettoyer la mémoire
tensor1D.dispose();
tensor2D.dispose();
// Ou utiliser tf.tidy() pour nettoyage automatique

Gestion de la Mémoire

// tf.tidy() nettoie les tensors intermédiaires automatiquement
const result = tf.tidy(() => {
  const a = tf.tensor([1, 2, 3]);
  const b = tf.tensor([4, 5, 6]);
  const c = a.add(b);
  const d = c.mul(tf.scalar(2));
  return d; // Seul d est conservé
});

// Vérifier l'utilisation mémoire
console.log(tf.memory());
// { numTensors: X, numDataBuffers: Y, numBytes: Z }

Utiliser des Modèles Pré-Entraînés

La façon la plus rapide de commencer est d'utiliser des modèles déjà entraînés.

Classification d'Images Avec MobileNet

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

async function classifyImage() {
  // Charge le modèle (la première fois peut être lente)
  const model = await mobilenet.load();

  // Élément image du DOM
  const img = document.getElementById('myImage');

  // Classifie l'image
  const predictions = await model.classify(img);

  console.log(predictions);
  // [
  //   { className: 'golden retriever', probability: 0.89 },
  //   { className: 'Labrador retriever', probability: 0.08 },
  //   { className: 'cocker spaniel', probability: 0.02 }
  // ]
}

Détection d'Objets Avec COCO-SSD

import * as cocoSsd from '@tensorflow-models/coco-ssd';

async function detectObjects() {
  const model = await cocoSsd.load();
  const img = document.getElementById('myImage');

  const predictions = await model.detect(img);

  predictions.forEach(prediction => {
    console.log(prediction);
    // {
    //   bbox: [x, y, width, height],
    //   class: 'person',
    //   score: 0.95
    // }
  });

  // Dessiner les bounding boxes
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');

  predictions.forEach(pred => {
    ctx.strokeStyle = '#00FF00';
    ctx.lineWidth = 2;
    ctx.strokeRect(...pred.bbox);
    ctx.fillStyle = '#00FF00';
    ctx.fillText(
      `${pred.class} (${Math.round(pred.score * 100)}%)`,
      pred.bbox[0],
      pred.bbox[1] - 5
    );
  });
}

Détection de Pose Avec PoseNet/MoveNet

Un des cas d'usage les plus populaires est la détection de pose corporelle.

Implémentation Basique

import * as poseDetection from '@tensorflow-models/pose-detection';

async function detectPose() {
  // Configure le détecteur
  const detectorConfig = {
    modelType: poseDetection.movenet.modelType.SINGLEPOSE_LIGHTNING
  };

  const detector = await poseDetection.createDetector(
    poseDetection.SupportedModels.MoveNet,
    detectorConfig
  );

  // Utilise la vidéo de la webcam
  const video = document.getElementById('webcam');

  async function detect() {
    const poses = await detector.estimatePoses(video);

    if (poses.length > 0) {
      const pose = poses[0];

      // Keypoints du corps
      pose.keypoints.forEach(keypoint => {
        if (keypoint.score > 0.5) {
          console.log(`${keypoint.name}: (${keypoint.x}, ${keypoint.y})`);
          // nose: (320, 180)
          // left_eye: (310, 170)
          // right_shoulder: (280, 250)
          // etc.
        }
      });
    }

    requestAnimationFrame(detect);
  }

  detect();
}

Application Pratique : Compteur d'Exercices

class ExerciseCounter {
  constructor() {
    this.squatCount = 0;
    this.isDown = false;
  }

  detectSquat(pose) {
    const leftHip = pose.keypoints.find(k => k.name === 'left_hip');
    const leftKnee = pose.keypoints.find(k => k.name === 'left_knee');
    const leftAnkle = pose.keypoints.find(k => k.name === 'left_ankle');

    if (!leftHip || !leftKnee || !leftAnkle) return;

    // Calcule l'angle du genou
    const angle = this.calculateAngle(
      leftHip,
      leftKnee,
      leftAnkle
    );

    // Logique de comptage
    if (angle < 90 && !this.isDown) {
      this.isDown = true;
    } else if (angle > 160 && this.isDown) {
      this.isDown = false;
      this.squatCount++;
      this.onSquatComplete(this.squatCount);
    }
  }

  calculateAngle(a, b, c) {
    const radians = Math.atan2(c.y - b.y, c.x - b.x) -
                    Math.atan2(a.y - b.y, a.x - b.x);
    let angle = Math.abs(radians * 180 / Math.PI);
    if (angle > 180) angle = 360 - angle;
    return angle;
  }

  onSquatComplete(count) {
    console.log(`Squat #${count} complété !`);
  }
}

Reconnaissance de Gestes de Main

Un autre cas d'usage puissant est la détection de gestes des mains.

import * as handPoseDetection from '@tensorflow-models/hand-pose-detection';

async function detectHands() {
  const model = handPoseDetection.SupportedModels.MediaPipeHands;
  const detectorConfig = {
    runtime: 'tfjs',
    maxHands: 2
  };

  const detector = await handPoseDetection.createDetector(
    model,
    detectorConfig
  );

  const video = document.getElementById('webcam');

  async function detect() {
    const hands = await detector.estimateHands(video);

    hands.forEach(hand => {
      // 21 keypoints par main
      const landmarks = hand.keypoints;

      // Détecter geste "pouce en l'air"
      if (isThumbsUp(landmarks)) {
        console.log('👍 Pouce en l\'air détecté !');
      }

      // Détecter geste "paix"
      if (isPeaceSign(landmarks)) {
        console.log('✌️ Paix détecté !');
      }
    });

    requestAnimationFrame(detect);
  }

  detect();
}

function isThumbsUp(landmarks) {
  const thumbTip = landmarks[4];
  const thumbBase = landmarks[2];
  const indexTip = landmarks[8];

  // Pouce vers le haut et autres doigts fermés
  const thumbUp = thumbTip.y < thumbBase.y;
  const indexDown = indexTip.y > landmarks[5].y;

  return thumbUp && indexDown;
}

function isPeaceSign(landmarks) {
  const indexTip = landmarks[8];
  const middleTip = landmarks[12];
  const ringTip = landmarks[16];

  // Index et majeur tendus, autres fermés
  const indexUp = indexTip.y < landmarks[5].y;
  const middleUp = middleTip.y < landmarks[9].y;
  const ringDown = ringTip.y > landmarks[13].y;

  return indexUp && middleUp && ringDown;
}

Classification de Texte Avec Universal Sentence Encoder

TensorFlow.js n'est pas seulement pour les images - il fonctionne très bien avec le texte.

import * as use from '@tensorflow-models/universal-sentence-encoder';

async function analyzeText() {
  const model = await use.load();

  // Embeddings de texte
  const sentences = [
    'J\'ai adoré ce produit, très bien !',
    'Terrible, je ne recommande pas.',
    'Le service était ok, rien d\'exceptionnel.'
  ];

  const embeddings = await model.embed(sentences);
  console.log(embeddings.shape); // [3, 512]

  // Classification de sentiment simple
  const positiveRef = await model.embed(['Excellent, merveilleux, parfait']);
  const negativeRef = await model.embed(['Terrible, horrible, mauvais']);

  sentences.forEach(async (sentence, i) => {
    const sentenceEmbed = embeddings.slice([i, 0], [1, 512]);

    const positiveSim = cosineSimilarity(sentenceEmbed, positiveRef);
    const negativeSim = cosineSimilarity(sentenceEmbed, negativeRef);

    const sentiment = positiveSim > negativeSim ? 'Positif' : 'Négatif';
    console.log(`"${sentence}" -> ${sentiment}`);
  });
}

function cosineSimilarity(a, b) {
  const dotProduct = tf.sum(tf.mul(a, b));
  const normA = tf.norm(a);
  const normB = tf.norm(b);
  return dotProduct.div(normA.mul(normB)).dataSync()[0];
}

Entraîner Votre Propre Modèle

En plus d'utiliser des modèles pré-entraînés, vous pouvez créer les vôtres.

Modèle Simple de Classification

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

async function trainModel() {
  // Données d'exemple : problème XOR
  const xs = tf.tensor2d([[0, 0], [0, 1], [1, 0], [1, 1]]);
  const ys = tf.tensor2d([[0], [1], [1], [0]]);

  // Architecture du modèle
  const model = tf.sequential({
    layers: [
      tf.layers.dense({
        inputShape: [2],
        units: 8,
        activation: 'relu'
      }),
      tf.layers.dense({
        units: 4,
        activation: 'relu'
      }),
      tf.layers.dense({
        units: 1,
        activation: 'sigmoid'
      })
    ]
  });

  // Compiler
  model.compile({
    optimizer: tf.train.adam(0.1),
    loss: 'binaryCrossentropy',
    metrics: ['accuracy']
  });

  // Entraîner
  await model.fit(xs, ys, {
    epochs: 100,
    callbacks: {
      onEpochEnd: (epoch, logs) => {
        console.log(`Epoch ${epoch}: loss = ${logs.loss.toFixed(4)}`);
      }
    }
  });

  // Tester
  const prediction = model.predict(tf.tensor2d([[0, 1]]));
  prediction.print(); // ~0.95 (proche de 1)

  // Sauvegarder le modèle
  await model.save('localstorage://mon-modele');
}

// Charger le modèle sauvegardé
async function loadModel() {
  const model = await tf.loadLayersModel('localstorage://mon-modele');
  return model;
}

Transfer Learning Avec MobileNet

async function customImageClassifier() {
  // Charge MobileNet sans la couche finale
  const mobilenet = await tf.loadLayersModel(
    'https://tfhub.dev/google/tfjs-model/mobilenet_v2_100_224/feature_vector/3/default/1',
    { fromTFHub: true }
  );

  // Gèle les poids de MobileNet
  mobilenet.trainable = false;

  // Ajoute des couches personnalisées
  const model = tf.sequential();
  model.add(mobilenet);
  model.add(tf.layers.dense({
    units: 128,
    activation: 'relu'
  }));
  model.add(tf.layers.dropout({ rate: 0.5 }));
  model.add(tf.layers.dense({
    units: 3, // Nombre de classes
    activation: 'softmax'
  }));

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

  return model;
}

Optimisation de Performance

Pour que le ML dans le navigateur fonctionne bien, l'optimisation est cruciale.

Backend WebGL vs CPU

// Vérifier le backend actuel
console.log(tf.getBackend()); // 'webgl' ou 'cpu'

// Forcer WebGL (plus rapide)
await tf.setBackend('webgl');

// Ou utiliser WebGPU (encore plus rapide, si disponible)
if (navigator.gpu) {
  await tf.setBackend('webgpu');
}

Quantisation de Modèles

// Lors de la conversion d'un modèle Python vers TensorFlow.js
// Utilisez la quantisation pour réduire la taille

// tensorflowjs_converter \
//   --input_format=tf_saved_model \
//   --output_format=tfjs_graph_model \
//   --quantize_float16=* \
//   ./saved_model \
//   ./tfjs_model

// Résultat : modèle 50% plus petit, performance similaire

Warm-up du Modèle

async function warmupModel(model) {
  // La première inférence est plus lente
  // Faites un "warm-up" avant l'usage réel

  const dummyInput = tf.zeros([1, 224, 224, 3]);
  const warmupResult = model.predict(dummyInput);
  warmupResult.dispose();
  dummyInput.dispose();

  console.log('Modèle préchauffé et prêt !');
}

Application Complète : Classificateur en Temps Réel

Mettons tout ensemble dans une application fonctionnelle.

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

class RealTimeClassifier {
  constructor(videoElement, resultsElement) {
    this.video = videoElement;
    this.results = resultsElement;
    this.model = null;
    this.isRunning = false;
  }

  async init() {
    // Configure WebGL
    await tf.setBackend('webgl');

    // Charge le modèle
    console.log('Chargement du modèle...');
    this.model = await mobilenet.load({
      version: 2,
      alpha: 1.0
    });

    // Préchauffe le modèle
    await this.warmup();

    // Initialise la webcam
    await this.setupCamera();

    console.log('Prêt !');
  }

  async setupCamera() {
    const stream = await navigator.mediaDevices.getUserMedia({
      video: { facingMode: 'environment' }
    });
    this.video.srcObject = stream;
    await new Promise(resolve => {
      this.video.onloadedmetadata = resolve;
    });
    this.video.play();
  }

  async warmup() {
    const dummyInput = tf.zeros([1, 224, 224, 3]);
    const result = this.model.infer(dummyInput, 'conv_preds');
    result.dispose();
    dummyInput.dispose();
  }

  start() {
    this.isRunning = true;
    this.detect();
  }

  stop() {
    this.isRunning = false;
  }

  async detect() {
    if (!this.isRunning) return;

    const predictions = await this.model.classify(this.video, 3);

    this.displayResults(predictions);

    requestAnimationFrame(() => this.detect());
  }

  displayResults(predictions) {
    this.results.innerHTML = predictions
      .map(p => `
        <div class="prediction">
          <span class="label">${p.className}</span>
          <span class="score">${(p.probability * 100).toFixed(1)}%</span>
          <div class="bar" style="width: ${p.probability * 100}%"></div>
        </div>
      `)
      .join('');
  }
}

// Utilisation
const classifier = new RealTimeClassifier(
  document.getElementById('video'),
  document.getElementById('results')
);

document.getElementById('startBtn').onclick = async () => {
  await classifier.init();
  classifier.start();
};

Conclusion

TensorFlow.js démocratise l'accès au machine learning, permettant à tout développeur JavaScript de créer des applications intelligentes sans avoir besoin d'infrastructure serveur ou de connaissances approfondies en Python.

En 2025, avec des GPUs plus puissants dans les appareils et des modèles de plus en plus optimisés, le navigateur devient une plateforme légitime pour le ML - offrant vie privée, performance et scale sans coût d'infrastructure.

Si vous voulez explorer d'autres façons d'améliorer la performance dans le navigateur, je recommande de consulter l'article WebAssembly et JavaScript : Performance Extrême où nous explorons comment combiner ces technologies.

C'est parti ! 🦅

Commentaires (0)

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

Ajouter des commentaires