MINI-COURS #25      ⏱️ 30 à 40 minutes de lecture

🔒 Contenu réservé aux membres du Cercle des Experts
Débloque la leçon complète et tous les autres mini-cours.

Introduction

J’ai eu l’opportunité de travailler sur de nombreux projets de machine learning en conditions réelles — d’abord comme data scientist à plein temps, puis en tant qu’indépendant, où j’accompagnais des entreprises sur leurs projets data.

Construire des projets de data science de bout en bout — du nettoyage des données jusqu’au déploiement des modèles — m’a confronté à une multitude de pièges, de biais et de bonnes pratiques… que je n’ai quasiment jamais vus formalisés clairement.

Honnêtement, tout ce que je vais partager ici, ce sont des leçons que j’aurais aimé apprendre beaucoup plus tôt.

La bonne nouvelle, c’est que tu n’as pas besoin de passer par les mêmes erreurs.

Dans ce mini-cours, j’ai rassemblé 8 pièges que tu rencontreras très probablement, ainsi que les réflexes à adopter pour les éviter.

D’après mon expérience, ces pièges sont presque toujours présents — mais rarement visibles au premier coup d’œil. Et c’est précisément ce qui fait dérailler un grand nombre de projets.

Commençons !

1) Inspecter ses arbres de décision

Si l’on visualise les règles de décision (les conditions évaluées à chaque nœud) de n’importe quel arbre de décision, on constate qu’elles sont toujours perpendiculaires aux axes des features, comme ci‑dessous :

Visualisation des règles de décision d'un arbre de décision, frontières perpendiculaires aux axes des features

En d’autres termes, un arbre de décision segmente progressivement l’espace des features au moyen de frontières perpendiculaires pour séparer les données.

En soi, ce n’est pas un “problème”.

C’est même précisément cette manière de découper l’espace qui rend l’arbre de décision capable de sur‑apprendre à la perfection n’importe quel dataset (voir la section sur l’expérience d’overfitting pour plus de détails).

Mais cela soulève un point intéressant, souvent ignoré lorsqu’on ajuste des arbres de décision.

Plus précisément : que se passe‑t‑il si notre dataset est séparé par une frontière de décision diagonale, comme ci‑dessous ?

Ensemble de données synthétique séparé par une frontière de décision diagonale, piège des arbres de décision

Il est alors assez intuitif de voir que la frontière de décision construite par un arbre prendra cette forme :

Découpes répétées d'un arbre de décision pour approcher une frontière de décision diagonale

En visualisant cet arbre, on constate qu'il est contraint de multiplier les divisions pour s'adapter à un dataset pourtant très simple à séparer. À l'inverse, un algorithme comme la régression logistique, un SVM (Support Vector Machine) ou un réseau de neurones basique résoudrait ce problème de façon beaucoup plus élégante :

Nœud de décision d'un arbre qui peine à modéliser une frontière diagonale, alors qu'un réseau de neurones basique y parvient

Cela devient encore plus évident si l’on zoome sur cet arbre et qu’on observe à quel point les seuils de split sont proches les uns des autres :

Arbre de décision zoomé montrant la faible différence entre les seuils de split des critères de division

Et là, c’est un peu inquiétant : on voit clairement que l’arbre essaie laborieusement de mimer une frontière diagonale, ce qui laisse penser que ce n’est peut‑être pas le meilleur modèle pour ce problème.

Pour vérifier cette intuition, voici une méthode de diagnostic simple que j'utilise régulièrement :

  • Préparation : On part de notre jeu d'entraînement classique, avec la matrice de features X

    • X de dimensions n × m)

    • la cible y (de dimension n × 1).

  • Rotation de l'espace : On applique une Analyse en Composantes Principales (PCA) sur X. L'objectif ici n'est pas de réduire la dimension, mais de projeter les données dans un nouvel espace orthogonal. On obtient ainsi X_pca, qui conserve les mêmes dimensions (n × m).

  • Modélisation et visualisation : On entraîne un nouvel arbre de décision sur ces données transformées (X_pca). Profitons du grand atout des arbres : leur interprétabilité visuelle ! On affiche donc le graphe de l'arbre obtenu.

  • Diagnostic : Si la profondeur de ce nouvel arbre est drastiquement inférieure à celle de l'arbre initial, le diagnostic est confirmé : la frontière de décision était bien diagonale par rapport aux axes d'origine.

Par exemple, voici les projections PCA du dataset utilisé plus haut :

Projections PCA du jeu de données pour inspecter la frontière de décision d'un arbre de décision

