Entendiendo la validación cruzada: Selección de la profundidad óptima en un árbol de decisión

Publicado el 13 septiembre 2024 por Daniel Rodríguez @analyticslane

En aprendizaje automático uno de los mayores desafíos es entrenar modelos que funcionen bien sobre datos nuevos. Evitando que el sobreajuste que un modelo es bueno cuando en realidad solo está memorizado las predicciones. En este punto es cuando entra en juego la técnica de la validación cruzada. En esta entrada, se explicará por qué la validación cruzada es importante, y cómo usarla para seleccionar la profundidad óptima de un árbol de decisión con un ejemplo práctico en Python.

¿Qué es la validación cruzada?

La validación cruzada es una técnica para evaluar la capacidad predictiva de un modelo y garantizar que su rendimiento sea robusto y generalizable. Evitando problemas de sobreajuste. En lugar de entrenar el modelo con un único conjunto de datos y validarlo con otro, la validación cruzada divide los datos en múltiples partes (o "folds") y realiza múltiples rondas de entrenamiento y validación. Permitiendo de esta manera comprobar si los resultados son estables con los datos.

Funcionamiento básico de la validación cruzada

Asumamos que se cuenta con un conjunto de datos para el entrenamiento de un modelo. Al usar validación cruzada, este conjunto de datos se divide en varias partes iguales, conocidas como "folds". Usando cada fold para validar el modelo entrenado con el resto de los folds. Repitiendo el proceso tantas veces como folds se hayan creado. El rendimiento esperado del modelo es el promedio a lo largo de todas estas iteraciones.

Por ejemplo, en una validación cruzada con 5 folds:

  1. El conjunto de datos se divide en 5 partes iguales.
  2. El modelo se entrena en 4 de estas partes y se evalúa en la parte restante.
  3. Este proceso se repite 5 veces, cada vez con una parte diferente como conjunto de validación.
  4. Finalmente, se calcula la precisión media a lo largo de las 5 iteraciones.

¿Por qué es importante la validación cruzada?

La validación cruzada es una herramienta esencial para el entrenamiento de modelos de aprendizaje automático porque:

  • Reduce el sobreajuste: Al evaluar el modelo en diferentes subconjuntos de datos, se asegura de que el modelo no esté simplemente memorizando los datos de entrenamiento.
  • Proporciona una evaluación más robusta: Al promediar el rendimiento sobre múltiples iteraciones, se obtiene una estimación más confiable del desempeño del modelo en condiciones reales.
  • Optimización de hiperparámetros: Ayuda a seleccionar los mejores parámetros del modelo, como la profundidad de un árbol de decisión.

Ventajas y desventajas de la validación cruzada

El uso de la validación cruzada ofrece múltiples ventajas para los modelos que se entrenan con esta técnica. Aunque también tiene algunos problemas.

Ventajas

  • Mejor estimación del rendimiento del modelo: La validación cruzada proporciona una mejor estimación del rendimiento del modelo en datos que no se han usado en entrenamiento, ya que utiliza múltiples subconjuntos de datos para la evaluación.
  • Reducción del sesgo y la varianza: Al promediar el rendimiento a través de múltiples folds, se reduce el sesgo y la varianza de la estimación del rendimiento del modelo.
  • Optimización de hiperparámetros: Permite una selección más precisa de los mejores hiperparámetros del modelo, mejorando su rendimiento general.

Desventajas

  • Costo computacional: La validación cruzada puede ser computacionalmente costosa, especialmente con conjuntos de datos grandes o modelos complejos, ya que requiere múltiples entrenamientos y evaluaciones del modelo.
  • Tiempo de ejecución: El tiempo necesario para ejecutar la validación cruzada puede ser significativamente mayor en comparación con una simple división de los datos en conjuntos de entrenamiento y validación.

Creación de un conjunto de datos de ejemplo

Antes de profundizar en la implementación del modelo mediante validación cruzada, es necesario contar con un conjunto de datos. Para ello, se puede crear un conjunto de datos sintético con make_classification de scikit-learn como se muestra a continuación:

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split


# Generar un conjunto de datos sintético
X, y = make_classification(n_samples=1000, n_features=20, n_informative=10, n_redundant=5, random_state=42)

# Dividir el conjunto de datos en entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

En donde:

    Generación del conjunto de datos: Se genera un conjunto de datos con 1000 muestras y 20 características, de las cuales 10 son informativas y 5 son redundantes.
# Generar un conjunto de datos sintético
X, y = make_classification(n_samples=1000, n_features=20, n_informative=10, n_redundant=5, random_state=42)
    División del Conjunto de Datos: Se reparten los datos en un conjunto de entrenamiento (70%) y un conjunto de prueba (30%).
# Dividir el conjunto de datos en entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

Seleccionar la profundidad óptima de un árbol de decisión

Ahora, se puede analizar cómo usar la validación cruzada para seleccionar la profundidad óptima de un árbol de decisión. Lo que se puede conseguir con el siguiente código

import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import cross_val_score


