StratifiedGroupKFold 产生空测试折,先数独立分组而非样本
StratifiedGroupKFold 在独立组数少于折数时可能生成空折,应核对真实分组、调整评估设计并验证组隔离。
分组交叉验证跑到某一折,模型突然报训练集或测试集为空。数据有很多行也不能排除配置问题:如果独立 groups 只有两个,却要求三折,分组约束下就不可能给每个测试折分到不同的组。
scikit-learn 1.8.0 的 StratifiedGroupKFold 在这个不可行配置上可能直接生成空折,失败于是推迟到模型拟合或评分阶段。1.9.0 已增加检查,n_splits 大于独立组数时明确抛出 ValueError。升级后提早报错,是在指出输入约束,不是升级又破坏了一份合法数据。
在训练前检查独立组数
下面保留一个具体触发形状:12 个样本,实际只有两组。
import numpy as np
from sklearn.model_selection import StratifiedGroupKFold
y = np.array([0, 2, 2, 2, 1, 2, 1, 1, 0, 0, 0, 0])
groups = np.array([0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1])
X = np.ones((len(y), 1))
n_splits = 3
n_groups = len(np.unique(groups))
print("samples:", len(y), "groups:", n_groups)
if n_splits > n_groups:
raise ValueError("独立分组不足,不能完成所要求的分组交叉验证")
分组键应表达真实的独立单位,例如受试者、设备或会话。不能把同一人的不同记录改成独立组来凑够折数,那会使同源样本进入训练与测试,改变评估含义。
对于这份数据,可以减少为两折,再核对每一折的组隔离和样本数量:
cv = StratifiedGroupKFold(n_splits=2, shuffle=False)
for train, test in cv.split(X, y, groups):
assert len(train) > 0 and len(test) > 0
assert set(groups[train]).isdisjoint(set(groups[test]))
print(len(train), len(test), np.unique(y[train]), np.unique(y[test]))
如果项目需要的独立组确实不足,应补充数据或重新设计评估。LeaveOneGroupOut 可作为逐组留出的另一种明确方案,但它的折数与评估方式不同,不能悄悄替换后仍报告原来的固定 k 折成绩。
空折检查仍需保留
n_groups >= n_splits 只是必要条件,不是每份数据都能实现理想类别平衡的保证。每类分布、组大小和分配算法都会影响结果;少数组可能只包含单一类别,测试折中缺类也会影响某些评分指标。
因此,更新到包含检查的版本后,仍应在调用训练器之前遍历实际 splits,检查非空、组隔离及评分所需类别。不要捕获空折异常后跳过这一折再对剩余分数取平均,那会把评估设计的失败隐藏成一个看似完整的 k 折结果。
如果独立组数已经足够却仍出现空折,继续保存具体 y、groups 和分配结果进行排查。这项修复针对组数少于折数的入口检查,不承诺解决组索引映射、浮点平分决策或所有分层质量问题。


