ML Atlas

Machine learning desde cero · Lección 4.5 · 24 de 33 lecciones listas · 14 min

Fuga de datos

En pocas palabras

Una fuga es información del test o del futuro que llega al entrenamiento. Con ruido puro finge un 82 % en validación. Conoce 8 tipos y encuéntralos tú.

Calentamiento Dos preguntas de lecciones anteriores. Un minuto, y no bloquea la lección.
De la lección 4.4: Ingeniería de características ¿Por qué un solo umbral sobre los flavonoides no puede separar la variedad 2?

Ver la respuesta

Respuesta correcta: Porque la variedad 2 está en el centro y un umbral no recorta un intervalo central

De media 2,08 frente a 2,98 y 0,78 de las otras. Cada umbral deja también otra variedad del lado «variedad 2». El mejor umbral acierta un 61,6% en entrenamiento frente al 60,0% de «siempre otra», y la SVM lineal no predice la variedad 2 en ningún sitio: 60,4%.

De la lección 4.3: Categorías y escalado Las islas se codifican Biscoe = 0, Dream = 1, Torgersen = 2. ¿Qué supone una regresión lineal con un solo peso para esa columna?

Ver la respuesta

Respuesta correcta: Que Dream está a medio camino entre Biscoe y Torgersen, en pasos iguales

Un solo peso significa pasos iguales: el modelo predice 625 g menos de Biscoe a Dream y otros 625 g de Dream a Torgersen. En los datos Dream y Torgersen pesan casi lo mismo.

En la lección 3.2 la validación cruzada daba una estimación honesta del modelo. Honesta con una condición: nada de la parte de test puede tocar antes el entrenamiento. Cuando lo toca, hablamos de fuga de datos. El modelo parece excelente en validación y se hunde en el uso real. Kapoor y Narayanan reunieron los informes de otros investigadores sobre fugas en 294 artículos científicos de 17 campos. Empecemos por el ejemplo más limpio: datos en los que no hay nada que aprender.

Predice y comprueba Tienes 60 muestras y 1000 columnas de ruido puro, y las etiquetas 0/1 son aleatorias. Eliges las 10 columnas más correlacionadas con la etiqueta usando todas las muestras y luego evalúas el modelo con validación cruzada de 5 pliegues. ¿Qué nota mostrará la validación?
¿Qué tan seguro estás?

Experimento interactivo

Este experimento interactivo funciona en un navegador con JavaScript activado. El texto que lo rodea describe lo que muestra.

Ver la respuesta

Respuesta correcta: Más del 80 %, aunque estos datos no tienen ninguna señal

La validación da un 81,7 %. Si eliges las columnas en cada pliegue solo con los datos de entrenamiento, la nota baja al 43,3 %, una moneda al aire. En 20 conjuntos de ruido nuevos la media es del 86,6 % con fuga y del 49,8 % de forma honesta.

Prueba esto

  • Con las variables elegidas antes de dividir, mueve k de 5 a 50. ¿Hasta dónde sube la nota con ruido puro?
  • Con la misma k, cambia a elegir dentro de la validación. ¿Dónde cae la barra y dónde los puntos de los 20 conjuntos de ruido nuevos?
  • En la segunda parte del lab, encuentra el paso con fuga en cada uno de los 8 procesos.

Cuando lo hayas comprobado en el lab, pongamos nombre a lo que ves.

La idea en pocas palabras

Una fuga de datos es cualquier información que el modelo no tendría en el momento de una predicción real y que aun así llegó a su entrenamiento o a su evaluación. La validación tiene que hacer de futuro: datos que el modelo nunca vio. La fuga estropea ese papel, así que la nota sube y el modelo no es mejor en absoluto.

En el lab, entre 1000 columnas de ruido siempre hay algunas que encajan con las etiquetas por azar. La mejor tiene una correlación de 0,48 con la etiqueta, cuando lo típico para el ruido es alrededor de 0,13. Elegir con las 60 muestras ajusta las columnas también a las muestras que después hacen de test. La validación cruzada comprueba entonces el modelo con datos que ya lo moldearon, y muestra un 81,7 %. Elegir dentro de un pliegue solo ve 48 muestras de entrenamiento. Las columnas que encajan con ellas por azar no dicen nada de las 12 muestras de test, así que la nota baja al 43,3 %, dentro de una moneda al aire.