Il apparaît distinctement que, suite à la projection, la frontière de décision devient quasi orthogonale à la seconde composante principale (X2).

L'ajustement d'un arbre de décision sur la matrice X_pca s'en trouve alors grandement simplifié, ce qui se traduit par une réduction drastique de sa complexité et de sa profondeur.

L'illustration ci-dessous en témoigne :

Arbre de décision entraîné sur X_pca, le dataset projeté par PCA, et sa frontière de décision

Ce constat nous amène à une conclusion claire : face à de telles données, nous aurions tout intérêt à changer d'algorithme.

L'alternative serait d'investir du temps dans un feature engineering spécifique, afin de fournir à l'arbre des variables sur lesquelles il pourra appliquer ses découpes orthogonales efficacement.

Une question légitime se pose alors : pourquoi ne pas simplement déployer en production l'arbre entraîné sur X_pca ?

Si rien ne l'interdit techniquement, n'oublions pas le prix à payer : la perte d'interprétabilité. Les composantes principales étant des combinaisons linéaires abstraites, nous sacrifions le sens métier des variables d'origine, ce qui est souvent rédhibitoire en entreprise.

Le mot de la fin : Ne considère jamais un arbre de décision comme une boîte noire ; prends toujours le temps d'inspecter sa structure.

Mon but n'est évidemment pas de te dissuader de les utiliser — ils restent, après tout, les briques fondatrices des modèles ensemblistes les plus puissants du marché (Random Forest, XGBoost...).

L'objectif est plutôt d'apprendre à 'penser' comme l'algorithme, pour maîtriser ses limites géométriques et savoir exactement quand il est le bon outil pour le bon problème.

2) Séparation aléatoire des données

Dans quasiment tous les cours d’introduction au machine learning, on nous explique qu’il faut séparer les données de façon ALÉATOIRE en un ensemble d’entraînement et un ensemble de validation :

Séparation aléatoire des données en ensemble d'entraînement et de validation, piège de la fuite de données

La séparation aléatoire peut sembler pertinente : elle répartit les données sans biais apparent.

Mais dans de nombreux cas, elle devient en réalité problématique — voire contre-productive — pour construire un modèle fiable. Et c’est un point souvent sous-estimé.

Et je ne parle même pas ici des données temporelles.

Voyons cela plus en détail.

Scénario

Imaginons que tu construises un modèle qui génère des légendes pour des images :

Schéma d'un modèle qui génère des légendes pour des images, exemple de fuite de données

En raison de la nature du langage, une même image peut avoir de nombreuses légendes différentes :

Schéma d'une même image associée à plusieurs légendes différentes, source de fuite de données

Tu le vois sans doute venir : le principal problème apparaît au moment clé de la séparation entre l’entraînement et la validation (train/val split).

Si l'on se contente de diviser ce dataset de manière purement aléatoire, que va-t-il se passer ?

Les multiples lignes générées à partir d'une seule et même image vont inévitablement se retrouver éparpillées à la fois dans l'ensemble d'entraînement et dans l'ensemble de validation.

Schéma des mêmes points de données présents dans l'ensemble d'entraînement et de validation, cas de data leakage

Il s'agit là d'un cas d'école de fuite de données (data leakage), garantissant un surapprentissage massif de ton modèle.

Dans le jargon de la Data Science, on appelle plus précisément ce phénomène le group leakage (fuite par groupe).

Solution

Au regard de ce qui précède, l’origine du problème est claire : elle provient de la séparation aléatoire des données.

Pour y remédier, on utilise plutôt une séparation par groupes (group shuffle split).

L’approche se déroule en deux étapes :

  • Regrouper toutes les instances liées à une même entité (une image, ou toute autre clé susceptible d’introduire une fuite : patient, utilisateur, session, etc.)

  • Une fois ces groupes définis, affecter chaque groupe en bloc soit au train, soit à la validation — mais jamais aux deux

Au vu de ce qui précède, l’origine du problème est claire : elle vient de la séparation aléatoire des données (random split).

Pour y remédier, on utilise une séparation par groupes.

L’approche se fait en deux étapes :

  • Regrouper toutes les instances associées à une même entité (une image, ou toute autre clé susceptible d’introduire une fuite : patient, utilisateur, session, etc.)

  • Une fois ces groupes définis, affecter chaque groupe en bloc soit à l’entraînement (train), soit à la validation — mais jamais aux deux

Schéma de la séparation par groupes : chaque groupe va soit à l'entraînement, soit à la validation

