Validación cruzada anidada y selección del mejor modelo de regresión: ¿es este el proceso SKLearn correcto?

8

Si entiendo correctamente, el CV anidado puede ayudarme a evaluar qué modelo y proceso de ajuste de hiperparámetro es mejor. El bucle interno ( GridSearchCV) encuentra los mejores hiperparámetros, y el bucle externo ( cross_val_score) evalúa el algoritmo de ajuste del hiperparámetro. Luego elijo qué combo de ajuste / modelo del bucle externo que minimiza mse(estoy mirando el clasificador de regresión) para mi prueba de modelo final.

He leído las preguntas / respuestas sobre validación cruzada anidada, pero no he visto un ejemplo de una tubería completa que utilice esto. Entonces, ¿mi código a continuación (ignore los rangos reales de hiperparámetros, esto es solo por ejemplo) y el proceso de pensamiento tiene sentido?

from sklearn.cross_validation import cross_val_score, train_test_split
from sklearn.grid_search import GridSearchCV
from sklearn.metrics import mean_squared_error
from sklearn.ensemble import RandomForestRegressor
from sklearn.svm import SVR
from sklearn.datasets import make_regression

# create some regression data
X, y = make_regression(n_samples=1000, n_features=10)
params = [{'C':[0.01,0.05,0.1,1]},{'n_estimators':[10,100,1000]}]

# setup models, variables
mean_score = []
models = [SVR(), RandomForestRegressor()]

# split into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.3)

# estimate performance of hyperparameter tuning and model algorithm pipeline
for idx, model in enumerate(models):
    clf = GridSearchCV(model, params[idx], scoring='mean_squared_error')

    # this performs a nested CV in SKLearn
    score = cross_val_score(clf, X_train, y_train, scoring='mean_squared_error')

    # get the mean MSE across each fold
    mean_score.append(np.mean(score))
    print('Model:', model, 'MSE:', mean_score[-1])

# estimate generalization performance of the best model selection technique
best_idx = mean_score.index(max(mean_score)) # because SKLearn flips MSE signs, max works OK here
best_model = models[best_idx]

clf_final = GridSearchCV(best_model, params[best_idx])
clf_final.fit(X_train, y_train)

y_pred = clf_final.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))

print('Final Model': best_model, 'Final model RMSE:', rmse)
BobbyJohnsonOG
fuente

Respuestas:

8

El suyo no es un ejemplo de validación cruzada anidada.

La validación cruzada anidada es útil para determinar si, por ejemplo, un bosque aleatorio o un SVM es más adecuado para su problema. El CV anidado solo genera una puntuación, no genera un modelo como en su código.

Este sería un ejemplo de validación cruzada anidada:

from sklearn.datasets import load_boston
from sklearn.cross_validation import KFold
from sklearn.metrics import mean_squared_error
from sklearn.grid_search import GridSearchCV
from sklearn.ensemble import RandomForestRegressor
from sklearn.svm import SVR
import numpy as np

params = [{'C': [0.01, 0.05, 0.1, 1]}, {'n_estimators': [10, 100, 1000]}]
models = [SVR(), RandomForestRegressor()]

df = load_boston()
X = df['data']
y = df['target']

cv = [[] for _ in range(len(models))]
for tr, ts in KFold(len(X)):
    for i, (model, param) in enumerate(zip(models, params)):
        best_m = GridSearchCV(model, param)
        best_m.fit(X[tr], y[tr])
        s = mean_squared_error(y[ts], best_m.predict(X[ts]))
        cv[i].append(s)
print(np.mean(cv, 1))

Por cierto, un par de pensamientos:

  • No veo ningún propósito para buscar en la cuadrícula n_estimatorssu bosque aleatorio. Obviamente, cuanto más, mejor. Cosas como max_depthes el tipo de regularización que desea optimizar. El error para el CV anidado de RandomForestfue mucho mayor porque no optimizó para los hiperparámetros correctos, no necesariamente porque es un modelo peor.
  • Es posible que también desee probar los árboles que aumentan la pendiente.
Ricardo Cruz
fuente
Gracias por eso. Mi objetivo es hacer exactamente lo que dijiste: averiguar qué algoritmo clasificador sería el más adecuado para mi problema. Creo que estoy confundido en términos de la documentación de SKLearn: scikit-learn.org/stable/tutorial/statistical_inference/… (bajo 'validación cruzada anidada')
BobbyJohnsonOG
Para probar el rendimiento del modelo mejor seleccionado, ¿haría una validación cruzada final en todo el conjunto de datos? ¿O debería dividir mi conjunto de datos en tren / prueba ANTES de CV anidado, ejecutar CV anidado en el tren y luego ajustar el mejor modelo en los datos del tren y probar en la prueba?
BobbyJohnsonOG
Perdón por el comentario aluvión. Entonces mi modelo final sería:best_idx = np.where(np.mean(cv,1).min())[0]; final_m = GridSearchCV(models[best_idx], params[best_idx]); final_m.fit(X,y)
BobbyJohnsonOG
Partiendo de lo que dijiste, esto era lo que buscaba con las funciones SKLearn integradas (da lo mismo que tu respuesta):for model, param in zip(models, params): clf = GridSearchCV(model, param) my_score = cross_val_score(clf, X, y, scoring='mean_squared_error') my_scores.append(my_score)
BobbyJohnsonOG
7

La validación cruzada anidada estima el error de generalización de un modelo, por lo que es una buena manera de elegir el mejor modelo de una lista de modelos candidatos y sus cuadrículas de parámetros asociadas. La publicación original está cerca de hacer un CV anidado: en lugar de hacer una sola división de prueba de tren, uno debería usar un segundo divisor de validación cruzada. Es decir, uno "anida" un divisor de validación cruzada "interno" dentro de un divisor de validación cruzada "externo".

