Verschachtelte Kreuzvalidierung und Auswahl des besten Regressionsmodells - ist dies der richtige SKLearn-Prozess?


8

Wenn ich das richtig verstehe, kann mir Nested-CV dabei helfen, zu bewerten, welcher Modell- und Hyperparameter-Optimierungsprozess am besten ist. Die innere Schleife ( GridSearchCV) findet die besten Hyperparameter, und die äußere Schleife ( cross_val_score) wertet den Algorithmus zur Optimierung der Hyperparameter aus. Ich wähle dann aus der äußeren Schleife aus, welche Tuning- / Modellkombination msefür meinen endgültigen Modelltest minimiert wird (ich betrachte den Regressionsklassifikator).

Ich habe die Fragen / Antworten zur verschachtelten Kreuzvalidierung gelesen, aber kein Beispiel für eine vollständige Pipeline gesehen, die dies nutzt. Ist mein Code unten (bitte ignorieren Sie die tatsächlichen Hyperparameterbereiche - dies ist nur zum Beispiel) und der Denkprozess sinnvoll?

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)

Antworten:


8

Ihre ist kein Beispiel für eine verschachtelte Kreuzvalidierung.

Eine verschachtelte Kreuzvalidierung ist hilfreich, um herauszufinden, ob beispielsweise eine zufällige Gesamtstruktur oder eine SVM für Ihr Problem besser geeignet ist. Der verschachtelte Lebenslauf gibt nur eine Punktzahl aus, kein Modell wie in Ihrem Code.

Dies wäre ein Beispiel für eine verschachtelte Kreuzvalidierung:

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

Übrigens ein paar Gedanken:

  • Ich sehe keinen Grund, n_estimatorsnach Ihrem zufälligen Wald zu suchen . Je mehr, desto besser. Dinge wie max_depthist die Art der Regularisierung, die Sie optimieren möchten. Der Fehler für den verschachtelten Lebenslauf von RandomForestwar viel höher, weil Sie nicht für die richtigen Hyperparameter optimiert haben, nicht unbedingt, weil es sich um ein schlechteres Modell handelt.
  • Vielleicht möchten Sie auch versuchen, Bäume mit Farbverlauf zu verstärken.

Dank dafür. Mein Ziel ist es, genau das zu tun, was Sie gesagt haben - herauszufinden, welcher Klassifikatoralgorithmus für mein Problem am besten geeignet ist. Ich glaube, ich bin verwirrt in Bezug auf die Dokumentation von SKLearn: scikit-learn.org/stable/tutorial/statistical_inference/… (unter 'verschachtelte Kreuzvalidierung')
BobbyJohnsonOG

Würde ich eine endgültige Kreuzvalidierung des gesamten Datensatzes durchführen, um die Leistung des am besten ausgewählten Modells zu testen? Oder sollte ich meinen Datensatz vor dem verschachtelten Lebenslauf in Zug / Test aufteilen, verschachtelten Lebenslauf im Zug ausführen und dann das beste Modell in die Zugdaten einpassen und im Test testen?
BobbyJohnsonOG

Entschuldigung für die Kommentarsperre. Mein letztes Modell wäre also: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

Aufbauend auf dem, was Sie gesagt haben, war dies das, was ich mit eingebauten SKLearn-Funktionen anstrebte (entspricht Ihrer Antwort):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

Die verschachtelte Kreuzvalidierung schätzt den Generalisierungsfehler eines Modells. Daher ist es eine gute Möglichkeit, das beste Modell aus einer Liste von Kandidatenmodellen und den zugehörigen Parametergittern auszuwählen. Der ursprüngliche Beitrag steht kurz vor der Erstellung eines verschachtelten Lebenslaufs: Anstatt einen einzelnen Zug-Test-Split durchzuführen, sollte stattdessen ein zweiter Kreuzvalidierungs-Splitter verwendet werden. Das heißt, man "verschachtelt" einen "inneren" Kreuzvalidierungs-Splitter in einem "äußeren" Kreuzvalidierungs-Splitter.

Der innere Kreuzvalidierungs-Splitter wird zur Auswahl von Hyperparametern verwendet. Der äußere Kreuzvalidierungs-Splitter mittelt den Testfehler über mehrere Zug-Test-Splits. Die Mittelung des Generalisierungsfehlers über mehrere Zug-Test-Teilungen liefert eine zuverlässigere Schätzung der Genauigkeit des Modells für unsichtbare Daten.

Ich habe den Code des ursprünglichen Beitrags geändert, um ihn auf die neueste Version von sklearn(mit sklearn.cross_validationersetzt durch sklearn.model_selectionund durch 'mean_squared_error'ersetzt durch 'neg_mean_squared_error') zu aktualisieren , und ich habe zwei KFoldKreuzvalidierungs-Splitter verwendet, um das beste Modell auszuwählen. Um mehr über verschachtelte Kreuzvalidierung finden Sie auf das sklearn‚s Beispiel auf verschachtelte Kreuzvalidierung .

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

Beim allerletzten Kommentar sagen Sie, dass Sie "... dieses beste Modell für den gesamten Trainingssatz umrüsten", aber Sie tun dies tatsächlich für den gesamten Datensatz ( Xund y). Soweit ich weiß, ist dies das Richtige, aber dann muss der Kommentar korrigiert werden. Was denken Sie?
Dror Atariah

Danke @DrorAtariah, dass du das verstanden hast. Du hast recht. Ich habe es repariert.
Charlie Brummitt

1

Du brauchst nicht

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

GridSearchCVmacht das für dich. Versuchen Sie es mit, um sich ein Bild vom Rastersuchprozess zu machen GridSearchCV(... , verbose=3)

Informationen zum Extrahieren von Punktzahlen für jede Falte finden Sie in diesem Beispiel in der Scikit-Learn-Dokumentation


Ich dachte, die Rastersuche dient ausschließlich der Optimierung von Hyperparametern. Wie würde ich Gridsearch in Verbindung mit etwas anderem verwenden, um den besten Klassifikatoralgorithmus herauszufinden (dh SVR vs. RandomForest)?
BobbyJohnsonOG

Ja. Für jede Kombination von Hyperparametern faltet GridSearchCV Falten und berechnet die Punktzahlen (in Ihrem Fall mittlerer quadratischer Fehler) für die ausgelassenen Daten. Somit erhält jede Kombination von Hyperparametern ihren eigenen Mittelwert. Die "Optimierung" wählt nur die Kombination mit dem besten Mittelwert. Sie können diese Mittelwerte extrahieren und direkt für verschiedene Modelle vergleichen.
Lanenok
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.