# Definimos las profundidades posibles del árbol de decisión
profundidades = [1, 2, 3, 5, 7, 9, 11, 13, 15]

# Para almacenar las precisiones medias de validación cruzada
mean_scores = []

# Iteramos sobre las profundidades y realizamos validación cruzada para cada una
for profundidad in profundidades:
    # Creamos el clasificador de árbol de decisión con la profundidad actual
    clf = DecisionTreeClassifier(max_depth=profundidad, random_state=42)
    
    # Realizamos validación cruzada con 5 folds
    scores = cross_val_score(clf, X_train, y_train, cv=5)
    
    # Calculamos la precisión media y la almacenamos
    mean_scores.append(scores.mean())

# Encontramos la profundidad óptima que maximiza la precisión media
optimal_depth = profundidades[np.argmax(mean_scores)]
print(f"La profundidad óptima es: {optimal_depth}")

# Entrenamos el modelo con la profundidad óptima usando todos los datos de entrenamiento
clf_optimal = DecisionTreeClassifier(max_depth=optimal_depth, random_state=42)
clf_optimal.fit(X_train, y_train)

# Evaluamos la precisión en el conjunto de entrenamiento
train_accuracy = accuracy_score(y_train, clf_optimal.predict(X_train))
print(f"Precisión en el conjunto de entrenamiento: {train_accuracy:.4f}")

# Evaluamos la precisión en el conjunto de prueba
val_accuracy = accuracy_score(y_test, clf_optimal.predict(X_test))
print(f"Precisión en el conjunto de prueba: {val_accuracy:.4f}")

Si se ejecuta este código, se obtiene como resultado

La profundidad óptima es: 9
Precisión en el conjunto de entrenamiento: 0.9900
Precisión en el conjunto de prueba: 0.8133

Lo que indica que el mejor modelo, para el conjunto de datos, es aquel que se entrena con una profundidad de 9. Los detalles de este código se explican a continuación:

    Definición de profundidades: Se define una lista de posibles profundidades para el árbol de decisión.
# Definimos las profundidades posibles del árbol de decisión
profundidades = [1, 2, 3, 5, 7, 9, 11, 13, 15]
    Inicialización de la lista de precisión media: Se crea una lista vacía para almacenar las precisiones medias de la validación cruzada para cada profundidad.
# Para almacenar las precisiones medias de validación cruzada
mean_scores = []
    Iteración y validación cruzada: Para cada profundidad, se crea un clasificador de árbol de decisión y se realiza validación cruzada para 5 folds con cross_val_score(). La precisión media se calcula y se almacena en mean_scores.
# Iteramos sobre las profundidades y realizamos validación cruzada para cada una
for profundidad in profundidades:
    # Creamos el clasificador de árbol de decisión con la profundidad actual
    clf = DecisionTreeClassifier(max_depth=profundidad, random_state=42)
    
    # Realizamos validación cruzada con 5 folds
    scores = cross_val_score(clf, X_train, y_train, cv=5)
    
    # Calculamos la precisión media y la almacenamos
    mean_scores.append(scores.mean())
    Selección de la profundidad óptima: Se selecciona la profundidad que maximiza la precisión media usando np.argmax(mean_scores).
# Encontramos la profundidad óptima que maximiza la precisión media
optimal_depth = profundidades[np.argmax(mean_scores)]
print(f"La profundidad óptima es: {optimal_depth}")
    Entrenamiento del modelo final: Se entrena el modelo con la profundidad óptima usando todos los datos de entrenamiento.
# Entrenamos el modelo con la profundidad óptima usando todos los datos de entrenamiento
clf_optimal = DecisionTreeClassifier(max_depth=optimal_depth, random_state=42)
clf_optimal.fit(X_train, y_train)
    Evaluación del modelo: Finalmente, se evalúa la precisión del modelo en el conjunto de entrenamiento y en el conjunto de prueba.
# Evaluamos la precisión en el conjunto de entrenamiento
train_accuracy = accuracy_score(y_train, clf_optimal.predict(X_train))
print(f"Precisión en el conjunto de entrenamiento: {train_accuracy:.4f}")

# Evaluamos la precisión en el conjunto de prueba
val_accuracy = accuracy_score(y_test, clf_optimal.predict(X_test))
print(f"Precisión en el conjunto de prueba: {val_accuracy:.4f}")

Conclusiones

La validación cruzada es una herramienta clave para entrenar los modelos de aprendizaje automático. Ofreciendo una mejor estimación del rendimiento de estos en condiciones reales. En esta entrada, se ha visto cómo funciona la validación cruzada, por qué es importante y cómo implementarla en Python para seleccionar la profundidad óptima de un árbol de decisión. Al utilizar esta técnica, se garantiza que los modelos obtenidos sean robustos y capaces de generalizar bien a datos nuevos.

En esta entrada, la función empleada para realizar validación cruzada es cross_val_score, aunque en Scikit-learn también se pueden usar las clases clases GridSearchCV y RandomizedSearchCV.

Imagen de jacqueline macou en Pixabay