Prétraiter avec un pipeline
- Leçon 13
- 80 min
- Module 4 : Créer les premiers modèles
Leçon 13 sur 27 · Apprentissage automatique : du problème à un modèle fiablePrétraiter avec un pipeline
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
Utilisez les dix variables dans un seul modèle, avec exactement la même préparation pour chaque billet. Cela vaut pour l’entraînement, la validation et, plus tard, chaque nouveau billet de soutien. Voyez d’abord ce qui pose problème quand vous préparez les variables à la main. Résolvez ensuite ces problèmes avec un pipeline et prouvez qu’il a appris seulement des données d’entraînement.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter contient tout le code, les tâches et les vérifications de cette leçon. Il contient aussi l’exercice du module et la vérification de fin. Il montre chaque problème de préparation manuelle des variables, puis écrit ticket_model.py avec une cellule %%writefile. Ses cellules de configuration installent pandas et scikit-learn et téléchargent les données d’exercice. Vous n’avez donc rien à configurer localement.
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
- La régression logistique, les scores,
predict_probaetpredict, et pourquoi 0,5 ne signale presque rien. Voir Comprendre la classification : predict_proba et predict. - Les données d’entraînement, de validation et de test, et pourquoi le modèle doit apprendre seulement des données d’entraînement. Voir Données d’entraînement, de validation et de test.
- L’ajustement, la transformation et les fuites dues au prétraitement. Voir Ajuster les transformations correctement : ajuster, puis transformer dans SQL et préparation des données pour l’IA.
- La précision et le rappel, dans Les métriques de classification. La précision moyenne et l’aire sous la courbe ROC, dans Le compromis entre précision et rappel.
- Les références de base sur les données de validation, dans Définir une référence de base.
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-model, avec son environnement.venvet les données dansdata/. Vous les avez créés dans Définir une référence de base : préparer votre projet. Si les données manquent ou ont changé, exécutezpython get_data.py --resetdans le dossier du projet. - Cette leçon ajoute
ticket_model.pyau dossier du projet, dans Écrire ticket_model.py. Si vous le perdez ou l’endommagez plus tard, copiez-le de nouveau depuis cette section. Ne copiez pas encore le fichier deC05/project: cette version finale contient déjà la caractéristique qu’ajoute le module 6.
Testé sur macOS avec Python 3.14.6, pandas 3.0.6 et scikit-learn 1.9.1. Les étapes pour Windows et Linux n’ont pas été testées. Le carnet a été testé dans Jupyter.
Pourquoi le modèle s’est arrêté
Section intitulée « Pourquoi le modèle s’est arrêté »Dans la dernière leçon, la régression logistique de Priya utilisait seulement quatre des dix variables. Ces quatre variables numériques ont une valeur pour chaque billet. Les six autres variables sont utiles aussi. L’équipe et l’historique du client devraient aider. Les billets payment (paiement) sont transférés à l’équipe spécialisée dans 20,7 % des cas, contre 5,5 % pour account (compte). Elle essaie donc d’ajouter une variable de chaque type :
import pandas as pdfrom sklearn.linear_model import LogisticRegression
TARGET = "escalated_72h"CATEGORIES = ["channel", "team", "segment", "region"]NUMBERS = ["priority", "order_value", "word_count", "customer_tenure_days", "prior_tickets_90d", "created_hour"]FEATURES = CATEGORIES + NUMBERStrain = pd.read_csv("data/train.csv")valid = pd.read_csv("data/valid.csv")
for columns in (["priority", "team"], ["priority", "order_value"]): try: LogisticRegression().fit(train[columns], train[TARGET]) except ValueError as error: print(columns, "->", str(error).splitlines()[0])La sortie :
['priority', 'team'] -> could not convert string to float: 'delivery'['priority', 'order_value'] -> Input X contains NaN.Les deux tentatives échouent, pour des raisons différentes. Pour comprendre pourquoi, regardez six vrais billets des données d’entraînement, avec cinq de leurs colonnes :
six = train.head(6)[["ticket_id", "team", "order_value", "word_count", "customer_tenure_days", "priority"]]print(six.to_string(index=False))La sortie :
ticket_id team order_value word_count customer_tenure_days priority T-100001 delivery 53.76 48 738.0 3 T-100002 warranty 24.30 85 1496.0 1 T-100003 returns 699.55 111 1243.0 3 T-100004 returns 108.07 112 1625.0 3 T-100005 account NaN 45 1186.0 3 T-100006 account NaN 46 46.0 2Trois problèmes sont visibles dans ces six lignes. team (équipe) contient du texte, et un modèle peut seulement multiplier des nombres par des poids. order_value (valeur de la commande) contient des valeurs manquantes. pandas affiche une cellule vide avec NaN, qui signifie « pas un nombre ». Et les nombres ont des échelles très différentes : la priorité va de 1 à 3, l’ancienneté atteint 1 625 jours. Avant de découvrir l’outil qui résout ces problèmes, examinez-les un à la fois.
Problème 1 : du texte que le modèle ne peut pas lire
Section intitulée « Problème 1 : du texte que le modèle ne peut pas lire »Une régression logistique calcule z = intercept + weight × feature pour chaque variable. « Livraison » multiplié par un poids ne veut rien dire. Chaque catégorie de texte doit donc devenir des nombres.
La méthode habituelle est l’encodage à chaud : une nouvelle colonne pour chaque catégorie. La colonne correspondant à la catégorie du billet reçoit 1, et les autres colonnes reçoivent 0. Dans pandas, get_dummies le fait :
print(pd.get_dummies(six["team"], dtype=int).to_string())La sortie :
account delivery returns warranty0 0 1 0 01 0 0 0 12 0 0 1 03 0 0 1 04 1 0 0 05 1 0 0 0Chaque billet a exactement un 1, dans la colonne de son équipe. Chaque équipe peut maintenant avoir son propre poids. Le modèle peut donc apprendre que payment ajoute du risque et que account en enlève.
« Mais pourquoi ne pas simplement numéroter les équipes? » demande Tomás. « Compte, c’est 1, livraison 2, paiement 3, et ainsi de suite. Une colonne au lieu de cinq. » Le problème, c’est que les nombres ont un ordre et des distances. Avec ces nombres, le modèle traiterait paiement comme « trois fois compte ». Il devrait aussi attribuer à livraison un risque exactement à mi-chemin entre compte et paiement. Les équipes n’ont pas cet ordre. Les colonnes d’encodage à chaud n’imposent rien de tel : chaque équipe reste simplement elle-même.
Gardez un détail en tête pour plus tard : get_dummies crée une colonne par catégorie dans les lignes qu’il reçoit. Ces six billets ont quatre équipes, donc il a créé quatre colonnes. Les données d’entraînement complètes en ont cinq.
Problème 2 : les valeurs manquantes
Section intitulée « Problème 2 : les valeurs manquantes »Les valeurs manquantes dans order_value ne sont pas des erreurs. Tous les billets account concernent un compte, pas une commande. Ils n’ont donc pas de valeur de commande. Environ une commande sur 100 n’a pas été trouvée non plus. De même, customer_tenure_days (ancienneté du client en jours) est vide pour les 202 clients invités, qui n’ont pas de compte. Un modèle ne peut pas multiplier une cellule vide par un poids. Il faut donc une valeur pour chaque cellule vide.
« Pourquoi ne pas simplement supprimer les lignes où il manque une valeur? » demande Tomás. Il y a deux raisons. D’abord, cela élimine beaucoup de données. Sur les 16 939 billets d’entraînement, 2 629 (15,5 %) ont au moins une valeur manquante. Ensuite, et c’est pire, cela ne fonctionne pas pour les nouveaux billets. Quand un client invité écrit demain, le service d’acheminement doit donner un score à son billet. Il ne peut pas l’ignorer.
Vous remplissez donc chaque cellule vide avec une valeur : c’est l’imputation. Un choix courant est la médiane de la colonne, la valeur du milieu. Voici pourquoi la médiane est plus sûre que la moyenne, sur les six billets :
known = six["order_value"].dropna()print("median:", round(known.median(), 2), "| mean:", round(known.mean(), 2))print("train tickets with a gap:", train[FEATURES].isna().any(axis=1).sum(), "of", len(train))missing = train["order_value"].isna()print("escalation rate, order_value missing:", round(train.loc[missing, TARGET].mean(), 3))print("escalation rate, order_value known:", round(train.loc[~missing, TARGET].mean(), 3))La sortie :
median: 80.91 | mean: 221.42train tickets with a gap: 2629 of 16939escalation rate, order_value missing: 0.059escalation rate, order_value known: 0.127Les quatre valeurs de commande connues sont 24,30, 53,76, 108,07 et 699,55. Une grosse commande tire la moyenne vers 221,42, une valeur qui ne représente aucun billet typique. La médiane, 80,91, reste au milieu. Les valeurs de commande des données d’entraînement complètes sont aussi inégales, avec quelques commandes de plus de 1 000 $. Dans le carnet, vous calculez la médiane et la moyenne de toutes les données d’entraînement et vous les comparez.
Les deux dernières lignes montrent autre chose. Les billets sans valeur de commande sont transférés moins souvent (5,9 %) que les autres (12,7 %). Ils concernent surtout des questions de compte. La valeur manquante est donc elle-même une information. Un bon outil d’imputation remplit la cellule vide et ajoute une colonne indicatrice. Elle contient 1 quand la valeur manquait, et 0 quand elle était présente. Le modèle peut alors donner son propre poids à « sans commande ».
Problème 3 : des échelles très différentes
Section intitulée « Problème 3 : des échelles très différentes »Les six variables numériques ont des étendues très différentes dans les données d’entraînement :
print(train[NUMBERS].agg(["min", "max"]).T.to_string())La sortie :
min maxpriority 1.0 3.0order_value 4.5 1450.0word_count 0.0 883.0customer_tenure_days 0.0 2607.0prior_tickets_90d 0.0 58.0created_hour 0.0 23.0Vous avez vu cet effet dans la dernière leçon. Dans le modèle à quatre variables, le poids de word_count (nombre de mots) était seulement de 0,0052. Pourtant, 300 mots ajoutaient 1,56 à z, plus qu’un niveau de priorité. La taille d’un poids dépendait de l’unité de sa variable. Vous ne pouviez donc pas comparer les poids. Des échelles très différentes ralentissent aussi le solveur, la méthode qui cherche les meilleurs poids. Il peut s’arrêter avant de les trouver. La variante autonome de cette leçon montre cet avertissement.
La mise à l’échelle place tous les nombres sur une échelle semblable. La méthode habituelle, la standardisation, soustrait la moyenne de la colonne et divise par son écart-type. Dans les données d’entraînement, word_count a une moyenne de 106,13 et un écart-type de 70,81. Un message de 300 mots devient donc (300 − 106,13) ÷ 70,81 = 2,74. Cela signifie « 2,74 écarts-types au-dessus de la longueur habituelle ». priority (priorité) a une moyenne de 2,37 et un écart-type de 0,72. La priorité 1 devient donc −1,91. Les deux nombres s’expriment maintenant sur la même échelle.
Tous les modèles n’ont pas besoin d’une mise à l’échelle. Les arbres de décision du module 5 posent des questions comme « le nombre de mots dépasse-t-il 200? ». La réponse ne change pas avec l’unité. La régression logistique en a besoin.
Le vrai problème : chaque étape apprend quelque chose
Section intitulée « Le vrai problème : chaque étape apprend quelque chose »Les trois corrections semblent simples. La difficulté se cache dans un fait : chaque correction apprend des nombres à partir des données. L’encodeur apprend la liste des catégories de chaque colonne. L’outil d’imputation apprend la médiane de chaque colonne. L’outil de mise à l’échelle apprend la moyenne et l’écart-type de chaque colonne. Ces nombres appris font partie du modèle, tout comme ses poids.
Imaginez un café qui prépare de la limonade. Le premier jour, la propriétaire goûte et décide de la recette : deux cuillères de sucre par verre. Elle l’écrit sur une fiche. Chaque verre préparé ensuite suit cette fiche, peu importe qui le prépare ou quand. Si chaque personne goûtait son verre et choisissait de nouveau la quantité de sucre, chaque verre serait différent. Si quelqu’un oubliait le sucre, le client recevrait une limonade acide. Personne ne le remarquerait avant une plainte.
La préparation des variables fonctionne de la même façon, avec deux règles :
- Apprenez les nombres seulement à partir des données d’entraînement. Les données de validation et de test doivent rester inconnues, comme dans Données d’entraînement, de validation et de test. Si la médiane vient de toutes les lignes, de l’information des données de validation entre dans le modèle. Le cours SQL et préparation des données pour l’IA a mesuré cela dans Ajuster les transformations correctement.
- Appliquez exactement les mêmes nombres et les mêmes étapes, dans le même ordre, à chaque ligne ultérieure. Cela comprend les données de validation, les données de test et chaque nouveau billet, des mois plus tard.
À la main : quatre objets à garder en ordre
Section intitulée « À la main : quatre objets à garder en ordre »Priya suit d’abord les deux règles à la main. Elle ajuste chaque étape sur les données d’entraînement. Elle écrit ensuite une fonction qui applique les trois étapes ajustées à n’importe quelles lignes :
import numpy as npfrom sklearn.impute import SimpleImputerfrom sklearn.preprocessing import OneHotEncoder, StandardScaler
imputer = SimpleImputer(strategy="median", add_indicator=True).fit(train[NUMBERS])scaler = StandardScaler().fit(imputer.transform(train[NUMBERS]))encoder = OneHotEncoder().fit(train[CATEGORIES])
def prepare_by_hand(rows): """The three steps, with what they learned from train, for any rows.""" numbers = scaler.transform(imputer.transform(rows[NUMBERS])) categories = encoder.transform(rows[CATEGORIES]).toarray() return np.hstack([categories, numbers])SimpleImputer, StandardScaler et OneHotEncoder sont les versions scikit-learn des trois corrections. Chacun a une méthode fit (ajuster), qui apprend les nombres, et une méthode transform (transformer), qui les applique. np.hstack joint les colonnes de catégories et les colonnes numériques côte à côte. Elle entraîne ensuite le modèle et calcule les scores des données de validation et d’un billet :
from sklearn.metrics import average_precision_score
model = LogisticRegression(max_iter=1000).fit(prepare_by_hand(train), train[TARGET])scores = model.predict_proba(prepare_by_hand(valid))[:, 1]print("AP on valid:", round(average_precision_score(valid[TARGET], scores), 3))one = valid.head(1)print("score of", one["ticket_id"].iloc[0] + ":", round(model.predict_proba(prepare_by_hand(one))[0, 1], 3))La sortie :
AP on valid: 0.353score of T-116940: 0.184Cela fonctionne. La précision moyenne (AP) est de 0,353. C’est beaucoup mieux que la règle « signaler la priorité 1 », qui obtient 0,228. Le tableau des résultats ci-dessous montre les deux. Mais il y a maintenant quatre objets ajustés : l’outil d’imputation, l’outil de mise à l’échelle, l’encodeur et le modèle. Une fonction doit les appeler dans le bon ordre. Le service d’acheminement, le script d’évaluation et chaque carnet ultérieur doivent tout répéter, exactement. Voici ce qui pose problème en utilisation réelle.
Ce qui pose problème : une étape oubliée
Section intitulée « Ce qui pose problème : une étape oubliée »Des mois plus tard, Tomás écrit le service d’acheminement. Il copie les étapes, mais oublie l’outil de mise à l’échelle. Rien ne l’en empêche :
def forgot_scaler(rows): """The same steps, but the scaler is missing.""" return np.hstack([encoder.transform(rows[CATEGORIES]).toarray(), imputer.transform(rows[NUMBERS])])
print("score of", one["ticket_id"].iloc[0] + ", scaler forgotten:", round(model.predict_proba(forgot_scaler(one))[0, 1], 3))bad = model.predict_proba(forgot_scaler(valid))[:, 1]print("valid tickets with a score of at least 0.5:", (bad >= 0.5).sum(), "of", len(valid))La sortie :
score of T-116940, scaler forgotten: 0.999valid tickets with a score of at least 0.5: 875 of 1179Le même billet obtient maintenant 0,999 au lieu de 0,184. Le modèle a appris ses poids pour des nombres mis à l’échelle, proches de 0. Sans mise à l’échelle, ces poids multiplient des valeurs brutes, comme 1 528 jours d’ancienneté, et z explose. Sur 1 179 billets, 875 obtiennent un score d’au moins 0,5, contre 18 avec la mise à l’échelle. Et il n’y a aucun message d’erreur : le service fonctionne, et les signalements submergent l’équipe spécialisée.
Ce qui pose problème : une catégorie absente de l’entraînement
Section intitulée « Ce qui pose problème : une catégorie absente de l’entraînement »Le 15 juillet 2026, Larkfield ajoute un nouveau canal de contact, social (réseaux sociaux). Le premier billet de ce canal arrive dans le code écrit à la main :
july = pd.read_csv("data/july_tickets.csv")social = july[july["channel"] == "social"].head(1)try: prepare_by_hand(social)except ValueError as error: print("ValueError:", str(error).splitlines()[0])La sortie :
ValueError: Found unknown categories ['social'] in column 0 during transformCette fois, il y a une erreur, et le service ne peut pas du tout calculer le score du billet. L’encodeur connaît seulement les cinq canaux des données d’entraînement. Vous pouvez lui indiquer quoi faire avec une nouvelle catégorie (handle_unknown="ignore"), mais vous devez y penser. Si vous utilisez plutôt get_dummies, c’est pire. Comme au problème 1, il crée des colonnes à partir des lignes qu’il reçoit. Un seul billet de réseaux sociaux aurait une seule colonne de canal, channel_social, que le modèle n’a jamais vue. Le nombre de colonnes ne correspondrait pas.
Ce qui pose aussi problème
Section intitulée « Ce qui pose aussi problème »Trois autres problèmes ont la même origine :
- Le mauvais ordre des colonnes. Le modèle n’a pas de noms pour ses 27 colonnes d’entrée, seulement des positions. Si le service envoie les colonnes numériques dans un autre ordre, chaque poids multiplie la mauvaise variable. scikit-learn s’arrête avec une erreur quand il reçoit un tableau dont les colonnes nommées sont dans un autre ordre. Mais un service Web envoie souvent des valeurs sans noms. scikit-learn peut alors seulement afficher un avertissement.
- L’ajustement sur les mauvaises lignes. Tomás veut « plus de données pour les médianes ». Il ajuste l’outil d’imputation sur les données d’entraînement et de validation réunies. Rien ne signale le problème. Les données de validation ont maintenant contaminé le modèle. Leur score n’est plus une vérification honnête.
- Rien à transmettre. Pour utiliser le modèle ailleurs, vous devez enregistrer quatre objets, les deux listes de colonnes et l’ordre des étapes. Le module 8 empaquette le modèle dans un fichier pour une autre équipe. Avec des objets séparés, ce fichier est une boîte de pièces sans plan d’assemblage.
Aucun de ces problèmes ne vient du modèle. Chacun vient de la difficulté à garder plusieurs éléments séparés identiques partout, pendant longtemps. Et ce sont des personnes qui ne les ont pas écrits qui doivent le faire.
La solution : un pipeline
Section intitulée « La solution : un pipeline »Un pipeline est un objet qui contient toutes les étapes de préparation et le modèle, dans l’ordre. C’est la fiche de recette du café, transformée en code. Vous appelez fit une fois, sur les données d’entraînement. Le pipeline ajuste alors chaque étape sur ces lignes, l’une après l’autre. Chaque étape apprend à partir de la sortie de l’étape précédente. Vous appelez predict_proba sur les lignes ultérieures, qu’il s’agisse d’un mois complet ou d’un seul billet. Le pipeline applique alors chaque étape ajustée dans le même ordre, avec les nombres appris des données d’entraînement. Ensuite, il applique le modèle. Le nom vient des conduites d’eau : ce qui sort d’une section entre dans la suivante.
Description textuelle du diagramme
Le côté gauche montre le travail à la main. Un nouveau billet passe dans deux branches. Les nombres vont à l’outil d’imputation, qui remplit les cellules vides avec les médianes d’entraînement. Ils passent ensuite à l’outil de mise à l’échelle, avec les moyennes et les écarts-types d’entraînement. Les catégories vont à l’encodeur, avec les catégories d’entraînement. Une étape de jonction place les colonnes côte à côte, dans le même ordre qu’à l’entraînement. Le modèle, avec ses poids d’entraînement, donne le score. Votre code doit appeler chacune de ces cinq boîtes. Le côté droit montre un pipeline. Le billet entre dans un seul objet, pipeline.predict_proba(ticket), qui contient les mêmes cinq étapes dans le même ordre. Un seul appel donne le score.
Le pipeline apporte une réponse à chaque problème présenté plus haut :
| Problème à la main | Ce que fait le pipeline |
|---|---|
| Une étape est oubliée au moment de prédire | Les étapes font partie de l’objet; predict_proba les exécute toujours toutes. |
| Une étape est ajustée sur les mauvaises lignes | Un seul appel à fit ajuste chaque étape sur les mêmes lignes. Si vous fournissez les données d’entraînement, toutes apprennent seulement de ces données. |
| Le mauvais ordre des colonnes | Le pipeline sélectionne les colonnes par leur nom et les joint dans le même ordre chaque fois. |
| Une nouvelle catégorie | OneHotEncoder(handle_unknown="ignore") est inscrit une fois dans le pipeline. Chaque appel gère donc les nouvelles catégories. |
| Rien à transmettre | Le pipeline ajusté est un seul objet. Vous pouvez donc l’enregistrer dans un seul fichier (module 8). |
Un pipeline ne choisit pas les lignes pour vous. Si vous appelez fit sur les données d’entraînement et de validation réunies, il apprend des deux. Le cas d’échec de cette leçon le montre.
Deux branches : le ColumnTransformer
Section intitulée « Deux branches : le ColumnTransformer »Les dix variables ont besoin de deux types de préparation. Les catégories ont besoin d’encodage à chaud, et les nombres d’imputation, puis de mise à l’échelle. Un ColumnTransformer envoie chaque liste de colonnes à ses propres étapes et joint les résultats. C’est la boîte de « jonction » du schéma. Dans le pipeline, il vient en premier, et le modèle vient en dernier.
Un pipeline sur quatre billets
Section intitulée « Un pipeline sur quatre billets »Avant les vraies données, essayez un pipeline sur quatre billets de soutien inventés, assez peu nombreux pour vérifier à la main. Ils ont une catégorie et un nombre, et une valeur de order_value manque. Le ColumnTransformer envoie channel à l’encodeur, et order_value à l’imputeur, puis au dispositif de mise à l’échelle :
from sklearn.compose import ColumnTransformerfrom sklearn.pipeline import Pipeline
tiny = pd.DataFrame({"channel": ["email", "chat", "email", "phone"], "order_value": [20.0, None, 60.0, 100.0]})numbers = Pipeline([("impute", SimpleImputer(strategy="median", add_indicator=True)), ("scale", StandardScaler())])prepare = ColumnTransformer([("categories", OneHotEncoder(handle_unknown="ignore"), ["channel"]), ("numbers", numbers, ["order_value"])], verbose_feature_names_out=False)prepare.fit(tiny)print(pd.DataFrame(prepare.transform(tiny), columns=prepare.get_feature_names_out()).round(2).to_string())La sortie :
channel_chat channel_email channel_phone order_value missingindicator_order_value0 0.0 1.0 0.0 -1.41 -0.581 1.0 0.0 0.0 0.00 1.732 0.0 1.0 0.0 0.00 -0.583 0.0 0.0 1.0 1.41 -0.58Vérifiez à la main :
- Encodage one-hot : l’ajustement a vu trois canaux, donc il y a trois colonnes de canal. Le billet de clavardage a 1 dans
channel_chatet 0 dans les autres. - Imputation : les valeurs connues sont 20, 60 et 100. La médiane est donc 60, et la valeur manquante devient 60.
- Mise à l’échelle : les valeurs sont maintenant 20, 60, 60 et 100. La moyenne est 60 et l’écart-type est 28,28. Donc 20 devient (20 − 60) ÷ 28,28 = −1,41, et 60 devient 0,00.
- Indicateur : le deuxième billet avait une valeur manquante, donc son indicateur vaut 1. Le dispositif met aussi la colonne d’indicateur à l’échelle (0 devient −0,58 et 1 devient 1,73). Cela ne pose aucun problème : elle indique toujours « valeur manquante ou non ».
Un nouveau billet arrive maintenant d’un canal jamais vu pendant l’ajustement, social, sans valeur de commande :
new_ticket = pd.DataFrame({"channel": ["social"], "order_value": [None]})print(pd.DataFrame(prepare.transform(new_ticket), columns=prepare.get_feature_names_out()).round(2).to_string())La sortie :
channel_chat channel_email channel_phone order_value missingindicator_order_value0 0.0 0.0 0.0 0.0 1.73Le problème du code manuel est résolu. Ce billet reçoit les mêmes cinq colonnes que les quatre billets d’entraînement. handle_unknown="ignore" donne au canal inconnu 0 dans chaque colonne de canal : « aucun des canaux que je connais ». La valeur manquante reçoit la médiane apprise pendant l’ajustement, 60, qui devient 0,0 après mise à l’échelle. Le pipeline n’apprend rien du nouveau billet. La transformation utilise seulement ce que l’ajustement a appris des quatre billets d’entraînement.
Ajuster sur les données d’entraînement seulement
Section intitulée « Ajuster sur les données d’entraînement seulement »Dans le vrai pipeline, la règle du cours SQL reste la même : seules les données d’entraînement atteignent fit (ajuster). Les données de validation et de test passent seulement dans le pipeline ajusté. Le cours SQL l’a montré dans Un pipeline respecte la règle pour vous.
Description textuelle du diagramme
Le fichier d’entraînement, soit 16 939 billets de juillet 2024 à avril 2026, est la seule entrée de fit. L’ajustement apprend tout dans le pipeline : médianes, moyennes et écarts-types, listes de catégories et coefficients du modèle (ses poids). Le résultat est le pipeline ajusté. Le fichier de validation, soit 1 179 billets de mai 2026, entre dans predict_proba avec le pipeline ajusté. Cela donne les scores utilisés pour comparer les modèles et choisir les réglages. Le fichier de test, soit 1 002 billets de juin 2026, entre dans predict_proba une seule fois, à la fin du module 6. Aucune flèche ne va de valid ou test vers fit.
Écrire ticket_model.py
Section intitulée « Écrire ticket_model.py »Priya place les listes de colonnes et le pipeline dans un seul fichier, ticket_model.py. Chaque leçon, script et carnet (notebook) Jupyter qui suit l’importe. Ainsi, chaque modèle utilise les mêmes colonnes et les mêmes étapes. C’est la fiche de recette, écrite une seule fois. Dans le carnet, une cellule %%writefile ticket_model.py l’écrit. Sur votre ordinateur, créez le fichier dans ~/projects/ticket-model, à côté de data/, avec exactement ce contenu :
"""The shared parts of Larkfield's escalation model: the columns, how to loada part of the data, and the preprocessing-plus-model pipeline.
Nextia Learning, C05, Module 4, lesson 3 (Preprocess with a pipeline)."""
from pathlib import Path
import pandas as pdfrom sklearn.compose import ColumnTransformerfrom sklearn.impute import SimpleImputerfrom sklearn.linear_model import LogisticRegressionfrom sklearn.pipeline import Pipelinefrom sklearn.preprocessing import OneHotEncoder, StandardScaler
DATA = Path("data")CATEGORIES = ["channel", "team", "segment", "region"]NUMBERS = ["priority", "order_value", "word_count", "customer_tenure_days", "prior_tickets_90d", "created_hour"]FEATURES = CATEGORIES + NUMBERSTARGET = "escalated_72h"
def read(path): """Read a ticket CSV file. Only an empty cell is missing: the text "unknown" stays a category.""" return pd.read_csv(path, parse_dates=["created_at"], dtype={"customer_id": "string"}, keep_default_na=False, na_values=[""])
def load(part): """One part of the split: train, valid or test.""" return read(DATA / f"{part}.csv")
def build_pipeline(model=None): """Preprocessing and model in one object. fit() learns the medians, the scaling and the categories from the rows it is given, and nothing else.""" 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", model if model is not None else LogisticRegression(max_iter=1000))])Chaque partie a sa raison d’être :
| Partie | Pourquoi |
|---|---|
CATEGORIES, NUMBERS, FEATURES, TARGET |
Des listes explicites. ticket_id, customer_id et created_at n’y figurent pas. Un identifiant ou une date ne peut donc pas devenir une variable par accident. |
read() |
Lit un fichier de la même façon chaque fois. keep_default_na=False, na_values=[""] considère uniquement une cellule vide comme manquante. La région unknown (inconnue) reste donc une catégorie. |
load(part) |
Un appel court pour "train", "valid" ou "test". |
build_pipeline(model=None) |
La préparation et un modèle dans un seul Pipeline, créé par une seule fonction. Chaque appel reçoit les mêmes étapes. Vous pouvez passer un autre modèle, par exemple une référence de base. Le modèle par défaut est LogisticRegression(max_iter=1000). |
max_iter=1000 |
Permet au solveur de fonctionner assez longtemps pour terminer sur ces données. |
build_pipeline() renvoie un pipeline non ajusté. Le pipeline n’apprend rien avant l’appel à fit. Vérifiez le fichier dans un terminal, dans le dossier du projet, avec .venv activé :
python -c "import ticket_model; print(len(ticket_model.FEATURES), 'features')"La sortie :
10 featuresSi vous voyez ModuleNotFoundError: No module named 'ticket_model' (module nommé « ticket_model » introuvable), le terminal n’est pas dans ~/projects/ticket-model. Si vous voyez un SyntaxError (erreur de syntaxe), comparez votre fichier au code ci-dessus, ligne par ligne.
Exemple détaillé : ajuster et examiner l’intérieur
Section intitulée « Exemple détaillé : ajuster et examiner l’intérieur »Priya ajuste le pipeline uniquement sur les données d’entraînement. Puis elle examine l’intérieur : l’imputeur a-t-il appris les médianes d’entraînement?
from ticket_model import CATEGORIES, FEATURES, NUMBERS, TARGET, build_pipeline, load
train, valid = load("train"), load("valid")pipeline = build_pipeline()pipeline.fit(train[FEATURES], train[TARGET])
prepare = pipeline.named_steps["prepare"]learned = prepare.named_transformers_["numbers"]["impute"].statistics_print(pd.DataFrame({"learned": learned, "train median": train[NUMBERS].median(), "valid median": valid[NUMBERS].median()}).to_string())La sortie :
learned train median valid medianpriority 3.00 3.00 3.00order_value 86.56 86.56 85.71word_count 89.00 89.00 90.00customer_tenure_days 1178.00 1178.00 1342.50prior_tickets_90d 5.00 5.00 6.00created_hour 14.00 14.00 14.00named_steps["prepare"] est le ColumnTransformer; named_transformers_["numbers"]["impute"] est l’imputeur ajusté qu’il contient. Ses statistics_ sont les médianes apprises. Elles égalent les médianes d’entraînement à chaque ligne, et quatre diffèrent des médianes de validation. Cela prouve que l’ajustement a appris des données d’entraînement. Si les lignes de validation avaient atteint l’ajustement, customer_tenure_days ne vaudrait pas 1 178.
Ensuite, les catégories apprises par l’encodeur :
encoder = prepare.named_transformers_["categories"]for column, categories in zip(CATEGORIES, encoder.categories_): print(column, list(categories))print(len(prepare.get_feature_names_out()), "columns go into the model")La sortie :
channel ['chat', 'email', 'phone', 'unknown', 'web_form']team ['account', 'delivery', 'payment', 'returns', 'warranty']segment ['business', 'guest', 'home', 'trade']region ['east', 'north', 'south', 'unknown', 'west']27 columns go into the model27 = 19 colonnes one-hot + 6 nombres + 2 indicateurs. Seules order_value et customer_tenure_days avaient des valeurs manquantes dans les données d’entraînement. Elles seules reçoivent donc un indicateur. Un billet ultérieur avec une catégorie absente de ces listes reçoit 0 dans toutes les colonnes de cette catégorie.
Maintenant, les scores sur les données de validation :
from sklearn.metrics import average_precision_score, roc_auc_score
scores = pipeline.predict_proba(valid[FEATURES])[:, 1]print("ROC AUC on valid:", round(roc_auc_score(valid[TARGET], scores), 3))print("AP on valid:", round(average_precision_score(valid[TARGET], scores), 3))La sortie :
ROC AUC on valid: 0.738AP on valid: 0.353Ce sont les mêmes scores que dans le code manuel de Priya, car les étapes sont les mêmes. predict_proba(valid) n’a rien ajusté. Il a transformé les billets de mai avec les médianes, moyennes, écarts-types et catégories d’entraînement. Puis il leur a attribué des scores avec les coefficients d’entraînement.
Résultats : référence de base, règle et modèle
Section intitulée « Résultats : référence de base, règle et modèle »Un nombre seul ne veut rien dire, donc chaque résultat accompagne les références de base. Priya définit summary, une ligne du tableau des résultats. Elle affiche les signalements, la précision et le rappel d’une décision, ainsi que l’aire sous la courbe ROC et la précision moyenne (AP) d’un score :
def summary(name, scores, flags): """One line of the results table, on the validation set.""" flagged = int(flags.sum()) caught = int((flags & (valid[TARGET] == 1)).sum()) precision = caught / flagged if flagged else float("nan") recall = caught / valid[TARGET].sum() auc = roc_auc_score(valid[TARGET], scores) ap = average_precision_score(valid[TARGET], scores) print(f"{name:9s} flagged {flagged:3d} ({flagged / len(valid):.1%}), precision {precision:.3f}, " f"recall {recall:.3f}, ROC AUC {auc:.3f}, AP {ap:.3f}")La référence de base majoritaire passe par le même pipeline, avec DummyClassifier comme modèle. Elle ignore les variables et donne à chaque billet le taux d’entraînement comme score. Les signalements 0/1 de la règle servent aussi de score.
from sklearn.dummy import DummyClassifier
majority = build_pipeline(DummyClassifier(strategy="prior")).fit(train[FEATURES], train[TARGET])summary("majority", majority.predict_proba(valid[FEATURES])[:, 1], majority.predict(valid[FEATURES]))
rule = (valid["priority"] == 1).astype(int)summary("rule", rule, rule)
summary("logistic", scores, pipeline.predict(valid[FEATURES]))La sortie :
majority flagged 0 (0.0%), precision nan, recall 0.000, ROC AUC 0.500, AP 0.155rule flagged 163 (13.8%), precision 0.374, recall 0.333, ROC AUC 0.615, AP 0.228logistic flagged 18 (1.5%), precision 0.556, recall 0.055, ROC AUC 0.738, AP 0.353Les mêmes résultats dans un tableau :
| Données de validation, 1 179 billets | Signalés | Précision | Rappel | Aire sous la courbe ROC | AP |
|---|---|---|---|---|---|
| Classe majoritaire : ne jamais transférer au niveau supérieur | 0 (0 %) | — | 0,000 | 0,5 | 0,155 |
| Règle : signaler la priorité 1 | 163 (13,8 %) | 0,374 | 0,333 | 0,615 | 0,228 |
Régression logistique, 10 variables (predict, 0,5) |
18 (1,5 %) | 0,556 | 0,055 | 0,738 | 0,353 |
Ce que dit le tableau :
- Le modèle classe beaucoup mieux que la règle : AP de 0,353 contre 0,228, et aire sous la courbe ROC de 0,738 contre 0,615. Dix variables apportent plus d’information qu’une seule.
- Sa décision à 0,5 est inutile pour Grace : 18 signalements, dont 10 transférés au niveau supérieur. C’est encore le seuil par défaut de la leçon précédente.
- Une comparaison équitable des décisions utilise la même charge de travail. L’exercice du module compare le modèle et la règle à 163 signalements chacun.
Quand utiliser un pipeline, et ce qu’il coûte
Section intitulée « Quand utiliser un pipeline, et ce qu’il coûte »Utilisez un pipeline dès qu’une étape de préparation apprend des données : médiane, moyenne, liste de catégories, vocabulaire de mots. Cela couvre presque tous les vrais modèles. Le modèle évalué est alors exactement celui que vous livrez. Un appel l’ajuste et un appel l’utilise.
Le coût est faible, mais réel. Le code est moins direct : pour examiner l’intérieur, il faut des noms comme named_steps["prepare"] et des chemins comme prepare__numbers__scale. Un message d’erreur provenant du fond d’un pipeline peut être plus difficile à lire. Le pipeline protège aussi l’ordre et la cohérence des étapes, pas le choix des lignes. Vous décidez toujours ce qui entre dans fit.
Exercice guidé : qu’a appris l’ajustement?
Section intitulée « Exercice guidé : qu’a appris l’ajustement? »- Ouvrez le carnet de la leçon dans Colab ou Kaggle, et exécutez les cellules de configuration.
- Dans les sections 1 à 4, exécutez les cellules du problème. Vous devriez voir les deux erreurs, les six billets, les quatre colonnes d’équipe,
median: 80.91(médiane : 80,91) et les plages des nombres. - Dans la section 5, exécutez les cellules manuelles. Vous devriez voir
score of T-116940: 0.184(score de T-116940 : 0,184), puis0.999avec la mise à l’échelle oubliée, puis leValueError(erreur de valeur) pour le billet social. - Dans la section 6, prédisez la médiane de 20, 60 et 100. Puis exécutez la cellule. Vous devriez voir 0,00 pour la valeur manquante remplie.
- Dans la section 7, exécutez la cellule
%%writefile. Vous devriez voirWriting ticket_model.py(écriture de ticket_model.py). - Dans la section 8, exécutez la cellule d’ajustement. Comparez les trois colonnes de médianes.
- Faites la tâche À vous de jouer : listez les colonnes numériques dont la médiane changerait si l’ajustement utilisait les données de validation.
- Exécutez la cellule de vérification. Elle affiche
Check passed(vérification réussie) quand votre liste est correcte.
Afficher la réponse à l’étape 7
valid_medians = valid[NUMBERS].median()would_change = sorted(name for name, value in zip(NUMBERS, learned) if value != valid_medians[name])print(would_change)La sortie :
['customer_tenure_days', 'order_value', 'prior_tickets_90d', 'word_count']Quatre des six médianes diffèrent. Seules priority (3) et created_hour (14) ont la même médiane dans les deux jeux de données. Les médianes sont donc une empreinte des lignes utilisées pour l’ajustement : elles montrent qu’il a utilisé les données d’entraînement.
Variante autonome : retirer la mise à l’échelle
Section intitulée « Variante autonome : retirer la mise à l’échelle »Changez une chose : sautez la mise à l’échelle dans le pipeline, pendant l’entraînement cette fois. set_params change une étape du pipeline sans modifier le fichier, et "passthrough" signifie « sauter cette étape ». Le nom prepare__numbers__scale est le chemin vers l’étape : prepare, puis sa branche numbers, puis son étape scale. Avant de l’exécuter, prédisez : le score changera-t-il beaucoup? Se passera-t-il autre chose? La section 10 du carnet contient la cellule.
Afficher la réponse
import warnings
unscaled = build_pipeline().set_params(prepare__numbers__scale="passthrough")with warnings.catch_warnings(record=True) as caught_warnings: warnings.simplefilter("always") unscaled.fit(train[FEATURES], train[TARGET])for w in caught_warnings: print(type(w.message).__name__ + ":", str(w.message).splitlines()[0])unscaled_scores = unscaled.predict_proba(valid[FEATURES])[:, 1]print("ROC AUC", round(roc_auc_score(valid[TARGET], unscaled_scores), 3), "AP", round(average_precision_score(valid[TARGET], unscaled_scores), 3))La sortie :
ConvergenceWarning: lbfgs failed to converge after 1000 iteration(s) (status=1):ROC AUC 0.739 AP 0.354Les scores changent à peine (0,739 et 0,354, contre 0,738 et 0,353). Mais scikit-learn avertit : le solveur, lbfgs, s’est arrêté après 1 000 étapes sans trouver les meilleurs poids. C’est le problème 3 en action. order_value va jusqu’à 1 450 $ et customer_tenure_days jusqu’à 2 607 jours, tandis que les colonnes one-hot valent 0 ou 1. Avec des échelles aussi différentes, le solveur a besoin de beaucoup plus d’étapes.
Comparez cela à la mise à l’échelle oubliée plus tôt. Le modèle avait été entraîné avec elle et utilisé sans elle, et les scores étaient devenus erronés. Ici, le modèle est entraîné et utilisé sans elle, de façon cohérente. Les scores sont donc corrects, mais les poids ne sont pas les meilleurs. Gardez la mise à l’échelle. Un avertissement ignoré aujourd’hui cache un vrai problème demain.
Erreur courante
Un pipeline ajusté sur les mauvaises lignes
Symptôme : Tomás lit qu’un pipeline « garde le prétraitement cohérent ». Il veut plus de données pour les médianes. Il l’ajuste donc sur les données d’entraînement et de validation réunies. Le score de validation augmente :
both = pd.concat([train, valid])leaky = build_pipeline().fit(both[FEATURES], both[TARGET])leaky_scores = leaky.predict_proba(valid[FEATURES])[:, 1]print("ROC AUC on valid:", round(roc_auc_score(valid[TARGET], leaky_scores), 3))print("AP on valid:", round(average_precision_score(valid[TARGET], leaky_scores), 3))print("learned tenure median:", leaky.named_steps["prepare"].named_transformers_["numbers"]["impute"].statistics_[3])La sortie :
ROC AUC on valid: 0.742AP on valid: 0.36learned tenure median: 1191.0Les scores sont 0,742 et 0,360, un peu meilleurs que 0,738 et 0,353. La médiane apprise de customer_tenure_days est 1 191, et non la médiane d’entraînement de 1 178.
Cause : le pipeline a fait exactement ce qu’on lui a demandé. Un appel à fit ajuste chaque étape sur les lignes reçues, qui incluent ici celles de mai. Les médianes, la mise à l’échelle et les poids du modèle ont tous appris des billets de validation, avec leurs cibles. Le score de validation mesure maintenant à quel point le modèle se souvient des billets déjà vus. Il ne mesure plus son fonctionnement sur de nouveaux billets.
Correction : appelez fit uniquement avec les données d’entraînement, et utilisez les données de validation seulement pour calculer les scores. Pour vérifier un pipeline ajusté, comparez ses valeurs apprises aux données d’entraînement, comme dans l’exemple détaillé. Les médianes doivent égaler train[NUMBERS].median().
Conséquence dans le travail en IA : un pipeline peut produire des erreurs cohérentes aussi facilement qu’un travail cohérent. Ici, le gain semble faible, mais chaque décision prise avec ces données de validation favorise maintenant le modèle. Avec une étape qui apprend davantage de la cible, comme les variables causant une fuite du module 6, le faux gain peut être grand.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Qu'apprend l'ajustement dans chacune des trois étapes de préparation de build_pipeline(), et comment montrer qu'il a appris uniquement des données d'entraînement?
Afficher la réponse
OneHotEncoder apprend la liste des catégories de chaque colonne catégorielle (categories_). SimpleImputer apprend la médiane de chaque colonne numérique (statistics_) et quelles colonnes avaient des valeurs manquantes. StandardScaler apprend la moyenne et l’écart-type de chaque colonne après imputation (mean_, scale_). Pour montrer que l’ajustement a utilisé uniquement les données d’entraînement, comparez les valeurs apprises aux données. Les six médianes apprises égalent train[NUMBERS].median(). Quatre diffèrent des médianes de validation, par exemple customer_tenure_days : 1 178 dans les données d’entraînement, 1 342,5 dans celles de validation.
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
Un billet arrive avec region = 'islands', une région jamais vue dans les données d'entraînement, et sans valeur de commande. Que fait le pipeline ajusté avec chacune des deux valeurs?
Afficher la réponse
Avec handle_unknown="ignore", l’encodeur donne à islands (îles) 0 dans les cinq colonnes de région : « aucune des régions que je connais ». Le modèle attribue un score au billet avec les autres variables. La valeur manquante de order_value reçoit la médiane apprise des données d’entraînement, 86,56, et l’indicateur missingindicator_order_value vaut 1. Le pipeline n’apprend rien du nouveau billet, et aucune erreur n’arrête le service. Le module 8 ajoute une vérification qui compte les catégories inconnues dans les nouvelles données.
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 dit : mon code manuel ajuste chaque étape sur les données d'entraînement et donne la même AP de validation que le pipeline, 0,353. Alors pourquoi avons-nous besoin d'un pipeline? Donnez deux raisons tirées de cette leçon.
Afficher la réponse
Le code manuel est correct seulement si chaque utilisateur répète chaque étape, dans le même ordre, avec les mêmes objets ajustés. Dans cette leçon, l’oubli de la mise à l’échelle a donné au billet T-116940 un score de 0,999 plutôt que 0,184. Parmi les 1 179 billets, 875 ont atteint 0,5, sans erreur. Un nouveau canal, social, a arrêté l’encodeur manuel avec un ValueError. Il y a aussi d’autres raisons. L’ordre des colonnes peut changer, quelqu’un peut ajuster une étape sur les mauvaises lignes, et quatre objets séparés sont difficiles à enregistrer et à transmettre. Un pipeline garde les étapes, leur ordre et leurs nombres appris dans un seul objet : un fit sur les données d’entraînement, puis un predict_proba pour n’importe quelles lignes.
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 d’entraîner une référence de base et un classificateur linéaire sur des données préparées. Vous devez ensuite les comparer honnêtement. Vous avez fait la première partie dans le tableau des résultats ci-dessus. Le même pipeline a été exécuté une fois avec DummyClassifier et une fois avec LogisticRegression. Les deux ont été ajustés sur les données d’entraînement, puis comparés à la règle sur les données de validation.
Votre tâche : le modèle contre la règle, à charge de travail égale
Section intitulée « Votre tâche : le modèle contre la règle, à charge de travail égale »Le tableau des résultats compare les classements (ROC AUC, AP), mais Grace a besoin de décisions. La règle signale 163 billets de soutien dans les données de validation. Signalez plutôt les 163 billets de validation avec les scores les plus élevés du modèle. Combien ont été transmis au niveau supérieur? Quels sont la précision et le rappel? Le modèle est-il meilleur que la règle à charge de travail égale? La section 12 du carnet (notebook) Jupyter contient la tâche et une vérification.
Afficher la réponse modèle
top = scores.argsort()[::-1][:163]caught_163 = int(valid[TARGET].iloc[top].sum())precision_163 = round(caught_163 / 163, 3)recall_163 = round(caught_163 / valid[TARGET].sum(), 3)print(caught_163, precision_163, recall_163)La sortie :
66 0.405 0.361La comparaison à charge de travail égale :
| 163 billets signalés dans les données de validation (13,8 %) | Billets transmis au niveau supérieur parmi ceux signalés | Précision | Rappel |
|---|---|---|---|
| Règle : signaler la priorité 1 | 61 | 0,374 | 0,333 |
| Régression logistique, 163 scores les plus élevés | 66 | 0,405 | 0,361 |
À charge de travail égale, le modèle repère 5 transmissions au niveau supérieur de plus que la règle. Le nombre de vérifications reste le même. C’est un gain réel, mais modeste. Il reste sous la cible d’amélioration de Définir une référence de base : repérer au moins 40 % des transmissions en signalant au plus 20 % des billets. Avec 163 billets signalés, le modèle n’atteint pas encore 20 %. Le module 5 essaie d’autres familles de modèles. Le module 6 ajoute une variable et choisit le seuil pour la capacité de 20 %.
scores.argsort()[::-1] trie les positions du score le plus élevé au plus faible; [:163] conserve les 163 premières. Votre code peut être différent, par exemple valid.assign(score=scores).nlargest(163, "score"). Vérifiez les trois nombres.
Vérification du module
Section intitulée « Vérification du module »Vérifiez votre compréhension
Rappel : nommez les trois étapes de préparation de build_pipeline(), les colonnes reçues par chacune et le modèle à la fin.
Afficher la réponse
OneHotEncoder(handle_unknown="ignore")reçoit les quatre catégories :channel,team,segmentetregion.SimpleImputer(strategy="median", add_indicator=True), puisStandardScaler(), reçoivent les six nombres :priority,order_value,word_count,customer_tenure_days,prior_tickets_90detcreated_hour.- Un
ColumnTransformerréunit les deux branches, et le modèle à la fin estLogisticRegression(max_iter=1000). Le résultat contient 27 colonnes.
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édire et expliquer : un nouveau billet de 2 000 mots arrive. Quelle probabilité de transmission au niveau supérieur la droite de Comprendre la classification prédit-elle selon word_count? Que prédit la régression logistique, et quel résultat Grace peut-elle utiliser comme score?
Afficher la réponse
La droite prédit 0,0488 + 0,00064 × 2 000 = 1,335 : une « probabilité » supérieure à 1, ce qui est impossible. La régression logistique transforme le même type de somme pondérée avec la courbe en S. Elle donne 0,999, une valeur qui reste entre 0 et 1. Grace peut utiliser seulement le nombre de la régression logistique comme score. Même alors, une variable qui augmente sans limite donne des scores extrêmes aux billets extrêmes. Le modèle avec les dix variables, préparées par le pipeline, classe beaucoup mieux les billets (ROC AUC de 0,738).
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 : reproduisez les résultats de ce module et confirmez que le prétraitement a été ajusté seulement sur les données d'entraînement. Notez vos résultats et la façon dont vous l'avez confirmé.
Afficher la réponse
Procédure (dans le carnet de la leçon, ou dans votre propre carnet dans ~/projects/ticket-model avec le noyau .venv) :
- Redémarrez le noyau et exécutez toutes les cellules (dans Colab : Runtime › Restart session and run all (exécution › redémarrer la session et tout exécuter)). Un nouveau départ prouve qu’aucune cellule ne dépend d’une ancienne valeur.
- La section 8 doit afficher
ROC AUC on valid: 0.738(ROC AUC sur les données de validation : 0,738) etAP on valid: 0.353(AP sur les données de validation : 0,353). La section 9 doit afficher les trois lignes du tableau des résultats. Majorité : ROC AUC de 0,500 et AP de 0,155. Règle : 163 billets signalés, précision de 0,374, rappel de 0,333. Régression logistique : 18 billets signalés au seuil de 0,5. - Exécutez la section 13. Elle compare le pipeline ajusté aux données :
numbers_step = prepare.named_transformers_["numbers"]filled = numbers_step["impute"].transform(train[NUMBERS])print("medians equal train medians:", np.allclose(numbers_step["impute"].statistics_, train[NUMBERS].median()))print("means equal train means:", np.allclose(numbers_step["scale"].mean_, filled.mean(axis=0)))print("medians equal valid medians:", np.allclose(numbers_step["impute"].statistics_, valid[NUMBERS].median()))La sortie :
medians equal train medians: Truemeans equal train means: Truemedians equal valid medians: FalseCe qu’une réponse complète indique :
- Les nombres reproduits : 0,738 et 0,353, et les trois lignes de référence de base.
- Les médianes apprises égalent celles des données d’entraînement (3, 86,56, 89, 1 178, 5, 14). Les moyennes de l’outil de mise à l’échelle égalent celles des colonnes d’entraînement dont les valeurs manquantes ont été remplacées.
- Les médianes diffèrent de celles des données de validation (par exemple, 1 342,5 pour
customer_tenure_days). Les lignes de validation n’ont donc pas servi à l’ajustement.
Les catégories offrent une vérification moins solide ici, car les données de validation contiennent les mêmes catégories que les données 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 quelque chose dans votre projet ne fonctionne plus ou a changé, revenez à l’état de fin de ce module :
- Ouvrez un terminal dans
~/projects/ticket-modelet activez.venv(source .venv/bin/activatesur macOS et Linux,.venv\Scripts\Activate.ps1sur Windows). - Exécutez
python get_data.py --reset. 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). - Remplacez
ticket_model.pypar le code de Écrire ticket_model.py. Vérifiez-le avecpython -c "import ticket_model; print(len(ticket_model.FEATURES), 'features')". Vous devriez voir10 features(10 variables). - Si
.venvne fonctionne plus, supprimez le dossier.venvet recréez-le, comme dans Définir une référence de base : préparer votre projet. - Pour un carnet de leçon, téléchargez une nouvelle copie depuis son lien View on GitHub (voir sur GitHub). Exécutez-le ensuite depuis le début.
À partir du module 6, Sélectionner les caractéristiques utiles, ticket_model.py contient une variable de plus. Le fichier terminé dans C05/project est alors celui à copier.
Récapitulatif
Dans ce module, vous avez créé les premiers modèles :
- Régression : une régression linéaire, comme un tarif de taxi, prédit le nombre de billets par jour. Les moindres carrés trouvent la droite. Le modèle saisonnier atteint une MAE de test de 6,45, contre 14,95 pour la constante. Ses résidus restent au-dessus de zéro en mai et juin (+4,98), car la boutique grandit. Une variable de tendance élimine la majeure partie de ce biais.
- Classification : une droite ne peut pas donner une probabilité (1,335 pour 2 000 mots). La régression logistique transforme la somme pondérée avec la courbe en S en un score entre 0 et 1.
predictutilise 0,5 et ne signale presque rien sur ces données. Utilisezpredict_probaet un seuil adapté à la décision. - Préparation : le texte, les valeurs manquantes et les échelles ont chacun besoin d’une étape. Chaque étape apprend des nombres seulement à partir des données d’entraînement. Vous devez les appliquer de la même façon à chaque billet. À la main, l’oubli de la mise à l’échelle a donné un score de 0,999 au lieu de 0,184. Un nouveau canal a aussi interrompu le code. Un pipeline dans
ticket_model.pycontient les étapes et le modèle. L’ajustement a utilisé seulement les données d’entraînement : les médianes apprises égalent celles des données d’entraînement. - Résultats sur les données de validation : le modèle classe mieux que la règle (ROC AUC de 0,738 et AP de 0,353, contre 0,615 et 0,228). À la charge de travail de la règle, soit 163 billets signalés, il repère 66 transmissions au niveau supérieur plutôt que 61.
Ensuite, le module 5 montre que l’apprentissage automatique est une boucle. Vous essayez d’autres familles de modèles avec le même pipeline et les mêmes données de validation. Vous mesurez chacune de la même façon et gardez ce qui aide vraiment.
Leçon suivante : L'apprentissage automatique est itératif