Implementação de validação cruzada aninhada

10

Estou tentando descobrir se meu entendimento da validação cruzada aninhada está correto, por isso escrevi este exemplo de brinquedo para verificar se estou certo:

import operator
import numpy as np
from sklearn import cross_validation
from sklearn import ensemble
from sklearn.datasets import load_boston

# set random state
state = 1

# load boston dataset
boston = load_boston()

X = boston.data
y = boston.target

outer_scores = []

# outer cross-validation
outer = cross_validation.KFold(len(y), n_folds=3, shuffle=True, random_state=state)
for fold, (train_index_outer, test_index_outer) in enumerate(outer):
    X_train_outer, X_test_outer = X[train_index_outer], X[test_index_outer]
    y_train_outer, y_test_outer = y[train_index_outer], y[test_index_outer]

    inner_mean_scores = []

    # define explored parameter space.
    # procedure below should be equal to GridSearchCV
    tuned_parameter = [1000, 1100, 1200]
    for param in tuned_parameter:

        inner_scores = []

        # inner cross-validation
        inner = cross_validation.KFold(len(X_train_outer), n_folds=3, shuffle=True, random_state=state)
        for train_index_inner, test_index_inner in inner:
            # split the training data of outer CV
            X_train_inner, X_test_inner = X_train_outer[train_index_inner], X_train_outer[test_index_inner]
            y_train_inner, y_test_inner = y_train_outer[train_index_inner], y_train_outer[test_index_inner]

            # fit extremely randomized trees regressor to training data of inner CV
            clf = ensemble.ExtraTreesRegressor(param, n_jobs=-1, random_state=1)
            clf.fit(X_train_inner, y_train_inner)
            inner_scores.append(clf.score(X_test_inner, y_test_inner))

        # calculate mean score for inner folds
        inner_mean_scores.append(np.mean(inner_scores))

    # get maximum score index
    index, value = max(enumerate(inner_mean_scores), key=operator.itemgetter(1))

    print 'Best parameter of %i fold: %i' % (fold + 1, tuned_parameter[index])

    # fit the selected model to the training set of outer CV
    # for prediction error estimation
    clf2 = ensemble.ExtraTreesRegressor(tuned_parameter[index], n_jobs=-1, random_state=1)
    clf2.fit(X_train_outer, y_train_outer)
    outer_scores.append(clf2.score(X_test_outer, y_test_outer))

# show the prediction error estimate produced by nested CV
print 'Unbiased prediction error: %.4f' % (np.mean(outer_scores))

# finally, fit the selected model to the whole dataset
clf3 = ensemble.ExtraTreesRegressor(tuned_parameter[index], n_jobs=-1, random_state=1)
clf3.fit(X, y)

Quaisquer pensamentos apreciados.

abudis
fonte
3
Você também pode fornecer uma versão do seu entendimento da validação cruzada em texto para quem não lê Python?
gung - Restabelece Monica
scikit-learnversão própria: scikit-learn.org/stable/auto_examples/model_selection/…
ayorgo

Respostas:

14

UPS, o código está errado, mas de uma maneira muito sutil !

a) a divisão do conjunto de trem em um conjunto de treinamento interno e conjunto de teste está correta.

b) o problema são as duas últimas linhas, que refletem o sutil mal-entendido sobre o objetivo de uma validação cruzada aninhada. O objetivo de um CV aninhado não é selecionar os parâmetros, mas ter uma avaliação imparcial da precisão esperada do seu algoritmo, neste caso ensemble.ExtraTreesRegressornesses dados com o melhor hiperparâmetro, sejam eles quais forem .

E é isso que seu código calcula corretamente até a linha:

    print 'Unbiased prediction error: %.4f' % (np.mean(outer_scores))

Ele usou o CV aninhado para calcular uma previsão imparcial do classificador. Mas observe que cada passagem do loop externo pode gerar um melhor hiperparâmetro diferente, como você sabia quando escreveu a linha:

   print 'Best parameter of %i fold: %i' % (fold + 1, tuned_parameter[index])

Então agora você precisa de um loop CV padrão para selecionar o melhor hiperparâmetro final, usando dobras:

tuned_parameter = [1000, 1100, 1200]
for param in tuned_parameter:

    scores = []

    # normal cross-validation
    kfolds = cross_validation.KFold(len(y), n_folds=3, shuffle=True, random_state=state)
    for train_index, test_index in kfolds:
        # split the training data
        X_train, X_test = X[train_index], X[test_index]
        y_train, y_test = y[train_index], y[test_index]

        # fit extremely randomized trees regressor to training data
        clf2_5 = ensemble.ExtraTreesRegressor(param, n_jobs=-1, random_state=1)
        clf2_5.fit(X_train, y_train)
        scores.append(clf2_5.score(X_test, y_test))

    # calculate mean score for folds
    mean_scores.append(np.mean(scores))

# get maximum score index
index, value = max(enumerate(mean_scores), key=operator.itemgetter(1))

print 'Best parameter : %i' % (tuned_parameter[index])

qual é o seu código, mas com referências ao interno removido.

Agora, o melhor parâmetro é tuned_parameter[index]e agora você pode aprender o classificador final clf3como no seu código.

Jacques Wainer
fonte
Obrigado! Eu considerei que posso selecionar bestparâmetros diferentes em dobras diferentes, mas não sabia como escolher os melhores. stats.stackexchange.com/questions/65128/… - aqui, na resposta é mencionado que é realmente indesejável selecionar o melhor modelo dentre os modelos k externos. Talvez ainda esteja entendendo mal alguma coisa, mas pensei que a idéia do loop interno do CV é selecionar o modelo com melhor desempenho e o loop externo do CV é estimar o desempenho. Você poderia fornecer o código completo modificado?
Abudis
Ok, acho que entendi. Eu gostaria de ver o código completo modificado, porém, apenas para ter certeza. Obrigado.
Abudis
11
Estou confuso em relação à resposta de Jacques Wainer e acho que vale a pena esclarecê-la. Então, Wainer sugere que um loop CV padrão siga o código fornecido pela pergunta inicial ou substitua apenas o código da peça "interna" inicial? thanx
O loop CV padrão segue o ciclo CV nested
Jacques Wainer
2
A primeira parte é calcular uma previsão imparcial do erro. Se estiver testando muitos algoritmos diferentes, você deve executar apenas a 1ª parte e, em seguida, selecionar o algoritmo com o menor erro e, somente para essa, executar a parte 2 para selecionar os hiperparâmetros. Se você usar apenas um algoritmo, a 1ª parte será menos importante, a menos que você deseje declarar ao seu chefe ou cliente que sua melhor previsão do erro futuro do classificador é x e você deve calcular x usando o primeiro CV aninhado.
Jacques Wainer
0

Para resumir a resposta de Jacques,

O CV aninhado é necessário para a estimativa de erro imparcial de um modelo. Podemos comparar a pontuação de diferentes modelos dessa maneira. Usando essas informações, podemos executar um loop CV dobrável em K separado para o ajuste dos parâmetros dos modelos selecionados.

Sharan Naribole
fonte