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 automatiqueGestion 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 similaireWarm-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.