Cela permet d’éviter les fuites entre groupes observés plus haut et de limiter le surapprentissage.

À noter : dans notre exemple de génération de légendes, toutes les features (les pixels de l’image) participent au critère de regroupement.

Mais plus généralement, seul un sous-ensemble de variables peut être utilisé pour définir ces groupes et réaliser une séparation correcte des données.

Par exemple, prenons un dataset d’imagerie médicale. Chaque échantillon peut contenir plusieurs images (différentes vues d’une même zone anatomique), et le modèle doit estimer la sévérité d’une pathologie.

Dans ce cas, il est essentiel de regrouper toutes les images appartenant à un même patient, puis de séparer les données par patient. Sinon, cela introduit une fuite d’information, et le modèle aura du mal à généraliser à de nouveaux patients.

Implémentation

Avec scikit‑learn, la classe GroupShuffleSplit implémente exactement cette idée.

Schéma de la classe GroupShuffleSplit de scikit-learn pour séparer les données en respectant les groupes

Imaginons que nous ayons un dataset du type :

Tableau de données organisées par groupe, avec les features x1 et x2, pour GroupShuffleSplit
  • x1 et x2 sont les features,

  • y est la variable cible,

  • group représente le critère de regroupement.

On peut créer ce dataset comme suit :

Code Python : création du dataset organisé par groupes pour tester GroupShuffleSplit

Ensuite, on importe GroupShuffleSplit depuis sklearn et on instancie l’objet :

Code Python : import et instanciation de l'objet GroupShuffleSplit de scikit-learn

Ici, on précise notamment :

  • n_splits : le nombre d’itérations de rééchantillonnage (re-tirage) / de séparation

  • test_size : la proportion de groupes à allouer à l’ensemble de test (arrondie à l’entier supérieur)

La méthode split() permet ensuite d’effectuer la séparation en respectant ces groupes :

Code Python : méthode split de GroupShuffleSplit qui renvoie un générateur d'indices en respectant les groupes

Cette méthode renvoie un générateur, que l’on peut déballer pour obtenir :

Code Python : indices d'entraînement et de validation obtenus avec GroupShuffleSplit

Comme on peut le constater :

  • Toutes les observations des groupes « A » et « C » sont regroupées dans l’ensemble d’entraînement

  • Toutes les observations du groupe « B » sont regroupées dans l’ensemble de validation / test

De cette façon, on évite la fuite de données observée précédemment.

3) KernelPCA vs PCA

Le problème du PCA

Lors d’une réduction de dimension, l’Analyse en Composantes Principales (PCA) cherche à projeter les données dans un sous-espace linéaire de plus faible dimension, tout en conservant au mieux leur structure.

Par exemple, considérons le dataset simplifié suivant :

Code Python : création d'un dataset linéaire simplifié pour illustrer le problème du PCA

Si l’on trace ce dataset, on obtient :

Graphique d'un dataset factice linéaire, avec un sous-espace linéaire de faible dimension évident

On voit assez clairement qu’il existe un sous‑espace linéaire le long duquel on peut représenter les données tout en conservant un maximum de variance :

Graphique d'un sous-espace linéaire représentant les données en conservant un maximum de variance avec le PCA

Mais que se passe‑t‑il si nos données s’alignent sur un sous‑espace de faible dimension, mais non linéaire ?

Par exemple, considérons ce dataset non linéaire :

Code Python : création d'un dataset factice non linéaire pour illustrer les limites du PCA

En le traçant, on obtient :

Graphique d'un dataset factice non linéaire dont le sous-espace de faible dimension n'est pas évident

Ici, le sous‑espace de faible dimension n’est pas évident au premier coup d’œil.

Pourtant, la figure suivante montre qu’il existe bien un sous‑espace non linéaire de faible dimension sur lequel nos données pourraient être projetées :

Graphique d'un sous-espace non linéaire, une courbe, qui représente les données du dataset factice

La courbe représente un sous‑espace continu, non linéaire et de faible dimension, sur lequel nos données pourraient être décrites.

Pourquoi ne pas simplement faire ça ?

Le problème est que le PCA est incapable d’identifier ce type de structure car les points ne sont pas alignés selon une relation linéaire.

En d’autres termes, le PCA est une technique linéaire de réduction de dimension — et elle montre ses limites dans ce type de situation.

Pourtant, face à ce dataset non linéaire, on pressent qu’une représentation en une seule dimension serait possible… à condition de capturer correctement cette structure courbe :

