想象你开了一家糖果店,雇了5位评委来品鉴你的新品。你从一大袋混合糖果里随手抓出几份请他们品尝,其中榴莲糖只占了10%。一位评委的袋子里恰好一颗榴莲糖都没有,他给出的评语是“这家店不擅长做榴莲味”。可你的榴莲糖明明很拿手——问题出在分袋方法上。机器学习中的交叉验证也会遇到同样的困境:当少数类样本稀少时,普通K折划分可能让某一折完全没有少数类,模型在该折上的评估结果就会“冤枉人”。分层K折(Stratified K-Fold)就像先把糖果按口味分类,再按比例分到每个袋子,保证每位评委都能尝到所有口味。本文将从零讲透这个“正确分袋姿势”,帮你拿到可信的模型评估结果。

岗位关联

掌握分层K折,你就能在类别不平衡的真实数据上获得可信评估,避免被漂亮但虚假的指标蒙蔽,这是机器学习工程师和数据科学家必备的模型评估基本功。

目录
  1. 可视化演示
  2. 交叉验证基础
  3. 类别不平衡困境
  4. 分层原理
  5. 分层算法步骤
  6. 分层对比
  7. 适用场景
  8. 注意事项
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示模型训练过程中损失和准确率的变化曲线,支持切换损失视图与准确率视图,调节学习率观察收敛效果。

本节配有交互动效(进入视口后自动加载,约 3KB)。

训练损失曲线 训练损失随迭代逐渐下降 验证损失用于检测过拟合 L = -1/N Σ yᵢlog(ŷᵢ) 观察两条曲线的差距判断模型状态

交叉验证基础

糖果店开业前,你决定把样品分成5袋,让每位评委只尝其中一袋,同时用另外4袋来辅助理解整体口味。这样每位评委都能独立给出评价,你也能从5份评价中拼出完整结论。机器学习里的交叉验证就是这个思路:把数据集分成K份,轮流取其中一份作为验证集,其余K-1份作为训练集。

这种“轮流当评委”的方法叫K折交叉验证。比如把100颗糖果分成5份,第一次用第1份当验证集,其他4份训练模型;第二次用第2份当验证集……重复5次,最终得到5个评估结果。比起只分一次训练集和测试集的“留出法”,K折能更充分地利用数据,尤其适合数据量有限的场景。

最终评估指标是这K次验证结果的平均值。设第 \( i \) 次验证的损失为 \( L_i \),则整体评估误差为:

K折交叉验证就像请5位评委分别打分再取平均,比只请1位评委(单次留出法)更可靠。但注意,这里的“分袋”方式至关重要——如果某位评委的袋子里少了某种口味,他的评分就会误导整个结论。这正是我们接下来要解决的隐患。

E=1K∑i=1KLi\displaystyle E = \frac{1}{K} \sum_{i=1}^{K} L_i
  1. 认识每个符号\( E \) 表示平均评估误差,\( K \) 是划分的份数,\( L_i \) 是第 \( i \) 折验证集上计算出的损失值。
    E=1K∑i=1KLi\displaystyle E = \frac{1}{K} \sum_{i=1}^{K} L_i
  2. 理解求和\( \sum_{i=1}^{K} L_i \) 表示把从第1轮到第K轮的所有损失加在一起。例如K=5时,就是 \( L_1 + L_2 + \cdots + L_5 \)。
    ∑i=1KLi=L1+L2+⋯+LK\displaystyle \sum_{i=1}^{K} L_i = L_1 + L_2 + \cdots + L_K
  3. 取平均将总和除以 \( K \),得到每轮的平均损失,这样评估结果不受折数多少的影响,便于对比。
    E=L1+L2+⋯+LKK\displaystyle E = \frac{L_1 + L_2 + \cdots + L_K}{K}

类别不平衡困境

回到糖果店的例子:你新研发的榴莲糖非常精致,但产量很少,只占整批糖果的5%。现在按老办法随手把糖果分成5袋,榴莲糖只有十几颗,完全可能全部落进同一袋。于是另4位评委尝不到榴莲味,纷纷给出差评。你冤枉不冤枉?

