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

决策树深度从 1 扫到 8:深度 1 时训练 0.7600、测试 0.7100(欠拟合); 深度 5 时测试到顶 0.8250; 深度 6 训练涨到 0.9300,测试掉回 0.8075,差距从 0.0800 跳到 0.1225。

📝 本节和前面几节的关系

上一节解决了「怎么可靠地评估」。这一节解决「评估结果不好时,问题出在哪」。 两条曲线分别回答两个不同的问题。

一、两条曲线,两个问题

验证曲线:固定数据集,改模型复杂度(树深度、近邻数、正则强度),看训练分和测试分怎么变。 它回答「模型该多复杂」。 学习曲线:固定模型,改训练样本量,看训练分和测试分怎么变。它回答「再加数据有没有用」。 两条曲线的横轴不一样,一个改模型、一个改数据。混着看会得出错误结论。 共同点:都要同时画训练分和测试分。只画一条看不出问题——单个分数高低说明不了任何事。

二、验证曲线:两头都不好

深度 1 的树只能切一刀,训练 0.7600、测试 0.7100。 两个都低,差只有 0.05——模型太简单,连训练集都没拟合好。这是欠拟合。 深度往上,两个数一起涨。深度 5 时测试到顶 0.8250,训练 0.9050,差 0.0800。 深度 6 时训练涨到 0.9300,测试反而掉到 0.8075,差跳到 0.1225。 这是过拟合:模型开始记训练集的细节了。 所以判断信号不是「测试分高不高」,是「训练分和测试分的差距」。差距突然放大,就是过拟合开始了。 这条曲线上的甜点(深度 5)是这一份数据、这一个模型的最优点。 换数据、换模型,甜点会移动——所以每次换都要重画,不能套用。

三、学习曲线:什么时候该停止标数据

固定深度 3,训练样本从 20 加到 200。 只有 20 个样本时:训练 1.0000、测试 0.7000,差 0.3000。树把 20 个点全背下来了。 加到 80 个:训练 0.9125、测试 0.7900,差收到 0.1225。 但从 80 加到 200,测试只在 0.7900 到 0.7700 之间打转,不再明显上升——曲线走平了。 走平意味着「再加数据没用」。这时候该去改模型或者改特征,不是继续标数据。 这一条能省很多钱——标注是最贵的一环。 反过来,如果测试分还在明显上升,那加数据就是最有效的办法,比调参收益大得多。

四、两种诊断,两种药

高偏差(欠拟合):训练分和测试分都低、差距小。药是加复杂度、加特征、减小正则。加数据没用。 高方差(过拟合):训练分高、测试分低、差距大。药是加数据、加正则、减复杂度、做特征选择。 这两种情况的药正好相反。所以「准确率上不去」这句话没法回答——得先知道是高偏差还是高方差。 画这两条曲线的成本很低(就是多训几次),但它决定后面所有工作的方向。 所以有经验的训练师拿到一个新任务,第一批图往往就是这两条。

五、动手 Demo:验证曲线与学习曲线:深度 6 时差距突然从 0.08 跳到 0.12

造 200 个 4 维训练样本和 400 个同分布测试样本,用贪心切分的决策树; 先扫最大深度 1 到 8 画验证曲线,再固定深度 3、扫训练样本 20 到 200 画学习曲线, 两条都同时记训练准确率和测试准确率。

def run(xtr, ytr, d):
    t = build_tree(xtr, ytr, allf, 0, d)
    tr = float(((tree_predict(t, xtr) > 0.5).astype(int) == ytr).mean())
    te = float(((tree_predict(t, Xt) > 0.5).astype(int) == yt).mean())
    return tr, te

实跑输出:

