观文听傑

返回

上一篇中,我们已经能把样本组成小批量,让损失沿着梯度稳定下降。假设训练结束后,训练集均方根误差只有 0.1,你会认为模型已经足够好吗?

未必。房价模型可能把训练社区的偶然噪声记得一清二楚,换到新社区误差却变成 5.0;另一种模型在训练集和验证集上的误差都很高,说明它连已有规律也没有学会。只报一个训练损失,会把这两种完全不同的问题混在一起。

今天只讲透一个核心问题:如何用训练误差、验证误差随数据量的变化,判断模型是欠拟合、过拟合,还是根本没有正确完成优化?

01 一次低训练误差只回答了半个问题#

设训练集为 DtrainD_{train},验证集为 DvalD_{val},回归任务用均方根误差(Root Mean Squared Error,RMSE):

E(D,θ)=1D(xi,yi)D(fθ(xi)yi)2E(D,\theta)=\sqrt{\frac{1}{|D|}\sum_{(x_i,y_i)\in D}\left(f_\theta(x_i)-y_i\right)^2}
  • DD:被评价的数据集合。
  • D|D|:集合中的样本数。
  • xiRdx_i\in\mathbb{R}^{d}:第 ii 个样本的 dd 维特征。
  • yiRy_i\in\mathbb{R}:真实连续目标。
  • fθ(xi)Rf_\theta(x_i)\in\mathbb{R}:模型预测。
  • E(D,θ)R0E(D,\theta)\in\mathbb{R}_{\ge 0}:以原目标单位表示的误差。

训练后同时计算:

Etrain=E(Dtrain,θ),Eval=E(Dval,θ)E_{train}=E(D_{train},\theta),\qquad E_{val}=E(D_{val},\theta)

二者分别回答不同问题:训练误差说明模型能否拟合已经见过的数据,验证误差说明同一参数能否迁移到未参与拟合的数据。

D_train [m,d] ──► fit ──► 参数 θ
      │                      │
      └──── predict ─────────┴──► E_train

D_val   [v,d] ──► predict(同一 θ) ──► E_val
                  不 backward,不更新
text

这里的验证集仍承担上一篇定义的模型选择职责,测试集继续封存。不要画一次曲线就提前消费最终测试集。

02 欠拟合和过拟合究竟差在哪里?#

**欠拟合(Underfitting)**指模型连训练数据中的主要规律都表达或学不好,训练误差已经偏高。**过拟合(Overfitting)**指模型在训练数据上表现很好,却把噪声或偶然模式当成可推广规律,验证误差明显更高。

二者可以先用一张表区分:

现象训练误差验证误差泛化差距 EvalEtrainE_{val}-E_{train}首要检查
欠拟合候选表达能力、特征、训练是否充分
过拟合候选数据量、模型容量、正则化
理想状态分布与业务指标是否仍匹配
优化失败高且波动不稳定学习率、梯度、输入尺度、代码

“低”和“高”必须相对于任务基线判断。房价 RMSE 为 5 万元可能很好,也可能不可用;应同时比较简单基线、标签噪声和业务容忍度。

03 学习曲线为什么要改变训练样本数?#

单次训练只给出一个点。**学习曲线(Learning Curve)**保持模型结构和训练规则不变,逐步增加训练样本数 mm,每次重新拟合并记录:

Etrain(m),Eval(m)E_{train}(m),\qquad E_{val}(m)

伪代码如下:

split development data into K folds

for m in increasing_train_sizes:
    for fold in 1 ... K:
        take m samples from this fold's training portion
        fit a fresh model from scratch
        record score on those m training samples
        record score on the untouched validation fold
    average the K training scores and K validation scores

plot error against m
text

注意每个点都要从头拟合一个新模型。把同一个模型继续训练更久,改变的是训练步数,不是训练集大小;那叫训练过程曲线,不是这里的样本量学习曲线。

两种典型形状#

误差                         误差
 ▲                            ▲
 │ val  ╲                     │ val  ╲____
 │       ╲____                │           ╲__
 │            ╲__             │ train ____╱
 │ train _______╱             │
 └──────────────► m           └──────────────► m
    仍有明显间隔                  很快汇合在高误差
       过拟合                         欠拟合
