scikit-learn

ScikitLearn

Tutorial ScikitLearn: Técnicas de validación cruzada

Descubre cómo implementar la validación cruzada K-Fold con Scikit-Learn para evaluar y mejorar la generalización de tus modelos de aprendizaje automático.

Aprende ScikitLearn GRATIS y certifícate

Validación cruzada K-Fold

La validación cruzada K-Fold es una técnica fundamental para evaluar la capacidad de generalización de un modelo. Consiste en dividir el conjunto de datos en K subconjuntos de tamaño similar llamados pliegues, y entrenar el modelo K veces, cada vez utilizando un pliegue diferente como conjunto de validación y los restantes como conjunto de entrenamiento.

Este enfoque permite que cada observación del conjunto de datos sea utilizada tanto para entrenamiento como para validación, garantizando una evaluación más robusta y reduciendo la varianza asociada a la partición de datos.

Procedimiento de la validación cruzada K-Fold

El proceso general de la validación cruzada K-Fold se puede describir en los siguientes pasos:

  1. Dividir el conjunto de datos en K pliegues de tamaño similar.
  2. Para cada uno de los K pliegues:
    1. Entrenar el modelo utilizando K - 1 pliegues.
    2. Validar el modelo en el pliegue restante.
  3. Calcular la métrica de evaluación en cada iteración.
  4. Promediar las métricas obtenidas para obtener una estimación final del rendimiento del modelo.

Este método proporciona una estimación más fiable del rendimiento del modelo en datos no vistos, ya que utiliza múltiples divisiones y evaluaciones.

Implementación con Scikit-Learn

A continuación, se muestra cómo implementar la validación cruzada K-Fold utilizando Scikit-Learn. Usaremos como ejemplo un modelo de clasificación con el conjunto de datos Iris.

import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import KFold
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# Cargar el conjunto de datos Iris
iris = load_iris()
X = iris.data
y = iris.target

# Definir el número de pliegues K
k = 5
kf = KFold(n_splits=k, shuffle=True, random_state=42)

# Inicializar lista para almacenar las puntuaciones
scores = []

# Definir el modelo
model = LogisticRegression(max_iter=200)

# Iterar sobre los pliegues
for train_index, test_index in kf.split(X):
    # Dividir los datos en conjuntos de entrenamiento y validación
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    
    # Entrenar el modelo
    model.fit(X_train, y_train)
    
    # Realizar predicciones
    y_pred = model.predict(X_test)
    
    # Calcular la precisión y almacenar el resultado
    score = accuracy_score(y_test, y_pred)
    scores.append(score)

# Calcular la media y desviación estándar de las puntuaciones
mean_score = np.mean(scores)
std_score = np.std(scores)

print(f'Precisión media: {mean_score:.2f}')
print(f'Desviación estándar: {std_score:.2f}')

En este ejemplo:

  • Se utiliza KFold para crear los pliegues con los parámetros shuffle=True y random_state=42 para garantizar la reproducibilidad.
  • El modelo se entrena y evalúa en cada iteración, y la precisión se almacena en la lista scores.
  • Se calcula la precisión media y la desviación estándar, proporcionando una estimación del rendimiento del modelo y su variabilidad.

Uso de cross_val_score para simplificar el proceso

Scikit-Learn ofrece la función cross_val_score que simplifica considerablemente la implementación de la validación cruzada K-Fold:

from sklearn.model_selection import cross_val_score

# Definir el modelo
model = LogisticRegression(max_iter=200)

# Realizar la validación cruzada
scores = cross_val_score(model, X, y, cv=5, scoring='accuracy', n_jobs=-1)

# Calcular la media y desviación estándar
mean_score = scores.mean()
std_score = scores.std()

print(f'Precisión media: {mean_score:.2f}')
print(f'Desviación estándar: {std_score:.2f}')

En este caso:

  • cv=5 especifica el uso de 5 pliegues.
  • scoring='accuracy' indica que se utilizará la precisión como métrica de evaluación.
  • n_jobs=-1 permite utilizar todos los núcleos disponibles para paralelizar el cálculo.

Selección del número de pliegues K

La elección del valor de K es crucial y depende de varios factores:

  • Tamaño del conjunto de datos: Para conjuntos de datos grandes, valores de K más pequeños pueden ser suficientes. Para conjuntos de datos pequeños, valores de K mayores (como K = 10) pueden proporcionar estimaciones más fiables.
  • Compromiso entre sesgo y varianza: Un K mayor reduce el sesgo de la estimación pero puede aumentar la varianza.
  • Coste computacional: Valores altos de K implican más iteraciones y, por tanto, mayor tiempo de cómputo.

Ventajas:

  • Proporciona una evaluación más estable y fiable del rendimiento del modelo.
  • Ayuda a detectar problemas de sobreajuste, ya que el modelo es evaluado en múltiples particiones.

Consideraciones:

  • No es adecuado para series temporales u otros tipos de datos donde la dependencia entre observaciones es importante.
  • Es importante aleatorizar los datos si no están ya mezclados para evitar sesgos en los pliegues.

Ejemplo completo con regresión

A continuación, se muestra un ejemplo utilizando un modelo de regresión lineal sobre el conjunto de datos Boston Housing:

from sklearn.datasets import fetch_california_housing
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold, cross_val_score
import numpy as np

# Cargar el conjunto de datos
housing = fetch_california_housing()
X = housing.data
y = housing.target

# Definir el modelo
model = LinearRegression()

# Definir K-Fold
k = 5
kf = KFold(n_splits=k, shuffle=True, random_state=42)

# Calcular el error cuadrático medio negativo
neg_mse_scores = cross_val_score(model, X, y, cv=kf, scoring='neg_mean_squared_error', n_jobs=-1)

