Le machine learning en partant de zéro · Leçon 4.5 · 24 sur 33 leçons prêtes · 14 min
Fuite de données
En bref
Une fuite, c’est une information du test ou du futur qui atteint l’entraînement. Sur du bruit pur, elle simule 82 %. Découvrez 8 types et trouvez-les.
Échauffement Deux questions de leçons précédentes. Une minute, sans jamais bloquer la leçon.
Dans la leçon 3.2, la validation croisée donnait une estimation honnête du modèle. Honnête à une condition : rien de la partie de test ne doit toucher l’entraînement avant. Quand c’est le cas, on parle de fuite de données. Le modèle a l’air excellent en validation et s’effondre en usage réel. Kapoor et Narayanan ont rassemblé les signalements d’autres chercheurs sur des fuites dans 294 articles scientifiques de 17 domaines. Commençons par l’exemple le plus pur : des données où il n’y a rien à apprendre.
Choisissez une réponse et votre degré de certitude.
Répondez pour débloquer le labo.
Labo interactif
Ce labo interactif fonctionne dans un navigateur avec JavaScript activé. Le texte qui l’entoure décrit ce qu’il montre.
Voir la réponse
Bonne réponse : Plus de 80 %, alors que ces données n’ont absolument aucun signal
La validation affiche 81,7 %. Si vous choisissez les colonnes dans chaque pli sur les seules données d’entraînement, le score tombe à 43,3 %, pile ou face. Sur 20 nouveaux jeux de bruit, la moyenne est de 86,6 % avec la fuite et de 49,8 % honnêtement.
À vous de jouer
- Avec les variables choisies avant le découpage, faites passer k de 5 à 50. Jusqu’où monte le score sur du bruit pur ?
- Avec le même k, passez au choix dans la validation. Où tombe la barre, et où tombent les points des 20 nouveaux jeux de bruit ?
- Dans la deuxième partie du lab, trouvez l’étape qui fuit dans chacune des 8 chaînes.
Une fois vérifié dans le lab, nommons ce que vous voyez.
L’idée en quelques mots
Une fuite de données, c’est toute information que le modèle n’aurait pas au moment d’une vraie prédiction et qui a quand même atteint son entraînement ou son évaluation. La validation doit jouer le futur : des données que le modèle n’a jamais vues. La fuite gâche ce jeu, donc le score monte alors que le modèle n’est pas meilleur du tout.
Dans le lab, parmi 1000 colonnes de bruit, il y en a toujours qui collent aux étiquettes par hasard. La meilleure a une corrélation de 0,48 avec l’étiquette, alors qu’environ 0,13 est typique pour du bruit. Choisir sur les 60 échantillons ajuste les colonnes aussi aux échantillons qui joueront ensuite le test. La validation croisée vérifie donc le modèle sur des données qui l’ont déjà façonné, et affiche 81,7 %. Choisir dans un pli ne voit que 48 échantillons d’entraînement. Les colonnes qui collent à eux par hasard ne disent rien des 12 échantillons de test, donc le score tombe à 43,3 %, dans les limites de pile ou face.
Kapoor et Narayanan ont classé les fuites en 8 types répartis en trois groupes :
- pas de découpage propre : pas de jeu de test séparé, prétraitement ajusté sur toutes les données, sélection de variables sur toutes les données, doublons des deux côtés du découpage ;
- une variable inconnue au moment de prédire, par exemple les jours passés à l’hôpital quand le modèle doit prédire l’hospitalisation ;
- un test issu d’une autre distribution que l’usage : un découpage au hasard de données temporelles, les mêmes personnes dans l’entraînement et le test, un test choisi autrement que les données sur lesquelles le modèle travaillera.
Vous trouverez chacun d’eux dans la deuxième partie du lab.
Jak to policzono
- Bruit : 60 échantillons, 1000 colonnes tirées d’une loi normale, 30 étiquettes 0 et 30 étiquettes 1 dans un ordre aléatoire. Le navigateur le génère avec la graine 1 ; les points sont 20 autres jeux avec d’autres graines.
- Modèle : les k colonnes de plus forte corrélation avec l’étiquette (en valeur absolue), un classifieur du centroïde le plus proche, une validation croisée stratifiée à 5 plis (6 échantillons de chaque classe dans chaque pli).
- Biopsies de « Erreur fréquente » : Breast Cancer Wisconsin (Diagnostic), UCI, licence CC BY 4.0, 569 biopsies. Types de fuite : S. Kapoor, A. Narayanan, « Leakage and the reproducibility crisis in machine-learning-based science », Patterns, 2023.
Pourquoi ça marche
Une évaluation n’est honnête que si les données de test n’ont influencé aucune décision sur le modèle. Sélectionner des variables est aussi une décision, tout comme combler des valeurs manquantes, mettre à l’échelle, choisir un seuil ou garder le meilleur de plusieurs modèles. Si l’une d’elles a vu les étiquettes du test, le test n’est plus indépendant. Plus le choix est grand (10 colonnes sur 1000), plus l’ajustement au hasard passe dans le score.
Un seul jeu de bruit pourrait donner cela par hasard. Mais dans aucun des 20 nouveaux jeux du lab, le score avec fuite à k = 10 ne descend sous 81,7 %. Dans le code ci-dessous, la même expérience tourne sur 50 jeux de bruit dans scikit-learn : choisir avant le découpage donne 86,4 % en moyenne (au minimum 73,3 %), et choisir dans chaque pli 50,0 %.
Erreur fréquente
Vrai ou faux : « une fuite, c’est seulement l’étiquette cachée dans les variables ». Faux. Dans le lab, aucune colonne ne contient l’étiquette, et la validation affiche quand même 81,7 % sur du bruit pur. Ce qui fuit, c’est une décision prise sur toutes les données. Sur les biopsies, suréchantillonner les malignes avant la validation croisée donne un rappel de 97,1 %, et suréchantillonner seulement dans les données d’entraînement 92,1 %, dans 50 découpages sur 50. Les copies tombent des deux côtés du découpage, donc le modèle reconnaît des biopsies qu’il a déjà vues. Les doublons dans les données agissent de la même façon, et un découpage au hasard de données temporelles met le futur dans l’entraînement.
Toutes les fuites ne sont pas aussi dangereuses. Un scaler ajusté sur toutes les biopsies change l’exactitude d’un modèle du plus proche voisin de 0,08 point en moyenne : il ne voit pas les étiquettes, donc il transmet très peu. La sélection de variables et le suréchantillonnage utilisent les étiquettes, donc ils transmettent beaucoup. La règle est unique : ajustez chaque étape qui apprend quelque chose des données sur la seule partie d’entraînement.
Quiz
Trois questions pour vérifier vos acquis. Choisissez une réponse ou dépliez la solution sous chaque question. Au clavier : les flèches choisissent, Entrée ou Espace valide.
À retenir
- Une fuite, c’est une information que le modèle n’aurait pas au moment de prédire et qui a quand même touché son entraînement ou son évaluation.
- Ajustez chaque étape qui apprend des données (sélection, imputation, mise à l’échelle, suréchantillonnage) sur la seule partie d’entraînement, idéalement dans un pipeline.
- Découpez les données comme le modèle sera utilisé : par période, par personne, sans doublons.
- Quand un score semble trop beau, cherchez d’abord une fuite.
Pour aller plus loin : formule, code, pratique
Les maths
Deviner sur n échantillons donne une exactitude de moyenne 0,5 et d’écart type √(0,5 · 0,5 / n). Pour n = 60, cela fait 0,065, donc pile ou face, c’est 50 % ± 13 points (deux écarts types).
Pour du bruit pur, la corrélation d’une colonne avec l’étiquette a une moyenne de 0 et un écart type d’environ 1 / √n, soit environ 0,13 pour n = 60. Parmi 1000 colonnes, la meilleure se trouve à plusieurs écarts types de zéro : 0,48 dans le lab.
La validation croisée honnête calcule  = moyenne sur k de A(f_k, T_k), où le modèle f_k (sélection de variables comprise) est construit uniquement avec les données hors du pli T_k. Si les variables sont choisies sur toutes les données, la sélection dépend des étiquettes de T_k, donc f_k n’est pas indépendant de T_k, et  n’estime plus l’exactitude sur de nouvelles données.
En code (Python)
La même expérience dans scikit-learn : choisir 10 colonnes sur 50 jeux de bruit et, sur les biopsies, mettre à l’échelle et suréchantillonner les malignes avant le découpage et dans chaque pli, sur 50 découpages.
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.model_selection import StratifiedKFold, cross_val_score, cross_val_predict
from sklearn.neighbors import NearestCentroid, KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import recall_score
def noise(seed): # 60 échantillons x 1000 colonnes de bruit pur, étiquettes 30/30 dans un ordre aléatoire
rng = np.random.default_rng(seed)
X, y = rng.normal(size=(60, 1000)), rng.permutation(np.repeat([0, 1], 30))
cv = StratifiedKFold(5, shuffle=True, random_state=seed)
leak = cross_val_score(NearestCentroid(), SelectKBest(f_classif, k=10).fit_transform(X, y), y, cv=cv).mean() # 10 colonnes choisies sur les 60
honest = cross_val_score(make_pipeline(SelectKBest(f_classif, k=10), NearestCentroid()), X, y, cv=cv).mean() # choisies dans chaque pli
return leak, honest
r = np.array([noise(s) for s in range(50)])
print(f"pure noise, 50 datasets: picked before the split {r[:, 0].mean():.1%} (lowest {r[:, 0].min():.1%}), inside CV {r[:, 1].mean():.1%}")
X, y = load_breast_cancer(return_X_y=True)
y = 1 - y # 1 = maligne
knn = lambda: KNeighborsClassifier(1)
scale = []
for s in range(50):
cv = StratifiedKFold(5, shuffle=True, random_state=s)
before = cross_val_score(knn(), StandardScaler().fit_transform(X), y, cv=cv).mean() # scaler ajusté sur toutes les biopsies
inside = cross_val_score(make_pipeline(StandardScaler(), knn()), X, y, cv=cv).mean() # ajusté sur chaque partie d'entraînement
scale.append(abs(before - inside))
print(f"breast cancer, 1-NN, 50 splits: scaling before the split changes accuracy by {np.mean(scale) * 100:.2f} points on average")
def oversample(rows, rng): # copie des malignes au hasard jusqu'à ce que les deux classes soient égales
mal, ben = rows[y[rows] == 1], rows[y[rows] == 0]
return np.r_[rows, rng.choice(mal, len(ben) - len(mal))]
rec = []
for s in range(50):
rng, cv = np.random.default_rng(s), StratifiedKFold(5, shuffle=True, random_state=s)
rows = oversample(np.arange(len(y)), rng) # les copies faites AVANT le découpage tombent aussi dans les plis de test
before = recall_score(y[rows], cross_val_predict(make_pipeline(StandardScaler(), knn()), X[rows], y[rows], cv=cv))
pred = np.empty(len(y))
for tr, te in cv.split(X, y): # copies seulement dans chaque partie d'entraînement
t = oversample(tr, rng)
pred[te] = make_pipeline(StandardScaler(), knn()).fit(X[t], y[t]).predict(X[te])
rec.append((before, recall_score(y, pred)))
rec = np.array(rec)
print(f"oversampling malignant before the split: recall {rec[:, 0].mean():.1%}, inside each training part: {rec[:, 1].mean():.1%}; higher before in {(rec[:, 0] > rec[:, 1]).sum()} of 50")
Sortie
pure noise, 50 datasets: picked before the split 86.4% (lowest 73.3%), inside CV 50.0%
breast cancer, 1-NN, 50 splits: scaling before the split changes accuracy by 0.08 points on average
oversampling malignant before the split: recall 97.1%, inside each training part: 92.1%; higher before in 50 of 50
Le lab montre un jeu de bruit et 20 autres, le code 50 autres jeux, donc les nombres diffèrent un peu : dans le lab, la fuite donne 81,7 % sur le premier jeu et 86,6 % en moyenne sur les 20 suivants, dans le code 86,4 % en moyenne. La conclusion est la même dans chaque jeu.
Télécharger le notebook (.ipynb). Ouvrez-le dans Jupyter ou importez-le dans Google Colab ; il télécharge lui-même les données manquantes.
Pour les praticiens
- Dans scikit-learn, mettez chaque étape qui apprend des données dans un
Pipelineavec le modèle, et n’évaluez qu’ensuite le tout aveccross_val_score: lefitde chaque étape ne voit alors que la partie d’entraînement. - Découpez les données temporelles avec
TimeSeriesSplit, et les données groupées (patients, clients, photos d’un même appareil) avecGroupKFold. - Utilisez le suréchantillonnage et SMOTE dans un
Pipelined’imbalanced-learn, sur les seules parties d’entraînement (leçon 2.5). - Sur Kaggle, la fuite est parfois dans le fichier lui-même : identifiants, ordre des lignes, horodatages. Un score bien meilleur que celui de tous les autres est un signal pour chercher une fuite avant de vous réjouir.
Cela vous a-t-il aidé ?
Merci ! Cela nous aide à nous améliorer.
Aller plus loin dans l’Atlas
Articles de l’encyclopédie sur les notions de cette leçon :