text
  • 数据增加后差距仍大,但验证误差持续下降:更多代表性数据通常有帮助。
  • 两条曲线很快汇合,却停在不可接受的高误差:继续收集同分布数据往往收益有限,应增加有效特征或模型能力。
  • 两条曲线都低且接近:当前容量与数据量较匹配,但仍需检查测试集和部署分布。

04 用六个点手算一条最小学习曲线#

假设真实关系接近 y=2xy=2x,但观测含少量噪声。我们固定两个验证样本,只改变训练子集大小,并比较同一个高容量模型:

训练池:(0,0), (1,2), (2,4), (3,6.5), (4,8), (5,9.5)
验证集:(1.5,3.1), (4.5,9.0)
text

每次重新拟合后得到:

训练样本数 mmEtrain(m)E_{train}(m)Eval(m)E_{val}(m)泛化差距
20.003.003.00
40.101.501.40
60.250.800.55

训练样本很少时,模型可以穿过每个训练点,所以训练误差为 0,却无法预测两点之间的位置。加入更多样本后,约束变多,训练误差反而略升;与此同时,验证误差和泛化差距下降。

这说明一个容易误判的事实:训练误差略微变差,不一定是退步;它可能意味着模型不再只服务于少量样本。

交互判断:看到最后两条曲线仍未汇合,应该先加数据还是先减小模型?

若验证误差随 mm 仍明显下降,新增同分布、可靠标注的数据值得尝试;若成本很高,也可以同时测试减小容量或加强正则化。曲线提供诊断证据,不会替代成本与任务约束。

05 用当前 scikit-learn API 复现#

下面构造一个一维非线性回归任务,用 8 次多项式制造容易过拟合的模型。输入输出形状为:

原始 X:               [N=120, D=1]
PolynomialFeatures:   [N=120, P=8]
y:                     [N=120]
train_scores:          [num_sizes=5, cv_folds=5]
validation_scores:     [5, 5]
text

关键 API 的语义:

  • PolynomialFeatures(degree=8, include_bias=False):把单特征 xx 展开为 [x,x2,,x8][x,x^2,\ldots,x^8];不额外生成常数列,因为线性回归会拟合截距。
  • make_pipeline(...):让特征展开、标准化和回归在每个交叉验证折内重新拟合,避免验证折影响预处理。
  • KFold(..., shuffle=True):产生 5 组训练/验证索引;时间或分组数据应换成与部署结构一致的划分器。
  • learning_curve(...):对每个训练规模和每个折克隆估计器并重新拟合,返回训练分数与验证分数矩阵。
  • neg_root_mean_squared_error:scikit-learn 的评分器遵循“越大越好”,因此返回负 RMSE;转回误差时必须取负号。

官方当前接口把第三个返回值命名为 test_scores,但它来自交叉验证中暂时留出的验证折,并不等于你封存的最终测试集。代码里改名为 validation_scores,是为了避免职责混淆。

06 怎样把曲线变成下一步行动?#

先确认训练流程能优化

        ├── E_train 高且曲线异常抖动
        │      └── 查学习率、梯度、标签、尺度与实现

        └── 优化基本正常

               ├── E_train 与 E_val 都高且接近
               │      └── 更有效特征、更强模型、训练更充分

               └── E_train 低,E_val 高
                      └── 更多数据、减小容量、正则化、数据增强
text

欠拟合候选#

可依次尝试:确认优化收敛;增加表达任务结构的特征;降低过强正则化;增加模型容量。若标签本身噪声很大,则两条误差曲线可能存在无法跨越的下限。

过拟合候选#

可尝试:增加代表性训练数据;减小树深、次数或网络宽度;加强正则化;使用符合问题结构的数据增强。选择哪一个,取决于验证曲线是否仍从更多数据中获益,以及工程成本。

不要只看均值#

交叉验证折间标准差很大,意味着结论依赖具体划分。此时应检查样本量、类别/群组分布和异常样本,而不是只相信一条平滑均值线。

07 常见错误与最小调试法#

  1. 用测试集画学习曲线。 曲线会参与模型决策,应只使用训练/验证开发数据。
  2. 不同训练规模使用不同训练预算。 小数据训练 100 轮、大数据只训练 100 步,会混入优化不足;记录每个规模的收敛状态。
  3. 预处理在交叉验证外拟合。 标准化、特征选择和填补必须放入 Pipeline。
  4. 把准确率差距直接当误差差距。 分数越大越好、损失越小越好;先统一方向再解释曲线。
  5. 只画一个随机划分。 小数据中一次划分方差很大,应报告多折均值和波动。
  6. 训练子集不具代表性。 有序数据直接取前缀,可能让小规模点只包含某一类别;需要正确洗牌或结构化采样。

最小检查清单:

assert X.ndim == 2 and y.ndim == 1
assert X.shape[0] == y.shape[0]
assert np.isfinite(X).all() and np.isfinite(y).all()
assert train_scores.shape == validation_scores.shape
assert np.all(np.diff(sizes) > 0)
python

若训练误差随样本量剧烈乱跳,先固定随机种子并打印每个折的分数;若某些折出现 nan,将 error_score='raise' 临时传给 learning_curve,让真实拟合异常直接暴露。

08 学习曲线不能告诉你什么?#

学习曲线是诊断工具,不是因果证明。它有明确边界:

  • 训练与验证分布都过时,两条曲线再漂亮也不能保证上线有效。
  • 标签泄漏会让两条误差同时虚假地低。
  • 概念漂移任务中,随机交叉验证可能掩盖时间变化。
  • 不同类别的平均误差可能相互抵消,需要再看分组指标。
  • 曲线无法自动区分“模型类不合适”和“特征缺少关键信息”。

它也不同于另外两类曲线:

曲线横轴主要回答
学习曲线训练样本数更多数据是否可能有用?
训练过程曲线epoch / step优化是否收敛、何时开始过拟合?
验证曲线某个超参数值哪个容量或正则化强度更合适?

09 今天真正需要记住什么?#

  1. 训练误差衡量拟合已有样本的能力,验证误差衡量同一参数的迁移能力;二者缺一不可。
  2. 欠拟合常表现为训练与验证误差都高且接近;过拟合常表现为训练误差低、验证误差高且差距大。
  3. 学习曲线通过改变训练样本数并反复重新拟合,判断更多数据是否仍可能缩小泛化差距。
  4. 高训练误差也可能来自优化或数据故障,诊断容量之前先验证训练管道。
  5. 测试集不参与画曲线,预处理必须留在每个交叉验证折内部。

10 思考题与小练习#

练习 1:判断下一步

当样本数从 1,000 增到 10,000 时,训练 RMSE 从 0.3 升到 0.5,验证 RMSE 从 2.0 降到 0.8,且仍在下降。主要证据指向过拟合正在缓解,继续增加代表性数据可能有效;训练误差略升不是训练失败。

练习 2:识别伪学习曲线

某脚本固定训练集,只记录第 1、5、20、100 个 epoch 的训练与验证误差。这是训练过程曲线,因为横轴是训练时间;真正的学习曲线要改变样本数,并在每个规模重新拟合。

练习 3:修改代码验证容量

把示例中的多项式次数依次改为 1、3、15,比较曲线末端的训练误差、验证误差和差距。解释为什么次数 1 更容易欠拟合,而次数 15 在小数据区域更容易过拟合。

相关工作#

11 下一篇预告#

学习曲线能指出“训练很好、验证很差”,却不会自动修复它。下一篇将给经验风险加上一项对大权重的惩罚,手算 L2 正则化如何收缩参数,并看清岭回归为什么能让高次多项式不再追逐每一个噪声点。

训练误差很低为何仍失效?用学习曲线诊断欠拟合与过拟合
https://zwjcode.cn/blog/learning-curves-underfitting-overfitting
作者
发布于 2026年8月20日
版权协议 CC BY-NC-SA 4.0
评论加载似乎遇到了问题,请尝试刷新页面。