Aller au contenu
English
English
MLOps medium

Monitoring ML : surveiller les modèles et détecter le drift

18 min de lecture

Un modèle en production se dégrade sans lever la moindre erreur, et le drift des données n'est qu'un des signaux, souvent trompeur. Ce guide montre, sur trois scénarios reproductibles, que la dérive peut crier sans conséquence et que la qualité peut s'effondrer sans qu'aucune dérive ne soit détectée. Vous repartirez avec ce qu'il faut surveiller en plus du drift, comment travailler quand les étiquettes arrivent avec un mois de retard, et la chaîne de décision qui va du signal au retour arrière. Public : intermédiaire à avancé.

Tous les chiffres publiés ici sortent d'un script seedé, rejouable à l'identique, avec Evidently 0.7.21, scikit-learn 1.7.2 et Python 3.12.

  • Distinguer ce que le drift détecte de ce qu'il manque, sur des cas mesurés
  • Reconnaître les deux faux signaux : dérive sans impact, effondrement sans dérive
  • Surveiller au-delà des distributions : qualité des données, calibration, tranches, métrique métier
  • Travailler avec des étiquettes retardées, et savoir ce que valent les substituts
  • Décider : du signal au challenger, puis à la promotion ou au rollback

Un modèle ML ne tombe pas en panne, il devient faux. Les prédictions restent techniquement valides, le service répond en quelques millisecondes, et la pertinence baisse. Un modèle de détection de fraude entraîné l'an dernier ignore les fraudes d'aujourd'hui, sans qu'aucune alerte d'infrastructure ne se déclenche.

Deux difficultés rendent cette surveillance particulière. La vérité terrain arrive souvent en retard : on ne sait qu'une transaction était frauduleuse qu'après la contestation du client, des semaines plus tard. Et les métriques système habituelles, latence et disponibilité, ne disent rien de la justesse des prédictions. Un modèle parfaitement disponible peut se tromper une fois sur deux.

C'est le point que la plupart des guides passent sous silence, et il change complètement la façon de bâtir une surveillance. Le drift des données d'entrée n'est ni nécessaire ni suffisant pour conclure à une dégradation. Les trois scénarios ci-dessous le montrent sur le même modèle, un RandomForestClassifier de détection de fraude, avec quatre features et 4000 lignes par jeu.

Le modèle de référence, entraîné avec la graine 42 :

exactitude 0.696
AUC 0.767
importance des features
nb_essais 0.340
anciennete 0.339
montant 0.162
heure 0.159

Scénario 1 : la dérive crie, la qualité ne bouge pas

Section intitulée « Scénario 1 : la dérive crie, la qualité ne bouge pas »

On décale la feature heure de six heures, soit deux écarts-types. C'est une dérive massive, et Evidently la voit sans ambiguïté :

colonnes derivees : 1/4 (part 0.25)
montant Wasserstein distance (normed) 0.0305 vs seuil 0.1 stable
anciennete Wasserstein distance (normed) 0.01546 vs seuil 0.1 stable
nb_essais Wasserstein distance (normed) 0.01076 vs seuil 0.1 stable
heure Wasserstein distance (normed) 1.992 vs seuil 0.1 DERIVE
exactitude : 0.675 (reference 0.696)
ecart d exactitude : -0.021

La distance mesurée vaut vingt fois le seuil, et l'exactitude perd deux points. Une alerte configurée sur « une colonne a dérivé » se déclenche ici, et réveille quelqu'un pour rien. La raison est dans le tableau d'importance : heure pèse 0,159, et surtout elle n'intervient pas dans la règle qui génère l'étiquette. Le modèle s'en sert à la marge, sa dérive ne le dérange pas.

Scénario 2 : aucune dérive, la qualité s'effondre

Section intitulée « Scénario 2 : aucune dérive, la qualité s'effondre »

