跑机器学习项目,模型效果上不去,最让人崩溃的不是模型复杂,而是你不知道该动哪里。我见过太多新手一上来就抱着超参数搜索工具猛跑,调了一天,验证集分数纹丝不动,最后发现是数据里有脏标签。这种事搁谁身上都难受,但确实也是每个做模型调优的人都会经历的弯路。
说实话,机器学习里的经典调优方法并不神秘,翻来覆去就是数据、模型复杂度、训练过程、超参数、验证这几块。真正的难点从来不是某个技巧多高深,而是你不知道当前这个模型到底卡在哪一层,该用哪把钥匙。这篇文章我就按自己平常排障的顺序,把这些经典方法串起来讲一遍,尽量让刚入门的人也能照着做,而不是零散地背一堆名词。
1. 先别急着调参,用两条曲线判断模型到底卡在哪
1.1 你可能搞混了欠拟合和过拟合的信号
我一再跟身边人强调,调优的第一步不是调参,是诊断。好多同学拿着验证集准确率低的结果来问我,第一句话就是“要不要把学习率调小一点”,我通常会反问一句:你的训练集准确率高不高?
这个问题的答案直接把问题分成两类。如果训练集上准确率也低,那模型大概率是欠拟合,也就是容量不够或者学习不充分;如果训练集上准确率很高,验证集上却很低,那才是过拟合,也就是泛化能力差。这两种情况的处理方向几乎完全相反。欠拟合你得加容量、减正则、加训练轮数;过拟合你得减容量、加正则、上数据增强。方向搞反了,再怎么调都是白费力气。
所以我的固定习惯是:任何模型在跑超参数搜索之前,先记录训练误差和验证误差随训练轮数的变化曲线。曲线是模型状态最直接的证据,比任何抽象指标都诚实。
1.2 手把手画一条学习曲线
这里的“学习曲线”有两种理解。一种是横轴为训练样本数量,纵轴为训练/验证分数,用来看模型是否还有数据红利;另一种是横轴为训练轮数,纵轴为损失或指标,用来看拟合动态。我自己做项目时两者都会看。
用 scikit-learn 画第一种曲线很直接,代码大概长这样:
python复制import numpy as np
from sklearn.model_selection import learning_curve
from sklearn.ensemble import RandomForestClassifier
import matplotlib.pyplot as plt
train_sizes, train_scores, val_scores = learning_curve(
RandomForestClassifier(n_estimators=100, random_state=42),
X_train, y_train,
train_sizes=np.linspace(0.1, 1.0, 8),
cv=5,
scoring='accuracy',
n_jobs=-1
)
train_mean = train_scores.mean(axis=1)
val_mean = val_scores.mean(axis=1)
plt.plot(train_sizes, train_mean, label='train')
plt.plot(train_sizes, val_mean, label='validation')
plt.xlabel('training set size')
plt.ylabel('accuracy')
plt.legend()
plt.show()
读这个图有个经验规律。如果两条曲线在右侧仍然没有收拢趋势,训练分数一直高于验证分数,说明模型当前的瓶颈是方差,多给数据大概率有用;如果两条曲线都停在比较低的位置并且几乎平行,说明问题出在模型容量或特征表达上,给再多数据也只是原地踏步。
所以我看到很多人一上来就疯狂收集数据,其实应该先画这张图,让数据自己告诉你它是不是关键瓶颈。
1.3 根据诊断结果选择正确的“第一刀”
把诊断落实到行动上,我习惯用一张对照表来辅助决策:
| 现象 | 判定 | 优先操作 |
|---|---|---|
| 训练误差高、验证误差也高 | 欠拟合 / 高偏差 | 增大模型容量、增加特征、减少正则、提高训练轮数 |
| 训练误差低、验证误差高 | 过拟合 / 高方差 | 增大数据量、增加正则、早停、降复杂度 |
| 训练误差低、验证误差也低但不达标 | 特征天花板 | 做特征工程、换模型家族、考虑集成 |
| 训练误差波动很大 | 优化不稳定 | 调学习率、换优 |
