Implémentation de la validation croisée imbriquée

10

J'essaie de comprendre si ma compréhension de la validation croisée imbriquée est correcte, j'ai donc écrit cet exemple de jouet pour voir si j'ai raison:

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)

Toutes les pensées appréciées.

abudis
la source
3
Pouvez-vous également fournir une version de votre compréhension de la validation croisée dans le texte pour ceux qui ne lisent pas Python?
gung - Rétablir Monica

Réponses:

14

UPS, le code est erroné, mais d'une manière très subtile !

a) la division du train en un ensemble d'entraînement intérieur et un ensemble de test est OK.

b) le problème est les deux dernières lignes, qui reflètent le malentendu subtil sur le but d'une validation croisée imbriquée. Le but d'un CV imbriqué n'est pas de sélectionner les paramètres, mais d'avoir une évaluation impartiale de la précision attendue de votre algorithme, dans ce cas ensemble.ExtraTreesRegressordans ces données avec le meilleur hyperparamètre quel qu'il soit .

Et c'est ce que votre code calcule correctement jusqu'à la ligne:

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

Il a utilisé le nested-CV pour calculer une prédiction non biaisée du classificateur. Mais notez que chaque passage de la boucle externe peut générer un meilleur hyperparamètre différent, comme vous le saviez lorsque vous avez écrit la ligne:

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

Alors maintenant, vous avez besoin d'une boucle CV standard pour sélectionner le meilleur hyperparamètre final, en utilisant des plis:

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

qui est votre code mais avec des références à l' intérieur supprimé.

Maintenant, le meilleur paramètre est tuned_parameter[index], et maintenant vous pouvez apprendre le classificateur final clf3comme dans votre code.

Jacques Wainer
la source
Merci! J'ai considéré que je pouvais sélectionner différents bestparamètres dans différents plis, mais je ne savais pas comment choisir les meilleurs. stats.stackexchange.com/questions/65128/… - ici, dans la réponse, il est mentionné qu'il n'est en fait pas souhaitable de sélectionner le meilleur modèle parmi les modèles k externes. Peut-être que je ne comprends toujours pas quelque chose, mais je pensais que l'idée de la boucle de CV interne est de sélectionner le modèle le plus performant et la boucle de CV externe est d'estimer les performances. Pourriez-vous s'il vous plaît fournir le code modifié complet?
abudis
D'accord, je pense que j'ai compris. J'aimerais voir le code entièrement modifié, juste pour être sûr. Merci.
abudis
1
Je suis confus quant à la réponse de Jacques Wainer et je pense que cela mérite d'être clarifié. Alors, Wainer suggère-t-il qu'une boucle de CV standard devrait suivre le code fourni par la question initiale ou qu'elle devrait simplement remplacer le code de pièce "interne" initial? Thanx
La boucle de CV standard suit la boucle de CV imbriquée
Jacques Wainer
2
La première partie consiste à calculer une prédiction non biaisée de l'erreur. Si vous testez de nombreux algorithmes différents, vous devez effectuer uniquement la 1ère partie, puis sélectionner l'algorithme avec l'erreur la plus faible, et uniquement pour celui-ci, effectuer la 2 partie pour sélectionner les hyperparamètres. Si vous êtes déterminé à utiliser un seul algorithme, la 1ère partie est moins importante, sauf si vous voulez indiquer à votre patron ou client que votre meilleure prédiction de l'erreur future du classificateur est x, et vous devez calculer le x à l'aide du 1er CV imbriqué.
Jacques Wainer
0

Pour résumer la réponse de Jacques,

Un CV imbriqué est requis pour l'estimation d'erreur non biaisée d'un modèle. Nous pouvons comparer le score de différents modèles de cette manière. À l'aide de ces informations, nous pouvons ensuite effectuer une boucle CV séparée en K pour le réglage des paramètres des modèles sélectionnés.

Sharan Naribole
la source