机器学习中这叫“类别不平衡问题”。假设一个二分类任务有1000条样本,其中正样本仅100条,负样本900条。如果用普通K折随机划分,出现某一折中正样本为0的概率并不低。当验证集里没有正样本时,模型在这折上只能预测负样本,计算出的准确率看上去很高,但召回率、F1等关键指标却无法真实反映模型对少数类的识别能力。

用比例表示少数类的稀缺程度:

当 \( p \) 很小,比如小于20%时,普通K折的“随机抓糖”式划分会制造出缺失少数类的验证折。这样的评估,就像让没尝过榴莲糖的评委写品鉴报告——结论自然不可信。

p=NminorNtotal\displaystyle p = \frac{N_{\text{minor}}}{N_{\text{total}}}
  1. 读分子\( N_{\text{minor}} \) 是少数类样本的数量,比如100颗糖里有5颗榴莲糖,那么它就是5。
    Nminor=5\displaystyle N_{\text{minor}} = 5
  2. 读分母\( N_{\text{total}} \) 是样本总数,代表整批糖果的数量100。
    Ntotal=100\displaystyle N_{\text{total}} = 100
  3. 算比例\( p \) 就是少数类占总体的比例,这里 \( p = 5/100 = 0.05 \),即5%。比例越低,随机划分时少数类“漏进”某一折的概率越大。
    p=5100=0.05\displaystyle p = \frac{5}{100} = 0.05

分层原理

如何避免评委漏吃榴莲糖?聪明的做法是:先把所有糖果按口味分到不同的大碗里——榴莲糖归榴莲糖,薄荷糖归薄荷糖。然后从每个大碗里按比例抓出相同份数,装进5个小袋。这样每袋的榴莲糖比例都等于整体比例5%。

分层K折的核心思想正是如此:在划分时,先按类别标签把样本分开,再在每个类别内部执行K折划分,最后把每类对应的第1份拼成第1折,第2份拼成第2折……如此得到K折,每一折中各类别比例都与原始数据集大致相同。

数学上,要求每一折 \( D_i \) 中类别 \( c \) 的比例接近总体比例:

分层就是用“先分类、再按比例分”代替“随手抓”,从源头杜绝了某折缺失少数类的风险。就像分糖果时,先数好每袋需要几颗榴莲糖再动手,怎么可能漏掉口味呢?

count(Di,c)∣Di∣≈count(D,c)∣D∣\displaystyle \frac{\text{count}(D_i, c)}{|D_i|} \approx \frac{\text{count}(D, c)}{|D|}
  1. 左边是折内的类别比例\( \text{count}(D_i, c) \) 表示第 \( i \) 个折 \( D_i \) 中类别 \( c \) 的样本数,\( |D_i| \) 是该折的样本总量,两者相除就是该折里类别 \( c \) 的占比。
    count(Di,c)∣Di∣\displaystyle \frac{\text{count}(D_i, c)}{|D_i|}
  2. 右边是整体的类别比例\( \text{count}(D, c) \) 是整个数据集中类别 \( c \) 的数量,\( |D| \) 是总样本数,相除即原始数据的类别分布。
    count(D,c)∣D∣\displaystyle \frac{\text{count}(D, c)}{|D|}
  3. 约等号的含义分层不要求完全相等,而是尽量接近。当样本数不能被折数整除时,允许个别折多一个或少一个样本,但比例会尽量贴合总体。
    ≈\displaystyle \approx

分层算法步骤

现在你站在糖果操作台前,准备亲手执行分层分袋。第一步,把所有糖果按口味倒进不同的大碗:榴莲碗、薄荷碗、巧克力碗。第二步,每个碗里的糖果自己切成K份:比如5份,尽量均匀。第三步,把所有大碗的第1份合并成第1袋,第2份合并成第2袋……这样每袋都有来自每个碗的糖果。

技术实现上,对类别 \( c \) 的 \( N_c \) 个样本进行K折划分,如果 \( N_c \) 能被 \( K \) 整除,每折正好分到 \( N_c / K \) 个;如果不能整除,则有些折会多一个。第 \( i \) 折中类别 \( c \) 的样本数可表示为:

