Feature Engineering & Pipelines
“Ordures à l'entrée, ordures à la sortie — l'art de transformer les données brutes en signaux prêts pour le modèle”
Pipeline complet : imputation, encodage catégoriel (OHE, Target, Ordinal), mise à l'échelle, création de features et sklearn Pipelines pour éviter le data leakage.
Prérequis
Concepts Couverts
∑Formules Clés
StandardScaler
Moyenne nulle, variance unitaire — sensible aux valeurs aberrantes
MinMaxScaler
Mise à l'échelle vers [0,1] — préserve la sparsité, sensible aux valeurs aberrantes
RobustScaler
Mise à l'échelle utilisant médiane et IQR — robuste aux valeurs aberrantes
Transformation Logarithmique
Compresse les distributions asymétriques — utile pour les revenus, les comptes de population
▶Simulation Interactive
Pourquoi l'Ingénierie des Caractéristiques Gagne les Compétitions
Andrew Ng a dit : « Trouver de bonnes caractéristiques est difficile, prend du temps et nécessite des connaissances d'experts. Le ML appliqué c'est fondamentalement de l'ingénierie de caractéristiques. » Dans les compétitions Kaggle, les solutions les mieux classées ont systématiquement une meilleure ingénierie de caractéristiques qu'une meilleure architecture de modèle. Un modèle linéaire avec des caractéristiques brillantes bat un réseau profond avec des caractéristiques brutes dans la plupart des problèmes de données tabulaires. Les caractéristiques encodent la connaissance humaine du domaine — elles sont le pont entre la mesure brute et la structure mathématique qu'un modèle peut exploiter.
Dans le Netflix Prize (1 M$), les caractéristiques de l'équipe gagnante incluaient des motifs temporels complexes, des signaux de rétroaction implicite et des interactions de métadonnées de films — pas la sophistication du modèle.
La Mentalité Pipeline
Pensez à l'ingénierie de caractéristiques comme une séquence de transformations : Données brutes → Imputation (remplir les valeurs manquantes) → Encodage (convertir les catégories en nombres) → Mise à l'échelle (mettre les caractéristiques sur des échelles comparables) → Sélection (supprimer les caractéristiques bruitées/redondantes). Chaque étape doit être ajustée uniquement sur les données d'entraînement et appliquée de façon cohérente aux données de test — utiliser les Pipelines scikit-learn pour garantir cela. Un Pipeline est aussi sérialisable, donc votre prétraitement est toujours groupé avec votre modèle pour le déploiement.
La fuite de données est le bug le plus dangereux en ML : si vos données de test influencent n'importe quelle étape de prétraitement, votre évaluation est un garbage optimiste. Les Pipelines empêchent cela par conception.
Le Pipeline en 5 Étapes
Imputation : SimpleImputer (moyenne/médiane/mode/constante) ou IterativeImputer (MICE multivarié)
Encodage : OrdinalEncoder pour les catégories ordonnées, OneHotEncoder pour les nominales (drop='first' pour éviter le piège des variables fictives)
Mise à l'échelle : StandardScaler pour les données gaussiennes, RobustScaler quand des anomalies existent, MinMaxScaler pour les entrées bornées
Création de caractéristiques : PolynomialFeatures (x², x·y interactions), décomposition de dates (jour/mois/jour de la semaine), transformées du domaine (log, sqrt)
Sélection : VarianceThreshold, SelectKBest (info mutuelle / chi²), SelectFromModel (importances des arbres), RFECV
Stratégies d'Encodage Catégoriel
L'encodage One-Hot crée une colonne binaire par catégorie — parfait pour les catégories non ordonnées à peu de valeurs. Avec les catégorielles à haute cardinalité (villes, codes postaux, IDs de produits), OHE explose la dimensionnalité. Utiliser l'Encodage par Cible à la place : remplacer chaque catégorie par la valeur cible moyenne de cette catégorie. Mais l'encodage par cible fuit si pas fait avec des plis de validation croisée. Pour les caractéristiques ordinales (Faible/Moyen/Élevé), toujours utiliser OrdinalEncoder avec l'ordre de catégorie explicite.
Haute cardinalité + OHE = catastrophe. 10 000 codes postaux → 10 000 colonnes, la plupart presque vides. Utiliser l'encodage par cible, les couches d'embedding ou le feature hashing à la place.
Choix de Mise à l'Échelle et Leurs Effets
StandardScaler : suppose une distribution gaussienne, rend mean=0 et std=1. Requis pour les SVM, les modèles linéaires régularisés (Lasso/Ridge), l'ACP, KNN, les réseaux de neurones. Pas nécessaire pour les modèles basés sur les arbres (Random Forest, XGBoost — les arbres n'utilisent que l'ordre des caractéristiques, pas la magnitude). MinMaxScaler : nécessaire quand l'algorithme requiert des entrées bornées. RobustScaler : utiliser quand des anomalies sont présentes — met à l'échelle en utilisant la médiane et l'IQR, le rendant robuste aux valeurs extrêmes.
Pipeline sklearn — Exemple Complet
from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import (StandardScaler, OneHotEncoder, RobustScaler, PolynomialFeatures) from sklearn.impute import SimpleImputer from sklearn.feature_selection import SelectFromModel from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.model_selection import cross_val_score, train_test_split import pandas as pd import numpy as np class="tok-comment"># ── DataFrame dclass="tok-str">'exemple ──────────────────────────────────────────────── np.random.seed(class="tok-num">42) n = class="tok-num">300 df = pd.DataFrame({ 'ageclass="tok-str">': np.random.randint(class="tok-num">18, class="tok-num">70, n).astype(float), 'revenuclass="tok-str">': np.random.exponential(class="tok-num">40000, n), 'scoreclass="tok-str">': np.random.uniform(class="tok-num">300, class="tok-num">850, n), 'villeclass="tok-str">': np.random.choice(['Parisclass="tok-str">', 'Lyonclass="tok-str">', 'Toulouseclass="tok-str">'], n), 'professionclass="tok-str">': np.random.choice(['ingénieurclass="tok-str">', 'enseignantclass="tok-str">', 'médecinclass="tok-str">'], n), 'cibleclass="tok-str">': np.random.randint(class="tok-num">0, class="tok-num">2, n), }) df.loc[np.random.choice(n, class="tok-num">20, replace=False), 'ageclass="tok-str">'] = np.nan df.loc[np.random.choice(n, class="tok-num">15, replace=False), 'villeclass="tok-str">'] = np.nan X_train = df.drop('cibleclass="tok-str">', axis=class="tok-num">1) y_train = df['cibleclass="tok-str">'] class="tok-comment"># ── Définir les groupes de colonnes ─────────────────────────────── col_num = ['ageclass="tok-str">', 'revenuclass="tok-str">', 'scoreclass="tok-str">'] col_cat = ['villeclass="tok-str">', 'professionclass="tok-str">'] class="tok-comment"># ── Prétraitement des colonnes numériques ───────────────────────── transform_num = Pipeline([ ('imputationclass="tok-str">', SimpleImputer(strategy='medianclass="tok-str">')), ('echelleclass="tok-str">', RobustScaler()), ]) class="tok-comment"># ── Prétraitement des colonnes catégorielles ───────────────────── transform_cat = Pipeline([ ('imputationclass="tok-str">', SimpleImputer(strategy='most_frequentclass="tok-str">')), ('encodageclass="tok-str">', OneHotEncoder(handle_unknown='ignoreclass="tok-str">', drop='firstclass="tok-str">')), ]) class="tok-comment"># ── Combiner avec ColumnTransformer ─────────────────────────────── preprocesseur = ColumnTransformer([ ('numclass="tok-str">', transform_num, col_num), ('catclass="tok-str">', transform_cat, col_cat), ]) class="tok-comment"># ── Pipeline complet : prétraitement → sélection → modèle ───────── pipeline = Pipeline([ ('prepclass="tok-str">', preprocesseur), ('polyclass="tok-str">', PolynomialFeatures(degree=class="tok-num">2, interaction_only=True, include_bias=False)), ('selectionclass="tok-str">', SelectFromModel(RandomForestClassifier(n_estimators=class="tok-num">50), threshold='medianclass="tok-str">')), ('clfclass="tok-str">', GradientBoostingClassifier(n_estimators=class="tok-num">200, learning_rate=class="tok-num">0.05)), ]) class="tok-comment"># Entraîner / évaluer — prétraitement toujours ajusté sur train uniquement pipeline.fit(X_train, y_train) scores = cross_val_score(pipeline, X_train, y_train, cv=class="tok-num">5, scoring='roc_auc') print(fclass="tok-str">"CV AUC : {scores.mean():.3f} ± {scores.std():.3f}")
Pièges du Prétraitement
Ajuster les normalisateurs sur l'ensemble de données complet (avant la division) est une fuite de données — les statistiques de test contaminent l'entraînement. Toujours ajuster dans un Pipeline ou sur X_train uniquement. Deuxième : OneHotEncoder sur les données de test peut rencontrer des catégories non vues → utiliser handle_unknown='ignore'. Troisième : imputer avec la moyenne avant la division fuit la moyenne du test dans l'entraînement. Quatrième : les caractéristiques polynomiales explosent la mémoire — 100 caractéristiques × degré=2 → 5 050 colonnes. Utiliser interaction_only=True et la sélection de caractéristiques en aval. Cinquième : l'encodage par cible sans validation croisée fuit les informations cibles.
L'objet Pipeline dans scikit-learn n'est pas seulement pratique — il est requis pour une validation croisée correcte. Tout prétraitement qui 'apprend' des données (normalisateurs, encodeurs, imputeurs) doit être dans le pipeline.
?Vérification des Connaissances
La progression est sauvegardée dans votre navigateur — aucun compte requis.
Besoin d'un ingénieur IA ou data scientist ?
Je conçois des modèles ML sur mesure, des agents IA, de la vision par ordinateur et de l'automatisation — de l'idée à la production.