Graphique des données alignées selon une courbe non linéaire en une dimension, intuition du KernelPCA

KernelPCA

KernelPCA (ou « l’astuce du noyau ») a justement été conçu pour pallier cette limitation du PCA.

Le principe est assez simple :

  • on projette d’abord les données dans un espace de dimension (potentiellement) plus élevée à l’aide d’une fonction noyau, de manière à ce qu’elles deviennent linéairement séparables. Scikit‑learn fournit une classe KernelPCA avec de nombreux noyaux populaires.

  • on applique ensuite un PCA standard dans cet espace transformé.

Voyons maintenant l’efficacité de KernelPCA par rapport au PCA.

Commençons par ajuster un PCA sur le dataset non linéaire précédent :

Code Python : ajustement d'un PCA sur le dataset non linéaire avec reconstruction des données

Dans ce code :

  1. Avant d’appliquer PCA, on standardise généralement les données pour que chaque feature ait une moyenne nulle et une variance unité. On utilise pour cela StandardScaler de scikit‑learn.

  2. On applique ensuite le PCA sur les données standardisées, avec n_components=1 pour réduire à une seule dimension.

  3. Après PCA, on peut reconstruire les données dans l’espace d’origine avec inverse_transform appliqué aux données projetées. Autrement dit, on obtient un X_original tel que son transformé PCA serait X.

En traçant data_standardized en nuage de points et data_pca_reconstructed en courbe, on obtient :

Graphique du PCA qui produit un sous-espace linéaire sur des données non linéaires

On voit que, même si les données sont non linéaires, le PCA produit malgré tout un sous‑espace linéaire pour la projection.

Passons maintenant au KernelPCA sur le même dataset :

Code Python : ajustement d'un KernelPCA sur le même dataset non linéaire

Dans ce code :

  1. KernelPCA est appliqué aux données standardisées, avec n_components=2 pour réduire à deux dimensions, kernel='polynomial' pour le noyau polynomial, et gamma comme paramètre du noyau choisi. L’option fit_inverse_transform=True permet d’activer l’inverse transform.

  2. Après KernelPCA, on peut revenir dans l’espace original avec la méthode inverse_transform de KernelPCA.

En traçant data_standardized en nuage de points et data_kpca_reconstructed en courbe, on obtient cette fois :

Graphique du KernelPCA qui produit un sous-espace non linéaire suivant la courbe des données

KernelPCA parvient cette fois à produire un sous‑espace non linéaire, cohérent avec ce que nous avions visualisé auparavant :

Graphique des données alignées selon une courbe non linéaire en une dimension, intuition du KernelPCA

Le revers de la médaille

Le prix à payer, c’est le temps de calcul.

Rappelons que la complexité en temps de PCA est déjà cubique par rapport au nombre de dimensions.

Schéma de la complexité en temps du PCA, déjà cubique par rapport au nombre de dimensions

KernelPCA, quant à lui, repose sur l’astuce du noyau, dont la complexité est quadratique par rapport au nombre d’observations n.

Au final, cela accroît encore le temps de calcul global.

C’est donc un aspect à garder en tête avant d’utiliser le KernelPCA.

💡 En continuant, tu vas découvrir :

  • Pourquoi visualiser un dataset en 2D avec le PCA peut être trompeur, et comment vérifier avec la variance expliquée cumulée que les deux premières composantes suffisent vraiment

  • Pourquoi la distance euclidienne se trompe quand les features sont corrélées, et comment la distance de Mahalanobis corrige cela, par exemple pour repérer des outliers

  • Pourquoi la corrélation ne mesure pas le pouvoir prédictif, et comment le Predictive Power Score et la corrélation de Spearman comblent ses limites

  • Pourquoi les statistiques de synthèse et les boxplots peuvent cacher des distributions très différentes, et quels graphiques utiliser en complément

  • Comment détecter un covariate shift multivarié que les tests variable par variable ne voient pas, grâce à la reconstruction de données avec un autoencodeur

logo

La suite de ce mini-cours est réservée aux membres du Cercle des Experts.

Tu as déjà le début. Pour accéder à l’explication complète et aux autres mini-cours, rejoins le Cercle des Experts.

Rejoindre le Cercle des Experts

En devenant membre, tu débloques :

  • L’intégralité de ce mini-cours, sans coupure
  • Tous les mini-cours et les ressources associées
  • Chaque nouveau mini-cours dès sa sortie

Commentaires

Avatar

ou participer

À lire ensuite

Des shots dans la même veine, à lire entre deux tâches.

Voir tous les shots
caret-right