这个流程在Scikit-Learn的 StratifiedKFold 中已经封装好。你只需要告诉它“标签是什么、折数是多少”,它会自动完成“分碗—切份—合并”三步。这就像超市帮你把混合糖果预先分袋,你直接买走就好。

⌊NcK⌋或⌈NcK⌉\displaystyle \left\lfloor \frac{N_c}{K} \right\rfloor \quad \text{或} \quad \left\lceil \frac{N_c}{K} \right\rceil
  1. 向下取整符号\( \lfloor N_c / K \rfloor \) 表示类别 \( c \) 的样本数除以折数后向下取整,也就是“每个折至少要分到的数量”。
    ⌊NcK⌋\displaystyle \left\lfloor \frac{N_c}{K} \right\rfloor
  2. 向上取整符号\( \lceil N_c / K \rceil \) 表示向上取整,也就是“当除不尽时,部分折可以多分到的那一档数量”。
    ⌈NcK⌉\displaystyle \left\lceil \frac{N_c}{K} \right\rceil
  3. 两者组合使用实际划分时,先给每折分 floor 份,余下的样本从第一折开始依次补1个,最终每折中该类样本数要么是floor要么是ceil。
    Nc=K⋅⌊NcK⌋+r,r<K\displaystyle N_c = K \cdot \left\lfloor \frac{N_c}{K} \right\rfloor + r, \quad r < K

分层对比

假设你做了两组实验:第一组让5位评委各自随手抓一把糖果品尝,第二组用分层法分袋后再请他们品尝。结果第一组里有人说“没吃到榴莲糖”,评价波动很大;第二组每位评委都尝到了榴莲糖,给出的分数比较集中。显然,第二组的评分更稳定、更可信。

普通K折和分层K折的差别就是“随机抓”和“按比例分”的差别。普通K折每次划分的类别比例都会随机波动,某些折可能出现极端情况;分层K折人为固定了比例,大大降低了这种波动。用统计语言说,分层K折的评估指标方差更小:

普通K折就像抛硬币,正面反面随机出现;分层K折则像提前按比例配好,每次都能等到“标准答案”。当你的数据类别不平衡时,分层带来的稳定性提升会非常明显。

Var⁡(L^stratified)<Var⁡(L^ordinary)\displaystyle \operatorname{Var}(\hat{L}_{\text{stratified}}) < \operatorname{Var}(\hat{L}_{\text{ordinary}})
  1. 方差代表波动程度\( \operatorname{Var}(\cdot) \) 衡量一组数值的波动大小。方差越大,说明不同折之间的评估结果差异越明显。
    Var⁡(X)=E[(X−E[X])2]\displaystyle \operatorname{Var}(X) = \mathbb{E}[(X - \mathbb{E}[X])^2]
  2. 下标区分两种方法\( \hat{L}_{\text{stratified}} \) 表示分层K折重复多次得到的评估指标序列,\( \hat{L}_{\text{ordinary}} \) 表示普通K折的指标序列。
    L^stratified,L^ordinary\displaystyle \hat{L}_{\text{stratified}}, \hat{L}_{\text{ordinary}}
  3. 不等式含义分层K折评估指标的方差更小,意味着不同折之间的结果更接近,最终平均值也就更稳定、更可信。
    Var⁡stratified<Var⁡ordinary\displaystyle \operatorname{Var}_{\text{stratified}} < \operatorname{Var}_{\text{ordinary}}

适用场景

并不是所有情况都需要“分层分袋”。如果你店里全是普通薄荷糖,怎么分口味都一样;但如果榴莲糖很少,分层就至关重要。机器学习的场景选择同理:当类别比例严重倾斜、少数类样本稀少时,分层K折是评估的首选方法。

具体来说,二分类中少数类占比很低(如欺诈检测、疾病诊断),或者多分类中某些类别样本很少(如语音指令的稀有指令),或者总体数据量很小需要最大化有效训练样本时,都应该使用分层K折来划分数据。分层还能保证验证集的类别分布与真实场景一致,让评估结果更贴近上线后的表现。

