Empaqueter les artefacts
- Leçon 25
- 70 min
- Module 8 : Préparer la passation du modèle
Leçon 25 sur 27 · Apprentissage automatique : du problème à un modèle fiableEmpaqueter les artefacts
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
Transformez le modèle de votre carnet en fichiers qu’une autre personne peut charger et exécuter. Puis prouvez que le modèle chargé donne exactement les mêmes scores.
Carnet JupyterFaites cette leçon dans un carnet
Le carnet (notebook) Jupyter écrit train.py et predict.py avec %%writefile, les exécute comme commandes et vérifie le résultat dans un nouveau processus. Il montre aussi chaque échec présenté sur cette page. Aucune configuration locale n’est nécessaire. Dans ce module, le projet local est le parcours principal, car une passation consiste en fichiers et en commandes. Utilisez le carnet si vous n’avez pas le projet, ou pour vérifier vos résultats.
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
- Ce qu’est un pipeline et ce que
fit(ajuster) apprend des données d’entraînement, dans Prétraiter avec un pipeline. - Le modèle final : régression logistique sur 11 variables, précision moyenne de validation de 0,391, dans Sélectionner les caractéristiques utiles : modifier ticket_model.py.
- La règle du seuil (le seuil le plus bas qui signale au plus 20 % des données de validation : 0,19), dans Seuils et calibration.
- Les versions exactes fixées et un nouvel environnement, dans Isoler les dépendances : noter les versions du cours Python pour l’ingénierie de l’IA en pratique. Les codes de sortie et
argparse, dans L’interface en ligne de commande du cours de Python. Les sommes de contrôle (SHA-256), 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 le parcours local; le carnet n’en a pas besoin)
- Le dossier de projet
~/projects/ticket-modelavec son environnement.venv, activé dans un terminal. Vous avez créé les deux dans Définir une référence de base : préparer votre projet. Sipython -c "import sklearn"échoue, activez de nouveau.venv(source .venv/bin/activatesur macOS et Linux,.venv\Scripts\Activate.ps1sur Windows). - Les données dans
data/, obtenues avecpython get_data.pydans la même leçon. La dernière ligne doit êtreReady: train 16939 rows (1983 escalated), valid 1179 (183), test 1002 (157).(prêt : entraînement, 16 939 lignes dont 1 983 transmises au niveau supérieur; validation, 1 179 dont 183; test, 1 002 dont 157). Sinon, exécutezpython get_data.py --reset. ticket_model.pyavec 7 entrées dansNUMBERS. Vous l’avez écrit dans Prétraiter avec un pipeline : écrire ticket_model.py. Vous avez ajoutéprior_escalations_90ddans Sélectionner les caractéristiques utiles : modifier ticket_model.py. Si votre fichier est différent ou ne fonctionne pas, téléchargez celui du cours :curl -fsSL -O https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C05/project/ticket_model.py(curl.exesur Windows).requirements.txt, créé dans Définir une référence de base : installer les paquets. Vous le modifiez dans Fixer chaque version.
Testé sur macOS avec Python 3.14.6, pandas 3.0.6, scikit-learn 1.9.1, numpy 2.5.3 et joblib 1.6.0. Les commandes Windows et Linux n’ont pas été testées. Le carnet a été testé dans Jupyter.
Une recette sans la température du four
Section intitulée « Une recette sans la température du four »Vous faites un gâteau, et vos amis l’aiment. Une amie vous demande la recette. Vous la notez : la farine, le sucre, les œufs, le beurre. Votre amie suit la note à la lettre, mais le gâteau est plat et sec. Qu’est-ce qui a mal tourné?
La note ne contenait rien de faux, mais elle était incomplète. Vous avez oublié la température du four, car votre four est toujours au bon réglage. « Une tasse de sucre » désignait votre tasse, plus grande que la plupart des autres. Ces renseignements étaient dans votre cuisine et dans votre tête. Ils n’ont donc pas accompagné la note. Votre amie ne pouvait pas les deviner.
Un modèle a le même problème. Dans ce module, le modèle de transmission au niveau supérieur de Larkfield quitte le carnet de Priya pour l’équipe de Grace. Cette équipe l’utilisera chaque jour, sur un autre ordinateur, longtemps après l’entraînement. Tout ce dont le modèle a besoin doit l’accompagner. La personne qui le reçoit doit pouvoir vérifier que le résultat correspond à celui de Priya.
Le problème : le modèle vit dans un carnet
Section intitulée « Le problème : le modèle vit dans un carnet »Jusqu’ici, le modèle vivait dans la mémoire d’un carnet. Quand le carnet se ferme, le pipeline ajusté disparaît. Pour le réutiliser, vous devez exécuter toutes les cellules dans le bon ordre, avec les mêmes fichiers sur le même ordinateur.
Omar pose une bonne question : « Pourquoi ne pas envoyer le carnet à l’équipe de Grace? » Il y a trois raisons. D’abord, la personne qui le reçoit doit entraîner le modèle de nouveau. Une petite différence dans les données ou les paquets donne un modèle différent. Ensuite, un carnet n’est pas une commande : un programme exécuté chaque matin ne peut pas cliquer sur les cellules. Enfin, personne ne peut vérifier que le modèle reçu est identique à celui de Priya. Rien ne consigne les résultats du modèle de Priya.
Priya enregistre donc le modèle dans des fichiers. Un fichier produit par l’entraînement, puis utilisé par un autre programme, est un artefact. (Dans le langage courant, un artefact est un objet fabriqué par des personnes. Ici, le processus d’entraînement l’a fabriqué.) Le reste de cette leçon répond à deux questions : que doit contenir l’artefact, et comment prouver qu’il fonctionne après réception?
Ce qui doit accompagner le modèle
Section intitulée « Ce qui doit accompagner le modèle »Commencez par le plus petit artefact possible. Dans Ajuster les transformations correctement du cours de SQL, un outil de standardisation a appris la moyenne et l’écart-type de cinq valeurs de commande : 20, 40, 50, 60 et 100. Il a ensuite transformé la valeur 400 en (400 − 54) ÷ 26,53 = 13,04. Enregistrez cet outil ajusté, rechargez-le et utilisez-le :
import joblibimport pandas as pdfrom sklearn.preprocessing import StandardScaler
train = pd.DataFrame({"order_value": [20.0, 40.0, 50.0, 60.0, 100.0]})scaler = StandardScaler().fit(train)joblib.dump(scaler, "tiny_scaler.joblib")
again = joblib.load("tiny_scaler.joblib")print("mean and SD:", again.mean_, again.scale_.round(2))print("400 ->", again.transform(pd.DataFrame({"order_value": [400.0]})).round(2).ravel())La sortie :
mean and SD: [54.] [26.53]400 -> [13.04]joblib.dump écrit l’objet ajusté dans un fichier. joblib.load le relit, avec tous les nombres qu’il a appris. L’outil de standardisation chargé donne 13,04, comme avant. joblib est un petit paquet que scikit-learn utilise pour enregistrer et charger ses objets.
Tomás a une idée pour économiser de l’espace : « Le modèle, ce sont les coefficients. Enregistrons seulement le modèle. On préparera les données de nouveau quand on calculera les scores. » C’est la température du four oubliée. Supposons que le programme de calcul des scores crée un nouvel outil de standardisation pour chaque lot de nouveaux billets de soutien. Le lot d’aujourd’hui contient deux commandes : 400 et 380.
batch = pd.DataFrame({"order_value": [400.0, 380.0]})rebuilt = StandardScaler().fit(batch)print("mean and SD:", rebuilt.mean_, rebuilt.scale_)print("400 ->", rebuilt.transform(batch.head(1)).round(2).ravel())mean and SD: [390.] [10.]400 -> [1.]Vérifiez à la main. La moyenne de 400 et 380 est 390. Les deux valeurs sont à 10 de la moyenne, donc l’écart-type est 10. Ensuite, (400 − 390) ÷ 10 = 1,0. La même commande donne 13,04 avec l’outil entraîné et 1,0 avec le nouvel outil. Le modèle a appris ce que signifie 13,04, pas ce que signifie 1,0. Aucune erreur ne s’affiche : le score est faux, sans avertissement.
La règle : enregistrez tout ce que fit a appris dans un seul objet. Dans ce cours, cet objet est le pipeline. Il contient les médianes, les moyennes, les écarts-types, la liste des catégories et les coefficients du modèle. Voilà pourquoi Prétraiter avec un pipeline regroupait toutes les étapes dans un seul objet : maintenant, c’est aussi un seul fichier.
Le contrat de passation
Section intitulée « Le contrat de passation »Le fichier du pipeline est nécessaire, mais il ne suffit pas. Revenons au gâteau. Votre amie a aussi besoin de la température du four, de la taille de la tasse et d’une photo du gâteau terminé. Elle pourra ainsi le comparer au sien. L’artefact de Priya est un dossier, model/, contenant trois fichiers qui remplissent ces trois rôles.
Le premier fichier, escalation_model.joblib, est le pipeline ajusté : le prétraitement et le modèle ensemble. Il permet de calculer les scores de nouveaux billets exactement comme pendant l’entraînement. C’est le mélange à gâteau qui contient déjà toutes les quantités apprises.
Le deuxième fichier, model_info.json, décrit le modèle en texte clair. Sa partie la plus importante est le schéma de caractéristiques. Un schéma décrit les données qu’un programme attend : les noms des colonnes, leurs types et les valeurs permises. Le fichier contient aussi le seuil de 0,19 et les versions des paquets. Il contient les sommes de contrôle des données et du fichier du modèle, ainsi que les résultats de validation. Ce sont la température du four et la taille de la tasse : les renseignements que personne ne peut deviner.
Le troisième fichier, check_sample.csv, contient 20 billets de validation et les scores que le modèle leur a donnés pendant l’entraînement. C’est la photo du gâteau. La personne qui le reçoit calcule les scores des mêmes 20 billets et les compare. Si les scores sont identiques, le modèle est arrivé complet.
| Fichier | Ce qu’il contient | Son utilité après réception |
|---|---|---|
escalation_model.joblib |
Le pipeline ajusté : prétraitement et modèle | Calculer les scores de nouveaux billets exactement comme pendant l’entraînement |
model_info.json |
Le schéma de caractéristiques, le seuil, les versions, les sommes de contrôle, les résultats de validation | Vérifier les nouvelles entrées, transformer les scores en signalements, savoir ce qui a produit le modèle |
check_sample.csv |
20 billets de validation avec leurs scores enregistrés | Prouver qu’un modèle chargé donne les mêmes scores |
Deux scripts utilisent ces fichiers. train.py crée les trois fichiers, une seule fois. predict.py les lit chaque fois qu’il calcule les scores d’un lot de nouveaux billets. Ensemble, les fichiers et les scripts forment un contrat entre l’entraînement et le calcul des scores. Chaque côté sait exactement ce que l’autre fournit.
Description textuelle du diagramme
python train.py ajuste le modèle sur les données d’entraînement et choisit le seuil sur les données de validation. Il écrit le dossier model/, l’artefact, avec trois fichiers : escalation_model.joblib (le pipeline ajusté), model_info.json (schéma de caractéristiques, seuil, versions, sommes de contrôle) et check_sample.csv (20 billets et leurs scores enregistrés). predict.py lit l’artefact et un fichier CSV de nouveaux billets. D’abord, les vérifications d’entrée comparent les billets au schéma. Une colonne manquante, un type incorrect ou une valeur hors limites arrête l’exécution avec le code de sortie 1. Aucun score n’est calculé. Une catégorie inconnue est permise, mais elle est comptée et signalée. Ensuite, le pipeline donne un score par billet. Un billet dont le score est d’au moins 0,19 est signalé. Le résultat est predictions.csv. Séparément, predict.py --check recharge l’artefact dans un nouveau processus et compare les 20 scores enregistrés.
Cette leçon crée train.py et la partie --check de predict.py. Les vérifications d’entrée viendront dans Planifier les vérifications en production, la dernière leçon de ce module.
Exemple détaillé : train.py
Section intitulée « Exemple détaillé : train.py »Priya écrit un script qui fait tout l’entraînement, des fichiers de données jusqu’à l’artefact. Ainsi, n’importe qui peut répéter l’entraînement avec une commande et obtenir les mêmes fichiers. Créez train.py dans votre dossier de projet, à côté de ticket_model.py. Le fichier comporte quatre parties; saisissez-les l’une après l’autre dans le même fichier.
La première partie contient les importations, la capacité de révision et deux petites fonctions. choose_threshold() applique la règle de Seuils et calibration : le seuil le plus bas qui respecte la capacité. sha256() calcule la somme de contrôle d’un fichier, comme dans le cours de SQL.
"""Train the escalation model, choose its threshold, and save the artifact.
Nextia Learning, C05, Module 8, lesson 1 (Package artifacts).
python train.py
Fits the pipeline on train only, chooses the threshold on valid for thereview capacity, and writes into model/:
escalation_model.joblib the fitted preprocessing-plus-model pipeline model_info.json feature schema, threshold, versions, checksums, valid results check_sample.csv 20 valid tickets and their scores, to check a reload
It never reads the test set: evaluate.py does that, once."""
import hashlibimport jsonimport platformfrom datetime import datetime, timezonefrom pathlib import Path
import joblibimport numpy as npimport pandas as pdimport sklearnfrom sklearn.metrics import average_precision_score, roc_auc_score
from ticket_model import CATEGORIES, DATA, FEATURES, NUMBERS, TARGET, build_pipeline, load
OUT = Path("model")REVIEW_CAPACITY = 0.20 # the senior team can review at most 1 in 5 new tickets
def choose_threshold(scores, capacity): """The lowest threshold (in steps of 0.01) that flags no more than `capacity` of the tickets. Lower thresholds flag more tickets and catch more escalations, so the lowest one that fits catches the most.""" for step in range(1, 100): threshold = step / 100 if (scores >= threshold).mean() <= capacity: return threshold return 1.0
def sha256(path): return hashlib.sha256(Path(path).read_bytes()).hexdigest()La deuxième partie ajuste le pipeline sur les données d’entraînement, puis calcule les scores de validation. Elle choisit le seuil et calcule les résultats de validation. Vous avez calculé chacun de ces nombres dans le module 6. La différence : un script les calcule maintenant, de la même façon chaque fois.
def main(): train, valid = load("train"), load("valid") pipeline = build_pipeline().fit(train[FEATURES], train[TARGET])
scores = pipeline.predict_proba(valid[FEATURES])[:, 1] threshold = choose_threshold(scores, REVIEW_CAPACITY) flagged = scores >= threshold caught = int((flagged & (valid[TARGET] == 1)).sum()) results = { "rows": len(valid), "escalated": int(valid[TARGET].sum()), "roc_auc": round(roc_auc_score(valid[TARGET], scores), 3), "average_precision": round(average_precision_score(valid[TARGET], scores), 3), "flagged": int(flagged.sum()), "flag_rate": round(flagged.mean(), 3), "precision": round(caught / flagged.sum(), 3), "recall": round(caught / valid[TARGET].sum(), 3), }La troisième partie enregistre l’artefact. joblib.dump enregistre le pipeline entier, pas seulement pipeline.named_steps["model"]. Ensuite, le script conserve les 20 premiers billets de validation, avec leurs colonnes et leurs scores, dans check_sample.csv.
OUT.mkdir(exist_ok=True) joblib.dump(pipeline, OUT / "escalation_model.joblib") sample = valid[["ticket_id", "created_at"] + FEATURES].head(20).copy() sample["score"] = scores[:20] sample.to_csv(OUT / "check_sample.csv", index=False)La quatrième partie écrit model_info.json et affiche un résumé. Le schéma prend les catégories de l’encodeur ajusté. Il énumère donc exactement les catégories que le modèle connaît. Une liste saisie à la main pourrait différer de ce que le modèle a appris.
categories = pipeline.named_steps["prepare"].named_transformers_["categories"].categories_ info = { "model": "Larkfield ticket escalation: logistic regression in a scikit-learn pipeline", "trained_at": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC"), "target": f"{TARGET}: 1 if the ticket is escalated within 72 hours of creation", "prediction_time": "when the ticket is created", "features": { "categories": {name: [str(v) for v in values] for name, values in zip(CATEGORIES, categories)}, "numbers": NUMBERS, }, "threshold": threshold, "review_capacity": REVIEW_CAPACITY, "valid": results, "train_rows": len(train), "data_sha256": {name: sha256(DATA / name) for name in ("train.csv", "valid.csv", "extra_features.csv")}, "artifact_sha256": sha256(OUT / "escalation_model.joblib"), "versions": {"python": platform.python_version(), "pandas": pd.__version__, "scikit-learn": sklearn.__version__, "numpy": np.__version__, "joblib": joblib.__version__}, } (OUT / "model_info.json").write_text(json.dumps(info, indent=2) + "\n", encoding="utf-8")
print(f"Trained on {len(train)} tickets. Threshold {threshold} for a review capacity of {REVIEW_CAPACITY:.0%}.") print("Valid: ROC AUC {roc_auc}, average precision {average_precision}, flagged {flagged} of {rows} " "({flag_rate:.1%}), precision {precision}, recall {recall}.".format(**results)) print(f"Saved {OUT / 'escalation_model.joblib'}, model_info.json and check_sample.csv.")
if __name__ == "__main__": main()La majeure partie du fichier sert à consigner les renseignements. Six lignes portent les idées de cette leçon. Le tableau les présente avec la raison de chacune :
| Lignes | Pourquoi |
|---|---|
fit(train[FEATURES], train[TARGET]) |
Le seul fit. Il reçoit uniquement les lignes d’entraînement. |
choose_threshold(scores, REVIEW_CAPACITY) |
Le seuil est choisi sur les données de validation et enregistré. Ainsi, predict.py n’utilise jamais la valeur par défaut de 0,5. |
joblib.dump(pipeline, ...) |
Le pipeline entier : prétraitement et modèle ensemble. |
"features" |
Le schéma. predict.py y compare les nouvelles entrées dans Planifier les vérifications en production. |
"data_sha256", "artifact_sha256" |
Les données exactes qui ont entraîné ce modèle, et le fichier exact de ce modèle. |
"versions" |
Les versions qui ont créé le fichier. Une autre version peut le lire différemment. |
Exécutez-le :
python train.pyLa sortie :
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.Ce sont les nombres du module 6 : aire sous la courbe ROC de 0,752, précision moyenne de 0,391, 227 billets signalés (19,3 %), précision de 0,379 et rappel de 0,470 (le script affiche 0.47). Le script prend environ une seconde.
Ouvrez model/model_info.json. La partie centrale est le schéma :
"features": { "categories": { "channel": ["chat", "email", "phone", "unknown", "web_form"], "team": ["account", "delivery", "payment", "returns", "warranty"], "segment": ["business", "guest", "home", "trade"], "region": ["east", "north", "south", "unknown", "west"] }, "numbers": ["priority", "order_value", "word_count", "customer_tenure_days", "prior_tickets_90d", "created_hour", "prior_escalations_90d"] }, "threshold": 0.19,(Le vrai fichier contient une valeur par ligne. Il est abrégé ici.) À la fin du fichier, vous voyez les sommes de contrôle et les versions :
"artifact_sha256": "a4acd259a398e7c84517b98bf50d811197f90b0f2f1ea15f1f9e168ba3aff736", "versions": { "python": "3.14.6", "pandas": "3.0.6", "scikit-learn": "1.9.1", "numpy": "2.5.3", "joblib": "1.6.0" }Votre heure trained_at (entraîné à) est différente. Sur macOS, avec les mêmes versions, artifact_sha256 était identique lors de deux exécutions : l’entraînement est déterministe ici. Les mêmes données et le même code donnent le même fichier. Sur un autre système, la somme de contrôle peut différer, même quand les scores sont identiques.
Fixer chaque version : requirements.txt
Section intitulée « Fixer chaque version : requirements.txt »Revenons à la recette : « une tasse » est clair seulement si tout le monde utilise la même tasse. Les paquets sont les tasses d’un modèle. L’artefact est une représentation d’objets Python, créée par une version de chaque paquet. Une autre version peut lire le même fichier différemment. La personne qui le reçoit doit donc installer les mêmes versions.
Dans Définir une référence de base, vous avez fixé les versions de pandas, scikit-learn et matplotlib. Une version fixée est une version exacte, écrite avec ==. L’artefact dépend aussi de deux paquets que scikit-learn a installés pour vous : numpy (les tableaux dans le pipeline) et joblib (le format de fichier). Vous ne les avez jamais installés par leur nom. Rien ne fixe donc encore leurs versions. Vérifiez leurs versions :
python -c "import numpy, joblib; print(numpy.__version__, joblib.__version__)"2.5.3 1.6.0Modifiez requirements.txt pour qu’il contienne ces cinq lignes, chacune avec une version exacte :
pandas==3.0.6scikit-learn==1.9.1numpy==2.5.3joblib==1.6.0matplotlib==3.11.2Supprimez la ligne ipykernel si votre fichier en contient une. Elle permet à votre éditeur d’exécuter des carnets, mais train.py et predict.py n’en ont pas besoin. Le paquet reste installé dans votre .venv. Dans un nouvel environnement, installez-le à la main si vous voulez y utiliser des carnets (python -m pip install ipykernel). Ce fichier est identique au fichier C05/project/requirements.txt du cours.
Un nouveau processus : predict.py –check
Section intitulée « Un nouveau processus : predict.py –check »Comment la personne qui reçoit les fichiers sait-elle qu’ils suffisent? Un test dans le carnet où vous avez entraîné le modèle prouve peu de choses. Ce processus garde le pipeline en mémoire, les importations effectuées et vos variables définies. C’est comme goûter le gâteau dans votre cuisine : bien sûr, tout fonctionne là. Le vrai test se passe dans la cuisine de votre ami, où rien de votre matériel n’est présent.
Un nouveau processus est une nouvelle exécution de Python qui démarre sans rien en mémoire : un nouveau terminal ou un nouvel environnement d’exécution Colab. Si le modèle chargé y donne les mêmes scores, les fichiers seuls suffisent.
Créez predict.py à côté de train.py. Voici sa première version. Il charge l’artefact et remplit deux fonctions : --check (vérifier) compare les 20 scores enregistrés, et un fichier d’entrée reçoit des scores. Dans Planifier les vérifications en production, vous ajoutez les vérifications des entrées.
"""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"""
import argparseimport jsonimport sysfrom pathlib import Path
import joblibimport numpy as npimport pandas as pd
from ticket_model import FEATURES, read
MODEL = Path("model")
def load_model(): info = json.loads((MODEL / "model_info.json").read_text(encoding="utf-8")) return joblib.load(MODEL / "escalation_model.joblib"), info
def check_sample(pipeline): sample = read(MODEL / "check_sample.csv") scores = pipeline.predict_proba(sample[FEATURES])[:, 1] largest = float(np.abs(scores - sample["score"]).max()) if largest > 1e-9: sys.exit(f"Check failed: the reloaded model's scores differ from the saved ones by up to {largest:.2e}.") print(f"Check passed: {len(sample)} scores are identical after reloading (largest difference {largest:.1e}).")
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) 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}.")
if __name__ == "__main__": main()check_sample() lit les 20 billets avec read() de ticket_model.py, la même fonction qui lit les données d’entraînement. Les colonnes reçoivent donc les mêmes types. La fonction calcule leurs scores avec le pipeline chargé et trouve le plus grand écart avec les scores enregistrés. Un écart supérieur à 1e-9 (0,000000001) arrête le programme avec le code de sortie 1. Un script ou un collègue voit ainsi l’échec.
Ouvrez un nouveau terminal. Allez dans le dossier du projet et activez .venv. Puis lancez la vérification :
python predict.py --checkLa sortie :
Check passed: 20 scores are identical after reloading (largest difference 1.1e-16).Pourquoi l’écart n’est-il pas exactement de 0? Les scores enregistrés sont passés par un fichier CSV sous forme de texte. Relire ce texte peut changer le dernier chiffre parmi environ 16 chiffres. Un écart de 1,1e-16 est bien inférieur à tout écart important, et bien inférieur à la limite de 1e-9. Un prétraitement différent donne des écarts d’environ 0,01, comme le montre le cas d’échec ci-dessous. La limite sépare donc « le même modèle » d’« un autre modèle » avec une grande marge.
Chargez seulement les fichiers auxquels vous faites confiance
Section intitulée « Chargez seulement les fichiers auxquels vous faites confiance »Un fichier joblib n’est pas comme une photo ou un tableau de nombres. Il ressemble plutôt à une liste d’instructions : quand vous le chargez, Python les suit pour reconstruire les objets. Habituellement, les instructions reconstruisent seulement un pipeline. Mais un fichier peut contenir n’importe quelle instruction.
Le carnet le montre avec un fichier inoffensif. Une petite classe demande à pickle d’appeler print quand l’objet est chargé :
import joblib
class Surprise: def __reduce__(self): return (print, ("This line ran while the file was loading.",))
joblib.dump(Surprise(), "surprise.joblib")joblib.load("surprise.joblib")This line ran while the file was loading.Ici, l’instruction affiche seulement du texte. Une vraie attaque pourrait supprimer des fichiers ou transmettre vos données ailleurs. La valeur artifact_sha256 dans model_info.json indique si le fichier du modèle a changé par accident après l’entraînement. Elle ne vous protège pas d’une personne qui modifie les deux fichiers. La protection des artefacts dans un vrai service (droits d’accès, fichiers signés, registre de modèles) est abordée dans Déployer des modèles d’IA pour de vrais utilisateurs.
Quand les versions diffèrent
Section intitulée « Quand les versions diffèrent »model_info.json enregistre les versions qui ont créé le fichier. Que se passe-t-il si la personne qui le reçoit utilise d’autres versions? Supposons qu’un collègue utilise scikit-learn 1.9.1, mais que le fichier a été créé avec 1.8.0. Le carnet le montre sans installer une ancienne version. Il modifie le texte de la version dans une copie du fichier, puis charge cette copie :
import warningsfrom pathlib import Path
import joblib
raw = Path("model/escalation_model.joblib").read_bytes()Path("old_version.joblib").write_bytes(raw.replace(b"1.9.1", b"1.8.0"))with warnings.catch_warnings(record=True) as caught: warnings.simplefilter("always") joblib.load("old_version.joblib")print(len(caught), "warnings. The first one:")print(type(caught[0].message).__name__ + ":", caught[0].message)12 warnings. The first one:InconsistentVersionWarning: Trying to unpickle estimator OneHotEncoder from version 1.8.0 when using version 1.9.1. This might lead to breaking code or invalid results. Use at your own risk. For more info please refer to:https://scikit-learn.org/stable/model_persistence.html#security-maintainability-limitationsChaque estimateur du pipeline produit un avertissement : l’encodeur, les imputateurs, l’outil de mise à l’échelle, le modèle et les pipelines qui les contiennent. Le fichier se charge quand même et peut même donner les bons scores. Mais scikit-learn ne le garantit pas. Les données internes d’un estimateur peuvent changer d’une version à l’autre. Les scores peuvent alors être faux sans produire d’erreur.
Solution : n’ignorez pas l’avertissement. Installez les versions de requirements.txt (python -m pip install -r requirements.txt). Ou refaites l’entraînement avec les nouvelles versions et créez un nouvel artefact. Puis exécutez de nouveau python predict.py --check.
Exercice guidé : créer et vérifier l’artefact
Section intitulée « Exercice guidé : créer et vérifier l’artefact »- Dans un terminal, allez dans
~/projects/ticket-modelet activez.venv. - Écrivez les quatre parties de
train.pydans un seul fichier. Enregistrez-le. - Exécutez
python train.py. Vous devriez voirThreshold 0.19(seuil de 0,19) etflagged 227 of 1179 (19.3%)(227 billets signalés sur 1 179, soit 19,3 %). - Listez le contenu du dossier
model/(ls model, oudir modelsous Windows). Vous devriez voir trois fichiers. - Ouvrez
model/model_info.json. Trouvez les cinq canaux et le seuil de 0,19. - Remplacez le contenu de
requirements.txtpar les cinq lignes aux versions fixées. - Écrivez
predict.py(première version). Enregistrez-le. - Ouvrez un nouveau terminal, allez dans le projet et activez
.venv. - Exécutez
python predict.py --check. Vous devriez voirCheck passed: 20 scores are identical after reloading(vérification réussie : les 20 scores sont identiques après le rechargement). - Exécutez
python predict.py data/july_tickets.csv predictions.csv. Vous devriez voirScored 1400 tickets with threshold 0.19: 282 flagged (20.1%). Wrote predictions.csv.(scores calculés pour 1 400 billets avec un seuil de 0,19 : 282 signalés, soit 20,1 %; fichier predictions.csv écrit).
L’étape 10 calcule les scores des billets de juillet, qui arrivent après l’entraînement. Cela fonctionne et ne dit rien de plus. Dans Planifier les vérifications en production, vous voyez ce que ce résultat ne vous dit pas.
Si une étape échoue
ModuleNotFoundError: No module named 'ticket_model'(module ticket_model introuvable) : vous n’êtes pas dans le dossier du projet. Exécutezcd ~/projects/ticket-model.FileNotFoundError: ... model/model_info.json(fichier introuvable) : exécutez d’abordpython train.py.KeyError: "['prior_escalations_90d'] not in index"(prior_escalations_90d absent de l’index) : votreticket_model.pyest la version du module 4, sans la variable du module 6. Téléchargez le fichier du cours (voir Avant de commencer).Check failed: ...(échec de la vérification) : le fichier du modèle etcheck_sample.csvproviennent d’exécutions différentes. Exécutez de nouveaupython train.py; il écrit les deux fichiers.
À vous : une équipe de révision plus petite
Section intitulée « À vous : une équipe de révision plus petite »Grace dit qu’en août, l’équipe expérimentée est plus petite. Elle peut réviser seulement 15 % des nouveaux billets. Changez une ligne dans train.py : REVIEW_CAPACITY = 0.15. Avant de l’exécuter, faites vos prédictions :
- Le seuil va-t-il monter ou descendre?
- La précision va-t-elle monter ou descendre? Et le rappel?
- La vérification
predict.py --checkva-t-elle encore réussir?
Puis exécutez python train.py et python predict.py --check. À la fin, remettez REVIEW_CAPACITY = 0.20 et exécutez python train.py pour retrouver l’artefact utilisé dans le cours.
Afficher la réponse
Trained on 16939 tickets. Threshold 0.22 for a review capacity of 15%.Valid: ROC AUC 0.752, average precision 0.391, flagged 174 of 1179 (14.8%), precision 0.431, recall 0.41.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).- Le seuil monte, de 0,19 à 0,22. On peut signaler moins de billets, donc un billet doit avoir un score plus élevé.
- La précision monte (de 0,379 à 0,431) : les billets signalés sont ceux qui ont les scores les plus élevés. Le rappel descend (de 0,470 à 0,410) : on détecte moins de transferts à l’équipe expérimentée. C’est le compromis présenté dans Le compromis entre précision et rappel. Un rappel de 0,41 atteint encore la cible de 40 %, mais de justesse.
- Oui. Le pipeline et les scores n’ont pas changé; seul le seuil a changé. L’aire sous la courbe ROC et la précision moyenne restent identiques, car elles ne dépendent pas du seuil. Le seuil fait partie de l’artefact (dans
model_info.json). Un changement de capacité exige donc un nouvel artefact, même avec le même modèle.
Erreur courante
Enregistrer seulement le classificateur
Symptôme : Tomás veut encore un fichier plus petit. Il enregistre seulement le modèle, joblib.dump(pipeline.named_steps["model"], ...), et prévoit de préparer les données dans le programme de calcul des scores. Au premier essai, il donne les colonnes brutes des billets au modèle chargé :
ValueError: could not convert string to float: 'chat'Un avertissement apparaît avant : UserWarning: X has feature names, but LogisticRegression was fitted without feature names (X a des noms de variables, mais LogisticRegression a été ajusté sans noms de variables). Le modèle seul n’a jamais vu les colonnes des billets, seulement les nombres produits par le prétraitement.
Il reconstruit donc le prétraitement avec build_pipeline() et l’ajuste sur les nouveaux billets. Sur les 20 billets de vérification :
ValueError: X has 22 features, but LogisticRegression is expecting 28 features as input.Sur les 1 400 billets de juillet :
ValueError: X has 29 features, but LogisticRegression is expecting 28 features as input.Sur les 1 179 billets de validation, le même code s’exécute sans erreur. Mais il signale 234 billets au lieu de 227. Sept billets changent de statut, et les scores diffèrent de jusqu’à 0,025.
Cause : le modèle a appris à partir de 28 nombres par billet. Il y avait 19 colonnes d’encodage à chaud pour les catégories, 7 nombres mis à l’échelle et 2 indicateurs de valeurs manquantes. Seul le prétraitement ajusté sait produire exactement ces 28 nombres. Vingt billets ne contiennent pas toutes les catégories, donc le nouvel encodeur crée seulement 22 colonnes. Juillet a un sixième canal, social, donc il en crée 29. En mai, toutes les catégories sont présentes par hasard. Le code s’exécute donc, mais les médianes, moyennes et écarts-types viennent maintenant de mai, pas des données d’entraînement. C’est l’erreur de 1,0 au lieu de 13,04 du petit exemple.
Solution : enregistrez tout le pipeline avec joblib.dump(pipeline, ...), comme le fait train.py. N’ajustez jamais rien dans le programme de calcul des scores. Puis exécutez python predict.py --check dans un nouveau processus.
Conséquence dans le travail en IA : les cas qui produisent une erreur sont les cas chanceux. Le cas silencieux donne un modèle différent chaque jour, car chaque lot de billets change le prétraitement. Le taux de signalement varie avec le lot, et personne ne voit d’erreur.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Nommez les trois fichiers dans model/ et dites ce que la prochaine personne perdrait sans chacun d'eux.
Afficher la réponse
escalation_model.joblib, le pipeline ajusté. Sans lui, personne ne peut calculer les scores des billets comme le modèle l’a appris. Reconstruire le prétraitement donne d’autres colonnes ou une autre mise à l’échelle.model_info.json: le schéma des variables, le seuil, les versions et les sommes de contrôle. Sans lui, personne ne connaît les colonnes et catégories attendues, ni le seuil de 0,19 plutôt que 0,5. Personne ne sait non plus quelles versions et données ont servi à créer le modèle.check_sample.csv: 20 billets avec leurs scores enregistrés. Sans lui, personne ne peut prouver que le modèle chargé donne les mêmes scores qu’au moment de l’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
Omar exécute predict.py --check sur un nouveau portable et voit InconsistentVersionWarning, puis Check passed. Il dit que l'avertissement n'a pas d'importance, puisque la vérification a réussi. A-t-il raison? Expliquez.
Afficher la réponse
Pas complètement. L’avertissement indique que le fichier a été créé avec une version de scikit-learn différente de celle installée. La vérification a réussi sur 20 billets, ce qui constitue une bonne preuve pour ces billets. Mais scikit-learn ne garantit pas que le reste se comportera de la même façon. Une autre version peut changer la façon dont un estimateur lit ses données stockées. La solution sûre consiste à installer les versions de requirements.txt (celles de model_info.json). Ou à refaire l’entraînement avec les nouvelles versions et à créer un nouvel artefact. Puis relancez la vérification, sans l’avertissement.
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
Pourquoi faut-il vérifier les scores identiques dans un nouveau processus, plutôt que dans le carnet où vous avez entraîné le modèle?
Afficher la réponse
Le carnet garde le pipeline ajusté, les importations et vos variables en mémoire. Une vérification peut les utiliser par erreur et réussir, même si un fichier manque ou est incorrect. Un nouveau processus démarre sans rien, comme celui de la personne qui reçoit les fichiers. Si les scores y sont identiques, les fichiers seuls suffisent à reproduire le modèle.
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.
Récapitulatif
Vous pouvez maintenant transmettre un modèle sous forme de fichiers, plutôt que de carnet :
python train.pyajuste le modèle sur les données d’entraînement, choisit le seuil de 0,19 sur les données de validation et écritmodel/. Ce dossier contient tout le pipeline (escalation_model.joblib), son schéma, son seuil, ses versions et ses sommes de contrôle (model_info.json). Il contient aussi 20 billets avec leurs scores (check_sample.csv).requirements.txtfixe la version de chaque paquet nécessaire à l’artefact, y compris numpy et joblib.python predict.py --checkdans un nouveau processus prouve que le modèle chargé donne les mêmes scores (écart maximal de 1,1e-16).- Chargez seulement les fichiers joblib auxquels vous faites confiance. Traitez
InconsistentVersionWarning(avertissement de versions différentes) comme un problème à corriger.
Les fichiers indiquent comment exécuter le modèle. Ensuite, vous écrivez à quoi il sert et les usages à éviter : la fiche du modèle.
Leçon suivante : Documenter les limites d'utilisation