使用 Keras 和 sklearn GridSearchCV 交叉验证提前停止

Early stopping with Keras and sklearn GridSearchCV cross-validation

我希望使用 Keras 和 sklean 的 GridSearchCV 实现提前停止。

下面的工作代码示例修改自 How to Grid Search Hyperparameters for Deep Learning Models in Python With Keras. The data set may be downloaded from here

修改增加KerasEarlyStopping回调class防止过拟合。为了使其有效,它需要 monitor='val_acc' 参数来监控验证准确性。要使 val_acc 可用,KerasClassifier 需要 validation_split=0.1 来生成验证准确性,否则 EarlyStopping 会提高 RuntimeWarning: Early stopping requires val_acc available!。注意 FIXME: 代码注释!

请注意,我们可以将 val_acc 替换为 val_loss

问题:如何使用GridSearchCV k-fold算法生成的交叉验证数据集而不是浪费10%的训练数据提前停止验证集?

# Use scikit-learn to grid search the learning rate and momentum
import numpy
from sklearn.model_selection import GridSearchCV
from keras.models import Sequential
from keras.layers import Dense
from keras.wrappers.scikit_learn import KerasClassifier
from keras.optimizers import SGD

# Function to create model, required for KerasClassifier
def create_model(learn_rate=0.01, momentum=0):
    # create model
    model = Sequential()
    model.add(Dense(12, input_dim=8, activation='relu'))
    model.add(Dense(1, activation='sigmoid'))
    # Compile model
    optimizer = SGD(lr=learn_rate, momentum=momentum)
    model.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])
    return model

# Early stopping
from keras.callbacks import EarlyStopping
stopper = EarlyStopping(monitor='val_acc', patience=3, verbose=1)

# fix random seed for reproducibility
seed = 7
numpy.random.seed(seed)
# load dataset
dataset = numpy.loadtxt("pima-indians-diabetes.csv", delimiter=",")
# split into input (X) and output (Y) variables
X = dataset[:,0:8]
Y = dataset[:,8]
# create model
model = KerasClassifier(
    build_fn=create_model,
    epochs=100, batch_size=10,
    validation_split=0.1, # FIXME: Instead use GridSearchCV k-fold validation data.
    verbose=2)
# define the grid search parameters
learn_rate = [0.01, 0.1]
momentum = [0.2, 0.4]
param_grid = dict(learn_rate=learn_rate, momentum=momentum)
grid = GridSearchCV(estimator=model, param_grid=param_grid, verbose=2, n_jobs=1)

# Fitting parameters
fit_params = dict(callbacks=[stopper])
# Grid search.
grid_result = grid.fit(X, Y, **fit_params)

# summarize results
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
    print("%f (%f) with: %r" % (mean, stdev, param))

[旧答案,在问题被编辑和澄清之前 - 请参阅上面更新和接受的答案]

我不确定我是否理解了您的确切问题(您的问题很不清楚,并且您包含了许多不相关的细节,这在提出 SO 问题时从来都不好 - 请参阅 here)。

不必 (实际上不应该)在 model = KerasClassifier() 函数调用中包含任何关于验证数据的参数(有趣的是为什么您不这样做感觉对 training 数据也有同样的需求)。您的 grid.fit() 将负责训练 验证折叠。因此,如果您想保留示例中包含的超参数值,则此函数调用应该很简单

model = KerasClassifier(build_fn=create_model, 
                        epochs=100, batch_size=32,
                        shuffle=True,
                        verbose=1)

您可以看到一些关于 GridSearchCV 与 Keras here.

一起使用的清晰且解释清楚的示例

[问题编辑澄清后回答:]

在匆忙解决实施问题之前,花一些时间思考方法和任务本身始终是一个好习惯;可以说,将提前停止与交叉验证程序混合在一起不是一个好主意。

让我们举个例子来突出论点。

假设您确实使用了 100 个 epoch 的提前停止,以及用于超参数选择的 5 折交叉验证 (CV)。还假设您最终得到的超参数集 X 给出了最佳性能,比如 89.3% 的二元分类准确率。

现在假设您的次优超参数集 Y 的准确率为 89.2%。仔细检查各个 CV 折叠,您会发现,对于最好的情况 X,5 个 CV 折叠中的 3 个耗尽了最多 100 个 epoch,而在另外 2 个提前停止中,分别在 95 和 93 个 epoch 中开始。

现在想象一下,检查你的第二好的集合 Y,你再次看到 5 个 CV 折叠中的 3 个耗尽了 100 个 epochs,而其他 2 个都在大约 80 个 epochs 时足够早地停止了。

你会从这样的实验中得出什么结论?

可以说,您会发现自己处于不确定的情况;进一步的实验可能会揭示哪个实际上是最好的超参数集,当然前提是您首先会考虑查看结果的这些细节。不用说,如果所有这一切都是通过回调自动完成的,那么尽管您实际上已经 尝试过 .

,但您可能已经错过了最佳模型

整个 CV 的想法隐含地基于 "all other being equal" 论点(当然这在实践中从来都不是真的,只是以最好的方式近似)。如果你觉得 epochs 的数量应该是一个超参数,只需将它显式地包含在你的 CV 中,而不是通过提前停止的后门插入它,从而可能危及整个过程(更不用说提前停止 本身有一个超参数, patience).

不混合使用这两种技术当然并不意味着你不能顺序使用它们:一旦你通过 CV 获得了你最好的超参数,你总是可以使用提前停止在你的整个训练集中拟合模型(当然前提是你有一个单独的验证集)。


深度神经网络领域还(非常)年轻,确实还没有建立 "best practice" 指导方针;再加上一个事实,多亏了一个了不起的社区,开源实现中有各种各样的工具可用,而且您很容易发现自己陷入了(诚然很诱人)混淆事物的境地,因为它们恰好可用。我不一定是说这就是您在这里尝试做的事情 - 我只是敦促在结合可能并非旨在协同工作的想法时更加谨慎...

以下是仅使用一次拆分的方法。

fit_params['cl__validation_data'] = (X_val, y_val)
X_final = np.concatenate((X_train, X_val))
y_final = np.concatenate((y_train, y_val))
splits = [(range(len(X_train)), range(len(X_train), len(X_final)))]

GridSearchCV(estimator=model, param_grid=param_grid, cv=splits)I

如果您想要更多拆分,可以使用 'cl__validation_split' 固定比例并构造满足该条件的拆分。

可能太偏执了,但我没有使用早停数据集作为验证数据集,因为它是间接用于创建模型的。

我还认为,如果您在最终模型中使用提前停止,那么在进行超参数搜索时也应该这样做。