但分层有一个前提条件:每个类别的样本数不能少于折数,否则无法保证每个折都包含该类别。即需要满足:

就像一个碗里只有3颗榴莲糖,硬要分成5袋,必然有袋子分不到。所以,分层之前先数数“碗里的糖够不够”,不够的话要么少分几袋,要么先把糖变多。

min⁡cNc≥K\displaystyle \min_c N_c \ge K
  1. 最小值符号\( \min_c N_c \) 表示对所有类别 \( c \) 统计样本数,取出其中最小的那个数值。这代表数据集中最稀有的类别的样本量。
    min⁡cNc\displaystyle \min_c N_c
  2. 折数K\( K \) 是交叉验证的折数,表示要把数据分成多少份。如果 \( K=5 \),就需要每个类别至少能分到5份。
    K=5\displaystyle K=5
  3. 整个条件最稀有类别的样本数不小于折数,分层才能保证每折都有该类别。若条件不满足,建议减少折数,或者先做数据增强再评估。
    min⁡cNc≥K\displaystyle \min_c N_c \ge K

注意事项

糖果分袋虽好,也有很多细节要小心。比如,如果你的榴莲糖只有2颗,却设置K=5,再怎么分层也保证不了每袋都有榴莲糖;这时你可能需要减少折数,或者用SMOTE等过采样方法先补充样本,再做分层评估。

另一个常见误区是:分层K折只能用于分类任务。回归任务的标签是连续数值(比如房价、温度),没有一个离散的“类别”可以分层。如果硬要分层,可以把连续标签切成几个区间当作伪分类,但这不是标准做法。此外,不要忘记对测试集也做同样的分层划分——如果测试集本身类别分布和训练集不一致,再好的交叉验证也救不了最终评估。

技术实现上,StratifiedKFold 默认不会打乱数据,你需要设置 shuffle=True 并固定 random_state 保证可复现。而且,分层K折解决的是“评估可靠性”问题,它并不会改变训练集的类别不平衡程度——模型该学不会的少数类依旧学不会,后续仍需要处理类别不平衡本身。

总之,分层K折是评估阶段的“分袋保险”,它让每一次验证都“雨露均沾”。但别指望它一次性解决所有问题:样本不足先补充,回归问题另作他想,测试集也要同步分层。只有这些细节都做到位,你的模型评估才算真正稳妥。

AI 应用场景

医疗影像罕见病检测

阶段:模型评估与选择

解决的问题:医疗影像数据中,罕见病样本可能只占2%~5%。普通K折划分时,某些折没有罕见病样本,模型对罕见病的灵敏度评估不稳定,导致误认为AI漏诊率高。分层K折保证每一折都包含罕见病样本,评估出的召回率与真实临床表现更一致。

具体案例:某医院糖尿病视网膜病变检测项目中,数据集共5000张眼底照片,其中病变样本仅250张。使用 StratifiedKFold(n_splits=5, shuffle=True) 评估后,模型对病变样本的召回率稳定在0.92;而使用普通KFold时,某折因无病变样本,召回率显示为0.00,造成严重误判。

金融反欺诈模型

阶段:模型评测与上线前验证

解决的问题:信用卡交易中欺诈样本占比通常不足1%。若验证集里没有欺诈样本,模型对欺诈的识别能力无法体现。分层K折确保每个验证折都保留与总数据一致的欺诈率,从而得到可信的AUC和Recall。

具体案例:某支付平台构建欺诈检测模型,使用80万笔交易数据,其中欺诈交易约3000笔。通过分层K折评估,模型在不同折上的F1分数波动仅为±0.01;而普通K折波动高达±0.08,让团队无法判断模型真实水平。

多分类意图识别

阶段:数据划分与模型调优

解决的问题:自然语言处理的意图识别任务中,“退订”等指令可能极少出现。普通划分容易让某一折完全缺少该意图样本,导致分类器在那个折上永远不会预测该意图。分层K折保持每个意图在各折占比一致,保障少样本意图的评估。

具体案例:智能客服语料共有20个意图类别,其中“投诉”仅占语料总量的1.5%。使用分层K折后,各折的“投诉”意图占比稳定在1.4%~1.6%,模型对该意图的F1从0.31提升到可信区间0.45±0.03。

