机器学习模型训练完以后,最常见的困惑是:训练集分数很高,测试集分数却很低;或者训练集和测试集分数都不高,怎么调都上不去。这两个问题通常对应过拟合和欠拟合。
这篇文章用可运行的 Python 示例解释过拟合、欠拟合和模型调优。读完以后,你应该能通过训练分数和验证分数判断问题类型,并知道优先从数据、模型复杂度、正则化和交叉验证哪一步下手。
如果你刚刚完成 模型训练与评估入门,这篇就是下一步:从“会看指标”进入“会诊断模型”。
一、过拟合和欠拟合是什么意思
过拟合 是模型把训练数据里的细节甚至噪声也记住了。表现是训练集分数很高,但验证集或测试集分数明显低。
欠拟合 是模型太简单,连训练数据里的主要规律都没学好。表现是训练集分数低,验证集分数也低。
可以用一句话记住:
- 训练集高、验证集低:优先怀疑过拟合
- 训练集低、验证集低:优先怀疑欠拟合
- 训练集和验证集都高:模型当前比较健康
二、用决策树演示模型复杂度
决策树很适合演示过拟合,因为树越深,越容易记住训练样本里的细节。下面的代码用不同深度的决策树比较训练集和验证集准确率。
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.25,
random_state=42,
stratify=y,
)
for depth in [1, 2, 3, 4, 6, 10, None]:
model = DecisionTreeClassifier(max_depth=depth, random_state=42)
model.fit(X_train, y_train)
train_score = model.score(X_train, y_train)
test_score = model.score(X_test, y_test)
print(f"max_depth={depth}: train={train_score:.3f}, test={test_score:.3f}")
保存为 decision_tree_depth_demo.py 后运行:
python decision_tree_depth_demo.py
你通常会看到浅树训练分数不高,说明表达能力有限;树越来越深时,训练分数会上升,但测试分数不一定继续提升。这就是模型复杂度带来的典型权衡。
三、如何判断是过拟合还是欠拟合
不要只看一个测试集分数。更可靠的诊断方式是同时记录训练分数和验证分数。
- 欠拟合:训练分数低,验证分数也低,说明模型没学到足够规律
- 过拟合:训练分数高,验证分数低,说明模型记住了训练数据细节
- 数据不足:训练分数波动大,验证分数不稳定,可能需要更多样本或交叉验证
- 指标选错:accuracy 很高但业务效果差,可能类别不平衡或错误成本不同
这也是为什么 机器学习完整流程 里要强调训练集、验证集和测试集拆分。没有可靠评估,就无法判断模型到底出了什么问题。
四、过拟合怎么解决
过拟合的核心问题是模型太容易记住训练数据。常见解决方向如下:
- 降低模型复杂度:限制树深度、减少特征数量、使用更简单模型
- 增加正则化:线性模型调大正则强度,神经网络可加 weight decay 或 dropout
- 增加数据量:更多样本能减少模型记住偶然噪声的机会
- 做数据增强:图像、文本、语音任务中尤其常见
- 交叉验证:减少只依赖一次划分带来的偶然性
- 提前停止:验证集效果不再提升时停止训练
如果是树模型,先限制 max_depth、min_samples_leaf 和 min_samples_split。如果是逻辑回归,先调正则化参数 C。
五、欠拟合怎么解决
欠拟合的核心问题是模型表达能力不够,或者输入特征没提供足够信息。常见解决方向如下:
- 换更强模型:从线性模型换到树模型、集成模型或神经网络
- 增加有意义特征:补充领域变量、交互特征或时间窗口特征
- 减少过强正则化:正则化太强会让模型学不动
- 训练更久:深度学习任务中,训练轮数太少可能欠拟合
- 检查标签质量:标签噪声严重时,模型很难学到稳定规律
欠拟合不是简单地“把模型调大”。如果原始特征没有信息,模型再复杂也只能学习噪声。
六、用交叉验证减少误判
一次训练测试拆分可能刚好碰到容易或困难的样本,导致判断不稳定。交叉验证可以让模型在多次不同划分上评估,结果更可靠。
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeClassifier
X, y = load_breast_cancer(return_X_y=True)
for depth in [2, 3, 4, 6, None]:
model = DecisionTreeClassifier(max_depth=depth, random_state=42)
scores = cross_val_score(model, X, y, cv=5, scoring="accuracy")
print(f"max_depth={depth}: mean={scores.mean():.3f}, std={scores.std():.3f}")
如果要单独验证交叉验证结果,可以把这一段保存为 tree_cross_validation_demo.py 后运行:
python tree_cross_validation_demo.py
mean 代表平均表现,std 代表不同划分之间的波动。平均值高但波动也很大时,说明模型稳定性仍然需要检查。
七、诊断表:先判断问题类型再调参
调参之前,先把训练分数、验证分数和交叉验证波动放在一起看。下面这张表可以避免把所有问题都误解为“模型不够强”。
| 观察结果 | 更可能的问题 | 优先动作 | 不要先做什么 |
|---|---|---|---|
| 训练高,验证低 | 过拟合 | 限制复杂度、增加正则、交叉验证 | 继续加深模型 |
| 训练低,验证低 | 欠拟合或特征无效 | 增加有效特征、降低正则、换更强基线 | 只调随机种子 |
| 均值高,std 也高 | 划分不稳定 | 增加样本、分层抽样、分组交叉验证 | 只报告最好一次结果 |
| accuracy 高,关键类 recall 低 | 指标不匹配或类别不平衡 | 查看混淆矩阵、macro F1、每类指标 | 只看总体准确率 |
八、一个实用调优顺序
不要一上来就大范围网格搜索。更稳的调优顺序是:
- 先确认数据拆分没有泄漏。
- 训练一个简单基线模型。
- 记录训练分数和验证分数。
- 判断是欠拟合还是过拟合。
- 只改一类因素,例如模型复杂度或正则化。
- 用交叉验证确认提升不是偶然结果。
- 最后用独立测试集做一次最终评估。
这个顺序比“同时改模型、特征、参数、数据划分”更容易复盘。机器学习调优最怕不可解释的提升,因为你不知道下次能不能复现。
验证分数比训练分数还高,是怎么回事
上面的判据都建立在「训练分数高于验证分数」这个常态上。但实际训练时经常会遇到反过来的情况:验证集的分数比训练集还好。这看起来像是好事,其实几乎总是某个地方不对,值得逐一排查。
最常见的原因:训练时开着正则化,评估时关掉了。Dropout 在训练期随机丢弃神经元,模型是「带着残缺」在做预测;到了验证阶段 dropout 关闭,全部神经元参与,模型反而更强。同理,训练时做了数据增强(随机裁剪、翻转、加噪),模型面对的是被扰动过的样本,而验证集是干净的。
这种情况不是问题,是预期行为。判断方法是看训练时记录的是哪个数字——如果训练损失是「带 dropout 和增强的损失」,它和验证损失本来就不可比。要比较的话,应该在关闭增强的模式下再跑一遍训练集。
第二种:验证集太小。只有一两百条样本时,验证分数的随机波动可以轻松达到几个百分点。这时候「验证比训练高」可能纯属偶然,换一个随机种子就反过来了。判断方法是换几个不同的划分种子重跑,看这个现象是否稳定出现。
第三种:划分不当,验证集恰好更简单。如果数据里存在分组结构(同一个人的多张照片、同一批次的多条记录),随机划分可能把某些「容易」的样本集中到了验证集。这种情况下验证分数偏高是虚假的,模型上线后会掉下来。
第四种,也是最需要警惕的:数据泄漏。如果验证集的信息以某种方式进了训练过程——比如在划分之前做了标准化或缺失值填充——模型对验证集有了「先验」,分数自然偏高。这一类的详细排查见机器学习完整流程里关于数据泄漏的部分。
排查顺序建议是:先确认是不是第一种(看有没有 dropout 或增强),这一步能解释掉大多数情况;排除之后再依次查划分和泄漏。不要因为「分数好看」就跳过这个排查——第三、第四种带来的高分是会在上线时消失的。
九、常见问题 FAQ
训练准确率 100% 一定是好事吗?
不一定。如果验证集或测试集明显低,训练准确率 100% 反而可能说明模型过拟合。要同时看训练分数和验证分数。
测试集分数低一定是模型不够强吗?
不一定。也可能是数据泄漏修正后分数回落、标签质量差、训练测试分布不同,或者评估指标不适合当前任务。
过拟合时应该删特征吗?
可以考虑,但不要盲删。优先检查特征是否泄漏未来信息、是否重复表达同一信号、是否只在训练数据里有效。保留有业务含义且稳定的特征。
交叉验证能替代测试集吗?
不能。交叉验证适合模型选择和调参,最终仍建议保留一个独立测试集,用来估计模型在未参与选择的数据上的表现。
十、下一步阅读
过拟合和欠拟合是机器学习调优的核心诊断方法。继续学习可以回到 Python 人工智能小实战,把这套诊断方法应用到完整 scikit-learn 分类流程里。