# Convertir a MSE positivo
mse_scores = -neg_mse_scores

# Calcular la raíz del error cuadrático medio
rmse_scores = np.sqrt(mse_scores)

# Calcular la media y desviación estándar
mean_rmse = rmse_scores.mean()
std_rmse = rmse_scores.std()

print(f'RMSE medio: {mean_rmse:.2f}')
print(f'Desviación estándar del RMSE: {std_rmse:.2f}')

En este ejemplo:

  • Se utiliza fetch_california_housing en lugar de Boston Housing ya que este último está descontinuado.
  • La métrica de evaluación es el error cuadrático medio (MSE), y se cambia el signo porque Scikit-Learn devuelve métricas de pérdida negativas en cross_val_score.

Prácticas recomendadas

  • Aleatorizar siempre los datos si no hay dependencia temporal o estructural.
  • Utilizar StratifiedKFold en problemas de clasificación para mantener la proporción de clases en cada pliegue.
  • Asegurar que los datos no se filtren entre los conjuntos de entrenamiento y validación, especialmente durante el preprocesamiento.

La validación cruzada K-Fold es una técnica esencial para obtener estimaciones fiables del rendimiento de los modelos y es una práctica estándar en el desarrollo de modelos de aprendizaje automático.

Validación cruzada estratificada

La validación cruzada estratificada es una extensión de la validación cruzada que preserva la proporción de las clases en cada pliegue. Es particularmente útil en problemas de clasificación donde las clases están desbalanceadas, garantizando que cada pliegue sea una representación fiel de la distribución original de las clases.

La estratificación asegura que los modelos entrenados en cada pliegue reciban una muestra equilibrada, lo que conduce a evaluaciones más fiables y reduce la variabilidad en las métricas de rendimiento.

Importancia de la validación cruzada estratificada

En conjuntos de datos con clases desbalanceadas, la simple partición aleatoria puede resultar en pliegues donde algunas clases están subrepresentadas o incluso ausentes. Esto puede sesgar la evaluación y dar una falsa impresión del rendimiento del modelo.

La validación cruzada estratificada aborda este problema manteniendo la proporción de cada clase en todos los pliegues. Esto es esencial para modelos que deben generalizar bien en todas las clases, especialmente cuando las clases minoritarias son de interés.

Implementación con Scikit-Learn

Scikit-Learn proporciona la clase StratifiedKFold para realizar validación cruzada estratificada. A continuación, se muestra cómo utilizarla en un problema de clasificación.

Ejemplo con StratifiedKFold

import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import StratifiedKFold
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score

# Cargar el conjunto de datos
data = load_breast_cancer()
X = data.data
y = data.target

# Definir el modelo
model = SVC(kernel='linear', random_state=42)

# Definir StratifiedKFold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# Lista para almacenar las puntuaciones
scores = []

# Iterar sobre los pliegues
for train_index, test_index in skf.split(X, y):
    # Dividir los datos
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    
    # Entrenar el modelo
    model.fit(X_train, y_train)
    
    # Realizar predicciones
    y_pred = model.predict(X_test)
    
    # Calcular la precisión
    score = accuracy_score(y_test, y_pred)
    scores.append(score)

# Calcular la precisión media y desviación estándar
mean_score = np.mean(scores)
std_score = np.std(scores)

print(f'Precisión media: {mean_score:.4f}')
print(f'Desviación estándar: {std_score:.4f}')

En este ejemplo:

  • Utilizamos el conjunto de datos Breast Cancer que presenta un ligero desbalanceo entre clases.
  • StratifiedKFold divide los datos en 5 pliegues estratificados, manteniendo la misma proporción de clases en cada uno.
  • El modelo SVM es entrenado y evaluado en cada pliegue, asegurando una evaluación consistente.

Uso de cross_val_score con estratificación

La función cross_val_score aplica automáticamente validación cruzada estratificada en problemas de clasificación:

from sklearn.model_selection import cross_val_score

# Definir el modelo
model = SVC(kernel='linear', random_state=42)

# Realizar validación cruzada estratificada
scores = cross_val_score(model, X, y, cv=5, scoring='accuracy', n_jobs=-1)

# Calcular la precisión media y desviación estándar
mean_score = scores.mean()
std_score = scores.std()

print(f'Precisión media: {mean_score:.4f}')
print(f'Desviación estándar: {std_score:.4f}')

Aquí:

  • cv=5 especifica el número de pliegues.
  • cross_val_score utiliza internamente StratifiedKFold cuando se trata de clasificación.
  • n_jobs=-1 permite paralelizar el cálculo utilizando todos los núcleos disponibles.

Comparación con K-Fold simple

Si utilizáramos una validación cruzada K-Fold simple sin estratificación en un conjunto de datos desbalanceado, podríamos obtener pliegues con distribuciones de clases muy diferentes. Esto afectaría negativamente a la fiabilidad de la evaluación.

Ejemplo de desbalanceo

from sklearn.datasets import make_classification

# Crear un conjunto de datos desbalanceado
X_unbalanced, y_unbalanced = make_classification(
    n_samples=1000,
    n_features=20,
    n_classes=3,
    weights=[0.05, 0.15, 0.8],
    flip_y=0,
    random_state=42,
    n_clusters_per_class=1
)

En este conjunto:

  • Las clases están desbalanceadas con proporciones del 5%, 15% y 80%.
  • Una partición no estratificada podría excluir completamente las clases minoritarias de algunos pliegues.

Verificación de la distribución de clases

Comparando K-Fold y StratifiedKFold:

from sklearn.model_selection import KFold