Python 演示

python27 行
from sklearn.datasets import make_classificationfrom sklearn.model_selection import KFold, StratifiedKFoldimport numpy as np X, y = make_classification(    n_samples=200, n_features=4, n_classes=2,    weights=[0.9, 0.1], random_state=42) def show_split(name, train_idx, val_idx):    y_train, y_val = y[train_idx], y[val_idx]    p_train = np.mean(y_train == 1)    p_val = np.mean(y_val == 1)    print(f"{name}: 训练集正样本比例={p_train:.3f}, "          f"验证集正样本比例={p_val:.3f}") print("== 普通 KFold ==")for fold, (train_idx, val_idx) in enumerate(    KFold(n_splits=5, shuffle=True, random_state=0).split(X, y)):    show_split(f"fold {fold+1}", train_idx, val_idx) print("\n== 分层 StratifiedKFold ==")for fold, (train_idx, val_idx) in enumerate(    StratifiedKFold(n_splits=5, shuffle=True, random_state=0).split(X, y)):    show_split(f"fold {fold+1}", train_idx, val_idx)
目的:对比普通K折与分层K折在类别不平衡数据上的划分效果,直观展示分层后每折类别比例的一致性。
  1. make_classification(weights=[0.9, 0.1])生成90%负样本、10%正样本的不平衡模拟数据。
  2. KFold(n_splits=5, shuffle=True, random_state=0).split(X, y)普通K折只按索引划分,不关心标签分布。
  3. StratifiedKFold(...).split(X, y)分层K折会根据标签 y 自动保持每折类别比例。
  4. np.mean(y_val == 1)计算验证集中正样本占比,用来观察划分是否失衡。
  5. for fold, (train_idx, val_idx) in enumerate(...)遍历每一折,取出训练集和验证集的索引。

常见误区

误区我觉得分层K折只是对付不平衡数据的,我的数据类别很均衡,用普通K折就够了,分层反而多余。

正解分层K折本质上是一种更精细的随机抽样,它保证了类别分布一致性。即使在平衡数据上,分层也不会带来坏处,只是提升不显著。但在小样本、高维度场景下,分层能减少偶然比例偏差,依然值得使用。

为何常见:初学者常把分层视作“补丁”,认为只有出了问题才需要打补丁,忽略了它作为标准稳妥做法的价值。

误区用了分层K折,模型对少数类的分类能力应该会自动变好,不需要再做其他处理了。

正解分层K折只负责让评估数据更合理,它不会增加少数类样本,也不会改变训练集的类别比例。模型在训练时仍然可能偏向多数类,后续仍需过采样、欠采样或调整损失权重来提升模型本身的能力。

为何常见:把“评估方法”和“训练优化”混为一谈,误以为数据划分策略能直接改变模型学习过程。

误区我需要评估回归模型,也可以直接套用分层K折来让每一折的标签分布一致。

正解分层K折依赖离散类别标签。回归任务的标签是连续值,无法直接分层。若想近似分层,可以将连续标签离散化为若干区间后分层,但这会丢失信息,不推荐作为标准做法。回归任务通常使用普通K折或保留重复的KFold。

为何常见:很多工具库中 StratifiedKFold 要求 y 必须是分类标签,但初学者没注意到这一限制,误以为所有监督任务都能用。

误区只要训练时用分层K折评估,最终测试集怎么划分都无所谓,反正模型已经评估好了。

正解最终测试集作为模拟真实场景的“终审评委”,如果它的类别分布与训练集差异很大,得到的泛化指标依旧不可信。训练评估阶段要分层,最终测试集也应该从原始数据中按分层抽样构建,保持类别比例一致。

为何常见:新手常把注意力集中在交叉验证的K折划分上,忽略了整个数据流水线的全局一致性。

面试题

Q1. 分层K折交叉验证主要解决什么问题?

单选题 · easy

  • A.提升训练速度
  • B.保证评估可靠性← 正确答案
  • C.减少模型过拟合
  • D.增加数据样本量

