使用 sklearn 在嵌套交叉验证中使用 GroupKFold
Posted
技术标签:
【中文标题】使用 sklearn 在嵌套交叉验证中使用 GroupKFold【英文标题】:Use GroupKFold in nested cross-validation using sklearn 【发布时间】:2020-07-14 17:51:22 【问题描述】:我的代码是基于sklearn网站上的例子:https://scikit-learn.org/stable/auto_examples/model_selection/plot_nested_cross_validation_iris.html
我正在尝试在内部和外部 cv 中使用 GroupKFold。
from sklearn.datasets import load_iris
from matplotlib import pyplot as plt
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV, cross_val_score, KFold,GroupKFold
import numpy as np
# Load the dataset
iris = load_iris()
X_iris = iris.data
y_iris = iris.target
# Set up possible values of parameters to optimize over
p_grid = "C": [1, 10, 100],
"gamma": [.01, .1]
# We will use a Support Vector Classifier with "rbf" kernel
svm = SVC(kernel="rbf")
# Choose cross-validation techniques for the inner and outer loops,
# independently of the dataset.
# E.g "GroupKFold", "LeaveOneOut", "LeaveOneGroupOut", etc.
inner_cv = GroupKFold(n_splits=3)
outer_cv = GroupKFold(n_splits=3)
# Non_nested parameter search and scoring
clf = GridSearchCV(estimator=svm, param_grid=p_grid, cv=inner_cv)
# Nested CV with parameter optimization
nested_score = cross_val_score(clf, X=X_iris, y=y_iris, cv=outer_cv, groups=y_iris)
我知道将 y 值放入 groups 参数不是它的用途! 对于此代码,我收到以下错误。
.../anaconda3/lib/python3.7/site-packages/sklearn/model_selection/_validation.py:536: FitFailedWarning: Estimator fit failed. The score on this train-test partition for these parameters will be set to nan. Details:
ValueError: The 'groups' parameter should not be None.
有人知道如何解决这个问题吗?
提前感谢您的帮助,
索伦
【问题讨论】:
【参考方案1】:我遇到了一个类似的问题,我发现@Samalama 的解决方案很好。
我唯一需要更改的是fit
电话。我也不得不对groups
进行切片,与火车组的X
和y
的形状相同。
否则,我会收到一条错误消息,指出三个对象的形状不相同。这是一个正确的实现吗?
for train_index, test_index in outer_cv.split(x, y, groups=groups):
x_train, x_test = x[train_index], x[test_index]
y_train, y_test = y[train_index], y[test_index]
grid = RandomizedSearchCV(estimator=model,
param_distributions=parameters_grid,
cv=inner_cv,
scoring=get_scoring(),
refit='roc_auc_scorer',
return_train_score=True,
verbose=1,
n_jobs=jobs)
grid.fit(x_train, y_train, groups=groups[train_index])
prediction = grid.predict(x_test)
【讨论】:
【参考方案2】:我自己一直在尝试使用 GroupKFold 实现嵌套 CV,还尝试按照您所引用的 sklearn 提供的示例进行操作,并最终遇到与您相同的错误,找到了这个线程。
我认为 ywbaek 的回答没有正确解决问题。
经过一番搜索,我发现在 sklearn Github 上提出了一些问题,与此特定问题或似乎是同一问题的其他形式有关。我认为这与未将 groups 参数传播到所有方法有关(我试图追踪它在脚本中失败的位置,但很快就迷路了)。
这里的问题:
https://github.com/scikit-learn/scikit-learn/issues/7646 https://github.com/scikit-learn/scikit-learn/issues/11429 https://github.com/scikit-learn/scikit-learn/issues/12052如您所见,这些可以追溯到一段时间(到 2016 年 10 月)。我对开发知之甚少,但显然解决这个问题并不是优先考虑的事情。我想这很好,但是嵌套 CV 的示例特别建议使用 GroupKFold 提供的方法,这是不可能的,因此应该更新。
如果你仍然想用 GroupKFold 做一个嵌套的简历,当然还有其他方法可以做到。 逻辑回归示例:
from sklearn.model_selection import GridSearchCV, GroupKFold
pred_y = []
true_y = []
model = sklearn.linear_model.LogisticRegression()
Cs=[1,10,100]
p_grid='C': Cs
inner_CV = GroupKFold(n_splits = 4)
outer_CV = GroupKFold(n_splits = 4)
for train_index, test_index in outer_CV.split(X, y, groups=group):
X_tr, X_tt = X[train_index,:], X[test_index,:]
y_tr, y_tt = Y[train_index], Y[test_index]
clf = GridSearchCV(estimator=model, param_grid=p_grid, cv=inner_CV)
clf.fit(X_tr,y_tr,groups=group)
pred = clf.predict(X_tt)
pred_y.extend(pred)
true_y.extend(y_tt)
然后,您可以根据自己的喜好根据事实评估预测。当然,如果你仍然对比较嵌套和非嵌套分数感兴趣,你也可以收集我在这里没有做过的非嵌套分数。
【讨论】:
【参考方案3】:对于现在回到这里并像我一样有兴趣将 GroupKFold 交叉验证传递给 cross_val_score() 的任何人...
cross_val_score() 分别接受 cv = GroupKFold() 和 groups 参数。
这对我想要达到的目标起到了作用。
例如:
cv_outer = GroupKFold(n_splits=n_unique_groups)
groups = X['your_group_name'] # or pass your group another way
.... ML Code ...
scores = cross_val_score(search, X, y, scoring='f1', cv=cv_outer, groups = groups)
【讨论】:
【参考方案4】:好的,所以嵌套 CV 的一个简单解决方案是使用 cross_validate
的 fit_params
函数:
nested_score = cross_val_score(clf, X=X_iris, y=y_iris, cv=outer_cv, groups=y_iris, fit_params="groups": y_iris)
这会将组下推到GridSearchCV
。但是,由于您的方法存在一些概念性问题,您正在做的事情仍然会引发一堆异常(这在某种程度上扩展和补充了 @ywbaek 's answer)。让我们检查一下:
因此,当您执行GroupKFold
时,它将确保一组中的所有样本都在训练或测试中。您将这些组设置为 iris 数据集中的三个目标类 ([0,1,2])。
这意味着outer_cv
(与n_splits=3
)将创建一个折叠,其中两个类在训练中,其余类在测试中。
for train_idx, test_idx in outer_cv.split(X_iris, y_iris, groups=y_iris):
print(np.unique(y_iris[test_idx]))
这实际上没有任何意义,因为模型不会了解有关测试数据的任何信息。但是让我们继续一下:
在inner_cv
中,我们将只有两个类,这将始终打破GroupKFold(n_splits=3)
,因为我们将只有两个可能的组。
所以让我们暂时将inner_cv
设置为GroupKFold(n_splits=2)
。这解决了之前的问题。但是我们将只有一堂训练课和一堂测试课。在这种情况下,分类器会抱怨训练数据中只有一个类,它什么都学不到。
因此,总的来说,虽然上面基于 fit_params
参数的解决方案允许您进行嵌套交叉验证,但它并不能解决您在方法中遇到的概念问题。我希望我的解释有助于更清楚地说明这一点。
【讨论】:
【参考方案5】:正如您从documentation 中看到的GroupKFold
,
当您想要K-fold 的非重叠组时,您可以使用它。
这意味着除非在创建 K-fold 时需要分离不同的数据组,否则不要使用此方法。
话虽如此,对于给定的示例,您必须手动创建groups
,
它应该是一个类似数组的对象,其形状与您的 y
相同。
和
不同组的数量必须至少等于 折叠次数
以下是文档中的示例代码:
import numpy as np
from sklearn.model_selection import GroupKFold
X = np.array([[1, 2], [3, 4], [5, 6], [7, 8]])
y = np.array([1, 2, 3, 4])
groups = np.array([0, 0, 2, 2])
group_kfold = GroupKFold(n_splits=2)
group_kfold.get_n_splits(X, y, groups)
可以看到groups
和y
的形状一样,
并且它有两个不同的组0, 2
,这与折叠的数量相同。
已编辑:GroupKFold
对象的get_n_splits(groups)
方法返回交叉验证器中的拆分迭代次数,我们可以将其传递为cv
关键字到 cross_val_score
函数的参数。
clf = GridSearchCV(estimator=svm,
param_grid=p_grid,
cv=inner_cv.get_n_splits(groups=y_iris))
nested_score = cross_val_score(clf, X=X_iris, y=y_iris,
cv=outer_cv.get_n_splits(groups=y_iris))
【讨论】:
谢谢,这就是为什么我使用 y 作为示例。您的示例使其更加清晰,但没有解决嵌套交叉验证中的异常。 我看不出这里编辑的解决方案是如何工作的。cv=outer_cv.get_n_splits(groups=y_iris)
意味着只会将一个 int 传递给 cv
,因此您最终会得到常规的 cv 拆分,而不是分组。【参考方案6】:
对于该聚会的后来者,此技术不需要更改 sklearn 对象(例如 LogisticRegressionCV、GridSearchCV),并且可以在多个 sklearn 设置中透明地工作。
我模拟了一个包含组的 cv 对象:
class PseudoGroupCV:
def __init__(self, cv_obj, groups):
self.cv = cv_obj
self.groups=groups
def split(self, X,y, groups=None):
return self.cv.split(X,y, groups=self.groups)
def get_n_splits(self, X, y, groups):
return self.cv.get_n_splits(X,y, groups)
然后您可以将其传递给例如像这样的GridSearchCV:
kfold = GroupKFold(n_splits=5) # desired CV object
clf = GridSearchCV(estimator=svm,
param_grid=p_grid,
cv=PseudoGroupKFold(kfold, groups)
)
这应该可以正常工作。它也适用于 Pipeline 对象。
唯一的缺点是您需要在类声明时提供组(即groups
需要匹配用于拟合的(X, y)
)。
【讨论】:
【参考方案7】:@Martin Becker 的回答是正确的。 GridSearchCV
与 GroupKFold
一起使用时,不仅期望得到 X
和 y
,而且在 fit
方法中得到 groups
。要传递该参数,您需要使用cross_val_score
函数的fit_params
参数。
这是一个例子。为了简单起见,我将GroupKFold
替换为LeaveOneGroupOut
。
import numpy as np
from sklearn.base import BaseEstimator
from sklearn.model_selection import \
LeaveOneGroupOut, cross_val_score, GridSearchCV
# Create 12 samples and 4 groups [0, 1, 2] [3, 4, 5], ...
X = np.arange(12)
y = np.random.randint(0, 1, len(X))
groups = X // 3
class DummyEstimator(BaseEstimator):
"""Estimator that just prints given folds."""
def fit(self, X, y=None):
print('Trained on', np.unique(X // 3))
return [0]*len(X)
def score(self, X, y):
print('Tested on', np.unique(X // 3))
return 0
logo = LeaveOneGroupOut()
clf = GridSearchCV(DummyEstimator(), param_grid=, cv=logo)
cross_val_score(
clf, X, y,
cv=logo, groups=groups, fit_params='groups': groups,
n_jobs=None)
代码生成以下训练/验证/测试组:
Trained on [2 3] <-- First inner loop (Test fold=0, Train=1, 2, 3)
Tested on [1]
Trained on [1 3]
Tested on [2]
Trained on [1 2]
Tested on [3]
Trained on [1 2 3] <-- fit best params on the whole training data
Tested on [0] <-- Score on the test fold 0
Trained on [2 3] <-- Second inner loop (Test fold=1, Train=0 2 3)
Tested on [0]
Trained on [0 3]
Tested on [2]
Trained on [0 2]
Tested on [3]
Trained on [0 2 3] <-- fit best params on the whole training data
Tested on [1] <-- Score on the test fold 1
... and so one
【讨论】:
以上是关于使用 sklearn 在嵌套交叉验证中使用 GroupKFold的主要内容,如果未能解决你的问题,请参考以下文章
SKlearn中具有嵌套交叉验证的分类报告(平均值/个体值)
如何在 sklearn 中编写自定义估算器并对其使用交叉验证?