Planifier les vérifications en production
- Leçon 27
- 75 min
- Module 8 : Préparer la passation du modèle
Leçon 27 sur 27 · Apprentissage automatique : du problème à un modèle fiablePlanifier les vérifications en production
1. Comprendre l'apprentissage automatique
2. Cadrer le problème de prédiction
3. Évaluer les modèles supervisés
4. Créer les premiers modèles
5. Essayer d'autres familles de modèles
6. Régler et finaliser le modèle
7. Comprendre et choisir le modèle
8. Préparer la passation du modèle
Ce que vous saurez faire
Planifiez les vérifications qui indiquent quand le modèle ne convient plus à sa tâche. Les vérifications des entrées bloquent les mauvaises données. Les signaux de surveillance montrent les changements. Une règle écrite indique quand réentraîner le modèle.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter ajoute les vérifications des entrées à predict.py et crée cinq problèmes dans les entrées. Ensuite, il calcule les scores des billets de soutien de juillet, tous les signaux de surveillance de cette page et joint les étiquettes. Il se termine par l’exercice du module : l’exécution à partir d’une nouvelle copie. Il ne nécessite aucune configuration locale, mais le projet local est l’approche principale de ce module.
Le carnet est en anglais. Colab et Kaggle ouvrent votre propre copie. Il faut un compte Google ou Kaggle gratuit. Vous êtes bloqué ? Consultez le carnet de solution.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- L’artefact et la première version de
predict.py, dans Empaqueter les artefacts. - La fiche du modèle et ses limites (calibration, nouvelles catégories), dans Documenter les limites d’utilisation.
- La précision, le rappel et le taux de signalement, dans Les métriques de classification. Les sous-groupes, dans L’analyse par sous-groupes.
handle_unknown="ignore": une catégorie absente des données d’entraînement reçoit 0 dans chaque colonne. Voir Ajuster les transformations correctement dans Livrer un jeu de données réutilisable.- Les vérifications des entrées qui arrêtent un pipeline avant qu’il charge de mauvaises données, dans Livrer un jeu de données réutilisable, du cours SQL et préparation des données pour l’IA.
Ce qu’il vous faut sur votre ordinateur (seulement pour l’approche locale; le carnet n’en a pas besoin)
- Le projet
~/projects/ticket-modelavec.venvactif, dans Définir une référence de base : préparer votre projet. model/ettrain.py, dans Empaqueter les artefacts : exemple détaillé : train.py. Simodel/manque, exécutezpython train.py.predict.py(première version), dans Empaqueter les artefacts : un nouveau processus. Vous le modifiez dans cette leçon.data/july_tickets.csvetdata/july_labels.csv, obtenus avecpython get_data.py. S’ils manquent, exécutezpython get_data.py --reset.MODEL_CARD.md, dans Documenter les limites d’utilisation : exercice guidé, etevaluate.py, dans Seuils et calibration : tester une seule fois avec evaluate.py, pour l’exercice du module.- Si un fichier du projet manque ou ne fonctionne plus, copiez la version du cours depuis C05/project. Par exemple :
curl -fsSL -O https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C05/project/predict.py(curl.exesous Windows).
Testé sous macOS avec Python 3.14.6, pandas 3.0.6 et scikit-learn 1.9.1. Les commandes Windows et Linux n’ont pas été testées. Le carnet a été testé dans Jupyter.
Les voyants d’alerte d’un tableau de bord
Section intitulée « Les voyants d’alerte d’un tableau de bord »Une voiture ne vous indique pas tout en permanence. Elle a quelques voyants d’alerte, et chacun surveille une chose. Un voyant d’huile rouge signifie « arrêtez maintenant » : continuer à rouler endommage le moteur. Un voyant de carburant orange signifie « agissez bientôt » : vous pouvez encore rouler, mais pas longtemps. Le carnet d’entretien indique « entretien tous les 15 000 km, ou plus tôt si un voyant reste allumé ». Les voyants ne conduisent pas la voiture. Ils avertissent la personne au volant d’un problème avant qu’il devienne coûteux.
Un modèle utilisé chaque jour a les mêmes besoins. Depuis le 1er juillet, le modèle de Larkfield calcule un score pour chaque nouveau billet. Personne ne peut lire 50 scores par jour et repérer un problème. Priya planifie donc quelques vérifications à l’avance. Certaines arrêtent l’exécution immédiatement, d’autres avertissent, et une règle écrite indique quand entretenir le modèle.
Le problème : trois billets, cinq entrées
Section intitulée « Le problème : trois billets, cinq entrées »Que fait la première version de predict.py quand l’entrée ne correspond pas à ce que le modèle attend? Chaque jour, Mei, l’ingénieure de données, exporte les nouveaux billets vers predict.py dans un fichier CSV. Ce fichier contient les mêmes colonnes que july_tickets.csv. Les exportations changent, des collègues renomment des colonnes et de nouveaux types de billets apparaissent.
Prenez les trois premiers billets de juillet et modifiez-les de quatre façons. Ensuite, exécutez la première version sur les cinq fichiers. Deux des cinq produisent une erreur avec une longue trace d’appels que seule une personne qui programme en Python peut lire. Deux ne donnent aucun message : un billet provenant d’un nouveau canal et des valeurs de commande en cents plutôt qu’en dollars. L’erreur de cents fait passer le nombre de signalements de un à trois, sans avertir personne.
| Fichier d’entrée | Ce qui a changé | La première version de predict.py |
|---|---|---|
tiny_ok.csv |
Rien | Scored 3 tickets with threshold 0.19: 1 flagged (33.3%). |
tiny_social.csv |
Le billet 3 a channel = social |
Scored 3 tickets with threshold 0.19: 1 flagged (33.3%). |
tiny_missing.csv |
La colonne prior_escalations_90d manque |
KeyError: "['prior_escalations_90d'] not in index" |
tiny_text.csv |
Le billet 2 a priority = high |
ValueError: Cannot use median strategy with non-numeric data: could not convert string to float: 'high' |
tiny_cents.csv |
order_value est en cents : 5438 au lieu de 54,38 |
Scored 3 tickets with threshold 0.19: 3 flagged (100.0%). |
Les cas silencieux sont les plus dangereux. Un modèle calcule un score pour tout ce qu’il reçoit. Il ne dit jamais « cette entrée semble étrange ». Le programme qui l’entoure doit donc le dire.
Deux types de vérifications
Section intitulée « Deux types de vérifications »Revenons au tableau de bord. Certains problèmes sont visibles immédiatement : un fichier endommagé, une colonne manquante. D’autres s’accumulent lentement au fil des jours. Un programme qui utilise un modèle a donc besoin de deux types de vérifications.
Les vérifications des entrées sont les voyants rouges. Elles s’exécutent avant le calcul des scores, sur chaque lot. Elles comparent le lot au schéma dans model_info.json. Ce schéma décrit les colonnes, les types et les valeurs attendus, présentés dans Empaqueter les artefacts. Un lot que le modèle ne peut pas traiter correctement arrête l’exécution, avec un message qui explique le problème.
La surveillance correspond aux voyants orange. Elle observe les résultats au fil du temps. Elle compare des nombres, comme la proportion de billets signalés, aux valeurs attendues. Elle repère les changements lents qu’aucun lot isolé ne révèle. Un changement des données au fil du temps s’appelle une dérive. Le nom vient d’un bateau que personne ne dirige. Chaque minute, le courant le déplace un peu. Après une heure, il est loin de son point de départ.
Les deux types de vérifications alimentent une décision écrite à l’avance : quand s’arrêter et réexaminer le modèle. Cette règle est le déclencheur de réentraînement, le carnet d’entretien du modèle. Le diagramme montre toute la boucle quotidienne.
Description textuelle du diagramme
De nouveaux billets passent par les vérifications des entrées chaque jour. Une vérification qui échoue arrête l’exécution avec le code de sortie 1, et aucun score n’est calculé. Un lot accepté, ou contenant seulement une catégorie inconnue, reçoit des scores et des signalements dans predictions.csv. Le jour même, quatre signaux ne nécessitent aucune étiquette : le taux de signalement, le score moyen, la proportion de catégories inconnues et la moyenne de chaque caractéristique numérique. Soixante-douze heures plus tard, les étiquettes arrivent et on peut calculer la précision et le rappel. Si une condition de déclenchement est vraie, Priya et Grace examinent le modèle. Elles trouvent la cause, puis corrigent les données, réentraînent le modèle ou mettent la fiche à jour. Sinon, le modèle reste en place, et les vérifications reprennent le lendemain.
Les vérifications des entrées dans predict.py
Section intitulée « Les vérifications des entrées dans predict.py »Mei et Priya décident ce que chaque problème signifie et ce que le programme doit faire. Leur règle : arrêter si les scores seraient incorrects; avertir si les scores restent utilisables, mais que l’entrée a changé. Une colonne manquante ou du texte dans une colonne numérique rend le calcul des scores impossible. Un nombre très éloigné de sa plage habituelle indique presque toujours une erreur d’unité ou d’exportation. Les scores seraient donc incorrects, sans avertissement. Une catégorie que le modèle n’a jamais vue est différente : le pipeline peut encore calculer le score du billet, avec moins d’information.
| Problème | Exemple | Action | Pourquoi |
|---|---|---|---|
| Une colonne obligatoire manque | prior_escalations_90d est absent du fichier |
Arrêter | Le modèle ne peut pas calculer les scores sans cette colonne. |
| Une colonne numérique contient du texte | priority = high |
Arrêter | La valeur n’a aucun sens pour le modèle. |
| Un nombre sort de sa plage | order_value = 5438 pour une commande de 54,38 $ |
Arrêter | Une erreur d’unité ou d’exportation. Les scores seraient incorrects, sans avertissement. |
| Une catégorie inconnue | channel = social |
Avertir et compter | Le pipeline peut calculer son score (handle_unknown="ignore"). Un arrêt bloquerait l’acheminement de tous les billets. Mais plusieurs catégories inconnues indiquent que l’entrée a changé. |
« Arrêter » signifie : ne calculer aucun score, afficher le problème et quitter avec le code 1. Un ordonnanceur ou un collègue voit l’échec immédiatement. Mais pourquoi ne pas calculer les scores des bonnes lignes seulement? Tomás le propose, et cela semble plus accommodant. Mais l’exécution semble alors réussie, les mauvaises lignes n’attendent dans aucune file, et personne ne remarque le problème avant une plainte. Un arrêt bien visible est le moyen le plus rapide d’obtenir une correction.
Modifiez predict.py à quatre endroits. D’abord, la chaîne de documentation décrit maintenant les vérifications :
"""Score new tickets with the saved escalation model.
Nextia Learning, C05, Module 8 (Package artifacts; Plan production checks).
python predict.py data/july_tickets.csv predictions.csv python predict.py --check reload the model and compare the fixed sample
Checks the input before it scores: the required columns, numbers that arenumbers, values in their allowed range, and categories that the model knows.A missing column or a wrong type stops the run (exit code 1). An unknowncategory is allowed (the model treats it as "none of the known ones") but iscounted and reported, because many of them mean that the input has changed."""Ensuite, sous MODEL = Path("model"), ajoutez la plage autorisée de chaque nombre. Les plages sont volontairement larges : elles détectent les erreurs d’unité et les exportations défectueuses, pas les billets inhabituels. Un billet de 3 000 mots est inhabituel, mais réel; une commande de 543 800 $ indique une erreur d’exportation.
RANGES = {"priority": (1, 3), "word_count": (0, 10000), "order_value": (0, 5000), "customer_tenure_days": (0, 20000), "prior_tickets_90d": (0, 500), "created_hour": (0, 23), "prior_escalations_90d": (0, 500)}Puis, ajoutez check_input() après load_model(). Cette fonction lit le schéma dans model_info.json, pour que les vérifications correspondent toujours à l’artefact :
def check_input(df, info): """Return a list of problems that stop the run, and a list of warnings.""" errors, warnings = [], [] missing = [c for c in FEATURES if c not in df.columns] if missing: return [f"missing columns: {', '.join(missing)}"], warnings for name in info["features"]["numbers"]: values = pd.to_numeric(df[name], errors="coerce") bad = values.isna() & df[name].notna() if bad.any(): errors.append(f"{name}: {bad.sum()} values are not numbers, for example {df.loc[bad, name].iloc[0]!r}") continue low, high = RANGES[name] outside = values.notna() & ((values < low) | (values > high)) if outside.any(): errors.append(f"{name}: {outside.sum()} values outside {low} to {high}") for name, known in info["features"]["categories"].items(): unknown = df[name].notna() & ~df[name].isin(known) if unknown.any(): values = ", ".join(sorted(df.loc[unknown, name].astype(str).unique())[:5]) warnings.append(f"{name}: {unknown.sum()} rows have a category the model never saw ({values})") return errors, warningsTrois lignes méritent une explication :
| Lignes | Pourquoi |
|---|---|
pd.to_numeric(..., errors="coerce") |
Le texte qui n’est pas un nombre devient une valeur manquante. Une valeur qui n’était pas manquante avant, mais l’est maintenant, était du texte. Les vraies valeurs manquantes (le order_value d’un billet account) restent autorisées : le pipeline les remplit. |
RANGES[name] |
Une valeur hors de sa plage est une erreur, pas un avertissement. |
~df[name].isin(known) |
known est la liste des catégories dans model_info.json, celles que l’encodeur a apprises. |
Enfin, dans main(), vérifiez l’entrée après sa lecture et avant le calcul des scores. Voici la fonction main() complète :
def main(): parser = argparse.ArgumentParser(description="Score new tickets with the saved escalation model.") parser.add_argument("input", nargs="?", help="CSV file with one row per new ticket") parser.add_argument("output", nargs="?", default="predictions.csv") parser.add_argument("--check", action="store_true", help="compare the fixed sample after reloading") args = parser.parse_args() pipeline, info = load_model() if args.check: check_sample(pipeline) return if not args.input: parser.error("give an input CSV file, or --check")
df = read(args.input) errors, warnings = check_input(df, info) for warning in warnings: print(f"Warning: {warning}") if errors: for error in errors: print(f"Error: {error}") sys.exit("Stopped: the input does not match the model's feature schema. Nothing was scored.")
scores = pipeline.predict_proba(df[FEATURES])[:, 1] out = pd.DataFrame({"ticket_id": df["ticket_id"], "score": scores.round(4), "flag": (scores >= info["threshold"]).astype(int)}) out.to_csv(args.output, index=False) print(f"Scored {len(out)} tickets with threshold {info['threshold']}: {out['flag'].sum()} flagged " f"({out['flag'].mean():.1%}). Wrote {args.output}.")Votre fichier predict.py correspond maintenant à celui du cours, C05/project/predict.py. Exécutez-le de nouveau sur les cinq petits fichiers (le carnet les crée) :
Warning: channel: 1 rows have a category the model never saw (social)Scored 3 tickets with threshold 0.19: 1 flagged (33.3%). Wrote tiny_out.csv.Error: missing columns: prior_escalations_90dStopped: the input does not match the model's feature schema. Nothing was scored.Error: priority: 1 values are not numbers, for example 'high'Stopped: the input does not match the model's feature schema. Nothing was scored.Error: order_value: 3 values outside 0 to 5000Stopped: the input does not match the model's feature schema. Nothing was scored.tiny_ok.csv donne la même ligne qu’avant. Chaque problème produit maintenant un message qui permet à Mei d’agir. Les cas silencieux ne le sont plus. Ce sont les points de contrôle du contrat présenté dans Empaqueter les artefacts :
Description textuelle du diagramme
Les nouveaux billets atteignent le pipeline seulement par les vérifications des entrées, qui utilisent le schéma dans model_info.json. Une colonne manquante, un mauvais type ou une valeur hors plage arrête l’exécution avec le code de sortie 1. Aucun score n’est calculé. Une catégorie inconnue passe, avec un avertissement et un compte. Ensuite, le pipeline calcule le score de chaque billet. Un score d’au moins 0,19 active le signalement dans predictions.csv.
Exemple détaillé : les billets de juillet
Section intitulée « Exemple détaillé : les billets de juillet »L’exportation de juillet contient 1 400 billets de soutien créés du 1 au 31 juillet 2026, après les données d’entraînement. Leurs cibles ne sont pas encore connues. Priya calcule leurs scores :
python predict.py data/july_tickets.csv predictions.csvWarning: channel: 91 rows have a category the model never saw (social)Scored 1400 tickets with threshold 0.19: 282 flagged (20.1%). Wrote predictions.csv.Depuis le 15 juillet, Larkfield répond aux messages sur les réseaux sociaux. Parmi les billets, 91 viennent du nouveau canal social (réseaux sociaux). Le modèle les traite comme « aucun des canaux que je connais ». L’exécution continue, et l’avertissement les compte. Le taux de signalement de 20,1 % dépasse légèrement la capacité de 20 %. Mais un seul nombre pour le mois cache le moment du changement. La surveillance suit les nombres dans le temps.
Signaux de surveillance
Section intitulée « Signaux de surveillance »Un signal de surveillance est un nombre que vous calculez pour chaque période et comparez à vos attentes. Les meilleurs signaux sont disponibles le jour même, car ils ne nécessitent aucune étiquette. Priya en choisit quatre. Pour chacun, elle connaît la valeur normale de mai, le mois de validation :
- Le taux de signalement, comparé à la capacité de 20 %. C’est la contrainte de Grace. En mai, il était de 19,3 %.
- Le score moyen. S’il change, les entrées ont changé, même si le taux de signalement n’a pas encore changé. En mai, il était de 0,120.
- La proportion de catégories inconnues. Elle révèle de nouvelles entrées que le modèle n’a jamais apprises. En mai, elle était de 0.
- La moyenne de chaque variable numérique, comparée aux données de validation. Elle révèle une dérive dans une entrée. Ici, la plus claire est
prior_tickets_90d, avec une moyenne de 9,00 en mai.
import json
import pandas as pd
from ticket_model import read
info = json.load(open("model/model_info.json"))july = read("data/july_tickets.csv").merge(pd.read_csv("predictions.csv"), on="ticket_id")july["days"] = pd.cut(july["created_at"].dt.day, [0, 7, 14, 21, 28, 31], labels=["1-7", "8-14", "15-21", "22-28", "29-31"])july["unknown"] = ~july["channel"].isin(info["features"]["categories"]["channel"])signals = july.groupby("days", observed=True).agg( tickets=("flag", "size"), flag_rate=("flag", "mean"), mean_score=("score", "mean"), unknown=("unknown", "mean"), prior_tickets=("prior_tickets_90d", "mean"))print(signals.round(3))La sortie :
tickets flag_rate mean_score unknown prior_ticketsdays1-7 311 0.174 0.113 0.000 5.3228-14 314 0.185 0.111 0.000 5.24515-21 302 0.262 0.138 0.126 3.89422-28 321 0.209 0.137 0.100 3.59229-31 152 0.158 0.125 0.138 3.033Comparez le tableau aux valeurs de mai. Les deux premières semaines de juillet, le taux de signalement est de 17,4 % et 18,5 %, sous la capacité. À partir du 15 juillet, tout change en même temps : 26,2 % de billets signalés, un score moyen de 0,138 et 12,6 % de canaux inconnus. La dernière ligne couvre seulement trois jours et 152 billets. Elle est donc moins fiable.
Les semaines donnent une vue peu détaillée. Le graphique montre le taux de signalement des 7 derniers jours, recalculé chaque jour à partir du 7 juillet :
- Taux de signalement, 7 derniers jours
- Capacité de 20 %
Description textuelle du graphique
Une ligne pour chaque jour du 7 au 31 juillet : la proportion des billets des 7 derniers jours signalés par le modèle. Du 7 au 14 juillet, elle se situe entre 15,7 % et 18,9 %, sous la ligne pointillée de capacité à 20 %. Le 15 juillet, elle atteint 21,0 %. Elle monte à 26,2 % le 21 juillet, puis reste entre 20,4 % et 22,4 % jusqu’au 30 juillet. Le 31 juillet, elle est de 18,6 %.
Qu’est-ce qui a changé? Priya examine les sous-groupes, comme dans L’analyse par sous-groupes :
- Les 91 billets
socialont un taux de signalement de 23,1 %. Les autres billets à partir du 15 juillet ont un taux de 21,8 %. Le nouveau canal n’est donc pas la seule cause. - La moyenne de
prior_tickets_90dpasse de 5,3 à 3,0 pendant le mois. En mai, elle était de 9,00. Mei vérifie l’exportation. Elle découvre que l’exportation de juillet compte seulement les billets du client avant le 1 juillet. Un billet du 25 juillet ne compte pas les billets du même client au début de juillet. Le calcul de cette variable diffère donc de celui de l’entraînement, et sa valeur diminue chaque jour.
Pourquoi une valeur plus faible de prior_tickets_90d augmente-t-elle les scores? Dans Expliquer les prédictions, vous avez vu son coefficient : −0,29. Moins de billets antérieurs signifie donc un score plus élevé. Priya peut le comprendre en une ligne, car le modèle est une régression logistique. C’est une des raisons présentées dans Quand un modèle simple est meilleur : un modèle simple est plus facile à déboguer quand ses entrées changent.
Voilà l’intérêt des signaux : ils montrent qu’un changement a eu lieu, et où chercher. Pour trouver la cause, il faut une personne qui connaît les données.
Quand les étiquettes arrivent
Section intitulée « Quand les étiquettes arrivent »La cible escalated_72h d’un billet de juillet est connue 72 heures après la création du billet. En production, il est normal que les étiquettes arrivent après les prédictions. Mei les envoie dans july_labels.csv. Priya peut maintenant calculer les nombres les plus importants pour la décision de Grace. Rappel : la précision est la proportion des billets signalés qui ont été transférés à l’équipe expérimentée. Le rappel est la proportion des transferts qui ont été signalés.
import pandas as pd
from ticket_model import read
july = read("data/july_tickets.csv").merge(pd.read_csv("predictions.csv"), on="ticket_id")july = july.merge(pd.read_csv("data/july_labels.csv"), on="ticket_id")day = july["created_at"].dt.dayfor name, part in (("1-14 July", day < 15), ("15-31 July", day >= 15), ("All July", day > 0)): d = july[part] caught = (d["flag"] & d["escalated_72h"]).sum() print(f"{name}: {len(d)} tickets, escalated {d['escalated_72h'].mean():.3f}, mean score {d['score'].mean():.3f}," f" precision {caught / d['flag'].sum():.3f}, recall {caught / d['escalated_72h'].sum():.3f}")1-14 July: 625 tickets, escalated 0.149, mean score 0.112, precision 0.366, recall 0.44115-31 July: 775 tickets, escalated 0.133, mean score 0.135, precision 0.312, recall 0.515All July: 1400 tickets, escalated 0.140, mean score 0.125, precision 0.333, recall 0.480Pour le mois : 14,0 % de billets transférés, une précision de 0,333 et un rappel de 0,480. Le modèle détecte encore plus de 40 % des transferts. Mais depuis le 15 juillet, il signale plus de billets alors que moins de billets sont transférés. La précision tombe à 0,312 : l’équipe expérimentée examine plus de billets qui n’en avaient pas besoin. Le score moyen (0,125) reste sous le taux réel (0,140), comme l’indique la fiche.
Mais pourquoi ne pas attendre les étiquettes et surveiller seulement la précision et le rappel? Omar pose cette bonne question : ce sont les nombres qui comptent. La réponse est le délai. Les étiquettes du 31 juillet arrivent le 3 août. Les signaux sans étiquettes ont changé le 15 juillet. Ce sont les voyants orange qui s’allument avant qu’on connaisse les dommages.
Un déclencheur de réentraînement écrit
Section intitulée « Un déclencheur de réentraînement écrit »Le réentraînement consiste à entraîner le modèle de nouveau, avec des données plus récentes, pour créer un nouvel artefact. Quand Priya devrait-elle le faire? « Quand le modèle devient moins bon » est trop vague. Personne ne sait qui décide, quand, ni ce que « moins bon » signifie. Un déclencheur de réentraînement est une règle écrite à l’avance qui lance une révision. C’est comme le carnet d’entretien d’une voiture : un nombre de kilomètres, ou un voyant qui reste allumé. Priya l’écrit dans une nouvelle section de la fiche du modèle :
## Monitoring and retraining
Every working day, compute over the last 7 days: the flag rate, the mean score, the share ofunknown categories, and the mean of each number feature. When the labels arrive (72 hourslater), compute precision and recall for each month.
Start a retraining review when one of these happens:
1. The flag rate is above 20% on 5 working days in a row.2. Unknown categories are more than 2% of the tickets of the last 7 days.3. Recall is below 0.40 on a month with labels.
The review is done by Priya and Grace within 5 working days. Until it ends, the senior teamreviews flagged tickets in score order, highest first, up to its capacity.Chaque condition vient d’un élément que vous connaissez déjà. La condition 1 correspond à la capacité de Grace. Elle précise « 5 jours de suite » pour qu’une seule journée chargée ne déclenche pas de révision. La condition 2 fixe une petite proportion, car le modèle n’a rien appris sur une nouvelle catégorie. La condition 3 reprend l’objectif d’amélioration de Définir une référence de base : détecter au moins 40 % des transferts.
Mais pourquoi ne pas réentraîner automatiquement chaque mois? Tomás aime l’idée : aucune réunion, toujours des données récentes. Le déclencheur lance une révision, pas un réentraînement automatique, pour une raison : une personne vérifie d’abord la cause. Si la cause est une exportation défectueuse, réentraîner sur des données erronées enseigne l’erreur au modèle. Le cas de juillet le montre : un réentraînement automatique apprendrait la mauvaise valeur de prior_tickets_90d.
En juillet :
| Condition | Juillet | Déclenchée? |
|---|---|---|
| 1. Taux de signalement supérieur à 20 % pendant 5 jours ouvrables de suite | Supérieur à 20 % les 15, 16, 17, 20 et 21 juillet | Oui, le 21 juillet |
| 2. Catégories inconnues supérieures à 2 % des 7 derniers jours | 7 billets social sur 319 le 15 juillet (2,2 %) |
Oui, le 15 juillet |
| 3. Rappel inférieur à 0,40 sur un mois étiqueté | Rappel de 0,480 | Non |
La révision trouve deux causes : le nouveau canal et l’erreur d’exportation dans prior_tickets_90d. Grace et Priya décident :
- Mei corrige l’exportation de
prior_tickets_90d. Elle ajoute une vérification qui compare la moyenne de chaque variable numérique à celle de mai. - Priya réentraîne en incluant mai et juin. Les nouvelles données d’entraînement se terminent le 31 mai. Le modèle apprend donc à partir de billets créés après le changement de politique. Juin sert à choisir le seuil. Juillet, avec ses étiquettes, devient les nouvelles données de test, utilisées une seule fois.
socialreste une catégorie inconnue jusqu’à ce qu’il y ait assez de billetssocialétiquetés pour apprendre. L’avertissement continue, et la fiche du modèle le précise.- Priya met à jour
MODEL_CARD.mdavec les nouveaux nombres, le nouveau SHA-256 de l’artefact et la nouvelle date de révision.
La deuxième étape aide aussi la calibration : le nouveau modèle apprend à partir de billets au taux de transfert plus élevé après le 1 mai.
Exécuter ces vérifications selon un horaire, stocker les prédictions, envoyer des alertes et revenir à un ancien artefact relève du déploiement. Déployer des modèles d’IA pour de vrais utilisateurs l’enseigne. Ce cours s’arrête au plan et aux scripts.
Exercice guidé : vérifications et signaux
Section intitulée « Exercice guidé : vérifications et signaux »- Dans
~/projects/ticket-model, activez.venv. - Modifiez
predict.pyaux quatre endroits indiqués dans Les vérifications des entrées dans predict.py. - Exécutez
python predict.py --check. Vous devriez voirCheck passed(vérification réussie). - Exécutez
python predict.py data/july_tickets.csv predictions.csv. Vous devriez voir l’avertissement concernant 91 lignessocial, puis282 flagged (20.1%)(282 billets signalés, soit 20,1 %). - Créez une copie défectueuse de l’entrée. Ouvrez
data/july_tickets.csvdans un éditeur de texte et enregistrez-le sousbroken.csv. Dansbroken.csv, renommez la colonnepriorityenprio. - Exécutez
python predict.py broken.csv out.csv. Vous devriez voirError: missing columns: priority(erreur : colonne manquante : priority), puisStopped:(arrêté). - Enregistrez
signals.pyet exécutez-le. Comparez votre tableau à la sortie ci-dessus. - Enregistrez
labels.pyet exécutez-le. Vous devriez voirprecision 0.333, recall 0.480(précision de 0,333, rappel de 0,480) pour tout juillet. - Ajoutez la section Surveillance et réentraînement à votre
MODEL_CARD.md.
Si une étape échoue
- L’étape 3 ou 4 affiche
NameError: name 'RANGES' is not defined(le nom RANGES n’est pas défini) : ajoutezRANGESsousMODEL = Path("model"). - L’étape 6 affiche une trace d’erreur
KeyError(erreur de clé) au lieu deError:(erreur) :main()n’appelle pascheck_input()avantpipeline.predict_proba. Comparezmain()au code ci-dessus. - L’étape 7 affiche
FileNotFoundError: predictions.csv(fichier predictions.csv introuvable) : exécutez d’abord l’étape 4.
À votre tour : la révision du 18 juillet
Section intitulée « À votre tour : la révision du 18 juillet »Changez une condition : nous sommes maintenant le 18 juillet, pas à la fin du mois. Les étiquettes du 1 au 14 juillet sont arrivées, 72 heures après le dernier de ces billets. Utilisez les sorties ci-dessus pour répondre :
- Lesquelles des trois conditions du déclencheur se sont déclenchées au 18 juillet?
- Quel est le rappel du 1 au 14 juillet? La condition 3 se déclenche-t-elle pour ces deux semaines?
- Priya et Grace devraient-elles lancer la révision le 18 juillet?
Afficher la réponse
- La condition 2 s’est déclenchée le 15 juillet : 7 billets sur 319 des 7 derniers jours (2,2 %) venaient de
social. La condition 1 ne s’est pas déclenchée. Au 17 juillet, dernier jour ouvrable avant le 18 juillet, le taux sur 7 jours dépassait 20 % depuis 3 jours ouvrables consécutifs. Les 15, 16 et 17 juillet, il était de 21,0 %, 21,5 % et 22,8 %. Il en faut 5. (Le 18 juillet, le taux est de 24,3 %, mais c’est un samedi.) - Le rappel est de 0,441 du 1 au 14 juillet, au-dessus de 0,40. La condition 3 ne se déclenche pas. Elle est définie sur un mois étiqueté. Deux semaines avant le changement nous renseignent peu sur la période qui le suit.
- Oui. Une seule condition suffit à lancer une révision. La révision du 18 juillet permet déjà de demander à Mei ce qu’elle sait du nouveau canal. Elle n’attend pas que le taux de signalement dépasse la limite pendant 5 jours.
Erreur courante
Valeurs des commandes en cents, sans vérification de plage
Symptôme : en août, le collègue de Mei modifie l’exportation. order_value est maintenant en cents. La première version de predict.py traite le fichier de juillet sous cette forme sans erreur ni avertissement. Elle signale 83,6 % des billets, et le score moyen est de 0,799. L’équipe expérimentée reçoit plus de quatre fois sa capacité.
Cause : le modèle a appris qu’une grosse commande signifie plus de risque. Une commande de 54,38 $ vaut maintenant 5438, et chaque commande semble énorme. La colonne contient encore des nombres, donc une vérification des types seulement réussit. Dans le cours sur SQL, vous avez rencontré le même problème avec l’ancien formulaire Web, qui envoyait des cents : Traiter les valeurs désordonnées.
Correction : une plage pour chaque nombre, comme dans RANGES. La version finale de predict.py arrête l’exécution :
Warning: channel: 91 rows have a category the model never saw (social)Error: order_value: 879 values outside 0 to 5000Stopped: the input does not match the model's feature schema. Nothing was scored.En cents, 879 des 1 400 valeurs de commandes dépassent 5 000 : toutes les commandes de plus de 50 $. Les autres commandes valent 50 $ ou moins, ou leur valeur manque. Elles restent donc dans la plage. Voilà pourquoi une vérification de plage détecte une erreur d’unité sur la plupart des lignes, pas sur toutes.
Conséquence pour le travail en IA : un modèle calcule un score pour tout ce qu’il reçoit. Il ne dit jamais « cette entrée semble étrange ». Le schéma et les plages sont votre seule protection contre un changement silencieux en amont. Gardez-les dans le programme de calcul des scores, à côté du modèle, pas dans un document.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Pourquoi predict.py s'arrête-t-il pour une colonne manquante, mais avertit-il seulement pour une catégorie inconnue?
Afficher la réponse
Sans la colonne, le pipeline ne peut pas calculer les variables, ou il échoue avec une erreur qui n’explique pas le problème. Aucun score ne peut être calculé correctement. L’exécution s’arrête donc avec un message clair et le code de sortie 1. Une catégorie inconnue est différente. Le pipeline peut calculer son score, car OneHotEncoder(handle_unknown="ignore") lui donne 0 dans chaque colonne de canal. Arrêter bloquerait l’acheminement de tous les billets à cause de quelques-uns. Mais beaucoup de valeurs inconnues signifient que l’entrée a changé. Le programme les compte donc et les signale, et la surveillance prévoit un déclencheur pour leur proportion.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
En juillet, le taux de signalement mensuel est de 20,1 %, presque exactement la capacité. Tomás dit que la surveillance n'a rien trouvé. Utilisez les signaux sur 7 jours pour expliquer pourquoi il se trompe.
Afficher la réponse
Le mois cache le changement. Du 1 au 14 juillet, le taux de signalement est de 17,4 % et 18,5 % par semaine. À partir du 15 juillet, il est de 26,2 %, puis de 20,9 % : au-dessus de la capacité de 20 %. Le taux sur 7 jours reste supérieur à 20 % du 15 au 30 juillet. Le score moyen passe d’environ 0,112 à 0,138. Les canaux inconnus passent de 0 à 12,6 %, et la moyenne de prior_tickets_90d baisse. Deux déclencheurs s’activent : les catégories inconnues le 15 juillet, et le taux de signalement le 21 juillet. Quand les étiquettes arrivent, la précision à partir du 15 juillet est de 0,312, contre 0,366 les deux premières semaines.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Exercice du module
Section intitulée « Exercice du module »Ce module a transformé un modèle de carnet (notebook) Jupyter en livrable : fichiers, commandes et documents qu’une autre personne peut exécuter et vérifier. L’exercice du module vous demande de livrer le classificateur et un rapport d’évaluation. Un collègue doit pouvoir reproduire chaque nombre avec les commandes seulement.
Votre tâche : livrer le classificateur
Section intitulée « Votre tâche : livrer le classificateur »Livrez le dossier du projet avec :
- Le code :
ticket_model.py,train.py,evaluate.py,predict.py,requirements.txt,get_data.py. MODEL_CARD.md, avec la section Surveillance et réentraînement.- Un court rapport d’évaluation,
reports/evaluation.md: le moment de prédiction et la cible, la comparaison à la référence de base, le seuil et sa méthode de sélection, les résultats du test (une seule fois), les principales limites, et les commandes exactes qui reproduisent chaque nombre du rapport, avec leurs sorties attendues. - La preuve que tout fonctionne à partir de zéro : exécutez les commandes dans une nouvelle copie du projet, dans un nouveau terminal, avec un nouvel environnement
.venv. L’artefact doit y donner des scores identiques.
N’envoyez pas .venv, data/ ni model/ : les commandes les recréent. C’est le but du test.
Afficher la réponse modèle
reports/evaluation.md :
# Evaluation report: ticket escalation model
Prediction time: when a ticket is created. Target: escalated_72h (escalated to a senioragent within 72 hours). Decision: a flagged ticket goes straight to the senior team, whichcan review at most 20% of new tickets.
Data: train 16,939 tickets (Jul 2024 to Apr 2026), validation 1,179 (May 2026),test 1,002 (1 to 27 June 2026). Synthetic data, no real people.
Model: logistic regression in one pipeline (one-hot categories, median imputation withindicators, scaling), 11 features, fitted on train only. Chosen over gradient boosting bythe one-standard-error rule (CV average precision 0.3393 against 0.3424, standard error0.008). Threshold 0.19: the lowest that flags at most 20% of the validation set.
| At threshold 0.19 | Validation | Test (once) | Rule "priority 1" (test) ||---|---|---|---|| Flagged | 227 (19.3%) | 196 (19.6%) | 150 (15.0%) || Precision | 0.379 | 0.357 | 0.367 || Recall | 0.470 | 0.446 | 0.350 || Average precision | 0.391 | 0.345 | |
The model meets the target (at most 20% flagged, at least 40% caught) on validation andtest. The rule catches 35%. Nothing was changed after the test.
Limits: scores are too low since the policy change of 2026-05-01 (do not show them as achance); weak on account and priority 3 tickets; new channels are scored with lessinformation. See MODEL_CARD.md.
## Reproduce
python -m pip install -r requirements.txt python get_data.py -> Ready: train 16939 rows (1983 escalated), valid 1179 (183), test 1002 (157). python train.py -> Threshold 0.19 ... flagged 227 of 1179 (19.3%), precision 0.379, recall 0.47. python predict.py --check -> Check passed: 20 scores are identical after reloading python evaluate.py -> reports/test_report.json: precision 0.357, recall 0.446
Artifact SHA-256 (macOS, the versions in requirements.txt):a4acd259a398e7c84517b98bf50d811197f90b0f2f1ea15f1f9e168ba3aff736L’exécution dans une nouvelle copie. Créez un nouveau dossier, copiez-y seulement les fichiers livrés et exécutez les commandes :
mkdir -p ~/projects/ticket-model-fresh/reportscd ~/projects/ticket-modelcp ticket_model.py train.py evaluate.py predict.py requirements.txt get_data.py MODEL_CARD.md ~/projects/ticket-model-fresh/cp reports/evaluation.md ~/projects/ticket-model-fresh/reports/cd ~/projects/ticket-model-freshpython3 -m venv .venvsource .venv/bin/activatepython -m pip install -r requirements.txtpython get_data.pypython train.pypython predict.py --checkpython evaluate.pypython predict.py data/july_tickets.csv predictions.csvshasum -a 256 model/escalation_model.joblib predictions.csv ../ticket-model/predictions.csvNew-Item -ItemType Directory -Force ~\projects\ticket-model-fresh\reportscd ~\projects\ticket-modelCopy-Item ticket_model.py, train.py, evaluate.py, predict.py, requirements.txt, get_data.py, MODEL_CARD.md ~\projects\ticket-model-fresh\Copy-Item reports\evaluation.md ~\projects\ticket-model-fresh\reports\cd ~\projects\ticket-model-freshpython -m venv .venv.venv\Scripts\Activate.ps1python -m pip install -r requirements.txtpython get_data.pypython train.pypython predict.py --checkpython evaluate.pypython predict.py data/july_tickets.csv predictions.csvGet-FileHash model\escalation_model.joblib, predictions.csv, ..\ticket-model\predictions.csvLa sortie des cinq dernières commandes sur macOS (le JSON de evaluate.py est abrégé) :
Trained on 16939 tickets. Threshold 0.19 for a review capacity of 20%.Valid: ROC AUC 0.752, average precision 0.391, flagged 227 of 1179 (19.3%), precision 0.379, recall 0.47.Saved model/escalation_model.joblib, model_info.json and check_sample.csv.Check passed: 20 scores are identical after reloading (largest difference 1.1e-16).{ "rows": 1002, "escalated": 157, "threshold": 0.19, "roc_auc": 0.749, "average_precision": 0.345, ... "precision": 0.357, "recall": 0.446, ...}Warning: channel: 91 rows have a category the model never saw (social)Scored 1400 tickets with threshold 0.19: 282 flagged (20.1%). Wrote predictions.csv.a4acd259a398e7c84517b98bf50d811197f90b0f2f1ea15f1f9e168ba3aff736 model/escalation_model.joblib91936b4eff2c28a1b0c3382b8b88a05f2f7e677fc8395b2ab4486830a3dd9ff1 predictions.csv91936b4eff2c28a1b0c3382b8b88a05f2f7e677fc8395b2ab4486830a3dd9ff1 ../ticket-model/predictions.csvLes deux fichiers predictions.csv ont le même SHA-256. La nouvelle copie donne exactement le même score et le même signalement pour les 1 400 billets de juillet. evaluate.py s’exécute dans la nouvelle copie, car elle n’a pas encore de rapport. Cette exécution reproduit le même modèle; elle ne choisit rien. Ses nombres doivent être identiques, et ils le sont.
Sur Windows, pandas écrit les fichiers CSV avec d’autres fins de ligne. Les sommes de contrôle diffèrent donc de celles-ci. Comparez les deux fichiers du même ordinateur : ils doivent être identiques.
Un livrable complet comprend : chaque nombre du rapport reproduit par une commande indiquée; predict.py --check qui réussit dans la nouvelle copie; des prédictions identiques dans les deux copies; et une fiche et un rapport qui indiquent le même seuil et le même artefact.
Vérification du module
Section intitulée « Vérification du module »Vérifiez votre compréhension
Rappel : énumérez le contenu d'une passation du modèle d'escalade et l'utilité de chaque élément.
Afficher la réponse
model/escalation_model.joblib: le pipeline entraîné complet (prétraitement et modèle), enregistré avecjoblib.dump.model/model_info.json: le schéma des variables, le seuil de 0,19, les versions des paquets, les sommes de contrôle des données et du fichier du modèle, et les résultats de validation.model/check_sample.csv: 20 billets de soutien avec leurs scores enregistrés, pourpredict.py --check.requirements.txt: les versions exactes de pandas, scikit-learn, numpy, joblib et matplotlib.train.py,predict.py(avec vérifications des entrées),evaluate.pyetticket_model.py: les commandes qui créent, vérifient et utilisent l’artefact.MODEL_CARD.md: l’utilisation prévue, les utilisations exclues, les données, le choix du modèle, l’évaluation, les sous-groupes, les limites, les responsables, la date de révision, la surveillance et le déclencheur de réentraînement.- Un rapport d’évaluation avec les commandes qui reproduisent ses résultats.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Prédiction : un nouvel export contient le canal whatsapp dans 40 billets sur 1 000 et les priorités P1, P2 et P3. Qu'affiche predict.py, et calcule-t-il les scores du lot?
Afficher la réponse
Il affiche un avertissement pour le canal : channel: 40 rows have a category the model never saw (whatsapp) (40 lignes contiennent une catégorie inconnue du modèle). Ensuite, il affiche une erreur pour la priorité : priority: 1000 values are not numbers, for example 'P1' (1 000 valeurs ne sont pas des nombres, par exemple « P1 », ou la première valeur du fichier). Puis il affiche Stopped: the input does not match the model's feature schema. Nothing was scored. (arrêt : les entrées ne respectent pas le schéma des variables du modèle; aucun score n’a été calculé), avec le code de sortie 1. Les scores du lot ne sont pas calculés, à cause de la priorité. Avec seulement le problème de canal, les scores seraient calculés, avec un avertissement. 40 sur 1 000 représente 4 %, au-dessus du seuil de déclenchement de 2 %. Cela lancerait donc aussi une révision du besoin de réentraînement.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Vérification finale : chargez l'artefact dans un nouveau processus et vérifiez que les prédictions sont identiques sur un échantillon fixe. Donnez les commandes, la sortie qui le prouve et la raison d'utiliser un nouveau processus.
Afficher la réponse
Dans un nouveau terminal (ou une nouvelle copie du projet avec un nouvel environnement .venv), dans le dossier du projet :
python predict.py --checkpython predict.py data/july_tickets.csv predictions.csv--check charge model/escalation_model.joblib et calcule les scores des 20 billets de model/check_sample.csv. Il les compare aux scores enregistrés pendant l’entraînement : Check passed: 20 scores are identical after reloading (largest difference 1.1e-16). (vérification réussie : les 20 scores sont identiques après rechargement; la plus grande différence est de 1,1 × 10⁻¹⁶). La différence est bien inférieure à la limite de 10⁻⁹; elle vient seulement de l’enregistrement des scores en texte. Pour un échantillon fixe plus grand, la somme SHA-256 de predictions.csv pour les 1 400 billets de juillet reste identique. C’est le cas après deux exécutions et dans une nouvelle copie (91936b4e… sur macOS).
Un nouveau processus est important parce qu’il démarre sans rien en mémoire : aucun pipeline entraîné, aucune variable, aucune importation du carnet. Si les scores y sont identiques, les fichiers seuls reproduisent le modèle. Une réponse complète montre la commande et la vérification réussie avec sa plus grande différence. Elle explique aussi pourquoi la vérification ne se fait pas dans le carnet d’entraînement.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Revenir à un état connu
Section intitulée « Revenir à un état connu »Si votre projet ne fonctionne plus ou si vous voulez recommencer le module 8 :
- Supprimez les sorties : les dossiers
model/etreports/, et les fichierspredictions.csv,out.csvetbroken.csv. - Exécutez
python get_data.py --reset. Vous devriez voir la ligneReady:(prêt) avec 16 939, 1 983, 1 179, 183, 1 002 et 157. - Si un fichier de code ne fonctionne plus, copiez la version du cours depuis C05/project :
curl -fsSL -O https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C05/project/<file>(curl.exesur Windows). - Exécutez
python train.py, puispython predict.py --check. Vous devriez voirThreshold 0.19(seuil de 0,19) etCheck passed(vérification réussie). - Exécutez
python evaluate.pyseulement si vous avez de nouveau besoin dereports/test_report.json. Il donne les mêmes résultats, parce que le modèle est le même.
Récapitulatif
Dans ce module, vous avez préparé la passation du modèle :
- Empaqueter (Empaqueter les artefacts) :
train.pyenregistre le pipeline complet avec son schéma, son seuil de 0,19, ses versions, ses sommes de contrôle et un échantillon de vérification.requirements.txtfixe chaque version.predict.py --checkprouve que les scores sont identiques dans un nouveau processus. - Documenter (Documenter les limites d’utilisation) : la fiche du modèle précise ses utilisations prévues et exclues, avec les preuves. Elle présente le rappel de test de 0,446 avec 19,6 % de billets signalés, contre 0,350 pour la règle. Elle décrit les sous-groupes faibles avec leurs effectifs, l’écart de calibration et les raisons du choix d’un modèle simple.
- Vérifier (cette leçon) :
predict.pys’arrête si une colonne manque, si un type est incorrect ou si une valeur dépasse les limites. Il compte aussi les catégories inconnues. La surveillance compare le taux de signalement, le score moyen, les catégories inconnues et les entrées aux valeurs attendues. En juillet, deux des trois conditions de déclenchement ont été remplies : le nouveau canalsocial(réseaux sociaux) et un taux de signalement sur 7 jours atteignant 26,2 %. Avec les étiquettes, le rappel de juillet est de 0,480 et la précision de 0,333. - Réentraîner selon une règle écrite et une révision, pas selon une impression. Les mécanismes de déploiement seront abordés dans le cours sur le déploiement.
Dans ce cours, vous avez appris ce qu’est l’apprentissage automatique et comment juger un modèle avant d’en créer un. Vous avez cadré un problème de prédiction et dépassé des références de base simples. Vous avez créé un pipeline, essayé des familles de modèles et consigné chaque expérience. Vous avez réglé le modèle sans toucher aux données de test et choisi un seuil adapté à la capacité. Vous avez expliqué les erreurs et choisi un modèle simple pour de bonnes raisons. Vous l’avez maintenant livré avec ses limites. Viennent ensuite les études de cas : des projets réels sur des données publiques, de la question à la recommandation. Réalisez-en au moins une pour obtenir le certificat. Le travail final vous demande ensuite de tout faire vous-même, de la référence de base au rapport d’évaluation.
Leçon suivante : Prévoir la demande de vélopartage