答案:B。分层K折通过保持每折类别比例一致,解决类别不平衡下某折缺失少数类导致的评估失真问题,因此核心是保证评估可靠性。提升训练速度、减少过拟合、增加样本量都不是它的直接作用。

Q2. 普通K折与分层K折的核心区别是什么?

单选题 · easy

  • A.每折样本数量不同
  • B.保持类别比例一致← 正确答案
  • C.使用更多训练轮次
  • D.增加验证集大小

答案:B。普通K折随机划分样本,分层K折则保持每折中各类别比例与总体一致。样本数量、训练轮次、验证集大小都不是两者的本质区别。

Q3. 某数据集共1000条样本,A类900条,B类100条,采用5折分层K折,每折中B类样本大约有多少条?

单选题 · mid

  • A.约0条
  • B.约20条← 正确答案
  • C.约100条
  • D.约180条

答案:B。分层K折使每折类别比例与总体一致。B类占比10%,每折总样本约200条,因此B类约20条。普通K折才有可能出现0条,分层就是为了排除这种可能。

Q4. 分层K折最适合以下哪种任务?

单选题 · easy

  • A.回归预测任务
  • B.不平衡分类任务← 正确答案
  • C.无监督聚类任务
  • D.特征降维任务

答案:B。分层K折依赖离散类别标签,最适用于类别不平衡的分类任务。回归标签连续无法直接分层,聚类和降维更不需要类别分层。

Q5. 某类别的样本数只有3个,要用5折分层K折,会发生什么?

单选题 · mid

  • A.正常完成划分
  • B.无法保证每折都有该类← 正确答案
  • C.系统自动舍弃该类
  • D.自动改为留出法

答案:B。分层的前提是每个类别的样本数不少于折数。3个样本分到5折,至少有两折拿不到该类别,无法实现真正的分层。应该减少折数或先扩充样本。

Q6. 关于分层K折,下列说法正确的是?

多选题 · mid

  • A.每折类别比例近似总体← 正确答案
  • B.可以消除类别不平衡
  • C.适合分类任务场景← 正确答案
  • D.可以处理连续标签

答案:AC。分层K折让每折类别比例近似总体(A对),它只是划分策略,不能消除不平衡(B错),适用于分类(C对),无法直接处理连续标签(D错)。

Q7. 使用分层K折有哪些好处?

多选题 · hard

  • A.降低评估结果方差← 正确答案
  • B.提升模型泛化能力
  • C.让少数类指标更可信← 正确答案
  • D.显著减少计算开销

答案:AC。分层K折通过稳定每折类别分布,降低了评估指标的方差(A对),从而让少数类召回率、F1等指标更可信(C对)。它不改变模型算法,不会直接提升泛化能力,也不会减少计算量。

Q8. 分层K折中的“分层”指的是按什么分组?

单选题 · easy

  • A.按特征值分组
  • B.按类别标签分组← 正确答案
  • C.按样本顺序分组
  • D.按随机种子分组

答案:B。分层是按样本的类别标签进行分组,确保每一折中各类别比例与总体一致。特征值、样本顺序、随机种子都不是分层的依据。

Q9. 以下哪些场景应优先考虑分层K折?

多选题 · hard

  • A.少数类占比仅为5%← 正确答案
  • B.二元分类且类别不均← 正确答案
  • C.数据量巨大且类别均衡
  • D.多分类中少数类极少← 正确答案

答案:ABD。少数类占比低时普通K折容易“漏分”,需要分层(A)。二元分类类别不均同样需要(B)。多分类中少数类极少时,分层能防止稀有类别在某折消失(D)。数据量巨大且类别均衡时分层收益很小,不是“优先”场景。

Q10. 分层K折划分时,若某类别样本数不能整除折数,通常怎么处理?

单选题 · extreme

  • A.丢弃多余的样本
  • B.部分折多分一个样本← 正确答案
  • C.增加少数类样本
  • D.改用普通K折

答案:B。当 \( N_c \) 不能被 \( K \) 整除时,多数实现会让部分折多分一个样本,即每折样本数为 floor 或 ceil,而不是丢弃数据。这样既保证了比例接近,又不浪费样本。