🧠 第九章 模型评估和优化
📄 一句话概括

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 秒实验

  1. 把重复次数从 30 改成 100,看标准差会不会变小。
  2. 把样本量从 400 改成 2000,再看四种比例的标准差。
  3. 想一想:为什么「拿测试集反复调参」会让最后的分数虚高?

常见误区

  1. 「切一次就够了」——90/10 切分下不同种子能差出 0.2 以上。
  2. 「切分不用打乱」——数据有顺序时,训练集和测试集的分布会完全不同。
  3. 「看测试分数不好就改参数」——这一改,测试集就不再是没见过的数据了。
自测三问(点开对答案) 1. Holdout 的基本做法是什么? —— 把数据切成训练集和测试集,训练集训模型,测试集估性能。 2. 为什么测试集比例越小结果越不稳? —— 测试样本少,单次估计的方差大,换个随机种子结论就变。 3. 「拿测试集调参」为什么是错的? —— 调参过程会让模型拟合测试集的噪声,测试集失去没见过的数据这个性质。

Holdout 不稳,那就多切几次。下一节讲交叉验证。

相关笔记