ml · temario
2.1tema 1 de 4

ML Pipeline — Train / Validation / Test

El flujo correcto de datos: cómo separar, transformar y evaluar sin contaminar.

Todo proyecto de Machine Learning tiene una pregunta fundamental antes de escribir una sola línea de modelo: ¿de dónde vienen los datos que usaré para evaluar si mi modelo funciona? Si usas los mismos datos para entrenar y para medir el desempeño, estás trampeando el examen. El modelo memoriza respuestas en lugar de aprender a razonar.

La solución es dividir el dataset en tres conjuntos con roles distintos. El conjunto de entrenamiento (train) es donde el modelo aprende. El de validación (val) es donde tú, el desarrollador, tomas decisiones: qué hiperparámetros usar, qué features incluir. El conjunto de test es el examen final — se usa una única vez, al final de todo, para reportar el desempeño real del modelo.

El data leakage (filtración de datos) ocurre cuando información del futuro o del conjunto de evaluación se cuela en el entrenamiento. El error más común: ajustar el scaler o el imputer con todo el dataset, incluyendo el test set, antes de dividirlo. Así, el modelo 've' estadísticas del test durante el entrenamiento y los resultados son optimistamente falsos.

Para evitar leakage, la regla es simple: fit solo en train, transform en todos. Esto se automatiza con sklearn.pipeline.Pipeline, que encadena preprocesamiento y modelo en un objeto único. Cuando llamas pipeline.fit(X_train, y_train), todo el preprocesamiento se ajusta solo con datos de entrenamiento. Cuando llamas pipeline.predict(X_test), aplica las transformaciones ya ajustadas.

La validación cruzada (cross-validation) va un paso más allá: en lugar de una sola partición train/val, divide el train en K pliegues (folds) y entrena K veces, cada vez usando un fold distinto como validación. El resultado es una estimación más robusta del desempeño real, especialmente cuando el dataset es pequeño. KFold y StratifiedKFold (para clasificación con clases desbalanceadas) son las implementaciones estándar.

Un pipeline bien construido es reproducible, portable y resistente a leakage. Es la diferencia entre un experimento que funciona solo en tu laptop y uno que puede desplegarse con confianza en producción.

structure.txt
# Split estratégico
from sklearn.model_selection import train_test_split

X_train, X_temp, y_train, y_temp = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)
X_val, X_test, y_val, y_test = train_test_split(
    X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp
)

# Pipeline con preprocesamiento + modelo
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier

pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('model', RandomForestClassifier(n_estimators=100, random_state=42))
])

pipe.fit(X_train, y_train)          # fit solo en train
val_score = pipe.score(X_val, y_val) # evalúa en val

# Cross-validation (K=5)
from sklearn.model_selection import cross_val_score, StratifiedKFold

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipe, X_train, y_train, cv=cv, scoring='f1')
print(f'CV F1: {scores.mean():.3f} ± {scores.std():.3f}')

Debugging lab

Detecta y corrige el error en el código.

0/5 tests passing0%
  1. 2.1.5.1

    scaler = StandardScaler() X_scaled = scaler.fit_transform(X) X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2)

  2. 2.1.5.2

    scores = cross_val_score(model, X, y, cv=5) # ¿Cuál es el problema si X incluye el test set reservado?

  3. 2.1.5.3

    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) pipe.fit(X_train, y_train) for C in [0.1, 1, 10]: pipe.set_params(model__C=C) print(pipe.score(X_test, y_test)) # elige el mejor C

  4. 2.1.5.4

    pipe = Pipeline([('scaler', StandardScaler()), ('model', RandomForestClassifier())]) pipe.fit(X_train, y_train) scores = cross_val_score(pipe, X_train, y_train, cv=5) # ¿Hay leakage en este código?

  5. 2.1.5.5

    kf = KFold(n_splits=5) scores = cross_val_score(model, X_train, y_train, cv=kf) # El dataset tiene 90 % clase 0 y 10 % clase 1. ¿Cuál es el problema?