Validação cruzada aninhada e seleção do melhor modelo de regressão - este é o processo SKLearn correto?


8

Se eu entendi direito, o CV aninhado pode me ajudar a avaliar qual modelo e processo de ajuste de hiperparâmetro é melhor. O loop interno ( GridSearchCV) encontra os melhores hiperparâmetros e o loop externo ( cross_val_score) avalia o algoritmo de ajuste do hiperparâmetro. Em seguida, escolho qual combinação de ajuste / modelo do loop externo que minimiza mse(estou olhando para o classificador de regressão) para o meu teste final de modelo.

Eu li as perguntas / respostas sobre validação cruzada aninhada, mas não vi um exemplo de um pipeline completo que utiliza isso. Então, meu código abaixo (por favor, ignore os intervalos de hiperparâmetros reais - isto é apenas por exemplo) e o processo de pensamento faz 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)

Respostas:


8

O seu não é um exemplo de validação cruzada aninhada.

A validação cruzada aninhada é útil para descobrir se, digamos, uma floresta aleatória ou um SVM é mais adequado para o seu problema. O CV aninhado gera apenas uma pontuação, não gera um modelo como no seu código.

Este seria um exemplo de validação cruzada aninhada:

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

A propósito, alguns pensamentos:

  • Não vejo sentido em pesquisar em grade n_estimatorssua floresta aleatória. Obviamente, quanto mais, melhor. Coisas como max_depthé o tipo de regularização que você deseja otimizar. O erro para o CV aninhado RandomForestfoi muito maior porque você não otimizou para os hiperparâmetros corretos, não necessariamente porque é um modelo pior.
  • Você também pode tentar árvores com aumento de gradiente.

Obrigado por isso. Meu objetivo é fazer exatamente o que você disse - descobrir qual algoritmo classificador seria o mais adequado para o meu problema. Eu acho que estou confuso em termos da documentação do SKLearn: scikit-learn.org/stable/tutorial/statistical_inference/… (em 'validação cruzada aninhada')
BobbyJohnsonOG

Para testar o desempenho do modelo mais bem selecionado, eu faria uma validação cruzada final em todo o conjunto de dados? Ou devo dividir meu conjunto de dados em trem / teste ANTES do CV aninhado, executar o CV aninhado no trem e, em seguida, ajustar o melhor modelo nos dados do trem e testar no teste?
BobbyJohnsonOG

Desculpe pela barragem de comentários. Então, meu último modelo seria: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

Construindo o que você disse, era isso que eu pretendia com as funções integradas do SKLearn (dá o mesmo que a sua resposta):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 5/16

7

A validação cruzada aninhada estima o erro de generalização de um modelo, portanto, é uma boa maneira de escolher o melhor modelo em uma lista de modelos candidatos e suas grades de parâmetros associadas. A postagem original está próxima de criar CV aninhado: em vez de fazer uma única divisão de teste de trem, deve-se usar um segundo divisor de validação cruzada. Ou seja, um "aninha" um divisor de validação cruzada "interno" dentro de um divisor de validação cruzada "externo".

O divisor interno de validação cruzada é usado para escolher hiperparâmetros. O divisor externo de validação cruzada calcula a média do erro de teste em várias divisões de teste de trem. A média do erro de generalização em várias divisões de teste de trem fornece uma estimativa mais confiável da precisão do modelo em dados não vistos.

Modifiquei o código da postagem original para atualizá-lo para a versão mais recente de sklearn(com sklearn.cross_validationsubstituído sklearn.model_selectione 'mean_squared_error'substituído por 'neg_mean_squared_error') e usei dois KFolddivisores de validação cruzada para selecionar o melhor modelo. Para saber mais sobre validação cruzada aninhada, consulte o sklearn's exemplo de validação cruzada aninhada .

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

No último comentário, você diz que "... recoloca esse melhor modelo em todo o conjunto de treinamento", mas na verdade o faz em todo o conjunto de dados ( Xe y). Pelo que entendi, é a coisa certa a se fazer, mas o comentário deve ser corrigido. O que você acha?
Dror Atariah

Obrigado @DrorAtariah por entender isso. Você está certo. Eu consertei isso.
Charlie Brummitt

1

Você não precisa

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

GridSearchCVfaz isso por você. Para obter intuição do processo de pesquisa em grade, tente usar GridSearchCV(... , verbose=3)

Para extrair pontuações para cada dobra, consulte este exemplo na documentação do scikit-learn


Eu pensei que a pesquisa na grade era apenas para otimizar hiper parâmetros? Como eu usaria o gridsearch em conjunto com outra coisa para descobrir o melhor algoritmo do classificador (por exemplo, SVR vs. RandomForest)?
precisa saber é o seguinte

Sim. Para cada combinação de hiperparâmetros, o GridSearchCV faz dobras e calcula as pontuações (erro quadrático médio no seu caso) nos dados deixados de fora. Assim, cada combinação de hiperparâmetros obtém sua própria pontuação média. A "otimização" é apenas escolher a combinação com a melhor pontuação média. Você pode extrair essas pontuações médias e compará-las diretamente para vários modelos.
lanenok
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.