// Block de stabilisation OJS pour le projet Quarto_stabilize =true
6.1 📊 Métriques de Classification
Le piège des métriques de vanité
En 2026, l’industrie a enfin compris que l’Accuracy (exactitude) est souvent une “métrique de vanité”. Dans un système de détection de fraude où 99,9 % des transactions sont légitimes, une précision globale de 99,9 % peut être atteinte par un modèle qui ne détecte absolument rien. L’ingénierie moderne exige donc une vision multidimensionnelle du risque (Imani et al. 2026).
Vrais Positifs (TP) : Le modèle a prédit “Positif” et c’était vrai.
Vrais Négatifs (TN) : Le modèle a prédit “Négatif” et c’était vrai.
Faux Positifs (FP) : La “fausse alerte”. Le modèle a prédit “Positif” mais c’était faux.
Faux Négatifs (FN) : Le “cas manqué”. Le modèle a prédit “Négatif” mais c’était positif.
6.1.2 ⚖️ Précision vs Rappel
En tant qu’architecte, votre choix de métrique dépend du coût de l’erreur dans le monde réel (Pedigo 2026).
La Précision (Fiabilité) :
Question clé : “Sur toutes mes alertes, combien étaient vraies ?”
Optimisation : À optimiser lorsque le coût d’une fausse alerte est trop élevé.
Exemple : Un filtre anti-spam (classer un mail important en spam bloque l’utilisateur).
Le Rappel (Exhaustivité) :
Question clé : “Sur tous les cas réels, combien en ai-je capturés ?”
Optimisation : À optimiser lorsque rater un cas positif est extrêmement dangereux.
Exemple : Le diagnostic médical (mieux vaut une fausse alerte qu’un cancer non soigné).
6.1.3 🏆 Scores Globaux
Pour ne pas avoir à choisir entre les deux, on utilise des scores de synthèse :
F1-Score : C’est la moyenne harmonique entre précision et rappel. Il est indispensable pour les jeux de données déséquilibrés car il pénalise fortement les modèles qui sacrifient l’une des deux mesures (Bañuelos 2026).
ROC-AUC : Mesure la capacité du modèle à séparer les classes. Un score de 1.0 est parfait, 0.5 équivaut au hasard.
Attention : Pour des données extrêmement déséquilibrées (ex: 1 fraude pour 10 000 transactions), le ROC-AUC est souvent trop optimiste. On préférera alors le PR-AUC (Precision-Recall AUC) (Imani et al. 2026).
6.1.4 🌳 Boussole des Métriques
Pour guider le choix de votre indicateur d’évaluation selon la distribution de vos classes et la nature des risques métiers, suivez cet arbre décisionnel :
flowchart TD
A[Choix de la Métrique] --> B{Données Équilibrées ?}
B -->|Oui| C[Accuracy]
B -->|Non| D{Quel est le pire risque ?}
D -->|Fausse Alerte / FP| E[Optimiser la Précision]
D -->|Cas Manqué / FN| F[Optimiser le Rappel]
D -->|Équilibre Critique| G[F1-Score]
style E fill:#dc322f,stroke:#073642,color:#fdf6e3
style F fill:#859900,stroke:#073642,color:#fdf6e3
6.2 ⚙️ Optimisation des Hyperparamètres
Les manettes de l’algorithme
Les hyperparamètres ne sont pas appris par le modèle (comme les poids d’un réseau de neurones), ils sont définis par vous avant l’entraînement. C’est la profondeur maximale de votre Arbre de Décision, ou le paramètre K de votre algorithme KNN. Un modèle XGBoost mal réglé sera souvent battu par une simple Régression Logistique bien réglée (“Hyperparameter Tuning: Grid Search, Random Search, and Bayesian Optimization” 2024).
Pour trouver la combinaison parfaite d’hyperparamètres (le Tuning), l’industrie a connu trois grandes ères.
6.2.1 🧱 Grid Search et Random Search
Historiquement, le Data Scientist utilisait les outils de scikit-learn.
Grid Search (Recherche par Grille) : Vous définissez une liste de valeurs pour chaque paramètre (ex: 3 valeurs pour le paramètre A, 3 valeurs pour le paramètre B). Le moteur teste exhaustivement toutes les combinaisons possibles.
Le Problème : Exponentiellement coûteux. Avec 5 paramètres, on atteint des milliers d’entraînements, rendant la méthode inutilisable sur de grands volumes.
Random Search (Recherche Aléatoire) : L’algorithme choisit des combinaisons au hasard dans l’espace disponible pendant un temps défini.
L’Avantage : Beaucoup plus rapide. Mathématiquement, il trouve de meilleurs résultats que le Grid Search en explorant des zones inattendues.
Le Problème : C’est une approche “aveugle”. L’itération n’apprend rien des essais précédents.
6.2.2 🧠 Optimisation Bayésienne
En 2026, l’outil Optuna est devenu le standard absolu pour remplacer les anciennes méthodes de recherche. L’optimisation bayésienne agit comme un chercheur d’or : plutôt que de creuser au hasard, elle apprend de ses forages précédents pour déduire où se trouve le filon.
Sépare l’historique des essais en deux groupes : les bons et les mauvais résultats.
Modélise mathématiquement ces densités de probabilité : l(x) pour les bons et g(x) pour les mauvais.
Choisit les hyperparamètres qui maximisent le ratio l(x)/g(x). Il cible donc les zones à succès et fuit les échecs.
Élagage (Pruning) : Super-pouvoir d’Optuna. S’il détecte que la courbe d’apprentissage à mi-parcours est désastreuse par rapport aux essais précédents, il stoppe l’entraînement prématurément, économisant d’immenses ressources de calcul.
Interactif : La course à l’Optimum
Visualisez pourquoi l’approche Bayésienne écrase les anciennes méthodes. Observez comment chaque algorithme tente de trouver le point le plus bas (le minimum d’erreur) sur une fonction de coût.
🏎️ Simulateur de Course à l’Optimum
Sélectionnez une stratégie de recherche et lancez l’entraînement. Observez comment chaque algorithme “creuse” pour tenter de trouver le fond de la vallée (l’optimum global). Le score affiché est l’erreur (Loss) : le but est d’atteindre 0.0.
lossFunction = (x, y) => {const r2 = (x -2) * (x -2) + (y +1) * (y +1);return10* (1-Math.exp(-r2 /4)) +0.1* (x * x + y * y) +Math.sin(x) *Math.cos(y);}// Domaine de recherche : x et y entre -5 et 5domain = [-5,5]// Optimum Global réel (approximatif pour la viz)trueOptimum = [1.9,-0.9]// 3. Génération des points d'essai selon la stratégie// Nous simulons 100 essais pour chaque stratégie.trialsPool = {// Déclencheur sur le bouton run runSimulation;const nTrials =100;const seed =42;// Pour reproductibilitéconst lcg = (a) => () => (a = (a *16807) %2147483647) /2147483647;// Simple RNGconst rand =lcg(seed);let trials = [];if (searchStrategy.includes("Grid")) {// Grid Search : 10x10 rigideconst steps =10;const stepSize = (domain[1] - domain[0]) / (steps -1);for (let i =0; i < steps; i++) {for (let j =0; j < steps; j++) {const x = domain[0] + i * stepSize;const y = domain[0] + j * stepSize; trials.push({ x, y,loss:lossFunction(x, y) }); } } } elseif (searchStrategy.includes("Random")) {// Random Search : 100 points uniformesfor (let i =0; i < nTrials; i++) {const x = domain[0] +rand() * (domain[1] - domain[0]);const y = domain[0] +rand() * (domain[1] - domain[0]); trials.push({ x, y,loss:lossFunction(x, y) }); } } else {// Heuristique Bayésienne (TPE simplifiée pour la viz)// On commence random, puis on cluster autour du meilleur connu en réduisant la variance.let best =null;let currentBestLoss =Infinity;for (let i =0; i < nTrials; i++) {let x, y;if (i <10) {// Phase d'exploration initiale (Random) x = domain[0] +rand() * (domain[1] - domain[0]); y = domain[0] +rand() * (domain[1] - domain[0]); } else {// Phase d'exploitation (Cluster autour du meilleur)// On ajoute un bruit gaussien décroissant autour du meilleur point connuconst stdDev =2*Math.exp(-i /30);// La variance réduit avec le tempsconst gaussian = () => (rand() +rand() +rand() +rand() +rand() +rand() -3);// Approx Central Limit x =Math.max(domain[0],Math.min(domain[1], best.x+gaussian() * stdDev)); y =Math.max(domain[0],Math.min(domain[1], best.y+gaussian() * stdDev)); }const loss =lossFunction(x, y);const trial = { x, y, loss }; trials.push(trial);if (loss < currentBestLoss) { currentBestLoss = loss; best = trial; } } }return trials;}// 4. Animation : Affichage progressif des points// Un générateur OJS pour incrémenter les essais un par uncurrentTrialIndex = { trialsPool;// reset quand le pool changefor (let i =1; i <=100; i++) {yield promises.delay(20, i);// 20ms de délai entre chaque point }}// Données actuellement affichéescurrentTrials = trialsPool.slice(0, currentTrialIndex)// Meilleur point trouvé jusqu'icibestTrial = {let best =null;let minLoss =Infinity;for (const t of currentTrials) {if (t.loss< minLoss) { minLoss = t.loss; best = t; } }return best;}
import {Plot} from"@observablehq/plot"// Création d'une heatmap de fond pour représenter la fonction de pertemesh = {const res =50;const step = (domain[1] - domain[0]) / res;let grid = [];for (let x = domain[0]; x <= domain[1]; x += step) {for (let y = domain[0]; y <= domain[1]; y += step) { grid.push({x, y,loss:lossFunction(x, y)}); } }return grid;}Plot.plot({title:"Simulateur de Recherche d'Optimum Global (Grid/Random vs Bayésien)",height:600,width:800,style: { background:"var(--sol-base03)",color:"var(--sol-base0)",fontVariantNumeric:"tabular-nums" },x: { domain: domain,label:"Hyperparamètre X →",grid:true },y: { domain: domain,label:"Hyperparamètre Y ↑",grid:true },color: {type:"diverging",scheme:"Turbo",// Échelle de couleur 'chaude' (bleu=bas, rouge=haut)label:"Perte (Loss)",reverse:true,domain: [0,15] },marks: [// Fond : Heatmap de la fonction de perte cachée Plot.cell(mesh, {x:"x",y:"y",fill:"loss",inset:0.5,opacity:0.8}),// Marquage de l'optimum global réel (L'étoile cachée) Plot.dot([trueOptimum], {x: d => d[0],y: d => d[1],symbol:"star",r:10,fill:"var(--sol-base3)",stroke:"var(--sol-base03)",strokeWidth:2}),// Les points d'essai de l'algorithme Plot.dot(currentTrials, {x:"x",y:"y",fill:"var(--sol-base3)",stroke:"var(--sol-base03)",r:3,opacity:0.6 }),// Highlight sur le meilleur point trouvé bestTrial ? Plot.dot([bestTrial], {x:"x",y:"y",fill:"none",stroke:"var(--sol-yellow)",// Or pour le meilleurr:8,strokeWidth:3 }) :null ]})
// 6. Tableau de bord des statistiques & Feedback pédagogiquehtml`<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;"> <div style="padding: 20px; background: var(--sol-base3); border-radius: 12px; border: 1px solid var(--sol-base2); box-shadow: 0 4px 6px rgba(0, 43, 54, 0.05); text-align: center;"> <div style="font-size: 0.9em; color: var(--sol-base1); text-transform: uppercase; letter-spacing: 0.1em;">Essais réalisés</div> <div style="font-size: 3em; font-weight: 700; color: var(--sol-blue);">${currentTrialIndex} / 100</div> </div> <div style="padding: 20px; background: var(--sol-base3); border-radius: 12px; border: 1px solid var(--sol-base2); box-shadow: 0 4px 6px rgba(0, 43, 54, 0.05); text-align: center;"> <div style="font-size: 0.9em; color: var(--sol-base1); text-transform: uppercase; letter-spacing: 0.1em;">Meilleur Score (Loss)</div> <div style="font-size: 3em; font-weight: 700; color: var(--sol-green);">${bestTrial ? bestTrial.loss.toFixed(4) :"N/A"}</div> </div></div><div style="margin-top: 20px; padding: 20px; border-radius: 12px; font-size: 1.1em; background: var(--sol-base2); border: 1px solid var(--sol-base1);"> <strong>💡 Analyse pédagogique :</strong><br/> <p style="margin-top: 10px; color: var(--sol-base00);">${searchStrategy.includes("Grid")?"Le <b>Grid Search</b> quadrille la zone de manière rigide. Il gaspille énormément de temps à tester des régions 'bleues' (hautes erreurs) car il n'apprend pas de ses échecs. Il peut passer juste à côté du minimum global (l'étoile blanche) sans jamais le trouver s'il n'est pas sur un croisement de la grille.": searchStrategy.includes("Random")?"Le <b>Random Search</b> s'éparpille au hasard. Il a statistiquement plus de chances que la grille de tomber près de l'étoile centrale, car il explore des valeurs inattendues. Cependant, il est aveugle : l'essai n°99 n'utilise pas l'information critique découverte par l'essai n°10.":"L'<b>Optimisation Bayésienne</b> commence par explorer (Random), puis elle modélise mathématiquement où se trouvent les zones 'oranges/rouges' (faibles erreurs). Dès qu'elle détecte le début de la vallée, elle concentre intelligemment ses essais (clustering des points blancs) autour du minimum global pour converger rapidement vers l'étoile. C'est du réglage de précision."} </p></div>`
6.2.3 💻 Optuna vs Scikit-Learn
import optunafrom xgboost import XGBClassifier# 1. On définit une "fonction objectif" à optimiserdef objective(trial):# Optuna suggère intelligemment des valeurs à chaque essai param = {'max_depth': trial.suggest_int('max_depth', 3, 9),'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3) }# On entraîne le modèle avec ces paramètres model = XGBClassifier(**param) model.fit(X_train, y_train) score = model.score(X_test, y_test)return score # Optuna va essayer de maximiser ce score# 2. On lance l'étude (Le chercheur d'or se met au travail)study = optuna.create_study(direction='maximize')study.optimize(objective, n_trials=50) # 50 essais intelligentsprint("Meilleurs hyperparamètres :", study.best_params)
6.3 🕵️♂️ Mission 6
Charlie se vante sur le Dark Web : son algorithme de détection de la police aurait une “Accuracy” de 90%. Il se croit intouchable et a confirmé la transaction de ce soir pour la vente des sujets d’examen.
Vous avez réussi à intercepter les données de test de son algorithme. Exécutez la cellule pour examiner les résultats bruts de ses 100 dernières simulations :
import pandas as pdimport numpy as np# 0 = Pas de police (Voie libre), 1 = Police présente (Danger)# Dans 90 cas sur 100, il n'y a pas de police. Charlie a simplement fait un modèle qui prédit TOUJOURS 0 !y_vrai = np.array([0]*90+ [1]*10)y_prediction = np.array([0]*100)print(f"Précision globale (Accuracy) calculée par Charlie : {np.mean(y_vrai == y_prediction) *100}%")
90% de précision… Cela semble excellent. Mais en tant que Data Detectives, vous savez qu’il faut regarder sous le capot, surtout quand les événements (la présence de la police) sont rares.
Votre objectif : Générez la Matrice de Confusion pour analyser les erreurs du modèle de Charlie. Combien de fois son IA a-t-elle indiqué “Voie libre” (0) alors que la police était “Présente” (1) ? C’est ce qu’on appelle les Faux Négatifs, et c’est notre porte d’entrée !
6.4 🚢 Déploiement et MLOps
Le syndrome du ‘Ça marche sur ma machine’
En 2026, un modèle bloqué dans un Notebook Jupyter n’est qu’un “artefact inutile”. Le MLOps (Machine Learning Operations) est la discipline qui fusionne la Data Science et le DevOps pour transformer un code de recherche en un service logiciel industriel, robuste et accessible par des millions d’utilisateurs.
Le cycle de vie d’un modèle en production repose sur la résolution de trois grands défis : la mémoire, le partage, et l’accès.
6.4.1 📖 Traçabilité (MLflow & DVC)
Lorsqu’un Data Scientist fait 50 essais d’optimisation (voir Module 6.2), il oublie souvent quels paramètres ont donné quel résultat.
MLflow : C’est le standard industriel pour le suivi des expérimentations (Experiment Tracking). Il enregistre automatiquement chaque version de votre code, les hyperparamètres utilisés, et les métriques de performance obtenues. Son composant Model Registry fait office de “tour de contrôle” pour approuver le passage d’un modèle en production (Sharma 2026).
DVC (Data Version Control) : Fait la même chose que Git, mais pour vos énormes fichiers de données (qui sont trop lourds pour GitHub).
6.4.2 📦 Encapsulation (Docker & uv)
Imaginons que votre modèle fonctionne parfaitement sur votre ordinateur portable (Windows, Python 3.10, Pandas 2.0). Si vous l’envoyez sur le serveur de production (Linux, Python 3.12, Pandas 3.0), il va planter instantanément.
La Conteneurisation (Docker) : Docker résout ce problème d’environnement. Il crée une “boîte” (un conteneur) qui englobe votre modèle d’IA, la bonne version de Python, et exactement les bibliothèques nécessaires. Ce conteneur est immuable : s’il marche chez vous, il marchera à l’identique sur n’importe quel serveur au monde (Mathukiya 2026).
L’outil “uv” : En 2026, l’outil pip a largement été remplacé par uv (écrit en Rust) pour installer les bibliothèques Python. Il est 10 à 100 fois plus rapide, ce qui permet de construire ces conteneurs Docker de manière quasi-instantanée.
6.4.3 🔌 Service API (FastAPI)
Votre modèle est dans un conteneur, mais comment une application web ou mobile (ex: l’application de votre banque) peut-elle lui “parler” pour obtenir une prédiction ?
On utilise une API REST.
FastAPI : C’est le framework Python ultra-dominant pour servir des modèles. Il est asynchrone (très rapide) et génère automatiquement sa propre documentation technique (Otto 2026).
Le Fonctionnement : L’application web envoie une requête HTTP contenant un fichier JSON avec les données du client. FastAPI reçoit le JSON, le donne au modèle XGBoost, récupère la prédiction, et la renvoie à l’application web sous forme de réponse JSON.
Comprendre le système : L’API de Prédiction
Manipulez cette simulation pour comprendre comment un logiciel externe interagit avec votre modèle ML via une API REST (FastAPI).
🌐 Simulateur d’API de Prédiction
Testez cette interface client. Modifiez les paramètres du crédit et cliquez sur “Envoyer”. Observez comment l’application cliente transforme vos actions en un fichier JSON, l’envoie sur le réseau, et comment le serveur ML (FastAPI) calcule et renvoie sa prédiction.
viewof age = Inputs.range([18,80], {value:35,step:1,label:"Âge de l'emprunteur :"})viewof salary = Inputs.range([1500,10000], {value:3000,step:100,label:"Salaire mensuel (€) :"})viewof amount = Inputs.range([1000,100000], {value:50000,step:1000,label:"Montant demandé (€) :"})viewof sendBtn = Inputs.button("🚀 Envoyer la requête POST /predict")
// 2. Machine à états (Générateur asynchrone) pour simuler le cycle de vie de la requêteapiSimulation = { sendBtn;// Le générateur se relance à chaque clic sur le boutonif (sendBtn ===0) return { step:"idle",response:null };// Étape 1 : Le client envoie la requête sur le réseauyield { step:"sending",response:null };await Promises.delay(800);// Étape 2 : Le serveur FastAPI reçoit et lance l'inférence (le calcul du modèle ML)yield { step:"processing",response:null };await Promises.delay(1500);// Calcul d'un "vrai" faux modèle de risque pour rendre le simulateur ludiqueconst risk = (amount / (salary *12)) + (age <25?0.2:0) - (age >50?0.1:0);const proba =Math.min(0.99,Math.max(0.01, risk));const res = {statut: proba >0.45?"Refusé":"Accepté",probabilite_defaut:parseFloat(proba.toFixed(2)) };// Étape 3 : Le serveur renvoie la prédiction générée sur le réseauyield { step:"returning",response: res };await Promises.delay(800);// Étape 4 : Le client reçoit la réponse et met à jour son interfacereturn { step:"done",response: res };}
Le cycle de vie complet du déploiement industriel d’un modèle d’IA s’articule autour d’un pipeline automatisé, de l’expérimentation locale jusqu’au service Cloud exposé aux clients :
graph LR
subgraph Developpement [Environnement Local]
A[Notebook Jupyter] -->|Entraînement| B(MLflow Tracking)
end
subgraph Package [Intégration]
B -->|Validation| C{Model Registry}
C -->|Approuvé| D[Image Docker\n+ FastAPI]
end
subgraph Production [Serveur Cloud]
D -->|Déploiement| E((API REST))
F[Application Web] -- "JSON: {Age: 35...}" --> E
E -- "JSON: {Fraude: 95%}" --> F
end
style Developpement fill:#268bd2,stroke:#073642,color:#fdf6e3
style Package fill:#6c71c4,stroke:#073642,color:#fdf6e3
style Production fill:#859900,stroke:#073642,color:#fdf6e3
6.5 🚨 Surveillance et Dérive
Un modèle naît en mourant
Il existe un mythe tenace selon lequel une fois déployé, le travail du Data Scientist est terminé. En réalité, un modèle commence à “pourrir” dès son premier jour en production (“AI Model Monitoring in Production: Drift and Decay in 2026” 2026). Un algorithme est figé dans le passé (ses données d’entraînement), tandis que le monde réel, lui, évolue en permanence. Ce phénomène de dégradation silencieuse s’appelle la Dérive (Drift).
Il est vital de comprendre que si un modèle d’IA se trompe de plus en plus au fil du temps, ce n’est pas parce que son code s’est cassé, mais parce que le monde a changé.
6.5.1 📉 Les Deux Dérives
On distingue deux phénomènes majeurs qui nécessitent une surveillance algorithmique constante.
Dérive des Données (Data Drift) : La relation métier reste vraie, mais la population d’entrée a changé.
La Métaphore : Vous avez appris à conduire dans une petite ville de campagne (données d’entraînement) et on vous lâche en plein Paris. Les règles du code sont identiques, mais l’environnement (distribution des données) a totalement muté.
Exemple Business : Un modèle de crédit entraîné sur des clients de plus de 40 ans reçoit soudainement des profils étudiants de 20 ans.
Dérive de Concept (Concept Drift) : Les données d’entrée semblent identiques, mais la vérité mathématique a changé (les règles du jeu ont changé dans votre dos).
Exemple Business : La détection de fraude. Un achat de 500€ de nuit depuis l’étranger était systématiquement une fraude en 2024. En 2026, avec les néo-banques, c’est devenu banal et légitime. Le concept même de la fraude a évolué.
Interactif : Le Simulateur de Dérive
Pour bien saisir la différence entre ces deux dérives, manipulez ce simulateur. Observez comment la ligne de décision (votre modèle figé) devient obsolète face à l’évolution du monde.
🚨 Simulateur de Crash Silencieux (Drift)
Le graphique ci-dessous représente votre modèle en production. Le fond de couleur (Bleu/Rouge) représente les zones de décision figées de l’algorithme. Observez comment la précision de votre modèle s’effondre lorsque le monde réel (les points) évolue, sans même qu’une ligne de code n’ait été modifiée.
// 1. Contrôles de simulationviewof driftMode = Inputs.radio( ["État Initial (Tout va bien)","💥 Data Drift (Changement de population)","🎭 Concept Drift (Changement de comportement)" ], { value:"État Initial (Tout va bien)",label:"État du Monde Réel :" })
baseData = {const lcg = (a) => () => (a = (a *16807) %2147483647) /2147483647;const rand =lcg(42);// Fonction pour distribution normale (Box-Muller)const randn = () =>Math.sqrt(-2.0*Math.log(1-rand())) *Math.cos(2.0*Math.PI*rand());let pts = [];// 100 Transactions Légitimes (Bleu) à gauchefor(let i=0; i<100; i++) { pts.push({ id: i,base_x:-2.5+randn()*0.8,base_y:randn()*1.5,base_label:"Légitime" }); }// 100 Fraudes (Rouge) à droitefor(let i=0; i<100; i++) { pts.push({ id:100+i,base_x:2.5+randn()*0.8,base_y:randn()*1.5,base_label:"Fraude" }); }return pts;}// 3. Application du Drift selon le mode sélectionnécurrentData = baseData.map(d => {let x = d.base_x;let y = d.base_y;let label = d.base_label;if (driftMode.includes("Data Drift")) {// Les données se déplacent physiquement (Ex: la banque cible de nouveaux clients plus risqués)// Les transactions légitimes se décalent vers la droite. x = d.base_x+3.2; y = d.base_y-0.5; } elseif (driftMode.includes("Concept Drift")) {// Les points ne bougent pas, mais la définition de la fraude change !// (Ex: Les fraudeurs ont appris à imiter le comportement des clients légitimes)if (d.base_x>-2.5&& d.base_x<0&& d.base_y>0) { label ="Fraude";// Des points bleus deviennent soudainement rouges } }// Le modèle en production est FIGÉ : il prédit Fraude si X > 0, Légitime si X < 0.let prediction = x >0?"Fraude":"Légitime";let isCorrect = prediction === label;return { ...d, x, y, label, prediction, isCorrect };})// 4. Calcul de la Précision Globaleaccuracy = (currentData.filter(d => d.isCorrect).length/ currentData.length) *100;
import {Plot} from"@observablehq/plot"Plot.plot({title:"Simulateur de Dérive de Données et de Concept en Production",height:450,width:800,style: { background:"var(--sol-base3)",fontSize:"14px",borderRadius:"8px",border:"1px solid var(--sol-base2)" },x: { domain: [-6,6],label:"Caractéristique Client (ex: Score d'achat) →",grid:true },y: { domain: [-4,4],label:"← Comportement →",grid:true },marks: [// Zone de décision du modèle : Légitime (Bleu) Plot.rect([{x1:-10,x2:0,y1:-10,y2:10}], {x1:"x1",x2:"x2",y1:"y1",y2:"y2",fill:"rgba(38, 139, 210, 0.1)"}),// Zone de décision du modèle : Fraude (Rouge) Plot.rect([{x1:0,x2:10,y1:-10,y2:10}], {x1:"x1",x2:"x2",y1:"y1",y2:"y2",fill:"rgba(220, 50, 47, 0.1)"}),// Frontière de décision (Le modèle figé) Plot.ruleX([0], {stroke:"var(--sol-base03)",strokeWidth:4,strokeDasharray:"8,4"}), Plot.text([{x:0,y:3.5}], {text: () =>"Frontière du Modèle (Figée)",fill:"var(--sol-base03)",fontWeight:"bold",dy:-10}),// Affichage des données (qui évoluent selon le monde réel) Plot.dot(currentData, {x:"x",y:"y",fill: d => d.label==="Légitime"?"var(--sol-blue)":"var(--sol-red)",stroke: d => d.isCorrect?"var(--sol-base3)":"var(--sol-base03)",// Entouré en noir si le modèle se trompe !strokeWidth: d => d.isCorrect?1:2.5,r:6 }) ]})
// 6. Tableau de Bord et Pédagogiehtml`<div style="display: flex; gap: 20px; margin-top: 15px;"> <div style="flex: 1; padding: 20px; background: ${accuracy >85?'rgba(133, 153, 0, 0.1)':'rgba(220, 50, 47, 0.1)'}; border-radius: 12px; border: 1px solid ${accuracy >85?'var(--sol-green)':'var(--sol-red)'}; text-align: center; transition: background 0.5s;"> <div style="font-size: 0.9em; color: ${accuracy >85?'var(--sol-green)':'var(--sol-red)'}; text-transform: uppercase; font-weight: bold;">Précision en Production</div> <div style="font-size: 3.5em; font-weight: 800; color: ${accuracy >85?'var(--sol-green)':'var(--sol-red)'};">${accuracy.toFixed(1)}%</div> <div style="font-size: 0.9em; color: ${accuracy >85?'var(--sol-green)':'var(--sol-red)'}; margin-top: 5px;">${accuracy >85?'✅ Modèle Fiable':'🚨 ALERTE : Ré-entraînement requis (PSI Élevé)'} </div> </div> <div style="flex: 2; padding: 20px; border-radius: 12px; background: var(--sol-base2); border: 1px solid var(--sol-base1);"> <strong>📊 Diagnostic de Dérive :</strong><br/> <p style="margin-top: 10px; color: var(--sol-base00); line-height: 1.5;">${driftMode.includes("Initial")?"Tout va bien. La majorité des points bleus (Légitimes) sont dans la zone bleue du modèle, et les points rouges (Fraudes) dans la zone rouge. Le modèle est parfaitement aligné avec la réalité.": driftMode.includes("Data Drift")?"<b>Data Drift détecté !</b> La définition de la fraude n'a pas changé, mais la population oui. Les clients légitimes (points bleus) ont changé de comportement (ex: ils achètent plus à l'étranger) et se sont décalés vers la droite. Le modèle, figé, les perçoit comme des fraudeurs et génère une avalanche de <b>Faux Positifs</b> (les points bleus entourés de noir dans la zone rouge).":"<b>Concept Drift détecté !</b> Les points n'ont pas bougé (les comportements clients sont les mêmes), mais les règles du jeu ont changé. Les fraudeurs ont trouvé une faille et imitent désormais les clients légitimes (les points rouges à gauche de la ligne). Le modèle n'y voit que du feu et génère des <b>Faux Négatifs critiques</b>."} </p> </div></div>`
6.5.2 🕵️♂️ Boucle de Ré-entraînement
Pour survivre à la dérive, les architectures MLOps de 2026 intègrent des mécanismes de Continuous Training (Entraînement Continu) (“Model Monitoring & Drift Detection” 2026).
Indicateur d’Alarme (PSI) : Le Population Stability Index compare la distribution statistique des données d’aujourd’hui avec celle du jour de l’entraînement. Si le PSI dépasse 0.2, une alerte rouge est levée : la population a trop muté.
Boucle de Ré-entraînement : Dès que le PSI dépasse la limite, le pipeline extrait les données récentes, ré-entraîne le modèle, valide s’il est plus performant, et le déploie de manière transparente.
graph LR
A[Modèle en Production] -->|Monitoring en temps réel| B{Détection de Drift ?}
B -->|PSI > 0.2| C[Extraction des données récentes]
B -->|PSI < 0.2| A
C --> D[Ré-entraînement automatique\nvia Optuna]
D --> E{Le nouveau modèle\nest-il meilleur ?}
E -->|Oui| F[Déploiement Transparent\n(Shadow Mode)]
E -->|Non| G[Alerte Data Scientist]
F --> A
style B fill:#dc322f,stroke:#073642,color:#fdf6e3
style E fill:#cb4b16,stroke:#073642,color:#fdf6e3
style F fill:#859900,stroke:#073642,color:#fdf6e3
6.5.3 ⚖️ L’EU AI Act
En 2026, surveiller la dérive n’est plus seulement une bonne pratique technique, c’est une obligation légale.
L’EU AI Act (Législation européenne sur l’IA) impose des contraintes sévères sur les modèles déployés, en particulier ceux dits “à haut risque” (banque, médical, ressources humaines) (Pregasen 2026) :
Une entreprise dont l’algorithme “dérive” au point de devenir discriminatoire (ex: un Data Drift qui le pousse à refuser systématiquement des crédits à une minorité) s’expose à des amendes se chiffrant en millions d’euros. Le MLOps est votre bouclier juridique.
6.6 🌉 Conclusion et Transition
Nous avons maintenant des modèles robustes et évalués. La dernière étape, et non la moindre, consiste à communiquer ces résultats de manière claire et impactante aux parties prenantes.
Imani, Mehdi, Majid Joudaki, Ayoub Bagheri, and Hamid R. Arabnia. 2026. “Why ROC-AUC Is Misleading for Highly Imbalanced Data : In-Depth Evaluation of MCC, F2-Score, h-Measure, and AUC-Based Metrics Across Diverse Classifiers.”Technologies 14 (1). https://www.diva-portal.org/smash/record.jsf?pid=diva2:2037946.
---title: "🧪 Évaluation des Modèles"---```{ojs}//| echo: false//| output: false// Block de stabilisation OJS pour le projet Quarto_stabilize = true```{{< include _61_metriques_regression.qmd >}}{{< include _62_metriques_classification.qmd >}}{{< include _63_methodes_validation.qmd >}}{{< include _64_techniques_regularisation.qmd >}}## 🌉 Conclusion et TransitionNous avons maintenant des modèles robustes et évalués. La dernière étape, et non la moindre, consiste à communiquer ces résultats de manière claire et impactante aux parties prenantes.C'est l'art que nous allons explorer dans le **[Chapitre 7 : Communication des Résultats](../7_communication/index.qmd)**.