400 个样本、四种切分比例,每种重复 30 次。 测试准确率的标准差从 50/50 的 0.0186 涨到 90/10 的 0.0543; 90/10 那档三十次里最低 0.600、最高 0.825,跨度 0.225。
上一节讲完了五个评估指标。指标本身没问题,问题在于「用哪一份数据算」。这一节开始讲怎么切数据。
一、最简单的做法:切两半
把手上的数据分成两份,一份训练、一份测试。训练那份模型见得到,测试那份见不到。 然后拿测试集上的准确率当作「模型在真实世界能有多好」的估计。 这就是 Holdout,全部验证方法里最朴素的一个。 为什么必须留一份不参与训练?因为模型在见过的数据上一定表现更好——这不是能力,这是记忆。 只有没见过的数据才能测出真实水平。 常见比例是 70/30 或 80/20。 数据少的时候用 80/20(尽量多给训练),数据多的时候可以 90/10 甚至 99/1。
二、比例越小,结果越抖
Demo 用 400 个样本试了四种比例,每种重复 30 次(每次换随机种子)。 50/50:均值 0.7210、标准差 0.0186、范围 0.690 到 0.750。 90/10:均值 0.7283、标准差 0.0543、范围 0.600 到 0.825。 均值几乎一样(都在 0.72 上下),说明模型能力是定的。 但标准差差了三倍——测试集小的时候,切一次的结果很不稳。 90/10 那档三十次里最低 0.600、最高 0.825,跨度 0.225。 也就是说「换个随机种子,结论就从不能用变成还不错」。
三、三个坑
坑一:只切一次。 拿到一个数就当结论。上表已经证明了它有多不稳。 正确做法是多切几次取平均,或者直接上交叉验证。 坑二:不 shuffle。 如果数据是按类别排序存好的(比如前 500 行全是正常样本), 直接切前 80% 当训练集,训练集里可能一个正样本都没有。必须先打乱。 坑三:拿测试集调参。 这是最隐蔽的一个。 你看测试分数不好,改个参数再看,再改再看——改上二十次,测试集就被你「用」了二十次, 它已经不再是没见过的数据了。最后报出来的分数会明显虚高。 坑三的正确解法是从训练集里再切一份验证集出来调参,测试集只在最后用一次。
四、什么时候用 Holdout 就够了
数据量很大(十万条以上)时,Holdout 完全够用。 因为测试集本身够大,0.0543 那种抖动会随着样本量下降。 训练一次特别贵(比如大模型微调要几小时)时,也只能用 Holdout——交叉验证要训 K 次,成本顶不住。 反过来,数据几千条、模型几秒钟能训完,就该上交叉验证。下一节讲那个。 判断标准很简单:测试集够不够大到让方差不影响结论。 不够就用交叉验证,够了就用 Holdout,别为了严谨多花十倍算力。
五、动手 Demo:Holdout:90/10 切分下,三十次结果跨度 0.225
造 400 个 6 维的二分类样本(两类中心差 0.6,有重叠),用固定 300 步的逻辑回归; 对 50/50、70/30、80/20、90/10 四种切分各重复 30 次,统计测试准确率的均值、标准差和最低最高值。
for ratio in (0.5, 0.7, 0.8, 0.9):
scores = []
ntr = int(len(y) * ratio)
for _ in range(30):
idx = rg.permutation(len(y))
tr, te = idx[:ntr], idx[ntr:]
w, b = fit_lr(X[tr], y[tr])
scores.append(acc(X[te], y[te], w, b))
实跑输出:
--------------------------------------------------------------------
Holdout 检验:切分比例越小,测试分数抖得越厉害
--------------------------------------------------------------------
数据 400 个样本、6 维、两类中心差 0.6(有重叠)
模型 逻辑回归,固定 300 步梯度下降,不调参
做法 每种切分重复 30 次,统计测试准确率的均值和标准差
训练 / 测试(%) 训练样本数 测试准确率均值 标准差 最低到最高
----------------------------------------------------------------------
50 / 50 200 0.7210 0.0186 0.690 ~ 0.750
70 / 30 280 0.7119 0.0348 0.642 ~ 0.783
80 / 20 320 0.7308 0.0404 0.637 ~ 0.812
90 / 10 360 0.7283 0.0543 0.600 ~ 0.825
均值那一列基本不动(都在 0.72 上下),说明模型本身的能力是定的。
标准差那一列从 50/50 的 **0.0186** 一路涨到 90/10 的 **0.0543**——测试集越小,同一次切分的结果就越离谱。
最后一列最直观:90/10 时三十次里最低 0.600、最高 0.825,跨度 **0.225**。也就是说「换一次随机种子,结论就变了」。
所以 Holdout 的问题不是「不准」,是「不稳」。切一次得到的那个数,只是众多可能结果里的一个样本。
常见的补救办法:固定随机种子(保证可复现,但不解决偏差)、重复多次取平均(Demo 做的就是这件事)、或者直接上交叉验证。
四个均值是 0.7210、0.7119、0.7308、0.7283——最大差 0.019,都在噪声范围内。 所以切分比例不影响「中心」,只影响「抖动」。 标准差 0.0186、0.0348、0.0404、0.0543,单调上升。 这个趋势很干净,因为测试集从 200 个一路缩到 40 个。 90/10 那档跨度 0.225。 想象一下汇报场景:切到 0.600 你会说模型不行,切到 0.825 你会说效果很好——同一个模型,同一个数据集。 这里模型是固定的(300 步、不调参)。 如果每次都重新调参,抖动会更大——因为调参本身也在拟合测试集的噪声。
30 秒实验
- 把重复次数从 30 改成 100,看标准差会不会变小。
- 把样本量从 400 改成 2000,再看四种比例的标准差。
- 想一想:为什么「拿测试集反复调参」会让最后的分数虚高?
常见误区
- 「切一次就够了」——90/10 切分下不同种子能差出 0.2 以上。
- 「切分不用打乱」——数据有顺序时,训练集和测试集的分布会完全不同。
- 「看测试分数不好就改参数」——这一改,测试集就不再是没见过的数据了。
自测三问(点开对答案)
1. Holdout 的基本做法是什么? —— 把数据切成训练集和测试集,训练集训模型,测试集估性能。 2. 为什么测试集比例越小结果越不稳? —— 测试样本少,单次估计的方差大,换个随机种子结论就变。 3. 「拿测试集调参」为什么是错的? —— 调参过程会让模型拟合测试集的噪声,测试集失去没见过的数据这个性质。Holdout 不稳,那就多切几次。下一节讲交叉验证。
- ☐ (本节完,下一节 9.2.2 交叉验证:实现更稳定、可靠的模型性能)