Cette fois on ne touche à aucune distribution d'entrée. On change la relation : les fraudeurs passent désormais par des comptes anciens avec peu de tentatives, l'inverse exact de ce que le modèle a appris. C'est du concept drift.

colonnes derivees : 0/4 (part 0.00)
montant Wasserstein distance (normed) 0.02815 vs seuil 0.1 stable
anciennete Wasserstein distance (normed) 0.01077 vs seuil 0.1 stable
nb_essais Wasserstein distance (normed) 0.01693 vs seuil 0.1 stable
heure Wasserstein distance (normed) 0.04219 vs seuil 0.1 stable
exactitude : 0.312 (reference 0.696)
AUC : 0.247 (reference 0.767)
ecart d exactitude : -0.384

Zéro colonne dérivée, et le modèle perd 38 points d'exactitude. L'AUC tombe à 0,247, c'est-à-dire sous 0,5 : le modèle est devenu anti-corrélé, on ferait mieux en inversant ses prédictions. Une surveillance qui ne regarde que les distributions d'entrée n'aurait rien vu, pendant que le système prenait des décisions systématiquement fausses.

Scénario 3 : les étiquettes arrivent dans trente jours

Section intitulée « Scénario 3 : les étiquettes arrivent dans trente jours »

Reprenons le scénario 2, mais au jour J : la vérité terrain n'existe pas encore. Voici tout ce qu'on peut mesurer sans étiquette.

taux de positifs predit, reference : 0.493
taux de positifs predit, scenario 2: 0.496
ecart : +0.004
confiance moyenne, reference : 0.502
confiance moyenne, scenario 2 : 0.504

Le taux de positifs bouge de quatre millièmes, la confiance moyenne de deux. Autrement dit, le substitut le plus souvent recommandé, la surveillance de la distribution des prédictions, ne voit rien non plus. Ce n'est qu'à J+30, quand les contestations remontent, que l'exactitude réelle apparaît : 0,312.

Il faut en tirer la conséquence honnête : sur ce cas, aucune métrique sans étiquette ne donne l'alerte. Ce que vous pouvez faire, c'est raccourcir le délai plutôt que chercher un meilleur substitut, et c'est l'objet de la section suivante.

Détecter le drift avec Evidently, sans se tromper de métrique

Section intitulée « Détecter le drift avec Evidently, sans se tromper de métrique »

Evidently compare un jeu de référence à un jeu courant et signale les écarts. Installez-le dans un environnement isolé, avec sa version épinglée :

Fenêtre de terminal
pip install "evidently==0.7.21"
drift.py : comparaison reproductible, graine fixée
import numpy as np
import pandas as pd
from evidently import DataDefinition, Dataset, Report
from evidently.presets import DataDriftPreset
rng = np.random.default_rng(42) # sans graine, rien n'est reproductible
ref = pd.DataFrame({"feature": rng.normal(0.0, 1, 500)})
cur = pd.DataFrame({"feature": rng.normal(0.8, 1, 500)})
schema = DataDefinition(numerical_columns=["feature"])
rapport = Report([DataDriftPreset()]).run(
current_data=Dataset.from_pandas(cur, data_definition=schema),
reference_data=Dataset.from_pandas(ref, data_definition=schema),
)
rapport.save_html("drift_report.html")
for m in rapport.dict()["metrics"]:
print(m["metric_name"], "->", m["value"])

La métrique change à 1000 lignes, et le seuil avec

Section intitulée « La métrique change à 1000 lignes, et le seuil avec »

C'est le piège qui fait qu'un tableau de bord ne dit pas la même chose en laboratoire et en production. Evidently 0.7.21 choisit sa méthode selon la taille du jeu de données, et bascule à 1000 lignes. Mesuré, à graine constante, sur la même dérive de moyenne 0 vers 0,8 :

LignesMéthode retenueSeuilValeur
200K-S p_value0.052,57e-14
500K-S p_value0.051,57e-28
999K-S p_value0.056,09e-49
1000K-S p_value0.051,36e-48
1001Wasserstein distance (normed)0.10,755
4000Wasserstein distance (normed)0.10,820

