scikit-learn中提供了多种用于交叉验证的数据集分割方法。这里对这些方法的区别和应用场景做一个梳理。
基本的分割方法
首先,最常用的交叉检验方法是KFold、StratifiedKFold。
sklearn.model_selection.KFold 方法就是最简单的KFold折叠,将指定数据分为 K等分。
sklearn.model_selection.StratifiedKFold 则根据数据 label 分层。保证分割 后的K等份数据集中,每个 label 类别的数据比重与原数据集中各类别的比重基本相同。这对于分类学习来说是很重要的,因为严重的数据不均衡会影响学习效果。
KFold 和 StratifiedKFold 的split 方法也因此有所不同。KFold的split方法只需要传入数据集X;而StratifiredKFold 的split方法除了传入数据集 X 外,还要传入标签数据 y,否则会提少缺少参数。
skfolds = StratifiedKFold(n_splits=5)
skfolds.split(X)
>>>TypeError: split() missing 1 required positional argument: 'y'
实际上根据sklearn的API文档,KFold的split方法也可以接受标签数据 y,只是不会使用。
另一个 KFold 方法的变种就是GroupKFold。和StratifiedKFold 相反,不将相同类别的数据均匀分割,而是将相同类别的数据放在同一个分割数据集中。所依据的不是 数据标签label,而是可以自定义的类别 group 参数。如下面 y 是原数据标签,而 groups 是另外定义的类别。
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]) # 类别
分割后的数据集在数据量上不一定是平均的,但各自含有的 groups 类别数量上是相对均衡的。
其split方法需要输入 groups参数指定类别。
group_kfold = GroupKFold(n_splits=5)
group_kfold.split(X, y, groups) # 和KFold一样,可省略y
GroupKFold方法的存在是因为很多时候数据采集的过程存在不一致性。比如不同的时间,不同的采集对象,这些采集过程中的隐含类别,可能带来潜在影响。所以将相同隐含类别的数据放在同一组,能够知道类别间是否存在差异。在一部分类别上训练的学习器会不会对这些类别过拟合,从而了解到这些隐含类别的潜在影响程度。
由此自然地想到,groups参数 往往可以是采集数据中的时间、实体标号等等非标签数据。
Leave-Out 方法
leave-out是一种常见的验证方法,从样本中抽取一个或指定个数P个作为测试集,其他作为训练集。
这在sklearn中分别对应 LeaveOneOut和LeavePOut方法。
结合上面提到的GroupKFold方法,sklearn还提供了LeaveOneGroupOut、LeavePGroupOut方法。两者以group类别为单位来抽取测试集,也就是抽取某group类别或某P个group类别作为测试集,其他group类别作为训练集。
Shuffle随机方法
sklearn.model_selection.ShuffleSplit是比较简单粗暴的分割方法。不考虑标签类别和group类别,只将数据按指定次数随机排序后,再按指定的比例分割为训练集和测试集。
# n_splits 指定随机洗牌和分割迭代的次数 ,也就是得到训练测试集的组数
# test_size、tran_size为0~1之间的数,用于指定数据集比例,默认test_size = 0.1。
rs = ShuffleSplit(n_splits=5, test_size=.25, random_state=0)
rs.split(X)
进一步还有 StratifiedShuffleSplit 方法和 GroupShuffleSplit 方法。分别保证训练测试集中各label类别数据量均衡,以及同一group类别的数据只存在于训练集和测试集两者之一。
shuffle方法的意义一定程度上体现在,当Leave-Out方法用于大规模数据时,要得到所有可能的分组可能代价过大,这时使用shuffle方法指定次数,得到有限数目的训练测试集(可重复)。
Repeated重复方法
sklearn.model_selection.RepeatedKFold 方法是多次重复KFold的方法。
如下面只用KFold方法会执行一个K层叠。而使用RepeatedKFold方法,指定重复次数n_repeats=2,则执行两次KFold,且可以在一个for循环中取出分割数据。
X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
y = np.array([0, 0, 1, 1])
kf = KFold(n_splits=2)
for train_index, test_index in kf.split(X):
print("TRAIN:", train_index, "TEST:", test_index)
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
>>>
TRAIN: [2 3] TEST: [0 1]
TRAIN: [0 1] TEST: [2 3]
rkf = RepeatedKFold(n_splits=2, n_repeats=2, random_state=2652124)
for train_index, test_index in rkf.split(X):
print("TRAIN:", train_index, "TEST:", test_index)
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
>>>
TRAIN: [0 1] TEST: [2 3]
TRAIN: [2 3] TEST: [0 1]
TRAIN: [1 2] TEST: [0 3]
TRAIN: [0 3] TEST: [1 2]
类似的还有RepeatedStratifiedKFold方法。
Predefined预定义方法
sklearn.model_selection.PredefinedSplit 方法用来自定义数据分割。在定义时通过test_fold参数接受一个与样本长度一样的数据,使用0或1指定每个样本属于训练集或测试集。
该方法返回两组训练测试集,0和1的样本分别作为训练集和测试集。样本还可以被指定为-1,表示两次都属于训练集。
X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
y = np.array([0, 0, 1, 1])
test_fold = [0, 1, -1, 1]
ps = PredefinedSplit(test_fold)
for train_index, test_index in ps.split():
print("TRAIN:", train_index, "TEST:", test_index)
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
>>>
TRAIN: [1 2 3] TEST: [0]
TRAIN: [0 2] TEST: [1 3]
时间序列数据分割
时间序列的数据由于其时间上的关联性,不能进行重排序。sklearn.model_selection.TimeSeriesSplit 方法也是KFold方法的一个变种,在第k次分割时,只返回前 k 份数据作为训练集,第k+1份数据作为测试集。
X = np.array([[1, 2], [3, 4], [1, 2], [3, 4], [1, 2], [3, 4]])
y = np.array([1, 2, 3, 4, 5, 6])
tscv = TimeSeriesSplit(n_splits=3) # 可通过参数 max_train_size 指定训练集大小的上限
for train_index, test_index in tscv.split(X):
print("TRAIN:", train_index, "TEST:", test_index)
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
>>>
TRAIN: [0 1 2] TEST: [3]
TRAIN: [0 1 2 3] TEST: [4]
TRAIN: [0 1 2 3 4] TEST: [5]