使用Keras和sklearn GridSearchCV进行早停法和交叉验证

34

我希望使用Keras和sklearn的GridSearchCV实现早停功能。

下面的工作代码示例是修改自How to Grid Search Hyperparameters for Deep Learning Models in Python With Keras。可以从这里下载数据集。

添加了Keras EarlyStopping回调类以防止过拟合。为了使其有效,需要使用monitor='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))

https://github.com/cerlymarco/keras-hypetune - Marco Cerliani
3个回答

37
在着手实施问题之前,花些时间考虑方法论和任务本身总是一种好的做法。可以说,在交叉验证过程中混合早停是不明智的做法。
我们举一个例子来强调这个观点。假设你确实使用了100个epochs的早停和5倍交叉验证(CV)进行超参数选择。假设你最终得到了一个超参数集X,其性能最佳,例如89.3%的二元分类准确率。
现在假设你的第二个最佳超参数集Y给出了89.2%的准确率。仔细检查各个CV折叠后,你会发现对于最佳情况X,其中有3个CV折叠达到了最大的100个epochs,而在其他2个中早期停止生效,在95和93个epochs时分别停止。
现在想象一下,审查你的第二个最佳集Y时,你会发现同样有3个CV折叠达到了100个epochs,而其他2个都在约80个epochs时早期停止。
从这样的实验中,你会得出什么结论?
可以说,你会发现自己处于一个不确定的情况中;进一步的实验可能会揭示哪种超参数集才是真正的最佳选择,当然,前提是你已经考虑到了这些结果的细节。不用说,如果所有这些都是通过回调自动化完成的话,你可能会错过最佳模型,尽管你实际上已经尝试过它。
整个交叉验证的想法是基于“其他所有因素相等”的论点(当然,在实践中从未真正实现,只能以最好的方式近似)。如果你认为epochs数量应该是一个超参数,那么就明确地将其包含在你的CV中,而不是通过早期停止的后门插入,这可能会危及整个过程(更不用说早期停止本身有一个超参数,即“耐心”)。

当然不意味着您不能连续地使用这两种技术:一旦您通过CV获得了最佳超参数,您总是可以在整个训练集上拟合模型时采用早期停止(前提是您确实有一个单独的验证集)。


深度神经网络领域仍然非常年轻,它确实还没有建立起其“最佳实践”指南。由于社区提供了各种工具的开源实现,您可能很容易陷入混合使用这些工具的诱人位置。我并不是说这就是您在这里尝试做的 - 我只是敦促在组合那些可能没有被设计为配合使用的想法时更加谨慎...


11
晚上好 @desertnaut。 感谢您抽出星期天晚上的时间来回答我的问题。是的,我完全理解您的观点,确实是清晰、令人警醒的建议。您已经让我避免了被太多工具集所诱惑而走弯路。谢谢,Justin。 - Justin Solms
1
我不同意desertnaut的观点(但是由于声望不够无法评论)。使用early stopping,对于一组epoch计数,你无法确定哪个epoch对找到最佳超参数集有贡献,这是正确的。但这不是问题的起点。该方法所问的是“在最多n个epochs的情况下,并使用early stopping,什么是最佳超参数?”是的,early stopping会引入更多的超参数,你可能想要或不想要用grid search进行优化,但这对于模型中的任何超参数都是正确的。实际上,我认为在grid search期间使用early stopping可以加快训练速度并提高模型性能。 - user1721082

2

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

我不确定我是否理解了您确切的问题(您的问题非常不清楚,并且包含许多无关的细节,这在提问SO问题时从来不是好习惯 - 请参见此处)。

您不需要(实际上也不应该)在model = KerasClassifier()函数调用中包含任何有关验证数据的参数(有趣的是,您在这里为什么不感到同样需要训练数据)。您的grid.fit()将同时处理训练验证折叠。因此,只要您想保留示例中包含的超参数值,这个函数调用应该简单地是:

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

您可以在这里看到有关使用GridSearchCV和Keras的一些清晰且解释得很好的示例


感谢@desertnaut指出如何让我的问题更清晰。我完全重写了问题,使用了你指出的示例代码。 - Justin Solms

0

这是如何只用一个分割来完成的。

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'来设置一个固定比例,并构建符合该标准的数据集划分。

这可能有些过于谨慎,但我不会将早停止数据集用作验证数据集,因为它间接用于创建模型。

我认为,如果您在最终模型中使用了早停止技术,那么在进行超参数搜索时也应该使用它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接