Kapoor y Narayanan clasificaron las fugas en 8 tipos dentro de tres grupos:

  • sin una división limpia: sin conjunto de test separado, preprocesamiento ajustado a todos los datos, selección de variables con todos los datos, duplicados a ambos lados de la división;
  • una variable que no conoces al predecir, por ejemplo los días en el hospital cuando el modelo debe predecir el ingreso;
  • un test de otra distribución que la del uso: una división al azar de datos temporales, las mismas personas en entrenamiento y test, un test elegido de otra forma que los datos con los que trabajará el modelo.

Encontrarás cada uno en la segunda parte del lab.

Jak to policzono
  • Ruido: 60 muestras, 1000 columnas de una distribución normal, 30 etiquetas 0 y 30 etiquetas 1 en orden aleatorio. Lo genera el navegador con la semilla 1; los puntos son 20 conjuntos más con otras semillas.
  • Modelo: las k columnas con mayor correlación con la etiqueta (en valor absoluto), un clasificador de centroide más cercano, validación cruzada estratificada de 5 pliegues (6 muestras de cada clase en cada pliegue).
  • Biopsias en «Error frecuente»: Breast Cancer Wisconsin (Diagnostic), UCI, licencia CC BY 4.0, 569 biopsias. Tipos de fuga: S. Kapoor, A. Narayanan, «Leakage and the reproducibility crisis in machine-learning-based science», Patterns, 2023.

Por qué funciona

Una evaluación es honesta solo cuando los datos de test no influyeron en ninguna decisión sobre el modelo. Seleccionar variables también es una decisión, igual que rellenar valores faltantes, escalar, elegir un umbral o quedarse con el mejor de varios modelos. Si alguna de ellas vio las etiquetas del test, el test deja de ser independiente. Cuanto mayor es la elección (10 columnas de 1000), más ajuste al azar pasa a la nota.

Un solo conjunto de ruido podría salir así por azar. Pero en ninguno de los 20 conjuntos nuevos del lab la nota con fuga con k = 10 baja del 81,7 %. En el código de abajo el mismo experimento se hace con 50 conjuntos de ruido en scikit-learn: elegir antes de dividir da de media un 86,4 % (como mínimo un 73,3 %), y elegir en cada pliegue un 50,0 %.

Error frecuente

¿Verdadero o falso?: «la fuga es solo la etiqueta escondida en las variables». Falso. En el lab ninguna columna contiene la etiqueta, y aun así la validación muestra un 81,7 % con ruido puro. Lo que se filtra es una decisión tomada con todos los datos. En las biopsias, sobremuestrear las malignas antes de la validación cruzada da una sensibilidad del 97,1 %, y sobremuestrear solo en los datos de entrenamiento un 92,1 %, en 50 particiones de 50. Las copias caen a ambos lados de la división, así que el modelo reconoce biopsias que ya vio. Los duplicados en los datos funcionan igual, y una división al azar de datos temporales mete el futuro en el entrenamiento.

No todas las fugas son igual de peligrosas. Un escalador ajustado con todas las biopsias cambia la exactitud de un modelo de vecino más cercano en 0,08 puntos de media: no ve las etiquetas, así que transmite muy poco. La selección de variables y el sobremuestreo usan las etiquetas, así que transmiten mucho. La regla es una: ajusta cada paso que aprende algo de los datos solo con la parte de entrenamiento.

Test

Tres preguntas para comprobar lo aprendido. Elige una respuesta o despliega la solución bajo cada pregunta. Con el teclado: las flechas eligen, Enter o Espacio confirma.

1. Elegir 10 columnas con las 60 muestras da un 81,7 % en validación cruzada con ruido puro. ¿De dónde sale esa nota?
Ver la respuesta

Respuesta correcta: Las columnas se eligieron también según las etiquetas de las muestras de test

Eligiendo en cada pliegue solo con los datos de entrenamiento, el mismo modelo da un 43,3 %. En 20 conjuntos de ruido nuevos la media es del 86,6 % con fuga y del 49,8 % de forma honesta.

2. Rellenas los faltantes con la media de todo el archivo, divides en entrenamiento y test, entrenas con el entrenamiento y evalúas en el test. ¿Qué paso tiene fuga?
Ver la respuesta