kf = KFold(n_splits=5, shuffle=True, random_state=42)
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# Distribución con KFold
print("Distribución de clases con KFold:")
for train_index, test_index in kf.split(X_unbalanced):
    y_test = y_unbalanced[test_index]
    unique, counts = np.unique(y_test, return_counts=True)
    print(dict(zip(unique, counts)))

# Distribución con StratifiedKFold
print("\nDistribución de clases con StratifiedKFold:")
for train_index, test_index in skf.split(X_unbalanced, y_unbalanced):
    y_test = y_unbalanced[test_index]
    unique, counts = np.unique(y_test, return_counts=True)
    print(dict(zip(unique, counts)))

Resultados esperados:

  • KFold: Distribuciones de clases inconsistentes entre pliegues.
  • StratifiedKFold: Distribuciones de clases uniformes y representativas.

Uso de StratifiedShuffleSplit

Cuando se requiere una única partición estratificada, StratifiedShuffleSplit es la herramienta adecuada:

from sklearn.model_selection import StratifiedShuffleSplit

# Definir StratifiedShuffleSplit
sss = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)

# Obtener índices de entrenamiento y prueba
train_index, test_index = next(sss.split(X_unbalanced, y_unbalanced))
X_train, X_test = X_unbalanced[train_index], X_unbalanced[test_index]
y_train, y_test = y_unbalanced[train_index], y_unbalanced[test_index]

# Verificar la proporción de clases
print("Proporción de clases en y_train:", np.bincount(y_train) / len(y_train))
print("Proporción de clases en y_test:", np.bincount(y_test) / len(y_test))

Este método:

  • Realiza una partición estratificada manteniendo la proporción de clases.
  • Es útil para crear conjuntos de entrenamiento y prueba representativos en problemas de clasificación.

Validación cruzada estratificada en regresión

Aunque la estratificación es más común en clasificación, también es posible aplicarla en regresión mediante StratifiedKFold, creando bins de la variable objetivo:

from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import KBinsDiscretizer

# Discretizar la variable objetivo
k_bins = 5
est = KBinsDiscretizer(n_bins=k_bins, encode='ordinal', strategy='quantile')
y_binned = est.fit_transform(y.reshape(-1, 1)).ravel()

# Definir StratifiedKFold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

En este caso:

  • La variable continua y se discretiza en k_bins categorías.
  • StratifiedKFold utiliza estas categorías para estratificar.
  • Esto puede mejorar la representatividad en validación cruzada de regresión.

Prácticas recomendadas

  • Siempre que exista desequilibrio de clases, utilizar validación cruzada estratificada.
  • Comprobar la distribución de clases en los pliegues para asegurarse de que la estratificación funciona correctamente.
  • Combinar con técnicas como resampling o ponderación de clases cuando el desequilibrio es extremo.

Ejemplo completo con datos desbalanceados

from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.metrics import f1_score, make_scorer

# Crear conjunto de datos desbalanceado
X, y = make_classification(n_samples=2000, n_features=20, n_classes=2,
                           weights=[0.9, 0.1], flip_y=0, random_state=42)

# Definir el modelo
model = LogisticRegression(solver='liblinear', class_weight='balanced', random_state=42)

# Definir StratifiedKFold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# Definir la métrica F1
f1 = make_scorer(f1_score)

# Validación cruzada estratificada
scores = cross_val_score(model, X, y, cv=skf, scoring=f1, n_jobs=-1)

# Resultados
mean_score = scores.mean()
std_score = scores.std()

print(f'Puntuación F1 media: {mean_score:.4f}')
print(f'Desviación estándar: {std_score:.4f}')

En este ejemplo:

  • Se utiliza class_weight='balanced' para compensar el desequilibrio durante el entrenamiento.
  • La métrica F1 es apropiada para evaluar modelos con clases desbalanceadas.
  • La validación cruzada estratificada proporciona una evaluación consistente del rendimiento.

Ventajas de la validación cruzada estratificada

  • Representatividad: Cada pliegue es una muestra fiel del conjunto de datos original.
  • Fiabilidad: Las métricas de rendimiento son más consistentes y menos sensibles a las variaciones en la distribución de las clases.
  • Generalización: Los modelos entrenados son más propensos a generalizar bien en datos no vistos con distribuciones similares.

Consideraciones finales

  • La estratificación es esencial en problemas de clasificación con desbalanceo, pero no reemplaza la necesidad de un buen diseño experimental y de considerar otras técnicas si el desequilibrio es muy pronunciado.
  • Es importante combinar la validación cruzada estratificada con otras prácticas como la optimización de hiperparámetros y el preprocesamiento adecuado.

Leave-One-Out Cross-Validation (LOOCV)

La validación cruzada Leave-One-Out (LOOCV) es una técnica de evaluación donde el número de pliegues es igual al número de observaciones en el conjunto de datos. En cada iteración, se utiliza una sola instancia como conjunto de validación y el resto como conjunto de entrenamiento. Esto permite que el modelo sea probado en todas las muestras de forma exhaustiva, ofreciendo una estimación detallada de su rendimiento.

Características de LOOCV

  • Máximo uso de datos: Al entrenar el modelo con n - 1 muestras en cada iteración, se aprovecha al máximo la información disponible.
  • Evaluación exhaustiva: Cada observación es utilizada como conjunto de prueba, lo que proporciona una evaluación completa del modelo.
  • Sesgo reducido: Al utilizar casi todo el conjunto de datos para entrenar, se obtiene una estimación menos sesgada del error de generalización.

Implementación con Scikit-Learn

Scikit-Learn ofrece la clase LeaveOneOut para facilitar la implementación de LOOCV. A continuación, se presenta un ejemplo utilizando un modelo de regresión lineal sobre el conjunto de datos Diabetes.

from sklearn.datasets import load_diabetes
from sklearn.model_selection import LeaveOneOut
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
import numpy as np

# Cargar el conjunto de datos Diabetes
X, y = load_diabetes(return_X_y=True)

# Inicializar el modelo
modelo = LinearRegression()

# Configurar Leave-One-Out Cross-Validation
loo = LeaveOneOut()

# Lista para almacenar los errores
errores = []

# Iterar sobre los pliegues
for indice_entrenamiento, indice_prueba in loo.split(X):
    # Dividir los datos
    X_entrenamiento, X_prueba = X[indice_entrenamiento], X[indice_prueba]
    y_entrenamiento, y_prueba = y[indice_entrenamiento], y[indice_prueba]
    
    # Entrenar el modelo
    modelo.fit(X_entrenamiento, y_entrenamiento)
    
    # Realizar predicción
    y_pred = modelo.predict(X_prueba)
    
    # Calcular el error cuadrático medio
    error = mean_squared_error(y_prueba, y_pred)
    errores.append(error)

# Calcular el error medio
error_medio = np.mean(errores)
print(f'Error cuadrático medio promedio: {error_medio:.2f}')

En este ejemplo:

  • Se utiliza el conjunto de datos Diabetes, adecuado para tareas de regresión.
  • LeaveOneOut genera índices para cada iteración, dejando una observación para la validación.
  • El modelo se entrena con todas las muestras excepto una, y luego se evalúa en la muestra dejada fuera.
  • Se calcula el error cuadrático medio en cada iteración y se almacena en errores.

Uso de cross_val_score con LOOCV

La función cross_val_score simplifica la implementación al automatizar el proceso de validación:

from sklearn.model_selection import cross_val_score

# Inicializar el modelo
modelo = LinearRegression()

# Configurar Leave-One-Out Cross-Validation
loo = LeaveOneOut()

# Realizar validación cruzada
puntuaciones = cross_val_score(modelo, X, y, cv=loo, scoring='neg_mean_squared_error', n_jobs=-1)

# Convertir las puntuaciones a errores positivos
errores = -puntuaciones

# Calcular el error medio
error_medio = errores.mean()
print(f'Error cuadrático medio promedio: {error_medio:.2f}')

Aquí:

  • cv=loo especifica que se utilizará LOOCV.
  • scoring='neg_mean_squared_error' indica que se utilizará el error cuadrático medio negativo como métrica.
  • n_jobs=-1 permite utilizar todos los núcleos disponibles para paralelizar el cálculo.

Consideraciones sobre el costo computacional

El principal inconveniente de LOOCV es su alto costo computacional. Al requerir entrenar el modelo tantas veces como observaciones haya en el conjunto de datos, puede volverse impracticable para conjuntos de datos grandes. Es fundamental evaluar la viabilidad computacional antes de optar por esta técnica.

Ejemplo con clasificación

A continuación, se muestra un ejemplo de LOOCV aplicado a un problema de clasificación con el conjunto de datos Iris:

from sklearn.datasets import load_iris
from sklearn.model_selection import LeaveOneOut, cross_val_score
from sklearn.neighbors import KNeighborsClassifier
import numpy as np

# Cargar el conjunto de datos Iris
X, y = load_iris(return_X_y=True)

# Inicializar el modelo
modelo = KNeighborsClassifier(n_neighbors=3)

# Configurar Leave-One-Out Cross-Validation
loo = LeaveOneOut()

# Realizar validación cruzada
puntuaciones = cross_val_score(modelo, X, y, cv=loo, scoring='accuracy', n_jobs=-1)

# Calcular la precisión promedio
precision_promedio = puntuaciones.mean()
print(f'Precisión promedio: {precision_promedio:.4f}')

En este caso:

  • Se utiliza KNeighborsClassifier con 3 vecinos.
  • La métrica de evaluación es la precisión, adecuada para problemas de clasificación.
  • Se emplea cross_val_score para simplificar el proceso y aprovechar la paralelización.

Ventajas y desventajas

Ventajas:

  • Uso eficiente de datos: Ideal para conjuntos de datos pequeños donde aprovechar cada muestra es crucial.
  • Evaluación detallada: Proporciona información exhaustiva sobre el rendimiento del modelo en cada instancia.

Desventajas:

  • Computacionalmente intensivo: Poco práctico para conjuntos de datos grandes debido al número de iteraciones requeridas.
  • Varianza alta: Las estimaciones pueden tener mayor varianza, lo que puede afectar la fiabilidad de los resultados.
  • No adecuado para ciertos tipos de datos: En series temporales u otros datos con dependencia, LOOCV puede no ser apropiado.

Comparación con K-Fold Cross-Validation

Al comparar LOOCV con la validación cruzada K-Fold:

  • Sesgo vs. Varianza: LOOCV suele tener menor sesgo pero mayor varianza en la estimación del error, mientras que K-Fold ofrece un equilibrio más controlado.
  • Eficiencia: K-Fold es más eficiente computacionalmente y suele ser preferible para conjuntos de datos de tamaño medio o grande.
  • Representatividad: LOOCV maximiza el uso de datos para entrenamiento, pero K-Fold puede proporcionar estimaciones más estables.

Aplicaciones prácticas

LOOCV es especialmente útil en situaciones como:

  • Conjuntos de datos pequeños: Donde cada observación es valiosa y se requiere una evaluación precisa.
  • Modelos simples: Cuando se utilizan modelos con tiempos de entrenamiento rápidos, mitigando el impacto computacional.
  • Análisis detallado: Para entender el comportamiento del modelo en cada observación individual.

Limitaciones y precauciones

  • Evitar en conjuntos de datos grandes: El costo computacional puede volverse prohibitivo.
  • Considerar la varianza: Los resultados pueden ser susceptibles a variaciones significativas; es importante analizarlos con cautela.
  • No aplicable para todos los tipos de datos: En datos con dependencias estructurales, es preferible utilizar técnicas de validación más apropiadas.

Prácticas recomendadas

  • Evaluar alternativas: Considerar la validación cruzada K-Fold con valores de K altos como alternativa a LOOCV.
  • Analizar el modelo: Utilizar LOOCV para modelos sencillos o como complemento para validar resultados obtenidos con otras técnicas.
  • Optimizar código: Emplear paralelización y aprovechar recursos computacionales para reducir el tiempo de procesamiento.

En resumen, la validación cruzada Leave-One-Out es una técnica valiosa para evaluaciones exhaustivas en contextos específicos. Su implementación en Scikit-Learn es directa, y aunque presenta desafíos computacionales, puede ofrecer insights profundos sobre el rendimiento del modelo en cada observación del conjunto de datos.

Validación cruzada para series temporales

La validación cruzada para series temporales es una técnica específica que aborda los desafíos únicos de los datos secuenciales. A diferencia de los datos aleatorios, las series temporales presentan dependencias temporales y orden cronológico, lo que impide utilizar métodos tradicionales de validación cruzada como K-Fold sin violar la estructura inherente de los datos.

En problemas de series temporales, es fundamental mantener la secuencia temporal para preservar la correlación entre observaciones adyacentes. Por ello, se emplean métodos de validación cruzada que respetan el orden temporal y evalúan el rendimiento del modelo de manera realista.

Problemas con la validación cruzada tradicional en series temporales

Aplicar técnicas como K-Fold Cross-Validation en series temporales puede llevar a fugas de información, ya que dividir aleatoriamente los datos podría incluir información futura en el conjunto de entrenamiento. Esto resulta en una evaluación optimista y poco representativa del rendimiento real del modelo en datos no vistos.

Es esencial evitar que el modelo entrene con datos que suceden después de los datos utilizados para validación. De lo contrario, el modelo podría aprender patrones futuros, lo que es inviable en escenarios de pronóstico real.

TimeSeriesSplit en Scikit-Learn

Scikit-Learn ofrece la clase TimeSeriesSplit para llevar a cabo validación cruzada respetando el orden temporal. Este método crea múltiples divisiones incrementales, donde cada conjunto de entrenamiento incluye datos anteriores al conjunto de validación.

Características de TimeSeriesSplit:

  • No mezcla los datos: Mantiene la secuencia temporal sin reorganizar las observaciones.
  • Incrementa el tamaño del entrenamiento: En cada división, el conjunto de entrenamiento incluye más datos, simulando un escenario de despliegue real.
  • Calcula múltiples puntuaciones: Permite obtener una estimación del rendimiento más robusta.

Implementación con Scikit-Learn

A continuación, se muestra cómo utilizar TimeSeriesSplit con un ejemplo práctico. Supondremos un conjunto de datos sintético que simula una serie temporal.

import numpy as np
from sklearn.model_selection import TimeSeriesSplit, cross_val_score
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error
import matplotlib.pyplot as plt

# Crear una instancia de Generator
rng = np.random.default_rng(seed=42)

# Generar datos sintéticos
n_samples = 100
X = np.arange(n_samples).reshape(-1, 1)
y = np.sin(X.flatten() / 5) + rng.normal(scale=0.5, size=n_samples)

# Definir el modelo
modelo = Ridge(alpha=1.0)

# Configurar TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)

# Lista para almacenar los errores
errores = []

# Iterar sobre las divisiones temporales
for train_index, test_index in tscv.split(X):
    # Dividir los datos
    X_entrenamiento, X_prueba = X[train_index], X[test_index]
    y_entrenamiento, y_prueba = y[train_index], y[test_index]

    # Entrenar el modelo
    modelo.fit(X_entrenamiento, y_entrenamiento)

    # Realizar predicciones
    y_pred = modelo.predict(X_prueba)

    # Calcular el error cuadrático medio
    error = mean_squared_error(y_prueba, y_pred)
    errores.append(error)

    # Representar resultados
    plt.figure(figsize=(10, 4))
    plt.plot(X_entrenamiento, y_entrenamiento, label='Entrenamiento')
    plt.plot(X_prueba, y_prueba, label='Prueba')
    plt.plot(X_prueba, y_pred, label='Predicciones', linestyle='--')
    plt.legend()
    plt.show()

# Calcular el error medio
error_medio = np.mean(errores)
print(f'Error cuadrático medio promedio: {error_medio:.4f}')

En este ejemplo:

  • Se genera una serie temporal sintética utilizando una función sinusoidal con ruido.
  • TimeSeriesSplit divide los datos en 5 particiones sin mezclar las observaciones.
  • En cada iteración, el modelo se entrena con los datos anteriores y se prueba en el siguiente bloque temporal.
  • Se calcula el error cuadrático medio en cada partición y se almacenan los errores.
  • Se representan gráficamente los conjuntos de entrenamiento, prueba y las predicciones para visualizar el rendimiento.

Uso de cross_val_score con TimeSeriesSplit

La función cross_val_score puede combinarse con TimeSeriesSplit para simplificar el proceso:

from sklearn.model_selection import cross_val_score

# Definir el modelo
modelo = Ridge(alpha=1.0)

# Configurar TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)

# Realizar validación cruzada
puntuaciones = cross_val_score(modelo, X, y, cv=tscv, scoring='neg_mean_squared_error', n_jobs=-1)

# Convertir las puntuaciones a errores positivos
errores = -puntuaciones

