Implementación de validación cruzada anidada

10

Estoy tratando de averiguar si mi comprensión de la validación cruzada anidada es correcta, por lo tanto, escribí este ejemplo de juguete para ver si tengo razón:

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)

Cualquier pensamiento apreciado.

abudis
fuente
3
¿También puede proporcionar una versión de su comprensión de la validación cruzada en el texto para aquellos que no leen Python?
gung - Restablece a Monica
scikit-learnversión propia: scikit-learn.org/stable/auto_examples/model_selection/…
ayorgo el

Respuestas:

14

UPS, el código está mal, ¡pero de una manera muy sutil !

a) la división del conjunto de trenes en un conjunto de entrenamiento interno y un conjunto de prueba está bien.

b) el problema son las dos últimas líneas, que reflejan el malentendido sutil sobre el propósito de una validación cruzada anidada. El propósito de un CV anidado no es seleccionar los parámetros, sino tener una evaluación imparcial de cuál es la precisión esperada de su algoritmo, en este caso ensemble.ExtraTreesRegressoren estos datos con el mejor hiperparámetro, sean los que sean .

Y esto es lo que su código calcula correctamente hasta la línea:

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

Usó el CV anidado para calcular una predicción imparcial del clasificador. Pero tenga en cuenta que cada paso del bucle externo puede generar un mejor hiperparámetro diferente, como sabía cuando escribió la línea:

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

Así que ahora necesita un bucle CV estándar para seleccionar el mejor hiperparámetro final, usando pliegues:

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

cual es tu código pero con referencias a interno eliminado.

Ahora el mejor parámetro es tuned_parameter[index], y ahora puedes aprender el clasificador final clf3como en tu código.

Jacques Wainer
fuente
¡Gracias! Considere que puedo seleccionar diferentes bestparámetros en diferentes pliegues, pero no sabía cómo elegir los mejores. stats.stackexchange.com/questions/65128/… - aquí, en la respuesta, se menciona que en realidad no es deseable seleccionar el mejor modelo de los modelos k externos. Tal vez todavía estoy malinterpretando algo, pero pensé que la idea del bucle CV interno es seleccionar el modelo con mejor rendimiento y el bucle CV externo es estimar el rendimiento. ¿Podría proporcionar el código modificado completo?
abudis
De acuerdo, creo que tengo eso. Sin embargo, me gustaría ver el código completo modificado, solo para estar seguro. Gracias.
abudis
1
Estoy confundido con respecto a la respuesta de Jacques Wainer y creo que vale la pena aclararlo. Entonces, ¿sugiere Wainer que un bucle CV estándar debería seguir el código proporcionado por la pregunta inicial o que simplemente debería reemplazar el código de pieza "interno" inicial? gracias
El bucle CV estándar sigue el bucle CV anidado
Jacques Wainer
2
La primera parte es calcular una predicción imparcial del error. Si está probando muchos algoritmos diferentes, debe realizar solo la primera parte, luego seleccionar el algoritmo con el error más bajo, y solo para ese, realice la parte 2 para seleccionar los hiperparámetros. Si está configurado para usar solo un algoritmo, la primera parte es menos importante, a menos que desee decirle a su jefe o cliente que su mejor predicción del error futuro del clasificador es x, y debe calcular la x usando la primera CV anidado.
Jacques Wainer
0

Para resumir la respuesta de Jacques,

Se requiere CV anidado para la estimación de error imparcial de un modelo. Podemos comparar la puntuación de diferentes modelos de esta manera. Con esta información, podemos realizar un bucle CV K-fold separado para el ajuste de parámetros de los modelos seleccionados.

Sharan Naribole
fuente