决策树深度从 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 秒实验
- 把树的深度上限提到 12,看验证曲线右侧会怎么走。
- 把训练样本从 200 加到 400(和测试集一样大),看学习曲线后半段。
- 想一想:如果你的模型训练分 0.95、测试分 0.60,该加数据还是减复杂度?
常见误区
- 「测试分高就是好模型」——测试 0.83、训练 0.84 可能是欠拟合,测试 0.80、 训练 0.99 可能已经过拟合。
- 「准确率上不去就加数据」——高偏差的情况下加数据完全没用。
- 「验证曲线的最优点可以复用」——换数据、换模型,最优点就变了。
自测三问(点开对答案)
1. 验证曲线和学习曲线的横轴分别是什么? —— 验证曲线改模型复杂度,学习曲线改训练样本量。 2. 怎么从曲线上判断过拟合? —— 训练分和测试分的差距突然放大,同时训练分还在升、测试分开始降。 3. 学习曲线走平意味着什么? —— 再加数据不会提升效果,该去改模型或改特征。评估方法、诊断方法都有了。下一节讲一个不切分数据的评估办法:自助法。
- ☐ (本节完,下一节 9.2.4 自助法:通过重抽样技术来进行模型训练和评估)