--------------------------------------------------------------------
验证曲线与学习曲线:看出过拟合还是欠拟合
--------------------------------------------------------------------
  训练集                   200 个、4 维、两类中心差 1.1
  测试集                   另外单独造 400 个,同一个分布
  模型                     决策树,扫最大深度 1 到 8

  验证曲线:固定数据,扫模型的复杂度(决策树的最大深度)。
  最大深度   训练准确率   测试准确率   两者差
  ------------------------------------------
         1       0.7600       0.7100   0.0500
         2       0.7600       0.7100   0.0500
         3       0.8600       0.7700   0.0900
         4       0.8750       0.7900   0.0850
         5       0.9050       0.8250   0.0800
         6       0.9300       0.8075   0.1225
         8       0.9300       0.8075   0.1225

  深度 1 的树只能切一刀,训练 **0.7600**、测试 **0.7100**,两个都低——这是欠拟合,模型太简单,连训练集都没拟合好。
  深度往上加,两个数一起涨,测试在深度 **5** 到顶(**0.8250**)。
  再往上(深度 8)训练涨到 **0.9300**,测试反而掉回 **0.8075**,差距从 0.0800 拉到 **0.1225**——这是过拟合。
  所以「模型复杂度」有个甜点:往左欠拟合,往右过拟合。验证曲线就是找这个甜点。
  判断信号是最后那一列:深度 3 到 5 稳定在 0.08 到 0.09,深度 6 突然跳到 0.1225。跳的那一下就是过拟合开始的信号,比看单个分数更可靠。

  学习曲线:固定深度 3,扫训练样本数。
  训练样本数   训练准确率   测试准确率   两者差
  --------------------------------------------
          20       1.0000       0.7000   0.3000
          40       0.8500       0.7625   0.0875
          80       0.9125       0.7900   0.1225
         140       0.8929       0.7800   0.1129
         200       0.8600       0.7700   0.0900

  只有 20 个样本时,训练 **1.0000**、测试 **0.7000**,差 **0.3000**——树把 20 个点全背下来了。
  样本加到 80,测试升到 **0.7900**,差距收到 **0.1225**。
  但从 80 加到 200,测试只在 **0.7900** 到 **0.7700** 之间打转,不再明显上升——曲线走平了。
  这就是学习曲线的用法:**看到走平,就说明加数据没用了**,该去改模型或者改特征,别再去标数据。
  两条曲线合起来读:验证曲线差距大 → 过拟合,该减复杂度或加正则;学习曲线差距大且测试分数低 → 缺数据。这两种判断完全不同,得分开做。

深度 1 和 2 的数字完全一样(0.7600/0.7100)。 因为这两层数据上,第二次切分带来的基尼下降都低于我设的阈值 1e-4,树自己停了。 深度 4 和 5 差距 0.0850 和 0.0800,很稳;深度 6 跳到 0.1225。 这个跳变才是过拟合的信号,比单看测试分掉 0.0175 更清楚。 学习曲线最有用的信息是「走平点」:80 个样本之后测试分就没再涨。 这说明这份数据的信息量就这么多,堆样本堆不上去。 注意学习曲线里 200 个样本的测试分(0.7700)比 80 个样本(0.7900)还低一点。 这是噪声,不是「数据越多越差」——几条曲线的抖动幅度就这个量级。

30 秒实验

  1. 把树的深度上限提到 12,看验证曲线右侧会怎么走。
  2. 把训练样本从 200 加到 400(和测试集一样大),看学习曲线后半段。
  3. 想一想:如果你的模型训练分 0.95、测试分 0.60,该加数据还是减复杂度?

常见误区

  1. 「测试分高就是好模型」——测试 0.83、训练 0.84 可能是欠拟合,测试 0.80、 训练 0.99 可能已经过拟合。
  2. 「准确率上不去就加数据」——高偏差的情况下加数据完全没用。
  3. 「验证曲线的最优点可以复用」——换数据、换模型,最优点就变了。
自测三问(点开对答案) 1. 验证曲线和学习曲线的横轴分别是什么? —— 验证曲线改模型复杂度,学习曲线改训练样本量。 2. 怎么从曲线上判断过拟合? —— 训练分和测试分的差距突然放大,同时训练分还在升、测试分开始降。 3. 学习曲线走平意味着什么? —— 再加数据不会提升效果,该去改模型或改特征。

评估方法、诊断方法都有了。下一节讲一个不切分数据的评估办法:自助法。

相关笔记