Validación cruzada

Eval

Validación cruzada

まとめ
  • La validación cruzada divide el dataset en pliegues para estimar el rendimiento con mayor estabilidad.
  • Compara el enfoque con un único hold-out y observa cómo influye el número de pliegues.
  • Revisa consejos prácticos sobre el diseño de pliegues, el coste computacional y la presentación de resultados.

La validación cruzada separa los datos en varias partes, entrena el modelo con una porción y lo evalúa con la restante para comprobar la validez global del análisis.Validación cruzada (Wikipedia)


1. ¿En qué consiste? #

  • Divide los datos en varios “folds” e intercala qué fold actúa como validación.
  • Un único train_test_split puede arrojar gran varianza; la validación cruzada promedia el rendimiento.
  • Las variantes más comunes son k-fold y stratified k-fold (mantiene el balance de clases por fold).

2. Ejemplo básico en Python 3.13 #

Supongamos Python 3.13 y scikit-learn instalados:

python --version        # Python 3.13.0
pip install scikit-learn matplotlib

El siguiente ejemplo compara un hold-out simple con una validación cruzada de 5 pliegues en un dataset sintético desequilibrado.

from __future__ import annotations

import numpy as np
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import cross_validate, train_test_split

RANDOM_STATE = 42


def make_dataset() -> tuple[np.ndarray, np.ndarray]:
    """Genera un dataset binario desequilibrado."""
    features, labels = make_classification(
        n_samples=300,
        n_classes=2,
        weights=[0.2, 0.8],
        n_informative=4,
        n_features=6,
        n_clusters_per_class=2,
        shuffle=True,
        random_state=RANDOM_STATE,
    )
    return features, labels


def holdout_score() -> float:
    """Calcula el ROC-AUC con una sola partición hold-out."""
    features, labels = make_dataset()
    x_train, x_valid, y_train, y_valid = train_test_split(
        features,
        labels,
        test_size=0.2,
        stratify=labels,
        random_state=RANDOM_STATE,
    )
    model = RandomForestClassifier(max_depth=4, random_state=RANDOM_STATE)
    model.fit(x_train, y_train)
    predictions = model.predict(x_valid)
    return roc_auc_score(y_valid, predictions)


def cross_validation_scores() -> dict[str, float]:
    """Ejecuta validación cruzada 5-fold y promedia ROC-AUC y Accuracy."""
    features, labels = make_dataset()
    model = RandomForestClassifier(max_depth=4, random_state=RANDOM_STATE)
    scores = cross_validate(
        model,
        features,
        labels,
        cv=5,
        scoring=("roc_auc", "accuracy"),
        return_train_score=False,
        n_jobs=None,
    )
    return {
        "roc_auc": float(np.mean(scores["test_roc_auc"])),
        "accuracy": float(np.mean(scores["test_accuracy"])),
    }


if __name__ == "__main__":
    holdout = holdout_score()
    print(f"Hold-out ROC-AUC: {holdout:.3f}")

    cv_result = cross_validation_scores()
    print(f"ROC-AUC (5 folds): {cv_result['roc_auc']:.3f}")
    print(f"Accuracy (5 folds): {cv_result['accuracy']:.3f}")

Salida de ejemplo:

Hold-out ROC-AUC: 0.528
ROC-AUC (5 folds): 0.844
Accuracy (5 folds): 0.858

La partición única produce un ROC-AUC cercano al azar, mientras que la validación cruzada ofrece un estimador mucho más estable.


3. Recomendaciones de diseño #

  1. Número de pliegues
    Cinco o diez son lo habitual. Con datos muy escasos puede usarse LeaveOneOut, aunque el coste se dispara.
  2. Estratificación
    Ante clases desequilibradas utiliza StratifiedKFold (o el parámetro stratify) para conservar las proporciones.
  3. Múltiples métricas
    scoring acepta una tupla para calcular varias métricas a la vez. Combinar ROC-AUC y Accuracy ayuda a ver compensaciones.
  4. Integración con la búsqueda de hiperparámetros
    GridSearchCV y RandomizedSearchCV ejecutan validación cruzada internamente, lo que evita sobreajuste durante el tuning.

4. Lista de comprobación #

  • ¿La estrategia de partición es adecuada?
    Si hay dependencia temporal, recurre a TimeSeriesSplit en lugar de pliegues aleatorios.
  • ¿Las métricas clave están incluidas?
    Añade en scoring las métricas que importan al negocio para mantener alineadas las decisiones.
  • ¿Se ha estimado el coste?
    La validación cruzada entrena el modelo k veces; planifica el tiempo y recursos.
  • ¿El experimento es reproducible?
    Documenta versión de Python, semillas y configuración de pliegues en tus notebooks o scripts.

Resumen #

  • La validación cruzada reduce la varianza y ayuda a medir la capacidad de generalización.
  • cross_validate de scikit-learn facilita el cálculo simultáneo de varias métricas.
  • Diseña pliegues, métricas y presupuesto de cómputo con intención y lleva el proceso al entorno productivo.