Par Maxime Jumelle
CTO & Co-Founder
Publié le 27 nov. 2020
Catégorie Machine Learning
Dans la plupart des projets de Machine Learning, le jeu de données utilisé pour calibrer le modèle doit subir toute une série de transformations. Encodage de variables catégorielles, de la normalisation, du feature scaling et autres techniques spécifiques.
Cependant, cette série de transformations doit être appliquée plus d'une fois. D'une part, au moment où il faut entraîner le modèle, et d'autre part lorsqu'il faut obtenir une prédiction pour de nouvelles données. Comment ne pas mélanger les différentes étapes et garantir un traitement consistant entre ces deux étapes ? C'est tout l'intérêt des pipelines de scikit-learn. Ces pipelines vont résoudre un certain nombre de problèmes.
Pour illustrer les pipelines de scikit-learn, nous allons utiliser le jeu de données suivant.
1import pandas as pd 2 3dataset = pd.read_csv( 4 "https://dv495y1g0kef5.cloudfront.net/single_notebooks/data/car_acc.csv", 5 header=0, 6 names=["buying", "maint", "doors", "persons", "lug_boot", "safety", "eval"] 7) 8dataset.head()
Chaque ligne représente une voiture. Les variables explicatives sont :
buying
(qualitative) : un prix d’achat (bas, moyen, haut et très
haut).maint
(qualitative) : entretien nécessaire pour le fonctionnement
(bas, moyen, haut et très haut).doors
(quantitative) : le nombre de portes.persons
(quantitative) : le nombre de personnes.lug_boot
(qualitative) : la taille du coffre (petit, moyen et grand).safety
(qualitative) : le niveau de sécurité (faible, modéré et fort).La variable que l’on souhaite modéliser (i.e. la variable réponse) est
nommée eval
et représente le niveau de satisfaction de la voiture (unacc pour non satisfait ou acc pour satisfait).
Un Transformer est un objet scikit-learn
qui permet d'appliquer une transformation (encodage, normalisation, ...) sur un DataFrame. L'intérêt du Transformer est double.
À lire aussi : découvrez notre formation MLOps
Nous allons commencer par encoder les colonnes lug_boot
et safety
en one-hot avec le OneHotEncoder
.
1from sklearn.pipeline import Pipeline 2from sklearn.preprocessing import OneHotEncoder 3 4one_hot_encoder = Pipeline( 5 steps=[ 6 ('one_hot', OneHotEncoder(handle_unknown='ignore')) 7 ] 8)
ℹ️ Pas besoin de définir pour l'instant les colonnes qui vont subir cette transformation : cela sera effectué lorsque l'on assemblera les Transformers.
Nous pouvons également construire un Transformer sur mesure avec FunctionTransformer
. En particulier, nous allons construire un Transformer pour certaines variables explicatives pour lesquelles nous considérons qu'il n'est pas nécessaire d'utiliser un OneHotEncoder
. Les colonnes buying
et maint
prendront comme valeur 0, 1, 2 ou 3 (correspondant à low, med, high et vhigh).
1from sklearn.preprocessing import FunctionTransformer
2
3encoding = { "low": 0, "med": 1, "high": 2, "vhigh": 3 }
4
5def grad_encoder(df):
6 for col in df.columns:
7 df[col] = df[col].apply(lambda x: encoding[x])
8 return df
9
10eval_encoder = Pipeline(
11 steps=[
12 ('grad', FunctionTransformer(grad_encoder))
13 ]
14)
Enfin, il y a quelques cas particuliers pour les colonnes doors
et persons
: la valeur 5more est présente dans la colonne doors
s'il y a 5 portes ou plus, et de même dans la colonne persons
lorsque le véhivule peut accueillir 6 passagers ou plus.
Dans ce cas, puisque nous souhaitons uniquement des valeurs numériques dans ces colonnes, nous allons construire un dernier Transformer pour encoder ces variables.
1def num_encoder(df):
2 for col in df.columns:
3 df[col] = df[col].apply(lambda x: 5 if x == "5more" else x)
4 df[col] = df[col].apply(lambda x: 6 if x == "more" else x)
5 return df
6
7num_encoder = Pipeline(
8 steps=[
9 ('num', FunctionTransformer(num_encoder))
10 ]
11)
Une fois terminé, il ne reste plus qu'à combiner tous ces Transformers dans un ColumnTransformer
: cela va permettre d'appliquer chaque Transformer sur un ensemble de colonnes sur-mesure. Plutôt que d'écrire un Transformer par colonne, cela permet de gagner du temps en appliquant la même méthode d'encodage sur plusieurs colonnes.
1from sklearn.compose import ColumnTransformer 2 3preprocessor = ColumnTransformer( 4 transformers=[ 5 ('categorical', one_hot_encoder, ['lug_boot', 'safety']), 6 ('grad', eval_encoder, ['buying', 'maint']), 7 ('num', num_encoder, ['doors', 'persons']), 8 ] 9)
Par ailleurs, la documentation de scikit-learn présente une liste exhaustive des Transformers disponibles par défaut.
Une fois que le pipeline de preprocessing est en place, nous pouvons ajouter la couche prédictive, qui ici est un Random Forest.
À lire aussi : découvrez notre formation MLOps
1from sklearn.ensemble import RandomForestClassifier 2 3rf = Pipeline( 4 steps=[ 5 ('preprocessor', preprocessor), 6 ('classifier', RandomForestClassifier()) 7 ] 8)
La particularité, c'est que la variable rf
n'est pas uniquement le modèle, mais renferme également les étapes de preprocessing que nous appliquons aux données.
Ainsi, qu'il s'agisse d'un fit
ou d'un predict
, les données subiront les étapes du preprocessing au préalable.
Comme toujours, nous séparons le jeu de données en un ensemble d'entraînement et un ensemble de test.
1from sklearn.model_selection import train_test_split 2 3X = dataset.drop('eval', axis=1) 4y = dataset['eval'].apply(lambda x: 0 if x == "unacc" else 1) 5 6X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=24)
Puis nous calibrons directement le pipeline contenant le preprocessing et le modèle.
1rf.fit(X_train, y_train)
Si maintenant, on souhaite calculer un score, même principe, la fonction predict
appliquera toutes les transformations au DataFrame renseigné.
1from sklearn.metrics import f1_score 2 3y_pred = rf.predict(X_test) 4print("Score : {:2.1f}%".format(f1_score(y_test, y_pred) * 100))
L'intérêt des pipelines, en plus de la reproductibilité, est la modularité offerte par la combinaison linéaire des étapes. Par exemple, plutôt que d'avoir un seul modèle, nous souhaitons tester plusieurs modèles : il suffit juste de modifier la couche de classification, sans modifier le preprocessing.
Par exemple, ici, nous allons calibrer trois modèles : un Random Forest, un Gradient Boosting et un modèle binomial. Tous les trois auront le même jeu de données en entrée, ce qui permettra de les comparer plsu efficacement.
1from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier 2from sklearn.linear_model import LogisticRegression 3 4models_names = ["Random Forest", "Gradient Boosting", "Logistic Regression"] 5# Contient les trois pipelines (une pour chaque modèle) 6pipelines = [ 7 Pipeline(steps=[('preprocessor', preprocessor), ('classifier', RandomForestClassifier())]), 8 Pipeline(steps=[('preprocessor', preprocessor), ('classifier', GradientBoostingClassifier())]), 9 Pipeline(steps=[('preprocessor', preprocessor), ('classifier', LogisticRegression())]) 10]
Nous pouvons ensuite appeler chaque pipeline.
1for p, name in zip(pipelines, models_names): 2 p.fit(X_train, y_train) 3 y_pred = p.predict(X_test) 4 print("Score ({}) : {:2.1f}%".format( 5 name, 6 f1_score(y_test, y_pred) * 100 7 ))
Nous vons directement ici que les deux premiers modèles sont bien meilleurs que le dernier. On pourrait ainsi imaginer la même technique si l'on effectuait une recherche par grille pour optimiser les hyper-paramètres.
Vous souhaitez vous former au MLOps ?
Dans cet article
Articles similaires
20 sept. 2022
Machine Learning
Nada Belaidi
Data Scientist
Lire l'article
12 juil. 2022
Machine Learning
Nada Belaidi
Data Scientist
Lire l'article
4 juil. 2022
Machine Learning
Nada Belaidi
Data Scientist
Lire l'article
60 rue François 1er
75008 Paris
Blent est une plateforme 100% en ligne pour se former aux métiers Tech & Data.
Organisme de formation n°11755985075.
Data Engineering
À propos
Gestion des cookies
© 2023 Blent.ai | Tous droits réservés