训练误差很低为何仍失效?用学习曲线诊断欠拟合与过拟合
从训练误差与验证误差的差距出发,手算学习曲线,区分欠拟合、过拟合与优化失败,并用 sklearn 复现可执行的诊断流程。
上一篇中,我们已经能把样本组成小批量,让损失沿着梯度稳定下降。假设训练结束后,训练集均方根误差只有 0.1,你会认为模型已经足够好吗?
未必。房价模型可能把训练社区的偶然噪声记得一清二楚,换到新社区误差却变成 5.0;另一种模型在训练集和验证集上的误差都很高,说明它连已有规律也没有学会。只报一个训练损失,会把这两种完全不同的问题混在一起。
今天只讲透一个核心问题:如何用训练误差、验证误差随数据量的变化,判断模型是欠拟合、过拟合,还是根本没有正确完成优化?
01 一次低训练误差只回答了半个问题#
设训练集为 ,验证集为 ,回归任务用均方根误差(Root Mean Squared Error,RMSE):
- :被评价的数据集合。
- :集合中的样本数。
- :第 个样本的 维特征。
- :真实连续目标。
- :模型预测。
- :以原目标单位表示的误差。
训练后同时计算:
二者分别回答不同问题:训练误差说明模型能否拟合已经见过的数据,验证误差说明同一参数能否迁移到未参与拟合的数据。
D_train [m,d] ──► fit ──► 参数 θ
│ │
└──── predict ─────────┴──► E_train
D_val [v,d] ──► predict(同一 θ) ──► E_val
不 backward,不更新text这里的验证集仍承担上一篇定义的模型选择职责,测试集继续封存。不要画一次曲线就提前消费最终测试集。
02 欠拟合和过拟合究竟差在哪里?#
**欠拟合(Underfitting)**指模型连训练数据中的主要规律都表达或学不好,训练误差已经偏高。**过拟合(Overfitting)**指模型在训练数据上表现很好,却把噪声或偶然模式当成可推广规律,验证误差明显更高。
二者可以先用一张表区分:
| 现象 | 训练误差 | 验证误差 | 泛化差距 | 首要检查 |
|---|---|---|---|---|
| 欠拟合候选 | 高 | 高 | 小 | 表达能力、特征、训练是否充分 |
| 过拟合候选 | 低 | 高 | 大 | 数据量、模型容量、正则化 |
| 理想状态 | 低 | 低 | 小 | 分布与业务指标是否仍匹配 |
| 优化失败 | 高且波动 | 高 | 不稳定 | 学习率、梯度、输入尺度、代码 |
“低”和“高”必须相对于任务基线判断。房价 RMSE 为 5 万元可能很好,也可能不可用;应同时比较简单基线、标签噪声和业务容忍度。
03 学习曲线为什么要改变训练样本数?#
单次训练只给出一个点。**学习曲线(Learning Curve)**保持模型结构和训练规则不变,逐步增加训练样本数 ,每次重新拟合并记录:
伪代码如下:
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 mtext注意每个点都要从头拟合一个新模型。把同一个模型继续训练更久,改变的是训练步数,不是训练集大小;那叫训练过程曲线,不是这里的样本量学习曲线。
两种典型形状#
误差 误差
▲ ▲
│ val ╲ │ val ╲____
│ ╲____ │ ╲__
│ ╲__ │ train ____╱
│ train _______╱ │
└──────────────► m └──────────────► m
仍有明显间隔 很快汇合在高误差
过拟合 欠拟合text- 数据增加后差距仍大,但验证误差持续下降:更多代表性数据通常有帮助。
- 两条曲线很快汇合,却停在不可接受的高误差:继续收集同分布数据往往收益有限,应增加有效特征或模型能力。
- 两条曲线都低且接近:当前容量与数据量较匹配,但仍需检查测试集和部署分布。
04 用六个点手算一条最小学习曲线#
假设真实关系接近 ,但观测含少量噪声。我们固定两个验证样本,只改变训练子集大小,并比较同一个高容量模型:
训练池:(0,0), (1,2), (2,4), (3,6.5), (4,8), (5,9.5)
验证集:(1.5,3.1), (4.5,9.0)text每次重新拟合后得到:
| 训练样本数 | 泛化差距 | ||
|---|---|---|---|
| 2 | 0.00 | 3.00 | 3.00 |
| 4 | 0.10 | 1.50 | 1.40 |
| 6 | 0.25 | 0.80 | 0.55 |
训练样本很少时,模型可以穿过每个训练点,所以训练误差为 0,却无法预测两点之间的位置。加入更多样本后,约束变多,训练误差反而略升;与此同时,验证误差和泛化差距下降。
这说明一个容易误判的事实:训练误差略微变差,不一定是退步;它可能意味着模型不再只服务于少量样本。
交互判断:看到最后两条曲线仍未汇合,应该先加数据还是先减小模型?
若验证误差随 仍明显下降,新增同分布、可靠标注的数据值得尝试;若成本很高,也可以同时测试减小容量或加强正则化。曲线提供诊断证据,不会替代成本与任务约束。
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]textimport numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold, learning_curve
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
rng = np.random.default_rng(42)
X = np.linspace(-3, 3, 120, dtype=np.float64).reshape(-1, 1)
y = np.sin(X[:, 0]) + rng.normal(0.0, 0.20, size=X.shape[0])
model = make_pipeline(
PolynomialFeatures(degree=8, include_bias=False),
StandardScaler(),
LinearRegression(),
)
cv = KFold(n_splits=5, shuffle=True, random_state=42)
sizes, train_scores, validation_scores = learning_curve(
estimator=model,
X=X,
y=y,
train_sizes=np.linspace(0.2, 1.0, 5),
cv=cv,
scoring='neg_root_mean_squared_error',
shuffle=True,
random_state=42,
n_jobs=-1,
)
train_rmse = -train_scores.mean(axis=1)
validation_rmse = -validation_scores.mean(axis=1)
validation_std = validation_scores.std(axis=1)
for m, tr, va, spread in zip(
sizes, train_rmse, validation_rmse, validation_std
):
print(f'{m:3d} train={tr:.3f} validation={va:.3f} ± {spread:.3f}')python关键 API 的语义:
PolynomialFeatures(degree=8, include_bias=False):把单特征 展开为 ;不额外生成常数列,因为线性回归会拟合截距。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 常见错误与最小调试法#
- 用测试集画学习曲线。 曲线会参与模型决策,应只使用训练/验证开发数据。
- 不同训练规模使用不同训练预算。 小数据训练 100 轮、大数据只训练 100 步,会混入优化不足;记录每个规模的收敛状态。
- 预处理在交叉验证外拟合。 标准化、特征选择和填补必须放入 Pipeline。
- 把准确率差距直接当误差差距。 分数越大越好、损失越小越好;先统一方向再解释曲线。
- 只画一个随机划分。 小数据中一次划分方差很大,应报告多折均值和波动。
- 训练子集不具代表性。 有序数据直接取前缀,可能让小规模点只包含某一类别;需要正确洗牌或结构化采样。
最小检查清单:
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 今天真正需要记住什么?#
- 训练误差衡量拟合已有样本的能力,验证误差衡量同一参数的迁移能力;二者缺一不可。
- 欠拟合常表现为训练与验证误差都高且接近;过拟合常表现为训练误差低、验证误差高且差距大。
- 学习曲线通过改变训练样本数并反复重新拟合,判断更多数据是否仍可能缩小泛化差距。
- 高训练误差也可能来自优化或数据故障,诊断容量之前先验证训练管道。
- 测试集不参与画曲线,预处理必须留在每个交叉验证折内部。
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 在小数据区域更容易过拟合。
相关工作#
- Vapnik: The Nature of Statistical Learning Theory ↗:从容量控制与泛化角度建立统计学习理论基础。
- Geman, Bienenstock & Doursat: Neural Networks and the Bias/Variance Dilemma ↗:系统讨论学习系统中的偏差与方差权衡。
- Domingos: A Unified Bias-Variance Decomposition ↗:将偏差—方差分析推广到更一般损失的代表性工作。
- scikit-learn: Learning curve ↗:当前官方函数签名、返回形状与交叉验证语义。
- scikit-learn: PolynomialFeatures ↗:当前多项式特征生成规则与高次数过拟合提示。
11 下一篇预告#
学习曲线能指出“训练很好、验证很差”,却不会自动修复它。下一篇将给经验风险加上一项对大权重的惩罚,手算 L2 正则化如何收缩参数,并看清岭回归为什么能让高次多项式不再追逐每一个噪声点。