Deux conséquences pratiques. Une p-value se compare à 0,05 et plus elle est petite, plus la dérive est franche ; une distance de Wasserstein se compare à 0,1 et plus elle est grande, plus la dérive est franche. Le sens de lecture s'inverse. Et un seuil d'alerte écrit pour un échantillon de test à 500 lignes n'a aucun sens sur un flux de production à 50 000.

Le scénario 2 a montré qu'une surveillance réduite aux distributions d'entrée laisse passer le pire cas. Voici les couches à empiler, de la moins chère à la plus exigeante.

CoucheCe qu'elle attrapeCe qu'elle coûte
Qualité des donnéesValeurs manquantes, types changés, valeurs hors bornes, colonne figéeRien, c'est le socle
Drift des entréesUn nouveau segment d'utilisateurs, une saison, un capteur dérégléUn jeu de référence à figer
Drift des prédictionsLe modèle qui bascule vers une classe uniqueRien de plus que les logs
Écart entraînement/serviceLa feature calculée différemment en production et à l'entraînementInstrumenter les deux chemins
Qualité du modèleTout le reste, dont le concept driftDes étiquettes
CalibrationUn modèle sûr de lui et faux, ce qui casse tout seuil métierDes étiquettes, plus une courbe
TranchesLa dégradation sur un segment noyée dans la moyenneDécouper par segment
Métrique métierCe que la direction regardera de toute façonRelier prédictions et résultats

Deux de ces couches méritent un mot, parce qu'elles sont systématiquement oubliées.

L'écart entraînement/service est la cause d'incident la plus banale et la plus humiliante : la feature montant_moyen_30j est calculée sur 30 jours glissants à l'entraînement et sur le mois calendaire en production. Aucune dérive, aucun bug visible, un modèle qui reçoit des entrées qui ne sont pas celles sur lesquelles il a appris. Il se détecte en journalisant les features telles que le service les calcule, puis en les comparant au jeu d'entraînement.

Les tranches répondent à une question que la moyenne masque toujours : un modèle qui passe de 0,90 à 0,88 d'exactitude globale peut avoir perdu 20 points sur les nouveaux clients et gagné sur les anciens. Surveillez au moins les segments qui ont une conséquence métier ou réglementaire distincte.

Le délai d'étiquetage détermine votre stratégie de surveillance, plus que le choix de l'outil. Trois régimes, trois réponses.

DélaiCe sur quoi vous pilotez
Minutes à heures (clic, conversion)La qualité du modèle directement. Le drift devient accessoire
Jours à semaines (résiliation, contestation)Qualité en différé, plus les couches sans étiquette en attendant
Mois, ou jamais (risque de crédit à 3 ans)Un proxy assumé, et une surveillance humaine

Trois leviers permettent de raccourcir l'attente plutôt que de la subir.

  1. Étiqueter un échantillon, tout de suite

    Prélevez un petit échantillon aléatoire des prédictions du jour et faites-le trancher par un humain sous 24 h. Quelques centaines de cas suffisent à détecter un effondrement comme celui du scénario 2, bien avant les 30 jours. C'est le seul dispositif qui aurait vu cette panne.

  2. Garder une fraction non traitée comme témoin

    Sur les systèmes où le modèle agit (blocage, remise, priorisation), son action empêche d'observer ce qui serait arrivé sans lui. Laisser passer un petit pourcentage sans intervention donne une mesure non biaisée, au prix assumé d'un peu de risque.

  3. Mesurer le délai lui-même

    Suivez la distribution du délai d'étiquetage comme une métrique à part entière. Un allongement soudain signale souvent un problème de collecte, et il rend tous vos indicateurs de qualité optimistes sans prévenir.

