我正在为一个多类问题构建一个Keras简单分类器模型,其中类不是排他的。此外,我想添加sklearn穷尽参数搜索和交叉验证。这是一个平凡的模型:
def build_model(embedding_size):
model = Sequential()
model.add(Embedding(10000, embedding_size, input_length=100))
model.add(Flatten())
model.add(Dense(100, activation="relu"))
model.add(Dense(10, activation="sigmoid"))
model.compile(loss="binary_crossentropy", optimizer=Adam(), metrics=["accuracy"])
return model
batch_size = 64
epochs = 2
这是我第一个使用手动交叉验证(不使用参数搜索)的解决方案。
embedding_size=32
skf = KFold(n_splits=3, shuffle=True)
valid_evals = []
for train_indices in skf.split(train_full_x):
kfold_train_x = train_full_x[train_indices[0]]
kfold_valid_x = train_full_x[train_indices[1]]
kfold_train_y = train_full_y[train_indices[0]]
kfold_valid_y = train_full_y[train_indices[1]]
model = build_model(embedding_size)
model.fit(kfold_train_x, kfold_train_y, batch_size=batch_size, epochs=epochs)
valid_evals.append(model.evaluate(kfold_valid_x, kfold_valid_y))
valid_evals = np.array(valid_evals)
print(valid_evals)
print("LogLoss: %.4f +/- %.4f" % (valid_evals.mean(axis=0)[0], valid_evals.std(axis=0)[0]))
print("Accuracy: %.2f%% +/- %.2f%%" % (valid_evals.mean(axis=0)[1] * 100, valid_evals.std(axis=0)[1] * 100))
结果非常一致:
[[0.05730336 0.98051361]
[0.0606665 0.98065738]
[0.05717109 0.9801999 ]]
LogLoss: 0.0584 +/- 0.0016
Accuracy: 98.05% +/- 0.02%
此代码运行良好,返回约98%的验证精度和约
0.06
的日志丢失。然后,我尝试添加GridSearchCV
而不是滚动我自己的参数搜索解决方案(在下面的示例中,搜索空间被简化为仅一个选项)。embedding_size = [32]
classifier = KerasClassifier(build_fn=build_model, epochs=epochs, batch_size=batch_size)
kfold = KFold(n_splits=3, shuffle=True)
param_grid = dict(embedding_size=embedding_size)
grid = GridSearchCV(estimator=classifier, param_grid=param_grid, return_train_score=True, cv=kfold)
results = grid.fit(train_x, train_y)
print("Best: %f using %s" % (results.best_score_, results.best_params_))
print(results.cv_results_)
这又回来了
Best: 0.980600 using {'embedding_size': 32}
0.980600 (0.000187) with: {'embedding_size': 32}
{'split2_train_score': array([0.9882294]), 'mean_train_score': array([0.98768258]), 'split0_train_score': array([0.98707933]), 'split1_train_score': array([0.98773903]), 'std_train_score': array([0.00047121]), 'mean_fit_time': array([8.7320834]), 'split1_test_score': array([0.98079212]), 'split0_test_score': array([0.98066088]), 'std_fit_time': array([0.19671295]), 'rank_test_score': array([1], dtype=int32), 'std_test_score': array([0.00018667]), 'param_embedding_size': masked_array(data=[32], mask=[False], fill_value='?', dtype=object), 'std_score_time': array([0.00984431]), 'split2_test_score': array([0.98034717]), 'mean_score_time': array([0.58589784]), 'params': [{'embedding_size': 32}], 'mean_test_score': array([0.98060006])}
在这里,我得到了一个与Keras兼容的精度,可以手动分割训练/验证。但是,在我的问题中,相关的评分函数是log loss,所以我尝试添加一个
scoring="neg_log_loss"
作为GridSearchCV
的参数。但在这种情况下,我得到的分数就像-0.29
。Best: -0.292518 using {'embedding_size': 32}
-0.292518 (0.002988) with: {'embedding_size': 32}
{'split1_train_score': array([-0.27363595]), 'std_score_time': array([0.01245312]), 'rank_test_score': array([1], dtype=int32), 'mean_test_score': array([-0.29251778]), 'std_fit_time': array([0.17412529]), 'split0_train_score': array([-0.27816725]), 'split1_test_score': array([-0.28917072]), 'mean_score_time': array([0.40924891]), 'params': [{'embedding_size': 32}], 'split0_test_score': array([-0.29195843]), 'split2_test_score': array([-0.2964242]), 'split2_train_score': array([-0.26628012]), 'mean_fit_time': array([8.75646718]), 'mean_train_score': array([-0.27269444]), 'std_test_score': array([0.00298751]), 'std_train_score': array([0.00489836]), 'param_embedding_size': masked_array(data=[32], mask=[False], fill_value='?', dtype=object)}
我希望在符号上有所不同(因为
neg_log_loss
是如何工作的),但在值上没有。我做错什么了?或者。。。是否有其他方法可以让
GridSearchCV
处理交叉验证和日志丢失?谢谢你
注意,我没有设置一个随机种子,而是运行两个例子很多次,这样随机性不应该是显示差异的原因。
最佳答案
我认为你的问题是你漏掉了预测步骤。Logloss只需要y_test和y_predict,为了得到y_predict,需要调用predict方法。所以,当我使用GridSearchCV和knn分类器时,这里是对我有用的。GridSearchCV被实例化为cv。
cv.fit(X_train, y_train)
y_predict = cv.predict(X_test) # grid.predict(X_test) in your case
from sklearn.metrics import log_loss
print('Log_loss: {}'.format(log_loss(y_test, y_predict)))
增加:
我一直在玩一个相当不准确的knn/GridSearchCV分类器,并意识到了一些可能相关的事情:
如果我在没有和有评分的情况下运行cv,并按照我上面的建议独立计算logu损失,那么后者的结果是不同的,分别为6.536和6.716。因此,当优化基于优化对数损耗而不是优化精度时,该值更高。显然,每个优化条件的标签向量略有不同。这可能并不令人惊讶,正如你所注意到的,两者之间的差别并没有那么大。
由于您使用的是return_train_score=True,并且所有cv步骤的结果都存储在.cv_results中,因此可以很容易地可视化训练和测试分数。显然,评分用平均值测试和平均值训练分数列中的对数损失代替了准确性,如下所示。