3、分层K折交叉验证:处理类别不平衡数据
先问个问题:你手上有个二分类数据集,正样本只占5%。直接拿普通K折去划分,某折里可能一个正样本都没有——那这一折的评估结果直接崩掉。
我以前就踩过这个坑。有个风控项目,坏样本不到3%,我图省事直接用了KFold,结果某一折的recall直接是0。后来查原因才发现那折压根没分到坏样本,模型只能猜全好样本。折腾了大半天,最后改用StratifiedKFold,三分钟解决问题。
说白了,分层K折就是为了让每一折都保持和原始数据集一样的类别比例。它不解决模型内部的类别不平衡,它解决的是验证阶段的分布失真问题。
为什么普通K折不够用?
| 普通K折 (KFold) | 分层K折 (StratifiedKFold) |
|---|---|
| 随机打乱后平均切分 | 按类别比例分层抽样后切分 |
| 可能某折全是一类样本 | 每折各类比例与原始数据集一致 |
| 评估方差大,不稳定 | 评估结果更稳定、可信 |
| 对不平衡数据直接崩 | 对不平衡数据友好 |
你想想看,如果测试集中小类样本极少甚至没有,你算出来的准确率再高,上线后也可能一脸懵。所以分层K折的核心价值,就是让每一折的分布“代表”原始分布。
StratifiedKFold 怎么用?
用法其实和KFold很像,只是多了一个shuffle参数。我习惯把shuffle=True加上,同时设个固定random_state保证可复现。
from sklearn.model_selection import StratifiedKFold
import numpy as np
X = np.random.randn(100, 5)
y = np.array([0]*95 + [1]*5) # 95%负样本, 5%正样本
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
for fold, (train_idx, val_idx) in enumerate(skf.split(X, y)):
y_train, y_val = y[train_idx], y[val_idx]
print(f"Fold {fold+1}: 训练集正样本比例={y_train.mean():.3f}, "
f"验证集正样本比例={y_val.mean():.3f}")
运行结果大概是这样的:
Fold 1: 训练集正样本比例=0.050, 验证集正样本比例=0.050
Fold 2: 训练集正样本比例=0.050, 验证集正样本比例=0.050
Fold 3: 训练集正样本比例=0.050, 验证集正样本比例=0.050
Fold 4: 训练集正样本比例=0.050, 验证集正样本比例=0.050
Fold 5: 训练集正样本比例=0.050, 验证集正样本比例=0.050
注意看——每折都是5%正样本。换成普通K折,可能某折正样本比例是0%或20%,飘得厉害。
multilabel 和 multiclass 也能用
StratifiedKFold 不光能处理二分类,对多分类和 multilabel 也同样适用。多分类时按每个类别的比例分层;multilabel 时用的是MultilabelStratifiedKFold(来自iterative-stratification库)。
# 多分类示例
from sklearn.model_selection import StratifiedKFold
y_multiclass = np.array([0, 1, 2, 0, 1, 2, 0, 1, 2, 0])
skf = StratifiedKFold(n_splits=3, shuffle=True, random_state=0)
for train_idx, val_idx in skf.split(X, y_multiclass):
print(np.bincount(y_multiclass[val_idx]))
输出中每个验证集的类别计数比例会尽量与原数据集一致。
什么时候必须用分层?
- 二分类不平衡(正样本 < 20% 或 > 80%)。
- 多分类类别数多且分布偏(比如医学病变分级,常见级别占90%以上)。
- 小样本数据集(几百条以下,普通K折很容易把稀有类别全切到一折里)。
- 时间序列?不能直接分层,得用
TimeSeriesSplit;但其他场景我都建议先考虑分层。
避坑指南:分层K折不是万能的
另一个坑:分层K折适用于分类问题,回归问题不能用。为什么呢?回归的y是连续值,没法分层。如果非要分层,可以先把y分桶,变成有序分类,再跑分层K折——嗯,这种操作叫“分层回归”,但实际应用中没那么常见。
分层K折 vs 普通K折:一个直观对比
下面这张图能帮你秒懂:
总结一下优势
- 保比例:每一折的类别比例和原始数据集一致,评估结果不偏。
- 方差低:多次重复实验的评估指标波动小,模型对比更可靠。
- 对稀有类别友好:不至于因为采样随机让稀有类别在验证集里消失。
- 代码简单:就一个
StratifiedKFold,参数和KFold几乎一样。
小技巧:如果你在GridSearchCV或RandomizedSearchCV中做超参搜索,直接把cv参数设成StratifiedKFold(n_splits=5)就行,比默认的KFold更稳。
我个人习惯是:只要做分类问题的交叉验证,一律用分层K折。除非样本量极大且每类样本都很充足,否则分层带来的稳定性远远超过那一点点额外计算开销。