Ajuster les transformations correctement
- Leçon 15
- 55 min
- Module 5 : Préparer la modélisation sans risque
Leçon 15 sur 18 · SQL et préparation des données pour l'IAAjuster les transformations correctement
1. Sources et structure des données
2. L'essentiel de SQL
3. Des requêtes d'analyse utiles
4. Nettoyer et transformer les données
5. Préparer la modélisation sans risque
6. Contrats de données et passation
Ce que vous saurez faire
Préparez les caractéristiques d’un modèle sans laisser les données de validation ou de test contaminer l’entraînement. Mesurez ensuite l’effet d’une fuite sur un score pour reconnaître un score trop beau pour être vrai.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter contient tous les scripts, tâches et vérifications de cette leçon, avec les consignes. Il reproduit aussi tous les chiffres sur les fuites présentés ici. Ses cellules de préparation installent pandas et scikit-learn, téléchargent les données d’exercice et recréent la séparation de la leçon 2. Il fonctionne donc sans le projet local. Lisez les explications ici et exécutez le code dans le carnet.
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
- Les données d’entraînement, de validation et de test et la séparation temporelle, présentées dans Choisir une séparation.
- Les colonnes qui causent des fuites et pourquoi, présentées dans Caractéristiques et moment de la cible.
- Le sens d’une valeur manquante dans chaque colonne, présenté dans Traiter les valeurs désordonnées.
order_valuemanque pour les billets de soutien liés au compte (aucune commande) et les lignes « unknown » (inconnues).customer_tenure_daysmanque pour les visiteurs et les clients supprimés.
Ce qu’il vous faut sur votre ordinateur (seulement pour le parcours local; le carnet n’en a pas besoin)
- Le dossier
~/projects/ticket-data, créé dans Préparer une base de données d’exercice : créer la base de données. - L’environnement
.venvavec pandas 3.0.6, actif dans un terminal ouvert dans~/projects/ticket-data. Vous l’avez créé dans Profiler avant de modifier : le même profil dans pandas. Sipython -c "import pandas"échoue, activez de nouveau.venv(source .venv/bin/activatesur macOS et Linux,.venv\Scripts\Activate.ps1sur Windows). data/model/train.csv(927 lignes),valid.csv(227) ettest.csv(215). Vous les avez créés avecpython scripts/split.pydans Choisir une séparation : exemple détaillé : split.py. Si vous avez changé les dates dans À vous de jouer : reculer les dates d’un mois, remettez2026-05-01et2026-06-01, puis relancez le script.data/clean/tickets.csv, créé dans Traiter les valeurs désordonnées : exercice guidé : terminer clean.py. Cette leçon en tire les colonnes qui causent des fuites pour une expérience.- Si les fichiers de séparation manquent ou contiennent d’autres nombres de lignes, téléchargez le script du cours depuis C04/pipeline :
curl -fsSL -o scripts/split.py https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C04/pipeline/scripts/split.py(curl.exesur Windows). Exécutez ensuitepython scripts/split.py. Sitickets_model.csvmanque aussi, suivez d’abord les étapes de récupération dans l’encadré « Avant de commencer » de Choisir une séparation. - scikit-learn 1.9.1, que vous installez plus bas dans Installer scikit-learn. Il faut une connexion Internet, mais aucun compte.
Testé sur macOS avec Python 3.14.6, pandas 3.0.6 et scikit-learn 1.9.1 (numpy 2.5.3). Les commandes Windows n’ont pas été testées. Le carnet a été testé dans Jupyter.
Installer scikit-learn
Section intitulée « Installer scikit-learn »scikit-learn est la bibliothèque Python la plus courante pour l’apprentissage automatique classique. Ici, vous utilisez ses outils de prétraitement et un modèle simple. Installez la version utilisée dans le cours et ajoutez-la à requirements.txt :
python -m pip install scikit-learn==1.9.1Ouvrez ensuite requirements.txt et ajoutez une ligne pour qu’il contienne les deux paquets :
pandas==3.0.6scikit-learn==1.9.1Vérifiez l’installation :
python -c "import sklearn; print(sklearn.__version__)"1.9.1Si vous voyez ModuleNotFoundError: No module named 'sklearn' (aucun module nommé « sklearn »), l’environnement n’est pas actif. Activez .venv et refaites l’installation.
Ajuster, puis transformer
Section intitulée « Ajuster, puis transformer »Un modèle a besoin de nombres sans valeurs manquantes. Les caractéristiques passent donc d’abord par le prétraitement. Ces étapes remplissent les valeurs manquantes, placent les nombres sur une échelle semblable et convertissent les catégories en nombres. Chaque étape comprend deux parties :
- L’ajustement (fit) apprend des nombres à partir des données. Un outil d’imputation par la médiane apprend la médiane de chaque colonne. (L’imputation remplace une valeur manquante par une autre valeur; vous l’avez choisie au module 4.) Un outil de mise à l’échelle apprend la moyenne et l’écart-type.
- La transformation (transform) utilise les nombres appris pour modifier les données. L’outil d’imputation remplit chaque valeur manquante avec la médiane apprise. L’outil de mise à l’échelle calcule (valeur − moyenne) ÷ écart-type.
Voici un petit exemple : cinq valeurs d’entraînement de order_value, dont une manquante, et deux valeurs de test.
import pandas as pdfrom sklearn.impute import SimpleImputerfrom sklearn.preprocessing import StandardScaler
train = pd.DataFrame({"order_value": [20.0, 40.0, None, 60.0, 100.0]})test = pd.DataFrame({"order_value": [None, 400.0]})
imputer = SimpleImputer(strategy="median")imputer.fit(train)print("median learned from train:", imputer.statistics_)
scaler = StandardScaler()scaler.fit(imputer.transform(train))print("mean and SD learned from train:", scaler.mean_.round(2), scaler.scale_.round(2))
print("test after transform:", scaler.transform(imputer.transform(test)).round(2).ravel())median learned from train: [50.]mean and SD learned from train: [54.] [26.53]test after transform: [-0.15 13.04]Vérifiez à la main. Les valeurs d’entraînement connues sont 20, 40, 60 et 100. La médiane est donc (40 + 60) ÷ 2 = 50. Après l’imputation, la moyenne est (20 + 40 + 50 + 60 + 100) ÷ 5 = 54. La valeur de test manquante devient 50, et (50 − 54) ÷ 26,53 = −0,15. La valeur 400 devient (400 − 54) ÷ 26,53 = 13,04. Elle dépasse largement toutes les valeurs d’entraînement, et la valeur transformée le montre.
Voici maintenant l’erreur : ajuster sur les données d’entraînement et de test ensemble. Modifiez les deux appels à fit (ajuster) pour utiliser pd.concat([train, test]). Affichez ensuite les nombres appris et les valeurs d’entraînement transformées :
median: [60.] mean and SD: [105.71] [122.22]train after transform: [-0.7 -0.54 -0.37 -0.37 -0.05]Une valeur de test a modifié tous les nombres appris. La médiane, la moyenne et l’écart-type contiennent maintenant de l’information des données de test, avant le test. C’est une fuite par le prétraitement. La règle : ajustez sur les données d’entraînement seulement. Transformez ensuite les données d’entraînement, de validation et de test avec les mêmes nombres appris.
Description textuelle du diagramme
Le fichier d’entraînement, avec 927 billets de janvier à avril, est la seule entrée de l’ajustement. L’ajustement apprend les médianes, les moyennes, les écarts-types et la liste des catégories : c’est le prétraitement ajusté. Ce prétraitement transforme ensuite trois ensembles de données : train, valid (227 billets de mai) et test (215 billets de juin). Le modèle est ajusté sur les données d’entraînement transformées. Le modèle et les données de validation transformées donnent le score de validation, qui sert à choisir et à améliorer. Le modèle et les données de test transformées donnent le score de test, une seule fois, à la fin. Aucune flèche ne va de valid ou test vers l’ajustement.
Un pipeline respecte la règle pour vous
Section intitulée « Un pipeline respecte la règle pour vous »Il est facile d’oublier la règle dans un long script. Un pipeline réunit les étapes de prétraitement et le modèle dans un seul objet. pipeline.fit(train) ajuste chaque étape sur les lignes d’entraînement. pipeline.predict_proba(valid) transforme seulement les lignes de validation et donne des scores. Le code fait respecter la règle; vous n’avez pas à vous en souvenir.
Les caractéristiques de Mei sont de deux types. Le prétraitement comporte donc deux branches. Un ColumnTransformer envoie chaque liste de colonnes à ses propres étapes :
| Colonnes | Étapes | Ce que l’ajustement apprend |
|---|---|---|
Catégories : channel, team, segment, region |
OneHotEncoder(handle_unknown="ignore") |
La liste des catégories dans chaque colonne. |
Nombres : priority, order_value, word_count, customer_tenure_days, prior_tickets_90d, created_hour |
SimpleImputer(strategy="median", add_indicator=True), puis StandardScaler() |
La médiane et, après l’imputation, la moyenne et l’écart-type de chaque colonne. |
L’encodage à chaud (one-hot encoding) transforme une colonne de catégories en plusieurs colonnes de 0 et de 1. Il crée une colonne pour chaque catégorie vue pendant l’ajustement. Un billet de clavardage reçoit 1 dans channel_chat et 0 dans les autres colonnes de canal.
add_indicator=True ajoute une colonne de 0 et de 1 pour chaque colonne numérique ayant des valeurs manquantes dans l’entraînement. Le 1 indique que la valeur manquait. Au module 4, vous avez vu qu’un order_value manquant a un sens (un billet lié au compte, aucune commande). L’indicateur conserve ce sens après le remplacement de la valeur manquante par la médiane.
Exemple détaillé : model_check.py
Section intitulée « Exemple détaillé : model_check.py »Priya, la scientifique des données, demande à Mei une vérification rapide avant de commencer à travailler sur les modèles. La séparation est-elle utilisable? Les données contiennent-elles un signal? Mei écrit une courte vérification avec un modèle simple, une régression logistique. C’est une vérification des données, pas un produit. Le cours Apprentissage automatique : du problème à un modèle fiable entraîne et compare des modèles.
"""Check the prepared data with a simple model: fit on train only, score onvalid. A check for the data, not a model for Larkfield (C05 trains models)."""
import pandas as pdfrom sklearn.compose import ColumnTransformerfrom sklearn.impute import SimpleImputerfrom sklearn.linear_model import LogisticRegressionfrom sklearn.metrics import roc_auc_scorefrom sklearn.pipeline import Pipelinefrom sklearn.preprocessing import OneHotEncoder, StandardScaler
CATEGORIES = ["channel", "team", "segment", "region"]NUMBERS = ["priority", "order_value", "word_count", "customer_tenure_days", "prior_tickets_90d", "created_hour"]TARGET = "escalated_72h"
def build_pipeline(): numbers = Pipeline([ ("impute", SimpleImputer(strategy="median", add_indicator=True)), ("scale", StandardScaler()), ]) prepare = ColumnTransformer([ ("categories", OneHotEncoder(handle_unknown="ignore"), CATEGORIES), ("numbers", numbers, NUMBERS), ]) return Pipeline([("prepare", prepare), ("model", LogisticRegression(max_iter=1000))])
def main(): train = pd.read_csv("data/model/train.csv") valid = pd.read_csv("data/model/valid.csv") pipeline = build_pipeline() pipeline.fit(train[CATEGORIES + NUMBERS], train[TARGET])
learned = pipeline.named_steps["prepare"].named_transformers_["numbers"] medians = learned["impute"].statistics_ print("Medians learned from train:", {name: float(m) for name, m in zip(NUMBERS, medians)}) channels = pipeline.named_steps["prepare"].named_transformers_["categories"].categories_[0] print("Channels learned from train:", [str(c) for c in channels])
scores = pipeline.predict_proba(valid[CATEGORIES + NUMBERS])[:, 1] print("ROC AUC on valid:", round(roc_auc_score(valid[TARGET], scores), 3))
if __name__ == "__main__": main()| Lignes | Pourquoi |
|---|---|
CATEGORIES, NUMBERS, TARGET |
Des listes explicites. Les identifiants et les dates en sont exclus et ne peuvent donc pas devenir des caractéristiques par accident. |
build_pipeline() |
Le prétraitement et le modèle dans un objet, créé par une fonction. Chaque utilisation reçoit les mêmes étapes. |
pipeline.fit(train[...], train[TARGET]) |
Le seul fit du script, qui reçoit uniquement les lignes d’entraînement. |
named_steps[...], named_transformers_[...] |
Inspectent le pipeline ajusté pour voir ce que l’ajustement a appris. |
pipeline.predict_proba(valid[...])[:, 1] |
Transforme les lignes de validation avec les nombres appris à l’entraînement, puis donne à chaque billet un score entre 0 et 1. |
roc_auc_score(...) |
Un nombre qui mesure à quel point les scores classent bien les billets. |
L’aire sous la courbe ROC (ROC AUC) en une phrase : la probabilité qu’un billet transféré à un niveau supérieur, choisi au hasard, obtienne un score plus élevé qu’un autre billet choisi au hasard (0,5 correspond au hasard; 1,0, à un classement parfait). Le cours d’apprentissage automatique l’explique en détail.
python scripts/model_check.pyMedians learned from train: {'priority': 2.0, 'order_value': 86.21, 'word_count': 88.0, 'customer_tenure_days': 1488.5, 'prior_tickets_90d': 3.0, 'created_hour': 14.0}Channels learned from train: ['chat', 'email', 'phone', 'unknown', 'web_form']ROC AUC on valid: 0.6730,673 est un score modeste : meilleur que le hasard, loin d’être parfait. C’est honnête. Le modèle apprend sur janvier à avril et est testé sur mai, après le changement de politique. Quand Priya a terminé ses choix, elle ajuste le même pipeline une fois sur les données d’entraînement et de validation ensemble. Elle évalue ensuite juin une seule fois : 0,761. Jusque-là, personne ne regarde les données de test.
Une catégorie absente de l’entraînement
Section intitulée « Une catégorie absente de l’entraînement »Grace dit que Larkfield répondra bientôt aux messages sur les médias sociaux. Le premier billet avec channel = social arrivera, et aucun billet d’entraînement n’a ce canal. Que fait l’encodeur?
import pandas as pdfrom sklearn.preprocessing import OneHotEncoder
train = pd.read_csv("data/model/train.csv")new_ticket = pd.DataFrame({"channel": ["social"]})
strict = OneHotEncoder().fit(train[["channel"]])try: strict.transform(new_ticket)except ValueError as error: print("ValueError:", error)
tolerant = OneHotEncoder(handle_unknown="ignore").fit(train[["channel"]])print(tolerant.get_feature_names_out())print(tolerant.transform(new_ticket).toarray())print(tolerant.transform(pd.DataFrame({"channel": ["chat"]})).toarray())ValueError: Found unknown categories ['social'] in column 0 during transform['channel_chat' 'channel_email' 'channel_phone' 'channel_unknown' 'channel_web_form'][[0. 0. 0. 0. 0.]][[1. 0. 0. 0. 0.]]Avec le réglage par défaut, une nouvelle catégorie arrête la transformation avec une erreur. En utilisation réelle, le service d’acheminement échouerait au premier billet provenant des médias sociaux. Avec handle_unknown="ignore", ce billet reçoit 0 dans chaque colonne de canal : « aucun des canaux que je connais ». Le modèle lui donne quand même un score à partir des autres caractéristiques. Ne « corrigez » pas cela en ajustant l’encodeur sur toutes les lignes. Cela couvre les catégories de votre fichier actuel, pas la prochaine nouvelle catégorie. Au module 6, vous ajoutez une vérification qui compte les catégories inconnues dans les nouvelles données.
« Ajuster le prétraitement sur toutes les données »
Section intitulée « « Ajuster le prétraitement sur toutes les données » »Une erreur courante consiste à préparer toute la table, puis à la séparer. Les médianes, moyennes et écarts-types viennent alors de toutes les lignes, y compris celles de validation et de test. À quel point cela change-t-il le score ici? Mei le mesure. Elle ajuste le prétraitement sur les 1 369 lignes et le modèle sur l’entraînement seulement, comme le fait cette erreur :
import sys
import pandas as pdfrom sklearn.linear_model import LogisticRegressionfrom sklearn.metrics import roc_auc_score
sys.path.insert(0, "scripts")from model_check import CATEGORIES, NUMBERS, TARGET, build_pipeline
train = pd.read_csv("data/model/train.csv")valid = pd.read_csv("data/model/valid.csv")test = pd.read_csv("data/model/test.csv")everything = pd.concat([train, valid, test])features = CATEGORIES + NUMBERS
# The mistake: fit the preprocessing on every row, then the model on train.prepare = build_pipeline().named_steps["prepare"]prepare.fit(everything[features])model = LogisticRegression(max_iter=1000).fit(prepare.transform(train[features]), train[TARGET])scores = model.predict_proba(prepare.transform(valid[features]))[:, 1]print("Median prior_tickets_90d:", float(prepare.named_transformers_["numbers"]["impute"].statistics_[4]))print("ROC AUC on valid:", round(roc_auc_score(valid[TARGET], scores), 4))
# The correct way, for comparison, with four decimals.pipeline = build_pipeline().fit(train[features], train[TARGET])print("Train only, ROC AUC on valid:", round(roc_auc_score(valid[TARGET], pipeline.predict_proba(valid[features])[:, 1]), 4))Median prior_tickets_90d: 5.0ROC AUC on valid: 0.6726Train only, ROC AUC on valid: 0.6728La médiane apprise de prior_tickets_90d passe de 3 à 5, mais le score ne change qu’à la quatrième décimale. Soyez honnête à ce sujet : sur ces données, ajuster l’imputation par la médiane et la mise à l’échelle sur toutes les lignes change très peu le score. La médiane ou la moyenne d’une caractéristique contient très peu d’information sur la cible.
Corrigez quand même l’erreur. Son effet augmente avec les petits jeux de données, les catégories rares et les étapes qui apprennent de la cible. Un flux de travail ajusté sur toutes les lignes aujourd’hui finira un jour par ajuster quelque chose de plus puissant. Les fortes hausses viennent des caractéristiques et des agrégats qui causent des fuites. Le carnet de la leçon les mesure avec la même séparation et le même pipeline :
| Caractéristiques (ajustement sur l’entraînement, évaluation sur mai) | ROC AUC sur valid |
|---|---|
| Les 10 caractéristiques honnêtes | 0,673 |
+ priority_now (fixé à 1 quand un billet est transféré à un niveau supérieur) |
0,964 |
+ first_reply_minutes (connu après la première réponse) |
0,856 |
| + le taux de transfert à un niveau supérieur de chaque client, calculé sur toutes les lignes | 0,767 |
| Caractéristiques honnêtes, prétraitement ajusté sur toutes les lignes | 0,6726 |
La dernière fuite est la plus difficile à repérer. Le taux semble être un fait sur le client. Mais la valeur d’un billet de mai inclut sa propre cible et les cibles des billets ultérieurs. Le modèle y lit une partie de la réponse.
Exercice guidé : mesurer les fuites
Section intitulée « Exercice guidé : mesurer les fuites »Le carnet de la leçon reproduit chaque nombre du tableau ci-dessus pour que vous voyiez chaque fuite se produire. L’encadré en haut de cette page contient les liens Colab et Kaggle. Le carnet réintègre par jointure les deux colonnes causant des fuites depuis les billets nettoyés, pour les expériences.
- Ouvrez le carnet dans Colab ou Kaggle. Pour l’exécuter sur votre ordinateur, consultez l’encadré sous les étapes.
- Exécutez les cellules de préparation. Vous devriez voir
pandas 3.0.6etscikit-learn 1.9.1, puissplit.py: done(terminé). - Dans la section 1, vérifiez à la main la médiane 50, la moyenne 54 et la valeur −0,15.
- Dans la section 4, le prétraitement est ajusté sur toutes les lignes. Prédisez d’abord l’effet. Vous devriez voir 0,6726 et 0,6728.
- Dans la section 5, écrivez votre prédiction pour
priority_nowavant d’exécuter la cellule. Vous devriez voir 0,964. - Faites les tâches de la section 8 : exercice 1 et exercice 2. Chaque cellule de vérification affiche
Check passed.(vérification réussie) quand votre réponse est correcte. - Redémarrez et exécutez tout. Chaque cellule doit s’exécuter, et chaque cellule de vérification doit afficher
Check passed.
À vous de jouer : la moyenne plutôt que la médiane
Section intitulée « À vous de jouer : la moyenne plutôt que la médiane »Changez une chose dans scripts/model_check.py : mettez strategy="mean" dans le SimpleImputer. Avant de l’exécuter, prédisez : la valeur qui remplace un order_value manquant va-t-elle augmenter ou diminuer? Le score changera-t-il beaucoup? Exécutez ensuite le script. (La première ligne dit encore « Medians » (médianes), mais elle affiche maintenant les moyennes.) Remettez la stratégie à "median" à la fin.
Afficher la réponse
Medians learned from train: {'priority': 2.348435814455232, 'order_value': 120.30397741530741, 'word_count': 107.33549083063646, 'customer_tenure_days': 1427.0087912087913, 'prior_tickets_90d': 5.858683926645091, 'created_hour': 13.627831715210355}Channels learned from train: ['chat', 'email', 'phone', 'unknown', 'web_form']ROC AUC on valid: 0.672La valeur de remplacement pour order_value augmente, passant de 86,21 à 120,30. Quelques grosses commandes font monter la moyenne, mais pas la médiane. Le score change à peine (0,672 au lieu de 0,673). La colonne indicatrice marque encore chaque valeur remplacée, et le modèle peut en tirer de l’information. Le choix compte toujours pour le sens : un billet lié au compte « a » maintenant une commande de 120,30 $. Au module 4, vous avez décidé qu’une valeur de commande manquante n’est pas un nombre inconnu. La médiane avec un indicateur est donc le choix le plus honnête ici. Le score ne contredit pas ce choix.
Erreur courante
fit_transform sur les données de validation
Symptôme : Tomás écrit le prétraitement et le modèle comme des étapes séparées, sans pipeline. L’entraînement fonctionne. Il prépare ensuite les données de validation comme les données d’entraînement, et la prédiction échoue :
prepare = build_pipeline().named_steps["prepare"]train_x = prepare.fit_transform(train[features])model = LogisticRegression(max_iter=1000).fit(train_x, train[TARGET])valid_x = prepare.fit_transform(valid[features]) # fit again, on validmodel.predict_proba(valid_x)ValueError: X has 27 features, but LogisticRegression is expecting 28 features as input.Cause : fit_transform (ajuster et transformer) sur les données de validation ajuste de nouveau le prétraitement, sur les billets de mai. Les billets de mai n’ont aucun client du segment unknown (inconnu, pour les comptes supprimés). L’encodeur a donc appris une catégorie de moins, et la table contient 27 colonnes au lieu de 28. L’erreur est le cas favorable. Si mai avait les mêmes catégories, le code s’exécuterait sans erreur. Mais la mise à l’échelle utiliserait la moyenne et l’écart-type de mai pour chaque colonne. La même valeur aurait un sens différent dans l’entraînement et la validation. Le score serait faux, sans signe visible.
Correction : utilisez fit ou fit_transform seulement sur les données d’entraînement; utilisez seulement transform (transformer) sur toutes les autres données. Mieux encore, utilisez un seul pipeline, comme dans model_check.py. Il n’y a alors qu’un appel à fit, qui reçoit les lignes d’entraînement.
Conséquence en travail d’IA : la même erreur en production est pire. Un service qui réajuste son prétraitement sur chaque nouveau lot de billets donne un sens différent à la même entrée chaque jour. Personne ne voit d’erreur.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Nommez ce que l'ajustement apprend à chacune des trois étapes de prétraitement, et les lignes qu'il peut utiliser.
Afficher la réponse
SimpleImputer(strategy="median") apprend la médiane de chaque colonne numérique. StandardScaler apprend la moyenne et l’écart-type de chaque colonne après l’imputation. OneHotEncoder apprend la liste des catégories de chaque colonne catégorielle. Les trois peuvent apprendre uniquement à partir des lignes d’entraînement. Les lignes de validation et de test sont seulement transformées avec ce qui a été appris.
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
Tomás ajoute une colonne hours_open, soit les heures entre created_at et closed_at. L'aire sous la courbe ROC de validation passe de 0,673 à 0,709. Expliquez pourquoi, et dites ce que vous verriez dans le vrai service d'acheminement.
Afficher la réponse
closed_at est enregistré à la fermeture du billet de soutien. Donc, hours_open n’est pas connu au moment de la prédiction. C’est une fuite de données. Les billets transmis à un niveau supérieur restent ouverts plus longtemps. Dans les données d’entraînement, leur médiane est d’environ 53 heures, contre 27 heures pour les autres. La colonne contient donc de l’information sur la cible, et le score augmente. Dans le service d’acheminement, un nouveau billet n’a pas de closed_at. La colonne est donc vide pour chaque billet. Le classement réel du modèle est moins bon que ce qu’indiquait son score de validation. Une petite hausse reste une fuite. Ce n’est pas l’ampleur du changement qui compte, mais le moment où la valeur est enregistrée.
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 »L’exercice du module vous demande de planifier avant d’ajuster un modèle. Un plan écrit rend chaque décision sur les fuites visible. Un collègue peut ainsi la vérifier avant qu’elle entraîne des coûts.
Exemple détaillé : le plan de Mei pour le modèle de transmission à un niveau supérieur
Section intitulée « Exemple détaillé : le plan de Mei pour le modèle de transmission à un niveau supérieur »Mei écrit son plan sur une page, avec les nombres de ce module :
| Question | Décision | Preuve |
|---|---|---|
| Moment de la prédiction | À la création du billet. | Larkfield achemine les billets à leur arrivée. |
| Cible | escalated_72h : un événement escalated (transmis à un niveau supérieur) dans les 72 heures après created_at. |
188 lignes sur 1 369 (13,7 %). |
| Maturité de la cible | Seulement les billets créés au plus tard le 28 juin 2026 à 6 h (instantané − 72 heures). | 29 billets retirés et comptés. |
| Caractéristiques | channel, team, priority, segment, region, order_value, word_count, customer_tenure_days, prior_tickets_90d, created_hour. |
Toutes connues à la création. Historique tiré uniquement des billets antérieurs. |
| Jamais des caractéristiques | priority_now, first_reply_minutes, closed_at, événements de résultat, csat, tout taux par client calculé sur toutes les lignes. |
Enregistrés après la création, ou contiennent des cibles. |
| Séparation | Par date : entraînement avant mai, validation en mai, test du 1er au 27 juin. | 927 / 227 / 215 lignes; taux de 11,7 % / 19,8 % / 16,3 %; 77 des 89 clients de test dans les données d’entraînement. |
| Prétraitement | Un seul pipeline : encodage disjonctif avec handle_unknown="ignore"; imputation par la médiane avec indicateurs; mise à l’échelle. Ajustement sur les données d’entraînement seulement. |
Médianes des données d’entraînement, par exemple 86,21 pour order_value. |
| Vérifications avant tout modèle | Les comptes de lignes concordent; les dates ne se chevauchent pas; aucune colonne avec fuite dans la sortie; une seule caractéristique très forte signale une fuite possible. | build_summary.json, split_manifest.json. |
Votre tâche : un billet résolu sera-t-il rouvert?
Section intitulée « Votre tâche : un billet résolu sera-t-il rouvert? »Grace a une nouvelle question. Quand un agent résout un billet, un modèle peut-il prédire si le client le rouvrira dans les 14 jours? Un chef d’équipe pourrait alors vérifier ces billets avant le retour du client. Copiez le tableau de Mei dans un fichier, reports/reopen-plan.md, et remplissez chaque ligne pour ce nouveau modèle. N’ajustez pas de modèle. Pour chaque décision, écrivez une phrase qui la justifie. Examinez bien le moment de la prédiction : ce n’est plus la création du billet.
Afficher la réponse modèle
| Question | Décision | Justification |
|---|---|---|
| Moment de la prédiction | Le moment de l’événement de résultat resolved (résolu), soit recorded_at. |
Le chef d’équipe agit quand le billet est résolu. |
| Cible | reopened_14d : un événement reopened (rouvert) du même billet dans les 14 jours après cet événement resolved. |
Précis : quel événement, quelle fenêtre. Un billet résolu deux fois donne deux lignes, une par résolution. |
| Maturité de la cible | Seulement les résolutions au plus tard le 1er juillet 2026 à 6 h − 14 jours = 17 juin 2026 à 6 h. Comptez les lignes retirées. | Une résolution plus récente n’a pas encore ses 14 jours d’observation. Son résultat « aucune réouverture » n’est pas définitif. |
| Caractéristiques | Les caractéristiques à la création, plus les faits connus à la résolution : first_reply_minutes, transmission à un niveau supérieur avant la résolution, resolution_code, heures entre la création et cette résolution, réouvertures antérieures de ce billet. |
Le moment de la prédiction a changé. first_reply_minutes causait une fuite pour le modèle de transmission à un niveau supérieur. Mais cette valeur est connue à la résolution, donc c’est une caractéristique valide ici. La fuite dépend du moment de la prédiction, pas de la colonne. |
| Jamais des caractéristiques | csat (enregistré après la résolution), closed_at (pour un billet rouvert, c’est la fermeture finale, après la réouverture), priority_now (valeur à l’instantané, pas à la résolution), tout événement de résultat ultérieur, tout taux de réouverture par client calculé sur toutes les lignes. |
Chaque valeur est enregistrée après le moment de la prédiction ou contient des cibles. |
| Séparation | Par date de résolution : par exemple, entraînement avant mai, validation en mai, test du 1er au 16 juin. Gardez toutes les résolutions d’un billet dans la même partie. | Le modèle prédit les réouvertures futures à partir des réouvertures passées. Deux lignes d’un billet dans des parties différentes permettraient au modèle de voir son historique pendant l’entraînement. |
| Prétraitement | Le même type de pipeline, ajusté sur les données d’entraînement seulement. resolution_code est une nouvelle colonne catégorielle, avec handle_unknown="ignore". |
Même règle; un nouveau code de résolution ne doit pas arrêter le service. |
| Vérifications avant tout modèle | Lignes par partie et taux de réouverture par mois; aucun chevauchement de dates ou de billets entre les parties; pour chaque caractéristique, son enregistrement précède le recorded_at de la résolution. |
Le taux peut aussi changer après le 1er mai. La dernière vérification porte sur les fuites et se fait sur papier. |
Votre réponse peut différer dans les détails, par exemple avec une autre fenêtre ou d’autres dates. Vérifiez trois choses : le moment de la prédiction est la résolution; csat et closed_at sont exclus; la maturité de la cible utilise le moment de la résolution, pas celui de la création.
Vérification du module
Section intitulée « Vérification du module »Vérifiez votre compréhension
Rappel : donnez les trois questions qui permettent de choisir une séparation aléatoire, par date ou par groupe.
Afficher la réponse
- Le modèle prédira-t-il l’avenir à partir du passé? Alors, séparez par date.
- Le modèle rencontrera-t-il de nouveaux clients ou d’autres nouveaux groupes? Alors, gardez chaque groupe dans une seule partie.
- Les lignes sont-elles indépendantes, sans ordre temporel ni groupes répétés? Une séparation aléatoire constitue un test valide seulement dans ce cas.
Pour Larkfield, la réponse à la première question est oui. La séparation se fait donc par date.
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édisez et expliquez : Priya ajuste l'imputation et la mise à l'échelle sur les 1 369 lignes, plutôt que sur les données d'entraînement. Qu'arrive-t-il au score de validation sur ces données? Pourquoi vaut-il quand même la peine de corriger cela?
Afficher la réponse
Presque rien : 0,6726 au lieu de 0,6728. Une médiane, une moyenne et un écart-type contiennent très peu d’information sur la cible. Cela vaut quand même la peine de corriger le problème. Les nombres appris viennent du futur : la médiane de prior_tickets_90d passe de 3 à 5. L’effet augmente aussi avec peu de données, des catégories rares et des étapes qui apprennent à partir de la cible. Un pipeline ajusté sur les données d’entraînement ne coûte rien et évite tous ces problèmes.
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 de l'exercice : dans l'exercice 2 du carnet Jupyter, une des trois nouvelles colonnes cause une fuite. Nommez-la, donnez les scores qui le montrent et expliquez comment elle a gonflé l'évaluation. Expliquez ensuite comment vous la trouveriez sans modèle.
Afficher la réponse
La colonne qui cause une fuite est reply_speed. Avec les caractéristiques sans fuite, l’aire sous la courbe ROC de validation est de 0,673. L’ajout de weekday donne 0,679 et celui de order_size, 0,687 : de petits changements. L’ajout de reply_speed donne 0,809.
Comment elle a gonflé l’évaluation : reply_speed vaut fast (rapide) quand first_reply_minutes est inférieur à 30. La première réponse arrive après la création du billet. Les agents expérimentés répondent rapidement aux billets transmis à un niveau supérieur. La colonne apporte donc une trace de la cible dans les lignes de validation. Le modèle classe ces billets plus haut à cause de cette information. Un nouveau billet n’a pas encore de première réponse. En utilisation réelle, la colonne n’a donc aucune valeur. Le classement réel est proche du score sans fuite de 0,673, et non de 0,809.
Sans modèle : demandez quand chaque valeur est enregistrée. reply_speed vient de first_reply_minutes, qui figure dans la liste des valeurs enregistrées après la création dans build_features.py. Un nouveau nom et une forme moins précise n’éliminent pas une fuite.
Une réponse complète nomme la colonne et compare son score au score sans fuite. Elle relie la fuite au moment où la valeur est enregistrée et décrit ce qui arrive en utilisation réelle.
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
Dans ce module, vous avez préparé les données pour un modèle sans fuites :
- Chronologie : le moment de la prédiction est la création du billet. La cible
escalated_72hest précise, et les 29 billets sans cible définitive sont retirés. Les caractéristiques viennent uniquement du passé.priority_now,first_reply_minutes,closed_atet les événements de résultat ne sont jamais des caractéristiques. - Séparation : par date, comme Larkfield utilisera le modèle. L’entraînement compte 927 lignes, la validation 227 et le test 215. Les taux de transmission à un niveau supérieur sont de 11,7 %, 19,8 % et 16,3 %. Une séparation aléatoire masque le changement de mai. Chaque séparation aléatoire est un seul tirage (de 0,665 à 0,827).
- Ajustement : le prétraitement apprend uniquement des données d’entraînement, dans un seul pipeline.
handle_unknown="ignore"empêche une nouvelle catégorie d’arrêter le modèle. - Preuves : les caractéristiques sans fuite donnent un score de 0,673. Les fuites le gonflent à 0,964 (
priority_now), 0,856 (first_reply_minutes) et 0,767 (un taux par client sur toutes les lignes). L’ajustement du prétraitement sur toutes les lignes n’a changé que la quatrième décimale du score.
Ensuite, dans le module 6, vous écrirez les règles que chaque fichier livré doit respecter. La prochaine personne pourra alors faire confiance au jeu de données sans refaire vos vérifications.
Leçon suivante : Les règles de validation