# Calcular el error medio
error_medio = np.mean(errores)
print(f'Error cuadrático medio promedio: {error_medio:.4f}')

Aquí:

  • cv=tscv especifica el uso de TimeSeriesSplit.
  • scoring='neg_mean_squared_error' indica la métrica a utilizar.
  • n_jobs=-1 permite aprovechar todos los núcleos disponibles para paralelizar el cálculo.

Estructura de las divisiones en TimeSeriesSplit

La clase TimeSeriesSplit divide los datos de la siguiente manera:

  • Para cada división, el conjunto de entrenamiento incluye las primeras k observaciones, y el conjunto de prueba incluye las siguientes.
  • El tamaño del conjunto de entrenamiento incrementa en cada iteración, mientras que el tamaño del conjunto de prueba es constante (excepto posiblemente en la última iteración).

Es posible visualizar los índices generados:

tscv = TimeSeriesSplit(n_splits=3)

for i, (train_index, test_index) in enumerate(tscv.split(X)):
    print(f'División {i}')
    print(f'Indices de entrenamiento: {train_index}')
    print(f'Indices de prueba: {test_index}\n')

Esto muestra cómo los conjuntos de entrenamiento crecen progresivamente, simulando un escenario de predicción en el tiempo.

Personalización de TimeSeriesSplit

TimeSeriesSplit permite ajustar ciertos parámetros:

  • max_train_size: Establece el tamaño máximo del conjunto de entrenamiento.
  • gap: Define un lapso entre el conjunto de entrenamiento y el de prueba para evitar dependencia inmediata.

Ejemplo usando gap:

tscv = TimeSeriesSplit(n_splits=5, gap=2)

# Iterar sobre las divisiones con gap
for train_index, test_index in tscv.split(X):
    X_entrenamiento, X_prueba = X[train_index], X[test_index]
    y_entrenamiento, y_prueba = y[train_index], y[test_index]
    # Entrenamiento y evaluación del modelo

El parámetro gap es útil cuando existe autocorrelación en los datos cercana en el tiempo, y se desea evitar que el modelo se beneficie de información demasiado reciente.

Consideraciones al utilizar validación cruzada en series temporales

  • Respetar el orden temporal: Nunca mezclar datos futuros en el conjunto de entrenamiento.
  • Verificar estacionariedad: Las propiedades estadísticas de la serie pueden cambiar con el tiempo, afectando el rendimiento del modelo.
  • Evaluar el modelo en datos recientes: En algunos casos, es más relevante conocer el rendimiento del modelo en tiempos recientes.

Ejemplo práctico con datos financieros

Supongamos que disponemos de datos históricos de precios de acciones y queremos predecir el precio de cierre. Aplicaremos TimeSeriesSplit para evaluar un modelo de regresión.

import pandas as pd
from sklearn.model_selection import TimeSeriesSplit, cross_val_score
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error

# Cargar datos históricos simulados
rng = np.random.default_rng(seed=42)
fechas = pd.date_range(start='2022-01-01', periods=200)
precios = np.cumsum(rng.normal(loc=0.1, scale=1.0, size=200))
datos = pd.DataFrame({'Fecha': fechas, 'Precio': precios})

# Características y variable objetivo
X = np.arange(len(datos)).reshape(-1, 1)
y = datos['Precio'].values

# Definir el modelo
modelo = RandomForestRegressor(n_estimators=100, random_state=42)

# Configurar TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)

# Realizar validación cruzada
puntuaciones = cross_val_score(modelo, X, y, cv=tscv, scoring='neg_mean_absolute_error', n_jobs=-1)

# Calcular el error medio absoluto promedio
errores = -puntuaciones
error_medio = np.mean(errores)
print(f'Error medio absoluto promedio: {error_medio:.4f}')

En este ejemplo:

  • Se genera una serie temporal simulada de precios.
  • Se aplica un RandomForestRegressor para predecir el precio.
  • Se utiliza la métrica error medio absoluto (MAE) adecuada para problemas de regresión en finanzas.

Técnicas avanzadas: Bloqueo y validación anidada

Para escenarios más complejos, se pueden aplicar técnicas como:

  • Bloqueo temporal: Dividir los datos en bloques mayores para capturar patrones estacionales o cambios de régimen.
  • Validación cruzada anidada: Combinar la selección de hiperparámetros con TimeSeriesSplit para evitar sobreajuste.

Implementación de validación anidada:

from sklearn.model_selection import GridSearchCV

# Definir el espacio de hiperparámetros
param_grid = {'n_estimators': [50, 100, 200]}

# Configurar GridSearchCV con TimeSeriesSplit
grid_search = GridSearchCV(modelo, param_grid, cv=tscv, scoring='neg_mean_absolute_error', n_jobs=-1)

# Ajustar el modelo
grid_search.fit(X, y)

# Mejor estimador y puntuación
mejor_modelo = grid_search.best_estimator_
mejor_error = -grid_search.best_score_
print(f'Mejor número de árboles: {grid_search.best_params_["n_estimators"]}')
print(f'Mejor error medio absoluto: {mejor_error:.4f}')

Aquí:

  • Se realiza una búsqueda en cuadrícula de hiperparámetros utilizando TimeSeriesSplit.
  • Se evita el sobreajuste al validar los hiperparámetros en divisiones temporales adecuadas.

Prácticas recomendadas

  • No aleatorizar los datos: Mantener los datos en su orden original es crucial.
  • Experimentar con el número de divisiones: Ajustar n_splits para equilibrar el tamaño de los conjuntos y la representatividad.
  • Ser consciente de la estacionalidad: Considerar patrones estacionales al diseñar las divisiones.

La validación cruzada para series temporales es esencial para evaluar modelos en contextos donde el tiempo y la secuencia importan. Utilizar herramientas como TimeSeriesSplit en Scikit-Learn permite adaptar las técnicas de validación cruzada a estas necesidades, proporcionando estimaciones fiables del rendimiento del modelo en datos futuros.

Implementación de validación cruzada en Scikit-Learn

La validación cruzada es una herramienta esencial en la evaluación y selección de modelos en Scikit-Learn. Este framework proporciona funciones y clases que facilitan la implementación de diversas estrategias de validación cruzada, permitiendo evaluar el rendimiento de los modelos de forma eficiente y estandarizada.

Uso de cross_validate para obtener métricas múltiples

La función cross_validate ofrece una forma flexible de realizar validación cruzada, permitiendo calcular múltiples métricas de evaluación y obtener información adicional sobre los tiempos de entrenamiento y prueba.

Ejemplo de uso de cross_validate con múltiples métricas:

from sklearn.datasets import load_iris
from sklearn.model_selection import cross_validate
from sklearn.svm import SVC

# Cargar el conjunto de datos Iris
X, y = load_iris(return_X_y=True)

# Definir el modelo
modelo = SVC(kernel='linear', random_state=42)

# Definir las métricas
metricas = ['accuracy', 'precision_macro', 'recall_macro']

# Realizar la validación cruzada
resultados = cross_validate(modelo, X, y, cv=5, scoring=metricas, return_train_score=True, n_jobs=-1)

# Mostrar los resultados
print('Puntuaciones de validación:')
for metrica in metricas:
    puntuacion = resultados[f'test_{metrica}']
    print(f'{metrica}: {puntuacion.mean():.4f} ± {puntuacion.std():.4f}')

print('\nTiempos de entrenamiento y prueba:')
print(f"Tiempo medio de entrenamiento: {resultados['fit_time'].mean():.4f} segundos")
print(f"Tiempo medio de prueba: {resultados['score_time'].mean():.4f} segundos")

En este ejemplo:

  • Se utilizan varias métricas de evaluación especificando una lista en el parámetro scoring.
  • El argumento return_train_score=True permite obtener las puntuaciones en el conjunto de entrenamiento, útil para detectar posibles sobreajustes.
  • Los resultados incluyen los tiempos de ajuste (fit_time) y de puntuación (score_time), proporcionando información sobre la eficiencia del modelo.

Utilización de estrategias de validación personalizadas

Scikit-Learn permite implementar estrategias de validación cruzada personalizadas mediante las clases de validación de model_selection. Si se requiere una partición específica de los datos, se pueden crear generadores de validación cruzada personalizados.

Ejemplo con PredefinedSplit:

from sklearn.model_selection import PredefinedSplit, cross_val_score
from sklearn.datasets import load_wine
from sklearn.tree import DecisionTreeClassifier

# Cargar el conjunto de datos Wine
X, y = load_wine(return_X_y=True)

# Suponiendo que tenemos un conjunto de validación predefinido
test_fold = [-1 if i < 130 else 0 for i in range(len(X))]

# Crear el objeto PredefinedSplit
ps = PredefinedSplit(test_fold)

# Definir el modelo
modelo = DecisionTreeClassifier(random_state=42)

# Realizar la validación cruzada
puntuaciones = cross_val_score(modelo, X, y, cv=ps, scoring='accuracy')

print(f'Precisión en el conjunto de validación predefinido: {puntuaciones.mean():.4f}')

En este ejemplo:

  • PredefinedSplit permite definir las particiones de antemano, útil cuando se tiene un esquema de validación específico.
  • Se utiliza el vector test_fold para indicar qué muestras pertenecen al conjunto de prueba.

Uso de métricas de evaluación personalizadas

Es posible definir funciones de evaluación personalizadas para adaptar la validación cruzada a métricas específicas del problema.

Ejemplo de métrica personalizada:

from sklearn.metrics import make_scorer, mean_absolute_percentage_error
from sklearn.model_selection import cross_val_score
from sklearn.datasets import fetch_california_housing
from sklearn.linear_model import LinearRegression

# Definir la métrica personalizada
def mape(y_true, y_pred):
    return mean_absolute_percentage_error(y_true, y_pred)

mape_scorer = make_scorer(mape, greater_is_better=False)

# Definir el modelo y los datos
X, y = fetch_california_housing(return_X_y=True)
modelo = LinearRegression()

# Realizar la validación cruzada con la métrica personalizada
puntuaciones = cross_val_score(modelo, X, y, cv=5, scoring=mape_scorer, n_jobs=-1)

print(f'MAPE promedio: {-puntuaciones.mean():.4f}')

En este ejemplo:

  • Se utiliza make_scorer para crear un scorer personalizado, invertido con greater_is_better=False porque el MAPE es una métrica de error.
  • cross_val_score utiliza esta métrica durante la validación cruzada.

Validación cruzada con pipelines

La integración de pipelines en la validación cruzada garantiza que todas las etapas del preprocesamiento sean aplicadas correctamente en cada iteración, evitando fugas de datos.

Ejemplo de validación cruzada con Pipeline:

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris

# Cargar los datos
X, y = load_iris(return_X_y=True)

# Definir el pipeline
pipeline = Pipeline([
    ('escalado', StandardScaler()),
    ('clasificador', KNeighborsClassifier(n_neighbors=3))
], memory = None)

# Realizar la validación cruzada
puntuaciones = cross_val_score(pipeline, X, y, cv=5, scoring='accuracy', n_jobs=-1)

print(f'Precisión promedio con pipeline: {puntuaciones.mean():.4f}')

En este ejemplo:

  • Se crea un pipeline que primero escala las características y luego aplica un clasificador KNN.
  • La validación cruzada se realiza sobre el pipeline completo, asegurando un preprocesamiento correcto en cada pliegue.

Manejo de datos desbalanceados en validación cruzada

En problemas de clasificación con clases desbalanceadas, es importante utilizar estrategias que consideren este desequilibrio.

Ejemplo utilizando StratifiedKFold:

from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier

# Generar datos desbalanceados
X, y = make_classification(n_samples=1000, n_classes=2, weights=[0.9, 0.1], random_state=42)

# Definir el modelo
modelo = RandomForestClassifier(random_state=42)

# Definir StratifiedKFold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# Realizar la validación cruzada
puntuaciones = cross_val_score(modelo, X, y, cv=skf, scoring='f1_macro', n_jobs=-1)

print(f'Puntuación F1 macro promedio: {puntuaciones.mean():.4f}')

En este ejemplo:

  • Se utiliza StratifiedKFold para asegurar que cada pliegue mantenga la misma proporción de clases.
  • La métrica f1_macro es apropiada para evaluar modelos en datos desbalanceados.
Aprende ScikitLearn GRATIS online

Ejercicios de esta lección Técnicas de validación cruzada

Evalúa tus conocimientos de esta lección Técnicas de validación cruzada con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de ScikitLearn

Accede a todas las lecciones de ScikitLearn y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Aprendizaje Automático

scikit-learn

Introducción Y Entorno

Introducción E Instalación

scikit-learn

Introducción Y Entorno

Introducción Al Preprocesamiento De Datos

scikit-learn

Preprocesamiento De Datos

Identificación Y Tratamiento De Valores Faltantes

scikit-learn

Preprocesamiento De Datos

Escalado De Datos

scikit-learn

Preprocesamiento De Datos

Normalización De Datos

scikit-learn

Preprocesamiento De Datos

Codificación De Variables Categóricas

scikit-learn

Preprocesamiento De Datos

Ingeniería De Características

scikit-learn

Preprocesamiento De Datos

Selección De Características

scikit-learn

Preprocesamiento De Datos

Extracción De Características

scikit-learn

Preprocesamiento De Datos

Particionamiento De Datos

scikit-learn

Preprocesamiento De Datos

Preprocesamiento De Datos Desbalanceados

scikit-learn

Preprocesamiento De Datos

Introducción A La Regresión

scikit-learn

Regresión

Regresión Lineal

scikit-learn

Regresión

Regresión Knn Kneighborsregressor

scikit-learn

Regresión

Regresión Svm Con Svr

scikit-learn

Regresión

Regresión Con Árboles Decisiontreeregressor

scikit-learn

Regresión

Regresión Con Algoritmos De Conjunto

scikit-learn

Regresión

Introducción A La Clasificación

scikit-learn

Clasificación

Clasificación Con Regresión Logística

scikit-learn

Clasificación

Clasificación Knn Kneighborsclassifier

scikit-learn

Clasificación

Clasificación Svm Con Svc

scikit-learn

Clasificación

Clasificación Con Árboles Decisiontreeclassifier

scikit-learn

Clasificación

Clasificación Con Algoritmos De Conjunto

scikit-learn

Clasificación

Reducción De La Dimensionalidad Con Pca

scikit-learn

Aprendizaje No Supervisado

Clustering Con Kmeans

scikit-learn

Aprendizaje No Supervisado

Clustering Jerárquico

scikit-learn

Aprendizaje No Supervisado

Clustering De Densidad Con Dbscan

scikit-learn

Aprendizaje No Supervisado

Preprocesamiento De Textos Para Nlp

scikit-learn

Nlp

Representación De Texto Y Extracción De Características

scikit-learn

Nlp

Clasificación De Texto Con Scikit Learn

scikit-learn

Nlp

Análisis De Sentimiento

scikit-learn

Nlp

Técnicas Avanzadas De Extracción De Características

scikit-learn

Nlp

Introducción Al Análisis De Series Temporales

scikit-learn

Series Temporales

Preprocesamiento De Datos De Series Temporales

scikit-learn

Series Temporales

Ingeniería De Características Para Series Temporales

scikit-learn

Series Temporales

Transformación Y Escalado De Series Temporales

scikit-learn

Series Temporales

Validación Y Evaluación De Modelos En Series Temporales

scikit-learn

Series Temporales

Validación Y Evaluación De Modelos

scikit-learn

Validación De Modelos

Técnicas De Validación Cruzada

scikit-learn

Validación De Modelos

Métricas De Regresión

scikit-learn

Validación De Modelos

Métricas De Clasificación

scikit-learn

Validación De Modelos

Ajuste De Hiperparámetros

scikit-learn

Validación De Modelos

Introducción A Pipelines

scikit-learn

Pipelines Y Despliegue

Creación De Pipelines Básicos

scikit-learn

Pipelines Y Despliegue

Preprocesamiento De Datos Con Pipelines

scikit-learn

Pipelines Y Despliegue

Pipelines Y Validación Cruzada

scikit-learn

Pipelines Y Despliegue

Pipelines Con Columntransformer

scikit-learn

Pipelines Y Despliegue

Exportar E Importar Pipelines

scikit-learn

Pipelines Y Despliegue

Accede GRATIS a ScikitLearn y certifícate

Objetivos de aprendizaje de esta lección

  • Comprender el concepto y propósito de la validación cruzada K-Fold.
  • Implementar K-Fold utilizando la librería Scikit-Learn.
  • Analizar métricas de evaluación: precisión, varianza.
  • Evaluar y elegir el número óptimo de pliegues K.
  • Aplicar la técnica en problemas de clasificación y regresión.