机器学习进阶(15):过拟合
第十五篇过拟合到底是怎么发生的——为什么训练集表现很好模型却不一定真懂了学机器学习的人几乎都会在某个时刻碰到一种非常熟悉的场景。你训练了一个模型。训练集上的效果特别好甚至好得让你有点兴奋。比如准确率 99%损失几乎降到底看起来模型已经把任务吃透了然后你把它拿去跑验证集或者测试集。结果一下子掉下来。有时候是从 99% 掉到 85%。有时候甚至更夸张。这个时候很多人第一反应是是不是验证集太难了是不是运气不好是不是这次数据切得有问题这些可能性当然不是完全没有。但更常见的情况其实是模型过拟合了。这个词你可能已经见过很多次。但如果只把它记成“训练集好测试集差”其实还不够。因为这只是现象不是理解。这一篇我们就把它拆开讲清楚过拟合到底是什么它为什么会发生模型到底是在“学规律”还是“记细节”你怎么判断自己是不是过拟合了为什么模型越复杂有时反而越危险1. 先别急着背定义先看一个特别像学生复习的类比如果你把机器学习模型看成一个学生过拟合这件事其实特别好理解。假设这个学生明天要考试。他手里有一套练习题。如果他真正理解了知识点那他面对新题也能做出来。这对应的是模型真正学到了规律有泛化能力。但如果他只是把练习题和答案背下来了会发生什么练习题他会做得特别好。可一换题型成绩马上掉。这就是过拟合最接近本质的地方模型不是没有学而是学得太贴着训练数据了。它学到的不只是规律还把训练数据里的很多局部细节、偶然模式、甚至噪声也一起当成了“规律”。所以一旦换到新数据上这些“假规律”就开始失效。2. 过拟合最表面的现象是什么过拟合最典型的表面现象就是训练集表现很好验证集 / 测试集表现明显差很多比如分类任务里你可能看到训练集准确率99%验证集准确率84%回归任务里你可能看到训练集 MSE 很低验证集 MSE 明显更高这时候你就应该警觉模型可能不是在真正学习可以推广的规律而是在记住训练集本身。注意这里不是说“训练集好”本身有问题。训练集效果好当然是我们想要的。问题在于训练集好到什么程度和验证集之间差了多少。如果差距很大才说明你可能碰到了过拟合。3. 模型到底是在学什么为什么会“学偏”这是理解过拟合最关键的一步。任何一个模型在训练时本质上都在做同一件事尽量让自己在训练数据上的误差变小。注意是“训练数据”。模型本身并不知道你真正想要的是“新数据也表现好”。它只知道当前损失函数是根据训练集算出来的所以它会拼命优化训练集上的结果。如果模型能力不够强它可能连训练集都学不好。这是欠拟合。但如果模型能力很强而且你又没有限制它它就可能会走向另一头它不只是学到整体规律还会把训练集里那些很偶然、很局部的东西也吃进去。比如某几个异常样本某些偶然同时出现的特征组合样本里的噪声训练集特有的一些小偏差这些东西在训练集上当然能帮它把分数做高。但它们对新数据并不一定成立。这时候模型看起来很努力实际上却在往错误的方向“钻细节”。4. 一个特别直观的例子拟合曲线时线太弯了过拟合在回归问题里特别容易想象。假设你有一批数据点整体趋势大概是一条平滑上升的曲线。如果你画一条合适的曲线它大概能抓住整体走势。但如果你给模型太大的自由度它可能会做什么它会想尽办法穿过每一个训练点。最后画出来的线可能弯弯绕绕像在“追着点跑”。你看起来会觉得哇训练点都贴得好准但换一批新点这条线的预测可能反而很差。因为它拟合的不是“整体趋势”而是“这些点刚好长成的样子”。这就是过拟合很经典的画面模型为了把训练集做到极致学得太细了。5. 在分类问题里过拟合又会长什么样分类任务里过拟合虽然不像回归那样容易“看到一条奇怪的曲线”但本质是一样的。假设两类样本大致能被一个比较平滑的边界分开。如果模型过拟合它可能会做什么它会把分类边界弄得特别弯、特别碎、特别贴着训练样本走。哪怕只是为了把几个边缘点、几个异常点也分对。这样做的结果是训练集几乎全对但边界变得很脆弱稍微来一点新样本分布变化就容易出错所以过拟合在分类里本质上就是边界开始服务于训练集的局部细节而不再服务于整体规律。6. 为什么模型越复杂越容易过拟合这个结论你可能已经听过很多次模型越复杂越容易过拟合。但这句话如果只停在口号层面其实帮助不大。我们还是得把它说成人话。所谓模型复杂不一定只是“算法名字更高级”。它更像是模型有多大的自由度去贴合训练数据。比如一条直线自由度比较低一个高阶多项式自由度更高一棵很深的决策树自由度很高一个参数很多的模型自由度也更高自由度高的好处是它更有能力捕捉复杂关系。但问题也在这里如果你不给它约束它也更有能力去记住噪声、例外、偶然模式。所以复杂模型不是原罪。真正的问题是当模型复杂度超过了数据真正需要的程度它就开始用多出来的能力去“记训练集”。这时候就容易过拟合。7. 这也是为什么“训练得越久”有时反而越危险很多人刚接触训练过程时会下意识觉得训练轮数越多模型应该越好吧不一定。特别是一些可以持续迭代优化的模型比如神经网络、GBDT、甚至某些 boosting 类模型训练初期通常会先学到比较稳定的整体规律。但如果你让它一直继续学它后面可能会开始去适应训练集里的噪声和细枝末节。结果就是训练集损失继续下降但验证集表现不升反降这其实也是过拟合的一种典型轨迹。所以并不是“训练得越狠越好”而是训练要刚好停在模型已经学到主要规律、但还没开始迷恋训练集细节的位置。这也是为什么后面你会看到“早停early stopping”这种方法。8. 数据少的时候为什么特别容易过拟合这个点也特别现实。如果数据量很少模型能看到的世界本来就有限。在这种情况下训练集里的偶然性会显得特别强。比如你只有几百条数据。这几百条里某些巧合、偏差、异常点本来只是样本太少带来的随机波动。但模型可能会误以为它们是规律。所以数据少的时候模型更容易把偶然现象当规律对训练集学得过于具体在验证集上掉分这也是为什么小数据场景下要更小心模型复杂度更要依赖交叉验证更要警惕“训练集太漂亮”的结果9. 噪声为什么会加剧过拟合过拟合很多时候不是模型“太笨”反而是模型“太认真”。只要训练集里有噪声比如标签标错了某些特征值填错了样本本身就是异常点数据采集过程有误差模型如果太有能力它就可能会去努力解释这些噪声。问题是噪声本来就不是真规律。你越努力把它学进去越会损害对新数据的泛化能力。所以有时候你会看到一种很微妙的情况一个复杂模型在训练集上比简单模型更好但验证集却反而不如简单模型这往往不是因为复杂模型“不够强”而是因为它强到连噪声都没放过。10. 过拟合不是某一种算法的问题而是一种普遍风险很多人一开始会误以为决策树容易过拟合神经网络容易过拟合所以是不是某些算法天生就“坏一点”其实不是这样。过拟合不是某个特定算法的专利而是几乎所有模型都会面临的风险。只不过不同算法表现形式不同严重程度不同。比如决策树树太深的时候特别容易把训练样本切得特别细直接记住数据。随机森林相比单棵树稳很多但如果数据有问题、特征很多、设置不合理也不是完全不会过拟合。GBDT因为它是一棵棵树不断纠错所以如果树太多、学习率太激进也会慢慢贴训练集太紧。SVM如果参数设置得过于激进比如C和gamma太大也会把边界搞得非常复杂。神经网络参数量大、训练轮次多时更是典型的高过拟合风险模型。所以你最好建立一个意识过拟合不是“哪个算法有问题”而是“模型能力、数据规模、噪声水平、训练方式”共同作用下的一种结果。11. 怎么判断自己是不是过拟合了这部分一定要写因为它最贴近读者真实操作时的困惑。最常见的判断方法有几个。第一看训练集和验证集的差距这是最直接的。比如训练集 99%验证集 84%或训练损失很低但验证损失明显更高这种明显差距通常就是危险信号。第二看模型复杂度变化后结果怎么变比如你不断增加树深树数量特征维度多项式阶数如果发现训练集持续变好但验证集在某个点之后开始变差那通常就是开始过拟合了。第三看训练过程中的曲线如果你记录训练集和验证集的损失/准确率曲线常见的过拟合迹象是训练集持续变好验证集先变好后变差这通常说明模型一开始学到了规律后面开始记细节了。12. 欠拟合和过拟合到底差在哪这两个词特别容易一起出现所以最好放在一块讲清楚。欠拟合underfitting模型太简单或者学得不够。结果是训练集表现都不好验证集也不好这说明模型连训练数据里的主要规律都没抓住。过拟合overfitting模型太贴训练集。结果是训练集很好验证集明显差这说明模型抓住了太多训练集特有的细节泛化能力下降。你可以把它们理解成两个极端欠拟合学不进去过拟合学得太进去真正理想的状态是模型复杂度刚刚好。它既能抓住主要规律又不至于把噪声当真。对训练集来说模型越复杂训练误差通常越低。因为它越来越有能力去贴训练数据。对验证集来说一开始模型变复杂验证误差会下降。因为模型学到了更多真实规律。但到某个点之后验证误差会开始上升。因为模型开始过拟合。所以验证误差曲线通常像一个 U 型。这个最低点就是你真正想找到的那个“复杂度刚刚好”的位置。这也是为什么调参不是越大越好、越深越好而是要找到一个平衡点。举个直观的例子我们用一个非常经典的方式来展示过拟合用多项式回归去拟合一条本来很简单的曲线。直观现象会是模型复杂度低 → 拟合不够欠拟合模型复杂度刚好 → 拟合合理模型复杂度过高 → 开始贴着训练点乱拐过拟合importnumpyasnpimportmatplotlib.pyplotaspltfromsklearn.preprocessingimportPolynomialFeaturesfromsklearn.linear_modelimportLinearRegressionfromsklearn.pipelineimportmake_pipeline# 构造数据np.random.seed(42)Xnp.linspace(0,10,20)ynp.sin(X)np.random.normal(0,0.2,20)XX.reshape(-1,1)# 创建三种复杂度模型models{degree1 (欠拟合):make_pipeline(PolynomialFeatures(1),LinearRegression()),degree3 (较好):make_pipeline(PolynomialFeatures(4),LinearRegression()),degree12 (过拟合):make_pipeline(PolynomialFeatures(12),LinearRegression())}# 画图X_plotnp.linspace(0,10,200).reshape(-1,1)plt.figure(figsize(8,6))plt.scatter(X,y)forlabel,modelinmodels.items():model.fit(X,y)y_plotmodel.predict(X_plot)plt.plot(X_plot,y_plot,labellabel)plt.legend()plt.title(欠拟合 vs 正常拟合 vs 过拟合)plt.show()这个例子说明模型复杂度越高不一定越好。当模型复杂度超过数据本身需要的程度它就开始用额外的自由度去拟合噪声。所以过拟合不是模型不努力而是努力过头了。14. 为什么过拟合本质上是“泛化能力出了问题”说到底机器学习真正关心的从来都不是你在训练集上能不能做到接近满分。真正关心的是你学到的东西能不能推广到没见过的新数据上。所以过拟合本质上不是一个“训练技巧问题”它是一个泛化问题。模型一旦过拟合就说明它把太多精力花在了训练集的专属细节上而没有学到足够稳定、可迁移的规律。这也是为什么你后面会发现很多看起来不一样的方法——比如正则化交叉验证剪枝Dropout数据增强早停它们最后都在服务同一件事降低过拟合提高泛化能力。15. 这一篇讲到这里你最该建立起来的感觉是什么我觉得过拟合最容易被误解的一点是很多人会把它理解成“模型犯了个错”。但其实不是。过拟合更像是模型过于忠诚地执行了训练目标。它拼命想把训练集做到最好结果好过头了。所以你可以说过拟合不是模型偷懒而是模型太认真认真到把训练集里不该学的东西也学进去了。这个理解一旦建立起来后面很多东西都会自然很多。比如你再去看为什么树要剪枝为什么 SVM 的 C 不能太大为什么 GBDT 不能一味堆树为什么神经网络会用早停和正则化你就会发现它们其实都在回答同一个问题怎么让模型别对训练集太上头。