早期停止与Keras和sklearn GridSearchCV交叉验证

时间:2018-01-06 12:54:02

标签: machine-learning scikit-learn keras cross-validation grid-search

我希望早日停止使用Keras和sklean的GridSearchCV

下面的工作代码示例是从How to Grid Search Hyperparameters for Deep Learning Models in Python With Keras修改的。数据集可能是downloaded from here

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

4 个答案:

答案 0 :(得分:15)

[问题编辑后的答案&澄清:]

在急于实施问题之前,花一些时间思考方法和任务本身总是一个好习惯;可以说,早期停止与交叉验证程序的混合是一个好主意。

让我们举一个例子来强调论证。

假设您确实使用100个时期的早期停止,以及超参数选择的5倍交叉验证(CV)。假设你最终得到了一个超参数集X,它可以提供最佳性能,比如89.3%的二进制分类精度。

现在假设你的第二个最佳超参数集Y的准确率为89.2%。仔细检查单个CV折叠,你会看到,对于你最好的情况X,5个CV折叠中的3个耗尽了最多100个时期,而在另外2个早期停止中,分别在89和93个时期。

现在想象一下,检查你的第二个最佳集Y,你会发现5个CV折叠中有4个用尽了100个时代,而第5个时期早于80个时期停止了。

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

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

整个简历的想法隐含地基于"所有其他相同的"论证(当然在实践中从来都不是真的,只能以最好的方式近似)。如果你觉得时代的数量应该是一个超参数,那么只需将它明确地包含在你的简历中,而不是通过早期停止的后门插入,这可能会影响整个过程(更不用说提前停止本身就是一个超参数patience)。

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

深度神经网络领域仍然(非常)年轻,而且它确实尚未建立其最佳实践"准则;事实上,由于一个令人惊叹的社区,开源实现中提供了各种各样的工具,你可以很容易地发现自己陷入了混乱的困境(因为它们碰巧可用)。我并不一定说这就是你在这里尝试做的事情 - 我只是在提出一些可能没有被设计为一起工作的想法时要求更加谨慎......

答案 1 :(得分:1)

[旧答案,问题编辑之前&澄清 - 见更新&接受上面的回答]

我不确定我是否理解了你的确切问题(你的问题很不清楚,而且你提到了很多不相关的细节,在提出问题时这种情况永远不会好 - 请参阅here)。

您不必(实际上不应该)在model = KerasClassifier()函数调用中包含有关验证数据的任何参数(有趣的是,您为什么不这样做?在这里也感受到对培训数据的需求。您的grid.fit()将负责培训验证折叠。因此,如果您希望保留示例中包含的超参数值,则此函数调用应该只是

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

您可以看到一些关于GridSearchCV与Keras here一起使用的明确且解释清楚的示例。

答案 2 :(得分:1)

我不同意Desertnaut(但缺乏发表评论的声誉)。尽早停止,对于一组历元计数,您确实无法分辨出其中哪个对找到的最佳超参数集有所贡献。但这不是开始的问题。方法要问的是“最多给出n个纪元并使用尽早停止,最佳的超参数是什么?”。是的,提早停止将引入更多的超参数,您可能会或可能不想通过网格搜索对其进行优化,但这对于模型中的任何超参数都是如此。实际上,我认为在网格搜索过程中尽早停止比在网格搜索后首先停止更有意义,因为您可以(至少是温和地)对其引入的超参数有所了解。

答案 3 :(得分: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'并构建符合该条件的分割。

它可能过于偏执,但我不会将早期停止数据集用作验证数据集,因为它间接用于创建模型。

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