El divisor interno de validación cruzada se usa para elegir hiperparámetros. El divisor externo de validación cruzada promedia el error de prueba en múltiples divisiones de prueba de tren. Promediar el error de generalización en múltiples divisiones de tren-prueba proporciona una estimación más confiable de la precisión del modelo en datos no vistos.

Modifiqué el código de la publicación original para actualizarlo a la última versión de sklearn(con sklearn.cross_validationreemplazado por sklearn.model_selectiony con 'mean_squared_error'reemplazado por 'neg_mean_squared_error'), y utilicé dos KFolddivisores de validación cruzada para seleccionar el mejor modelo. Para obtener más información sobre la validación cruzada anidada, ver el sklearn's ejemplo de anidado de validación cruzada .

from sklearn.model_selection import KFold, cross_val_score, GridSearchCV
from sklearn.datasets import make_regression
from sklearn.ensemble import RandomForestRegressor
from sklearn.svm import SVR
import numpy as np

# `outer_cv` creates 3 folds for estimating generalization error
outer_cv = KFold(3)

# when we train on a certain fold, we use a second cross-validation
# split in order to choose hyperparameters
inner_cv = KFold(3)

# create some regression data
X, y = make_regression(n_samples=1000, n_features=10)

# give shorthand names to models and use those as dictionary keys mapping
# to models and parameter grids for that model
models_and_parameters = {
    'svr': (SVR(),
            {'C': [0.01, 0.05, 0.1, 1]}),
    'rf': (RandomForestRegressor(),
           {'max_depth': [5, 10, 50, 100, 200, 500]})}

# we will collect the average of the scores on the 3 outer folds in this dictionary
# with keys given by the names of the models in `models_and_parameters`
average_scores_across_outer_folds_for_each_model = dict()

# find the model with the best generalization error
for name, (model, params) in models_and_parameters.items():
    # this object is a regressor that also happens to choose
    # its hyperparameters automatically using `inner_cv`
    regressor_that_optimizes_its_hyperparams = GridSearchCV(
        estimator=model, param_grid=params,
        cv=inner_cv, scoring='neg_mean_squared_error')

    # estimate generalization error on the 3-fold splits of the data
    scores_across_outer_folds = cross_val_score(
        regressor_that_optimizes_its_hyperparams,
        X, y, cv=outer_cv, scoring='neg_mean_squared_error')

    # get the mean MSE across each of outer_cv's 3 folds
    average_scores_across_outer_folds_for_each_model[name] = np.mean(scores_across_outer_folds)
    error_summary = 'Model: {name}\nMSE in the 3 outer folds: {scores}.\nAverage error: {avg}'
    print(error_summary.format(
        name=name, scores=scores_across_outer_folds,
        avg=np.mean(scores_across_outer_folds)))
    print()

print('Average score across the outer folds: ',
      average_scores_across_outer_folds_for_each_model)

many_stars = '\n' + '*' * 100 + '\n'
print(many_stars + 'Now we choose the best model and refit on the whole dataset' + many_stars)

best_model_name, best_model_avg_score = max(
    average_scores_across_outer_folds_for_each_model.items(),
    key=(lambda name_averagescore: name_averagescore[1]))

# get the best model and its associated parameter grid
best_model, best_model_params = models_and_parameters[best_model_name]

# now we refit this best model on the whole dataset so that we can start
# making predictions on other data, and now we have a reliable estimate of
# this model's generalization error and we are confident this is the best model
# among the ones we have tried
final_regressor = GridSearchCV(best_model, best_model_params, cv=inner_cv)
final_regressor.fit(X, y)

print('Best model: \n\t{}'.format(best_model), end='\n\n')
print('Estimation of its generalization error (negative mean squared error):\n\t{}'.format(
    best_model_avg_score), end='\n\n')
print('Best parameter choice for this model: \n\t{params}'
      '\n(according to cross-validation `{cv}` on the whole dataset).'.format(
      params=final_regressor.best_params_, cv=inner_cv))
Charlie Brummitt
fuente
En el último comentario, usted dice que "... reajusta este mejor modelo en todo el conjunto de entrenamiento" pero en realidad lo hace en todo el conjunto de datos ( Xy y). Hasta donde entiendo, esto es lo correcto, pero luego el comentario tiene que ser corregido. ¿Qué piensas?
Dror Atariah
Gracias @DrorAtariah por atrapar eso. Tienes razón. Lo arreglé.
Charlie Brummitt
1

Usted no necesita

# this performs a nested CV in SKLearn
score = cross_val_score(clf, X_train, y_train, scoring='mean_squared_error')

GridSearchCVhace esto por ti Para tener una idea del proceso de búsqueda en la cuadrícula, intente usar GridSearchCV(... , verbose=3)

Para extraer puntuaciones para cada pliegue, vea este ejemplo en la documentación de scikit-learn

Lanenek
fuente
¿Pensé que la búsqueda de cuadrícula era únicamente para optimizar hiperparámetros? ¿Cómo usaría gridsearch junto con algo más para descubrir el mejor algoritmo clasificador (es decir, SVR vs. RandomForest)?
BobbyJohnsonOG
Si. Para cada combinación de hiperparámetros, GridSearchCV hace pliegues y calcula las puntuaciones (error cuadrático medio en su caso) en los datos omitidos. Por lo tanto, cada combinación de hiperparámetros obtiene su propia puntuación media. La "optimización" es simplemente elegir la combinación con la mejor puntuación media. Puede extraer esos puntajes promedio y compararlos directamente para varios modelos.
lanenok