Validation croisée imbriquée et sélection du meilleur modèle de régression - est-ce le bon processus SKLearn?

8

Si je comprends bien, le CV imbriqué peut m'aider à évaluer le meilleur modèle et le processus de réglage d'hyperparamètre. La boucle interne ( GridSearchCV) trouve les meilleurs hyperparamètres et la boucle externe ( cross_val_score) évalue l'algorithme de réglage des hyperparamètres. Je choisis ensuite le combo réglage / modèle de la boucle externe qui minimise mse(je regarde le classificateur de régression) pour mon test de modèle final.

J'ai lu les questions / réponses sur la validation croisée imbriquée, mais je n'ai pas vu d'exemple de pipeline complet qui utilise cela. Donc, mon code ci-dessous (veuillez ignorer les plages hyperparamétriques réelles - c'est juste par exemple) et le processus de réflexion ont-ils du sens?

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
la source

Réponses:

8

Le vôtre n'est pas un exemple de validation croisée imbriquée.

La validation croisée imbriquée est utile pour déterminer si, par exemple, une forêt aléatoire ou un SVM est mieux adapté à votre problème. Le CV imbriqué ne produit qu'un score, il ne produit pas de modèle comme dans votre code.

Ce serait un exemple de validation croisée imbriquée:

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))

Soit dit en passant, quelques réflexions:

  • Je ne vois aucune raison de rechercher dans la grille n_estimatorsvotre forêt aléatoire. De toute évidence, plus on est de fous. Des choses comme max_depthle type de régularisation que vous souhaitez optimiser. L'erreur pour le CV imbriqué RandomForestétait beaucoup plus élevée car vous n'avez pas optimisé pour les bons hyperparamètres, pas nécessairement parce que c'est un modèle pire.
  • Vous voudrez peut-être également essayer les arbres renforçant le gradient.
Ricardo Cruz
la source
Merci pour ça. Mon objectif est de faire exactement ce que vous avez dit - déterminer quel algorithme de classificateur serait le mieux adapté à mon problème. Je suppose que je suis confus en termes de documentation de SKLearn: scikit-learn.org/stable/tutorial/statistical_inference/… (sous 'nested cross-validation')
BobbyJohnsonOG
Pour tester les performances du modèle le mieux sélectionné, est-ce que je ferais une validation croisée finale sur l'ensemble des données? Ou dois-je diviser mon ensemble de données en train / test AVANT le CV imbriqué, exécuter le CV imbriqué dans le train, puis adapter le meilleur modèle aux données du train et tester sur le test?
BobbyJohnsonOG
Désolé pour le barrage de commentaires. Donc mon modèle final serait: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
À partir de ce que vous avez dit, c'était ce que j'allais faire avec les fonctions SKLearn intégrées (donne la même chose que votre réponse):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 validation croisée imbriquée estime l'erreur de généralisation d'un modèle, c'est donc un bon moyen de choisir le meilleur modèle à partir d'une liste de modèles candidats et de leurs grilles de paramètres associées. Le poste d'origine est proche de faire un CV imbriqué: plutôt que de faire un seul fractionnement train-test, on devrait plutôt utiliser un deuxième séparateur de validation croisée. C'est-à-dire que l'on "imbrique" un séparateur de validation croisée "interne" à l'intérieur d'un séparateur de validation croisée "externe".

Le séparateur de validation croisée interne est utilisé pour choisir les hyperparamètres. Le séparateur de validation croisée externe fait la moyenne de l'erreur de test sur plusieurs fractionnements de train-test. La moyenne de l'erreur de généralisation sur plusieurs divisions train-test fournit une estimation plus fiable de la précision du modèle sur des données invisibles.

J'ai modifié le code de l'article d'origine pour le mettre à jour vers la dernière version de sklearn(avec sklearn.cross_validationremplacé par sklearn.model_selectionet avec 'mean_squared_error'remplacé par 'neg_mean_squared_error'), et j'ai utilisé deux KFoldrépartiteurs de validation croisée pour sélectionner le meilleur modèle. Pour en savoir plus sur la validation croisée imbriquée, voir le sklearnl » exemple sur la validation croisée imbriquée .

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
la source
Au tout dernier commentaire, vous dites que vous "... remettez en place ce meilleur modèle sur l'ensemble de l'entraînement" mais vous le faites en fait sur l'ensemble des données ( Xet y). Pour autant que je sache, c'est la bonne chose à faire, mais le commentaire doit être corrigé. Qu'est-ce que tu penses?
Dror Atariah
Merci @DrorAtariah d'avoir attrapé ça. Tu as raison. Je l'ai corrigé.
Charlie Brummitt
1

Tu n'as pas besoin

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

GridSearchCVfait cela pour vous. Pour obtenir l'intuition du processus de recherche de grille, essayez d'utiliser GridSearchCV(... , verbose=3)

Pour extraire les scores de chaque pli, voir cet exemple dans la documentation scikit-learn

lanenok
la source
Je pensais que la recherche dans la grille visait uniquement à optimiser les paramètres hyper? Comment utiliser gridsearch en conjonction avec autre chose pour trouver le meilleur algorithme de classificateur (c.-à-d. SVR vs RandomForest)?
BobbyJohnsonOG
Oui. Pour chaque combinaison d'hyper-paramètres, GridSearchCV fait des plis et calcule les scores (erreur quadratique moyenne dans votre cas) sur les données laissées de côté. Ainsi, chaque combinaison d'hyper-paramètres obtient son propre score moyen. L '"optimisation" consiste simplement à choisir la combinaison avec le meilleur score moyen. Vous pouvez extraire ces scores moyens et les comparer directement pour différents modèles.
lanenok