Détecter ne sert à rien sans une chaîne de décision écrite d'avance. Le réentraînement automatique dès qu'un seuil est franchi est un anti-pattern : le scénario 1 l'aurait déclenché pour rien, et un réentraînement sur des données déjà polluées propage le problème au lieu de le corriger.

  1. Signal : un seuil est franchi, sur n'importe quelle couche. Ce n'est qu'une alerte, pas un diagnostic.

  2. Investigation : la dégradation touche-t-elle la qualité du modèle ou seulement une distribution ? Une tranche ou tout le monde ? Le scénario 1 et le scénario 2 se séparent ici, et eux seuls décident de la suite.

  3. Candidat challenger : entraîner un modèle sur des données récentes et vérifiées, sans le mettre en production. Un challenger n'est pas une correction, c'est une hypothèse.

  4. Validation : comparer le challenger au modèle en place sur un jeu de test figé, tranche par tranche, et sur la métrique métier. Un gain global qui cache une perte sur un segment sensible n'est pas un gain.

  5. Promotion progressive : basculer une fraction du trafic, en observant les mêmes indicateurs. Pas de bascule totale d'un coup.

  6. Surveillance renforcée : garder les seuils resserrés quelques jours après la promotion, quand les régressions apparaissent.

  7. Rollback : revenir à la version précédente par une procédure testée, pas improvisée. Le modèle précédent, ses features et son préprocesseur doivent rester versionnés ensemble et redéployables, sinon le retour arrière est un nouveau déploiement risqué.

Aucun de ces outils ne remplace les couches décrites plus haut : ils les instrumentent. Le choix se fait sur ce que vous avez déjà en exploitation, pas sur la liste de fonctionnalités.

OutilCe qu'il couvreCe qu'il ne couvre pas
EvidentlyQualité des données, drift des entrées et des prédictions, rapports HTMLLa décision, et la collecte des étiquettes
Prometheus + GrafanaLatence, disponibilité, et toute métrique que vous exposezRien de spécifique au ML, tout est à construire
Arize AI / WhyLabsMonitoring managé, alertes, tranches, biaisLe coût, et la sortie des données hors de votre périmètre
MLflowVersionnement des modèles, ce qui rend le rollback possibleLa surveillance en production

Une combinaison pragmatique et peu coûteuse : Evidently pour les rapports de drift et de qualité des données, Prometheus et Grafana pour exposer la qualité du modèle et la métrique métier à côté des indicateurs système, et MLflow pour que la version précédente soit réellement redéployable.

  1. Le drift n'est ni nécessaire ni suffisant. Mesuré : une dérive à vingt fois le seuil coûte 2 points d'exactitude, et un effondrement de 38 points se produit avec zéro colonne dérivée.
  2. Le pire cas est silencieux. Dans le scénario de concept drift, l'AUC tombe à 0,247, sous le hasard, sans qu'aucune métrique sans étiquette ne bouge : le taux de positifs prédit varie de 0,004.
  3. Evidently change de métrique à 1000 lignes, de la p-value K-S à la distance de Wasserstein, et le sens de lecture s'inverse. Un seuil écrit pour 500 lignes n'a aucun sens en production.
  4. Sans graine, rien n'est reproductible. Un exemple np.random non seedé ne permet ni de vérifier un chiffre publié, ni de comparer deux exécutions.
  5. Empilez les couches : qualité des données, drift, écart entraînement /service, qualité du modèle, calibration, tranches, métrique métier.
  6. Raccourcissez le délai d'étiquetage plutôt que de chercher un meilleur substitut : un petit échantillon étiqueté sous 24 h voit ce qu'aucun proxy ne voit.
  7. Le réentraînement automatique sur seuil est un anti-pattern. La chaîne est signal, investigation, challenger, validation, promotion progressive, surveillance, rollback.
  8. Un rollback jamais joué n'existe pas. Testez-le hors incident, et versionnez le modèle avec ses features et son préprocesseur.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens ce site gratuitement, sans publicité, sans profilage et sans compte à créer. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn