Choisissez entre sélection et extraction de variables
Une image de chiffre manuscrit de 28 × 28 pixels, c’est 784 variables. Pourtant, deux pixels voisins ont presque toujours la même couleur, et les pixels du bord sont blancs sur toutes les images. La vraie information tient dans beaucoup moins de dimensions. Comment la retrouver, et pourquoi s’en donner la peine ?
Ce chapitre ouvre la deuxième partie du cours. Vous allez voir pourquoi réduire la dimension, puis découvrir les deux grandes familles de méthodes : sélectionner certaines variables existantes, ou extraire de nouvelles variables qui les résument. Notre fil rouge sera un jeu de données classique : 178 vins décrits par 13 mesures chimiques (alcool, acidité, intensité de la couleur, etc.).
Pourquoi réduire le nombre de variables ?
Section intitulée « Pourquoi réduire le nombre de variables ? »L’objectif est de diminuer le nombre de variables en conservant le plus d’information possible. Les raisons sont nombreuses :
- les coûts : moins de variables, c’est moins de stockage et des calculs plus rapides ;
- le surapprentissage : avec beaucoup de variables et peu d’exemples, un modèle trouve toujours des coïncidences à apprendre par cœur ;
- les variables non informatives : une mesure sans lien avec le problème n’apporte que du bruit ;
- la visualisation : on voit un nuage de points en 1, 2 ou 3 dimensions ; au-delà, c’est très difficile ;
- le fléau de la dimension, qui mérite qu’on s’y arrête.
Le fléau de la dimension
Section intitulée « Le fléau de la dimension »En grande dimension, l’espace devient immense et les points y sont tous isolés. Conséquence surprenante : les distances perdent leur contraste. Le point le plus proche et le point le plus éloigné d’une requête finissent par être presque à la même distance.
En dimension 1 000, le voisin le plus lointain n’est qu’à 9 % plus loin que le « plus proche ». Les méthodes fondées sur les distances (k plus proches voisins, k-means, DBSCAN) perdent alors leur sens. Réduire la dimension, c’est aussi leur rendre des distances qui discriminent.
Sélectionnez des variables existantes
Section intitulée « Sélectionnez des variables existantes »La sélection de variables (feature selection) garde un sous-ensemble des variables d’origine, sans les modifier. On distingue quatre familles.
Les méthodes de filtre
Section intitulée « Les méthodes de filtre »Un filtre attribue un score à chaque variable, indépendamment de tout modèle, puis garde les meilleures. Les scores usuels sont la variance (une variable presque constante n’apporte rien), la corrélation (avec la cible, ou entre variables pour éliminer les doublons) et l’information mutuelle, qui capte aussi les liens non linéaires. C’est rapide et indépendant du modèle, mais la performance est moyenne : chaque variable est jugée seule, sans voir les interactions.
Les méthodes enveloppantes
Section intitulée « Les méthodes enveloppantes »Une méthode enveloppante (wrapper) teste des sous-ensembles de variables en entraînant réellement le modèle sur chacun, et garde celui qui donne la meilleure performance. La recherche peut être :
- avant (forward) : on part de zéro variable et l’on ajoute, à chaque étape, celle qui améliore le plus le score ;
- arrière (backward) : on part de toutes les variables et l’on retire la moins utile ;
- exhaustive : on essaie tous les sous-ensembles.
C’est souvent très performant, mais très coûteux, et le résultat dépend du modèle choisi. Pour nos 13 mesures de vin, une recherche exhaustive devrait évaluer sous-ensembles ; une recherche avant complète n’en évalue que .
Ces méthodes ne sont pas réservées à l’apprentissage supervisé. En partitionnement, on juge un sous-ensemble de variables avec un critère de qualité des groupes : silhouette, indice de Davies-Bouldin, indice de Calinski-Harabasz ou inertie (voir le chapitre 7).
Les méthodes intégrées
Section intitulée « Les méthodes intégrées »Une méthode intégrée (embedded) sélectionne pendant l’apprentissage lui-même. La régression Lasso pénalise la somme des valeurs absolues des coefficients, ce qui en met certains exactement à zéro : les variables correspondantes sont éliminées. L’elastic net fait de même en combinant les deux pénalités. Les arbres de décision et les forêts fournissent une importance de chaque variable, qu’on peut seuiller. C’est un bon compromis entre coût et performance.
Les méthodes hybrides
Section intitulée « Les méthodes hybrides »Les méthodes hybrides enchaînent un filtre rapide, pour éliminer le plus gros, puis une méthode enveloppante ou intégrée sur ce qui reste. L’élimination récursive (RFE) entraîne un modèle, retire la variable la moins importante, et recommence. L’importance par permutation mesure la baisse de performance quand on mélange au hasard les valeurs d’une variable.
| Famille | Principe | Exemples | Coût | Performance |
|---|---|---|---|---|
| Filtre | score statistique, sans modèle | variance, corrélation, information mutuelle | faible | moyenne |
| Enveloppante | on teste des sous-ensembles avec le modèle | sélection avant, arrière, exhaustive | très élevé | bonne |
| Intégrée | sélection pendant l’apprentissage | Lasso, elastic net, importance des arbres | moyen | bonne |
| Hybride | filtre, puis enveloppante ou intégrée | RFE, permutation | moyen | bonne |
Extrayez de nouvelles variables
Section intitulée « Extrayez de nouvelles variables »L’extraction de variables (feature extraction) ne garde pas les variables d’origine : elle en construit de nouvelles, en combinant les anciennes. Plutôt que de choisir entre « alcool » et « intensité de la couleur », on fabrique par exemple une nouvelle variable de la forme « alcool couleur », dont les poids , … sont choisis par la méthode.
Ses avantages : elle réduit la redondance entre variables corrélées, peut capturer des relations complexes, parfois non linéaires, et permet de visualiser des données de grande dimension en 2 ou 3 dimensions. Les méthodes classiques sont :
- l’ACP (analyse en composantes principales), linéaire et non supervisée, que nous verrons en détail aux deux chapitres suivants ;
- la Kernel PCA, sa version non linéaire ;
- la LDA (analyse discriminante linéaire), supervisée : elle cherche les axes qui séparent le mieux les classes connues ;
- t-SNE et UMAP, non linéaires, conçues pour visualiser des groupes ;
- les auto-encodeurs, des réseaux de neurones qui apprennent à compresser puis reconstruire les données.
Sélection ou extraction : comment choisir ?
Section intitulée « Sélection ou extraction : comment choisir ? »Les deux approches sont complémentaires, et on les enchaîne souvent : un filtre pour retirer les variables constantes, puis une ACP sur ce qui reste.
| Situation | Plutôt |
|---|---|
| Il faut expliquer le modèle avec les variables métier | sélection |
| On veut réduire le coût de collecte (moins de capteurs, moins de questions) | sélection |
| Beaucoup de variables très corrélées entre elles (pixels, spectres) | extraction |
| On veut visualiser les données en 2D | extraction (ACP, t-SNE, UMAP) |
| On veut compresser pour accélérer un modèle en aval | extraction |
Voici à quoi ressemblent ces méthodes dans scikit-learn, sur le jeu des vins qui y est fourni :
from sklearn.datasets import load_winefrom sklearn.decomposition import PCAfrom sklearn.feature_selection import (SelectKBest, SequentialFeatureSelector, VarianceThreshold, mutual_info_classif)from sklearn.linear_model import LogisticRegressionfrom sklearn.pipeline import make_pipelinefrom sklearn.preprocessing import StandardScaler
X, y = load_wine(return_X_y=True) # 178 vins, 13 mesures, 3 cépages
# Filtre : on retire les variables de variance trop faible, puis on garde les 5 plus informativesfiltre = make_pipeline(VarianceThreshold(threshold=0.01), SelectKBest(mutual_info_classif, k=5))
# Enveloppante : sélection avant de 5 variables, jugée par validation croiséemodele = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))avant = SequentialFeatureSelector(modele, n_features_to_select=5, direction="forward", cv=5)
# Extraction : 2 composantes principales après standardisationacp = make_pipeline(StandardScaler(), PCA(n_components=2))X_2d = acp.fit_transform(X)En résumé
Section intitulée « En résumé »- Réduire la dimension diminue les coûts, limite le surapprentissage, élimine les variables inutiles et permet de visualiser.
- Le fléau de la dimension fait perdre leur contraste aux distances : en dimension 1 000, plus proche et plus lointain voisin sont presque à égalité.
- La sélection garde des variables existantes : filtres (rapides, sans modèle), méthodes enveloppantes (coûteuses, performantes), intégrées (Lasso, arbres) et hybrides (RFE, permutation).
- Ridge ne sélectionne pas : il rétrécit les coefficients sans les annuler.
- L’extraction construit de nouvelles variables : ACP, Kernel PCA, LDA (supervisée), t-SNE, UMAP, auto-encodeurs.
- Les deux approches se combinent, et toute réduction s’ajuste dans le pipeline pour éviter la fuite de données.
Exercices
Section intitulée « Exercices »Exercice · Compter le coût d'une recherche
facileUn jeu de données a 20 variables. Combien de sous-ensembles non vides une recherche exhaustive doit-elle évaluer ? Et une sélection avant menée jusqu’à inclure toutes les variables ? Et une sélection avant arrêtée à 3 variables ?
Voir la solution
- Recherche exhaustive : chaque variable est dans le sous-ensemble ou non, soit possibilités, moins l’ensemble vide : sous-ensembles.
- Sélection avant complète : à la première étape on teste 20 variables, puis 19, puis 18… jusqu’à 1, soit évaluations.
- Sélection avant à 3 variables : évaluations.
La sélection avant est donc des milliers de fois moins chère, au prix d’une recherche gloutonne : elle peut manquer une paire de variables qui ne sont utiles qu’ensemble.
En code
from math import comb
p = 20exhaustive = 2 ** p - 1 # tous les sous-ensembles non videsprint(exhaustive, sum(comb(p, k) for k in range(1, p + 1))) # 1048575 1048575
avant_complete = sum(range(1, p + 1)) # 20 + 19 + ... + 1avant_3 = sum(p - etape for etape in range(3)) # 20 + 19 + 18print(avant_complete, avant_3) # 210 57print(round(exhaustive / avant_complete)) # 4993 fois moins cherp <- 20exhaustive <- 2^p - 1 # tous les sous-ensembles non videsc(exhaustive, sum(choose(p, 1:p))) # 1048575 1048575
avant_complete <- sum(1:p) # 20 + 19 + ... + 1avant_3 <- sum(p - 0:2) # 20 + 19 + 18c(avant_complete, avant_3) # 210 57round(exhaustive / avant_complete) # 4993 fois moins cherExercice · Filtrer des variables redondantes
moyenDans un jeu de données immobilier, la matrice des corrélations montre : surface et nombre de pièces, ; surface et prix, ; nombre de pièces et prix, ; étage et prix, . Vous voulez prédire le prix avec un modèle linéaire en éliminant les variables redondantes. Que proposez-vous ? Pourquoi ne pas supprimer l’étage sur la seule foi de sa corrélation ?
Voir la solution
Surface et nombre de pièces sont très corrélées (0,92) : elles portent presque la même information, et les garder toutes deux rend les coefficients d’un modèle linéaire instables. Un filtre garderait la surface, plus corrélée au prix (0,81 contre 0,74), et retirerait le nombre de pièces.
Pour l’étage, une corrélation de 0,05 signifie seulement une absence de lien linéaire. L’effet pourrait être non linéaire (rez-de-chaussée moins cher, derniers étages avec vue plus chers, étages intermédiaires sans différence) ou n’apparaître qu’en interaction (étage élevé sans ascenseur). Une information mutuelle, un graphique du prix par étage ou une méthode enveloppante trancheraient mieux qu’un simple coefficient de corrélation.
En code
import numpy as np
# 1. Filtre : dans la paire redondante, on garde la plus corrélée au prixr_prix = {"surface": 0.81, "pieces": 0.74}print(max(r_prix, key=r_prix.get)) # surface (r surface-pièces = 0.92 > 0.9)
# 2. Interaction simulée : l'étage valorise un logement avec ascenseur# et le dévalorise sans ascenseurrng = np.random.default_rng(0)n = 1000etage = rng.integers(0, 9, n)ascenseur = rng.random(n) < 0.5prix = 200 + np.where(ascenseur, 6, -6) * etage + rng.normal(0, 10, n)
print(round(np.corrcoef(etage, prix)[0, 1], 2)) # 0.05 : globalement nulleprint(round(np.corrcoef(etage[ascenseur], prix[ascenseur])[0, 1], 2)) # 0.85 avec ascenseurprint(round(np.corrcoef(etage[~ascenseur], prix[~ascenseur])[0, 1], 2)) # -0.83 sans ascenseur# 1. Filtre : dans la paire redondante, on garde la plus corrélée au prixr_prix <- c(surface = 0.81, pieces = 0.74)names(which.max(r_prix)) # "surface" (r surface-pièces = 0.92 > 0.9)
# 2. Interaction simulée : l'étage valorise un logement avec ascenseur# et le dévalorise sans ascenseurset.seed(0)n <- 1000etage <- sample(0:8, n, replace = TRUE)ascenseur <- runif(n) < 0.5prix <- 200 + ifelse(ascenseur, 6, -6) * etage + rnorm(n, 0, 10)
round(cor(etage, prix), 2) # 0.01 : globalement nulleround(cor(etage[ascenseur], prix[ascenseur]), 2) # 0.83 avec ascenseurround(cor(etage[!ascenseur], prix[!ascenseur]), 2) # -0.82 sans ascenseurExercice · Choisir une méthode de réduction
moyenPour chaque situation, dites si vous choisiriez une sélection ou une extraction, et laquelle.
- Un hôpital veut un score de risque fondé sur le moins d’examens biologiques possible, pour réduire les coûts.
- On dispose de spectres infrarouges de 1 000 longueurs d’onde très corrélées, et l’on veut entraîner un modèle rapide.
- On veut montrer à un client une carte en 2D de ses 50 000 clients, pour repérer des groupes.
Voir la solution
- Sélection, car l’objectif est de ne plus mesurer certaines variables. Une méthode intégrée comme une régression logistique avec pénalité Lasso, ou une sélection avant, désigne les examens indispensables. Une extraction serait inutile ici : chaque composante combinerait tous les examens, qu’il faudrait donc tous continuer à faire.
- Extraction : les longueurs d’onde voisines sont redondantes, et une ACP en résume l’essentiel en quelques dizaines de composantes, ce qui accélère le modèle.
- Extraction pour la visualisation : une ACP en première approche (rapide, axes interprétables), ou t-SNE ou UMAP si les groupes sont mal séparés linéairement, en gardant en tête que ces dernières ne conservent pas fidèlement les distances entre groupes éloignés (voir le chapitre 6).
En code
import numpy as npfrom sklearn.decomposition import PCA
# Situation 2 : 200 spectres simulés sur 1 000 longueurs d'onde très corrélées,# fabriqués à partir de 3 constituants chimiques plus un peu de bruitrng = np.random.default_rng(0)longueurs = np.linspace(0, 1, 1000)pics = np.array([np.exp(-((longueurs - c) / 0.08) ** 2) for c in (0.2, 0.5, 0.8)])concentrations = rng.random((200, 3))spectres = concentrations @ pics + rng.normal(0, 0.01, (200, 1000))
acp = PCA().fit(spectres)cumul = np.cumsum(acp.explained_variance_ratio_)print(cumul[:4].round(3)) # [0.368 0.712 0.996 0.996] : 3 composantes suffisentprint(int(np.argmax(cumul >= 0.99)) + 1) # 3 axes pour 99 % de l'inertie, au lieu de 1 000 variables# Situation 2 : 200 spectres simulés sur 1 000 longueurs d'onde très corrélées,# fabriqués à partir de 3 constituants chimiques plus un peu de bruitset.seed(0)longueurs <- seq(0, 1, length.out = 1000)pics <- t(sapply(c(0.2, 0.5, 0.8), function(c) exp(-((longueurs - c) / 0.08)^2)))concentrations <- matrix(runif(200 * 3), 200, 3)spectres <- concentrations %*% pics + matrix(rnorm(200 * 1000, 0, 0.01), 200, 1000)
acp <- prcomp(spectres) # ACP centrée (non réduite)cumul <- cumsum(acp$sdev^2) / sum(acp$sdev^2)round(cumul[1:4], 3) # 0.364 0.709 0.996 0.996 : 3 composantes suffisentwhich(cumul >= 0.99)[1] # 3 axes pour 99 % de l'inertie, au lieu de 1 000 variables