Respuesta correcta: Rellenar los huecos con la media calculada con todo el archivo

La media incluye los datos de test, así que el test influye en el entrenamiento. Ajusta la imputación solo con el entrenamiento, mejor dentro de un pipeline.

3. Escalar antes de dividir cambia la nota en las biopsias 0,08 puntos de media, y sobremuestrear las malignas antes de dividir sube la sensibilidad del 92,1 % al 97,1 %. ¿Por qué la diferencia?
Ver la respuesta

Respuesta correcta: El escalador no mira las etiquetas, y las copias de malignas llegan al test

Una fuga transmite tanto como la decisión sabe del test. El escalador solo conoce medias y desviaciones, y una copia de una biopsia maligna en el test es una respuesta que el modelo ya vio.

Ideas clave

  • Una fuga es información que el modelo no tendría al predecir y que aun así tocó su entrenamiento o su evaluación.
  • Ajusta cada paso que aprende de los datos (selección, imputación, escalado, sobremuestreo) solo con la parte de entrenamiento, mejor dentro de un pipeline.
  • Divide los datos como se usará el modelo: por tiempo, por persona, sin duplicados.
  • Cuando una nota parece demasiado buena, busca primero una fuga.

Más a fondo: fórmula, código, práctica

Las matemáticas

Adivinar con n muestras da una exactitud de media 0,5 y desviación típica √(0,5 · 0,5 / n). Con n = 60 son 0,065, así que una moneda al aire es un 50 % ± 13 puntos (dos desviaciones).

Con ruido puro, la correlación de una columna con la etiqueta tiene media 0 y una desviación de alrededor de 1 / √n, unos 0,13 con n = 60. Entre 1000 columnas la mejor queda varias desviaciones lejos del cero: 0,48 en el lab.

La validación cruzada honesta calcula  = media sobre k de A(f_k, T_k), donde el modelo f_k (incluida la selección de variables) se construye solo con los datos fuera del pliegue T_k. Si las variables se eligen con todos los datos, la selección depende de las etiquetas de T_k, así que f_k no es independiente de T_k y  deja de estimar la exactitud con datos nuevos.

En código (Python)

El mismo experimento en scikit-learn: elegir 10 columnas en 50 conjuntos de ruido y, en las biopsias, escalar y sobremuestrear las malignas antes de dividir y dentro de cada pliegue, con 50 particiones.

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 muestras x 1000 columnas de ruido puro, etiquetas 30/30 en orden aleatorio
    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 columnas elegidas con las 60
    honest = cross_val_score(make_pipeline(SelectKBest(f_classif, k=10), NearestCentroid()), X, y, cv=cv).mean()  # elegidas dentro de cada pliegue
    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 = maligna
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()  # escalador ajustado con todas las biopsias
    inside = cross_val_score(make_pipeline(StandardScaler(), knn()), X, y, cv=cv).mean()  # ajustado con cada parte de entrenamiento
    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):  # copia malignas al azar hasta que las dos clases sean iguales
    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)  # las copias hechas ANTES de dividir también caen en los pliegues 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):  # copias solo dentro de cada parte de entrenamiento
        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")

Resultado

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

El lab muestra un conjunto de ruido y 20 más, el código otros 50 conjuntos, así que los números difieren un poco: en el lab la fuga da un 81,7 % en el primer conjunto y un 86,6 % de media en los 20 siguientes, en el código un 86,4 % de media. La conclusión es la misma en cada conjunto.

Para profesionales
  • En scikit-learn mete cada paso que aprende de los datos en un Pipeline junto con el modelo, y solo entonces evalúa el conjunto con cross_val_score: así el fit de cada paso ve solo la parte de entrenamiento.
  • Divide los datos temporales con TimeSeriesSplit y los datos con grupos (pacientes, clientes, fotos de una misma cámara) con GroupKFold.
  • Usa el sobremuestreo y SMOTE dentro de un Pipeline de imbalanced-learn, solo en las partes de entrenamiento (lección 2.5).
  • En Kaggle la fuga a veces está en el propio archivo: identificadores, orden de las filas, marcas de tiempo. Una nota mucho mejor que la de los demás es una señal para buscar una fuga antes de celebrarlo.

Profundiza en el Atlas

Entradas de la enciclopedia sobre las ideas de esta lección: