Validación Cruzada con Scikit-learn: Cruz Vales Cor y KFold
Resumen
¿Cómo implementar la validación cruzada en Python?
La validación cruzada es una técnica esencial en el análisis de datos que te permite evaluar el rendimiento de un modelo de aprendizaje automático de manera efectiva. Este proceso implica dividir los datos en subconjuntos para probar el modelo varias veces y así asegurar su robustez. Gracias a bibliotecas como Scikit-Learn, esta técnica puede ser implementada de manera sencilla y eficaz. Vamos a explorar cómo hacerlo paso a paso.
¿Cuáles módulos y funciones necesitamos?
Para llevar a cabo la validación cruzada en Python, comenzaremos importando los módulos necesarios:
import pandas as pd
import numpy as np
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import cross_val_score, KFold
Pandas: Utilizado para la manipulación de datos.
NumPy: Ayuda en cálculos matemáticos complejos.
DecisionTreeRegressor: Un modelo de árbol de decisión para regresiones.
cross_val_score y KFold: Funciones de Scikit-Learn que facilitan la implementación de la validación cruzada.
¿Cómo preparar los datos?
Vamos a utilizar un dataset conocido para llevar a cabo nuestra validación cruzada. Puedes cargarlo y prepararlo como se muestra a continuación:
data = pd.read_csv('data/felicidad.csv')X = data.drop(['country','score'], axis=1)y = data['score']
DataFrame data: Cargamos un CSV que contiene los datos.
Características X: Todas las columnas excepto el nombre del país y el score.
Objetivo y: La columna que queremos predecir, en este caso, el 'score'.
¿Cómo definir y evaluar el modelo?
En esta etapa, definimos nuestro modelo de árbol de decisión sin ajustes adicionales y procedemos a evaluarlo.
model = DecisionTreeRegressor()scores = cross_val_score( model, X, y, scoring='neg_mean_squared_error', cv=3)mean_score = np.mean(scores)abs_mean_score = np.abs(mean_score)
DecisionTreeRegressor: Se utiliza en su configuración predeterminada.
cross_val_score: Calcula el error cuadrático medio negativo para validar cruzadamente.
Media y valor absoluto: Convertimos el valor medio del score negativo a su valor absoluto para mayor claridad.
¿Cómo controlar las particiones de datos?
Usamos KFold para dividir los datos en subconjuntos específicos y controlar la aleatorización y consistencia de las particiones.
KFold: Permite definir el número de particiones (3 en nuestro caso), además de la opción de aleatorización.
Partición y asignación: Divide los datos en conjuntos de entrenamiento y prueba.
¡Así es como puedes implementar y controlar la validación cruzada de manera sencilla en Python! Experimentar con diferentes modelos y configuraciones te dará una profunda comprensión de la robustez y eficacia de tus modelos. Sigue explorando y aprendiendo, el único límite es tu curiosidad.
Validación Cruzada con Scikit-learn: Cruz Vales Cor y KFold
Al final me parece que quedó muy en el aire esa información, asi que quise complementar. Aquí les dejo mi codigo de la implementación del Cross-Validation con KFold:
data = dataset.drop(["country","score"],axis=1)targets = dataset["score"]kf =KFold(n_splits=3,shuffle=True)mse_values =[]for train,test in kf.split(data): x_train = pd.DataFrame(columns=list(data),index=range(len(train))) x_test = pd.DataFrame(columns=list(data),index=range(len(test))) y_train = pd.DataFrame(columns=['score'],index=range(len(train))) y_test = pd.DataFrame(columns=['score'],index=range(len(test)))for i inrange(len(train)): x_train.iloc[i]= data.iloc[train[i]] y_train.iloc[i]= targets.iloc[train[i]]for j inrange(len(test)): x_test.iloc[j]= data.iloc[test[j]] y_test.iloc[j]= targets.iloc[test[j]] model =DecisionTreeRegressor().fit(x_train,y_train) predict = model.predict(x_test) mse_values.append(mean_squared_error(y_test,predict))print("Los tres MSE fueron: ",mse_values)print("El MSE promedio fue: ", np.mean(mse_values))
Excelente la implementacion
Me parece que no es necesario todo lo que utilizas. Este es mi ejemplo usando tu codigo:
X = dataset.drop(['country','score'], axis=1) y = dataset['score']print(dataset.shape) model = DecisionTreeRegressor() score = cross_val_score(model, X,y, cv=3, scoring='neg_mean_squared_error')# con cv podemos controlar el numOfFoldsprint(score)print(np.abs(np.mean(score))) kf = KFold(n_splits=3, shuffle=True, random_state=42) mse_values =[]for train, test in kf.split(dataset):print(train)print(test) X_train = X.iloc[train] y_train = y.iloc[train] X_test = X.iloc[test] y_test = y.iloc[test] model = DecisionTreeRegressor().fit(X_train, y_train) predict = model.predict(X_test) mse_values.append(mean_squared_error(y_test, predict))print("Los tres MSE fueron: ", mse_values)print("El MSE promedio fue: ", np.mean(mse_values))
no entiendo como interpretar el resultado de score
El score es un arreglo de errores negativos medios cuadrados (es decir, cuanto mas pequeño en valor absoluto, mejor se ajusta el modelo a los datos) como salida del coss_val_score, este resultado se da ya que el modelo fue separado cv veces (en este caso 5 al principio y luego 3) en set de datos de entrenamiento y prueba, en lo que se puede notar que particiones fueron mas satisfactorias.
Ahora al aplicar el promedio y el valor absoluto, puedes observar el error medio cuadrado promedio calculado a partir de las salidas score que evalúan la adaptación promedio del modelo a los datos.
Muchas gracias por la respuesta
En este caso al hacer tres splits, es decir al dividir nuestros datos en A,B,C, tenemos solo tres posibles combinaciones AB para training y C para test, luego AC con B, y por ultimo BC con A.
Excelente!
Dejo la implementación de los entrenamientos dentro del ciclo for de la última parte. Es decir, cómo usar los índices generados por KFold y usar una medida como la que veníamos usando antes para la pérdida (mean_squared_error):
# se importa adicional a las que ya se tienen
from sklearn.metricsimport mean_squared_error
if __name__ =="__main__": dataset = pd.read_csv('./data/happiness.csv')X= dataset.drop(['country','score'], axis=1) y = dataset['score'] model =DecisionTreeRegressor() kf =KFold(n_splits=3, shuffle=True, random_state=42) losses =[]for train, test in kf.split(dataset):X_train=X.iloc[train]X_test=X.iloc[test] y_train = y[train] y_test = y[test] model.fit(X_train, y_train) predictions = model.predict(X_test) loss =mean_squared_error(y_test, predictions) losses.append(loss)print('Error para cada partición: ', losses)print('Promedio de los errores: ', np.mean(losses))
Implementación de cross validation con KFolds
X= dataset.drop(['country','score','rank'], axis=1)y = dataset['score']model =DecisionTreeRegressor()kf =KFold(n_splits=10, shuffle=True, random_state=1)results =cross_val_score(model,X, y, cv=kf, scoring='neg_mean_squared_error')print(f'Promedio de los cross validation scores: {np.abs(np.mean(results))}')
Que valor de neg_mean_squared_error es bueno?
NMSE es una metrica contraintuitiva. El mejor valor es 0.0. Se refiere a que un 100% de valores 0% son erroneos.
Les dejo un código que hice bastante claro y escalable:
La validación cruzada de K-Folds (K-Folds Cross Validation) es una técnica que se utiliza para evaluar el rendimiento de un modelo de manera más robusta que simplemente dividir los datos en conjuntos de entrenamiento y prueba. En lugar de realizar una sola división de los datos, K-Folds divide los datos en K particiones o "pliegues" (folds) y entrena y prueba el modelo K veces, utilizando un fold diferente como conjunto de prueba en cada iteración. Luego, se promedian las métricas de rendimiento de todas las iteraciones para obtener una estimación más precisa del rendimiento del modelo.
K-Folds Cross Validation es una técnica útil para evaluar el rendimiento del modelo de manera más robusta y es especialmente útil cuando se tiene un conjunto de datos pequeño o se desea obtener una estimación más precisa del rendimiento del modelo.
La clase se centró en la implementación de K-Folds Cross Validation utilizando Scikit-Learn. Se explicó cómo importar las funciones necesarias y se mostró cómo aplicar la validación cruzada para evaluar un modelo de regresión de árboles de decisión. Se mencionó cómo ajustar los pliegues y calcular el score, destacando la importancia de obtener métricas como el error cuadrático medio. También se abordó la organización de los datos y la ejecución de pruebas rápidas para validar los modelos de manera eficiente.
import pandas as pd
import numpy as np
import matplotlib.pyplotas plt
from sklearn.treeimportDecisionTreeRegressorfrom sklearn.model_selectionimport cross_val_score,KFoldfrom matplotlib.colorsimportListedColormapif __name__ =="__main__": # Carga de datos
data = pd.read_csv('./data/felicidad.csv')X= data.drop(['country','score'], axis=1)Y= data['score'] # Modelo y cross-validation
model =DecisionTreeRegressor() kf =KFold(n_splits=5, shuffle=False)#, random_state=42) scores =cross_val_score(model,X,Y, cv=kf, scoring='neg_mean_squared_error') scores_abs = np.abs(scores)print("Scores por fold:", scores)print("Error medio absoluto:", np.mean(scores_abs)) # Visualización de folds en tonos de gris discretos
n_samples =len(X) fold_matrix = np.zeros((5, n_samples))for i,(train_idx, test_idx)inenumerate(kf.split(X)): fold_matrix[i, train_idx]=1 # Train fold_matrix[i, test_idx]=2 # Test cmap =ListedColormap(["white","dimgray","lightgray"]) plt.figure(figsize=(12,5)) plt.imshow(fold_matrix, cmap=cmap, aspect="auto") plt.yticks(range(5),[f"Fold {i+1}"for i inrange(5)]) plt.xticks([]) plt.colorbar(ticks=[0,1,2], label="0 = vacío | 1 = Train | 2 = Test") plt.title("División de datos en K-Fold Cross-Validation (escala de grises discreta)") plt.show() plt.figure(figsize=(8,5)) plt.bar(range(1,6), scores_abs, color="gray", alpha=0.7) plt.plot(range(1,6), scores_abs,"o-", color="black") plt.xlabel("Fold") plt.ylabel("Error cuadrático medio") plt.title("Error por fold en validación cruzada") plt.show()
El ver lo fold ordenados se debe a suffle = False, importante remover random_state = 42 para que funcione
La diferencia entre el mejor fold (3) y el peor (5) es grande. Eso indica que el modelo (árbol de decisión) es inestable con respecto a cómo se dividen los datos. Dicho de otro modo, depende bastante de qué ejemplos caigan en train y cuáles en test.
¿Por qué se utiliza un valor negativo para elerror cuadrático medio (MSE) enscoring='neg_mean_squared_error'?
1. El objetivo de las métricas de evaluación
En bibliotecas como scikit-learn, las métricas se dividen en dos categorías principales:
Métricas para maximizar: como precisión, puntuación F1, o R², donde el modelo es mejor mientras el puntaje sea más alto.
Métricas para minimizar: como el error cuadrático medio (MSE), donde el modelo es mejor mientras el valor sea más bajo.
Sin embargo, las funciones de scikit-learn como cross_val_score están diseñadas para maximizar los puntajes, no minimizarlos. Esto presenta un problema con métricas como el MSE, que naturalmente debe minimizarse.
2. ¿Qué hace scikit-learn con el MSE?
Para que el MSE se ajuste al paradigma de "puntaje más alto = mejor modelo", scikit-learn invierte su signo. Así:
El MSE normal tiene valores positivos, y mientras más bajo, mejor.
Con neg_mean_squared_error, el MSE se multiplica por −1-1−1. Ahora:
Un menor MSE (mejor modelo) se traduce en un puntaje más alto.
Un mayor MSE (peor modelo) se traduce en un puntaje más bajo.
Por ejemplo:
Si el MSE real es 25, scikit-learn devuelve −25.0.
Si el MSE real es 10 (mejor modelo), scikit-learn devuelve −10, que es "más alto" en términos de puntaje.
3. ¿Por qué lo hacen negativo?
Esto es una convención de diseño en scikit-learn, porque las funciones como cross_val_score y GridSearchCV están pensadas para trabajar con métricas que crecen hacia valores más altos. Al usar el negativo, puedes seguir maximizando el puntaje sin necesidad de un tratamiento especial para métricas "de minimización".
4. Cómo interpretarlo en la práctica
Cuando usas scoring='neg_mean_squared_error':
Los valores de la métrica serán negativos (ejemplo: −10.0, −25.0).
Un valor menos negativo (más cercano a 0) indica un mejor modelo:
Ejemplo: −10.0 es mejor que −25.0.
Si necesitas el MSE real, simplemente invierte el signo de los resultados negativos:
Ejemplo: Si obtienes −25, el MSE real es 25.
Le hice unas pequeñas modificaciones al código para que se entienda más cuáles son las matrices de entrenamiento y cuáles las de prueba:
import pandas as pd
import numpy as np
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import(cross_val_score, KFold)if __name__ =="__main__": data = pd.read_csv('./data/felicidad.csv') X = data.drop(['country','score'], axis=1) Y = data['score'] model = DecisionTreeRegressor() score = cross_val_score(model, X,Y, cv=3, scoring='neg_mean_squared_error') score_mean_abs = np.abs(np.mean(score))print(score)print(score_mean_abs) kf = KFold(n_splits=3, shuffle=True, random_state=42)for train, test in kf.split(data):print("Training data:", train)print("Test data:", test)
El parámetro cv=3 en la función cross_val_score() indica el número de "folds" o divisiones que se utilizarán en la validación cruzada.
La validación cruzada es una técnica utilizada para evaluar el rendimiento de un modelo de machine learning. En este caso, al establecer cv=3, estamos utilizando la validación cruzada con 3 "folds" o divisiones.
En términos simples, esto significa que el conjunto de datos se divide en 3 partes iguales. Luego, el modelo se entrena y evalúa 3 veces, cada vez utilizando una de las partes como conjunto de prueba y las otras dos partes como conjunto de entrenamiento. Esto permite una evaluación más robusta del modelo, ya que se utiliza cada parte del conjunto de datos tanto para entrenamiento como para prueba en diferentes momentos. Luego, se calcula una métrica de rendimiento (en este caso, el error cuadrático medio) para cada fold y se informa el promedio de estas métricas como resultado final de la validación cruzada.
Aquí tienes el código optimizado y con comentarios explicativos:
from sklearn.model_selectionimport cross_val_score,KFoldfrom sklearn.treeimportDecisionTreeRegressorfrom sklearn.metricsimport mean_squared_error
import numpy as np
# Seleccionar características y etiquetas
X= dataset.drop(['country','score'], axis=1)y = dataset['score']# Imprimir la forma del dataset original
print("Forma del dataset original:", dataset.shape)# Crear un modelo de árbol de decisión
model =DecisionTreeRegressor()# Validación cruzada para evaluar el modelo
score =cross_val_score(model,X, y, cv=3, scoring='neg_mean_squared_error')print("Puntuaciones de validación cruzada:", score)# Calcular y imprimir el error cuadrático medio promedio absoluto
print("Error cuadrático medio promedio absoluto:", np.abs(np.mean(score)))# Configurar validación cruzada KFoldkf =KFold(n_splits=3, shuffle=True, random_state=42)# Inicializar una lista para almacenar los valores de error cuadrático medio
mse_values =[]# Iterar a través de las divisiones de entrenamiento y prueba
for train, test in kf.split(dataset):print("Índices de entrenamiento:", train)print("Índices de prueba:", test) # Dividir el conjunto de datos en entrenamiento y prueba
X_train, y_train =X.iloc[train], y.iloc[train]X_test, y_test =X.iloc[test], y.iloc[test] # Entrenar el modelo de árbol de decisión
model =DecisionTreeRegressor().fit(X_train, y_train) # Predecir utilizando el modelo entrenado
predict = model.predict(X_test) # Calcular y guardar el error cuadrático medio
mse_values.append(mean_squared_error(y_test, predict))# Imprimir los valores de error cuadrático medio para cada fold
print("Los tres MSE fueron:", mse_values)# Calcular y imprimir el MSE promedio
print("El MSE promedio fue:", np.mean(mse_values))```from sklearn.model\_selection import cross\_val\_score,KFoldfrom sklearn.treeimportDecisionTreeRegressorfrom sklearn.metricsimport mean\_squared\_error
import numpy as np
\# Seleccionar características y etiquetas
X= dataset.drop(\['country','score'], axis=1)y = dataset\['score']\# Imprimir la forma del dataset original
print("Forma del dataset original:", dataset.shape)\# Crear un modelo de árbol de decisión
model =DecisionTreeRegressor()\# Validación cruzada para evaluar el modelo
score = cross\_val\_score(model,X, y, cv=3, scoring='neg\_mean\_squared\_error')print("Puntuaciones de validación cruzada:", score)\# Calcular y imprimir el error cuadrático medio promedio absoluto
print("Error cuadrático medio promedio absoluto:", np.abs(np.mean(score)))\# Configurar validación cruzada KFoldkf =KFold(n\_splits=3, shuffle=True, random\_state=42)\# Inicializar una lista para almacenar los valores de error cuadrático medio
mse\_values = \[]\# Iterar a través de las divisiones de entrenamiento y prueba
for train, test in kf.split(dataset): print("Índices de entrenamiento:", train) print("Índices de prueba:", test)  \# Dividir el conjunto de datos en entrenamiento y prueba
 X\_train, y\_train =X.iloc\[train], y.iloc\[train] X\_test, y\_test =X.iloc\[test], y.iloc\[test]  \# Entrenar el modelo de árbol de decisión
  model =DecisionTreeRegressor().fit(X\_train, y\_train)  \# Predecir utilizando el modelo entrenado
  predict = model.predict(X\_test)  \# Calcular y guardar el error cuadrático medio
  mse\_values.append(mean\_squared\_error(y\_test, predict))\# Imprimir los valores de error cuadrático medio para cada fold
print("Los tres MSE fueron:", mse\_values)\# Calcular y imprimir el MSE promedio
print("El MSE promedio fue:", np.mean(mse\_values))
Una forma de validar nuestro modelo a través de K-folds es importar la función cross_validate.
Donde usa K-folds determinados por analista, ingresamos el modelo a usar, nuestros features, nuestra columna Target y podemos usar el parámetro return_train_score para que nos devuelva el score de cada uno de los modelos armados con cada K-fold.
Para este ejemplo, devuelve 50 resultados y se ve de esta manera:
Un poco complejo de leer, no? En realidad es todo data que ya vimos en el curso, donde podemos ver los scores de cada modelo armado.
De acá en adelante solo nos queda sacar el promedio de nuestros scores en cada etapa (train y test) y ver si debemos ajustar algo para corregir un posible overfitting o un underfitting.
print('Error para cada partición: ', mse_values)
print('Promedio de los errores: ', np.mean(mse_values))
Mi implementación de K-Folds
lst =[]cv =1for train , test in kf.split(X,y= y): # datasets
X_train= pd.DataFrame(X.iloc[train,:], columns=X.columns) y_train = pd.DataFrame(y.iloc[train,])X_test= pd.DataFrame(X.iloc[test,:], columns=X.columns) y_test = pd.DataFrame(y.iloc[test,]) #Modelo dt =DecisionTreeRegressor().fit(X_train,y_train) y_predict = dt.predict(X_test) lst.append(mean_squared_error(y_predict, y_test))print(f'The error for the folder number {cv} =',mean_squared_error(y_predict, y_test)) cv +=1print(f'\nThe mean of ther erros is equal to {np.mean(lst)}')
Resultado
The error for the folder number 1 = 0.260119109314712
The error for the folder number 2 = 0.21976508857008575
The error for the folder number 3 = 0.23677062451690725
The error for the folder number 4 = 0.31030546626208927
The mean of ther erros is equal to 0.2567400721659486