集成学习入门:Bagging、Boosting与Stacking的核心原理与实战选择 做数据分析这几年我见过太多模型调参到怀疑人生的时刻。不管你怎么换特征、怎么做归一化单个模型的结果就是卡在一个位置不动了——后来我慢慢明白那很可能不是模型不够努力而是单个模型的“视角”天生有盲区。真正让结果再上一个台阶的往往不是把某个模型磨到极致而是集成学习把一群模型组合起来让它们互相补位。这篇文章是集成学习系列的基础篇把最核心的知识框架一次性搭好为什么集成有效、Bagging、Boosting、Stacking三大流派怎么分工、偏差方差到底在说什么以及实际使用时最容易踩的坑。这一节我们先打好地基后面几节再逐个展开具体算法和实战项目。1. 单模型的瓶颈为什么“一个人”赢不了1.1 一个模型不够用通常卡在三种毛病上单个模型效果不好翻来覆去逃不过三种情况欠拟合、过拟合、结果不稳定。欠拟合就是模型学不动。表达能力太弱比如拿线性模型去拟合强非线性的数据或者特征里本身信息不足模型看到了也学不进去。欠拟合最直观的表现是训练集和测试集误差都偏高两头不讨好。过拟合则完全相反。模型太“较真”把训练数据里的随机噪声都背了下来训练集上表现漂亮一换新数据就崩。这种模型本质上是把训练集里的偶然当成了规律方差极大预测结果会随着训练集变动而剧烈波动。还有一种是“不上不下但来回晃”的情况。模型看起来有七八十分但每次重新训练结果都在上下浮动。这类模型通常还不至于过拟合但稳定性堪忧属于方差偏高。实际建模时很多人只关注“平均分数”忽略了“换一批数据分数漂不漂”结果模型上线后表现忽高忽低。问题主要现象对应的误差成分欠拟合训练集和测试集误差都偏高偏差起主导过拟合训练集误差低、测试集误差高方差起主导不稳定每次重训结果差异明显方差起主导1.2 群体决策为什么会比个体更可靠生活中的“集成思想”其实无处不在。比如一群朋友聚餐选餐厅一个人定很容易被自己的口味偏好带偏让几个口味不同、关注点也完全不同的人一起讨论最后的选择往往更均衡。集成学习也是一样的道理每个模型相当于一个决策者它有自己擅长的模式和固有的盲区组合起来就能互相纠偏。但组合不是随便堆。要让群体真正发挥效果一个关键前提被很多人忽略成员要“好而不同”。好指每个模型至少不能太差不同指模型犯的错误最好不要集中在同一批样本上。如果两个模型总是在同一个地方犯错组合起来就没有任何进步。做个简单的数学直觉测试。假设我们有若干个完全独立的分类器每个的准确率只有60%用多数投票来决定最终结果准确率会是多少我算过几个常见规模的结果集成规模单个准确率多数投票后准确率3个60%64.8%5个60%68.3%7个60%71.0%以5个为例多数正确至少需要3个人答对三对人的概率是10 * 0.6³ * 0.4²约等于0.3456四对人约0.2592五人对约0.0778合计约0.6826。一群60%的“普通人”通过投票可以逼近70%这正是集成学习威力的最直接证明。当然现实里的模型不可能完全独立效果没有理论上这么夸张但方向是对的。2. 三大流派的分水岭Bagging、Boosting与Stacking2.1 Bagging并行干活各练各的Bagging是Bootstrap Aggregation的简称核心动作是“自助采样”。训练每个基学习器时从原始数据里有放回地抽取一份和原始数据集大小相同的样本集每个模型用自己那份样本集训练。因为有放回不同模型拿到的数据会有重叠但不完全相同。一个样本在n次独立抽取中一次都没被抽到的概率大约是36.8%也就是说每个模型大约只能看到63.2%的原始样本剩下约36.8%的样本没有参与该树的训练这部分被称为袋外样本可以当作白送的验证集。Bagging的训练过程天然适合并行因为每个基模型互不依赖谁也不用等谁。组合方式也很简单分类问题多数投票回归问题取平均。为什么Bagging有效前面说过多个独立模型的预测取平均后方差会显著缩小Bagging正是通过数据扰动让基模型之间尽可能不相关从而把整体方差压下来。它特别适合那种“方差大、不稳定”的模型典型代表就是不剪枝的长决策树。2.2 Boosting串行修炼专攻短板Boosting的思路和Bagging完全相反。它一批一批地训练模型先训练第一个然后根据第一个模型的错误调整训练重点让下一个模型更关注之前做错的样本训练完第二个后再继续如此反复迭代最后把每轮的成果按权重组合起来。因为后一个模型必须等前一个训完才能接着做Boosting本质上是串行的不像Bagging那样可以轻松并行化。Boosting最大的意义是“从弱到强”单个弱学习器只要比随机猜测好一点经过多轮迭代加权组合后的模型可以被推得很强。AdaBoost、GBDT以及工业界主流的梯度提升树实现都是这条路的产物。2.3 Stacking让不同类型的模型学会分工Stacking和Bagging、Boosting的底层逻辑都不一样。它不靠同一类模型的“重复劳动”而是先用多个不同类型的模型当底层比如线性模型、近邻法、树模型、核方法混合着上然后把它们的预测结果拼成新的特征交给一个第二层模型。第二层模型学习的不是具体规律而是“什么时候该相信哪个底层模型”。它能看到每个模型在哪些样本上靠谱、在哪些样本上不靠谱从而学出一套权重或规则。这个思路很灵活但有一个非常致命的坑第二层的输入特征必须来自交叉验证不能直接用训练集上的预测。否则第一层模型早就把训练集背下来了喂给第二层的特征几乎保真第二层当时学得很开心一到新数据就打回原形。2.4 实际选型时怎么挑流派三者的对照关系可以这样记维度BaggingBoostingStacking训练方式并行串行分层两阶段主要作用降低方差降低偏差融合异质模型对基模型要求强一些更好弱模型也够用多样性更重要典型代表随机森林系列AdaBoost、GBDT族两层组合模型并行难度容易较难中等一个实用的判断题如果当前模型“一换数据就飘”优先试Bagging如果当前模型“怎么调都不够准、像欠拟合”优先试Boosting如果手头已经有几个擅长不同规律的模型而且它们的错误恰好互补用Stacking把它们串起来。这不是铁律但按这个方向去试往往比乱套模板快得多。3. 偏差-方差分解解释“为什么集成会赢”3.1 误差可以拆成三块用打靶来理解偏差和方差很直观靶心是真实规律弹着点是模型的预测。偏差高说明弹着点整体偏向某个方向系统性地不准方差高说明弹着点非常分散虽然平均位置可能还行但每一枪落在哪里很飘。更形式化一点对任意样本x模型预测h(x)与真实标签y的期望平方误差可以分解成三块期望平方误差 偏差的平方 方差 噪声方差第一项是模型平均预测与真实规律的系统性偏离第二项是模型围绕自身均值波动的程度第三项是数据本身不可去除的随机噪声。模型能优化和干预的只有前两项。很多人调参时总想干掉所有误差但噪声那部分无论如何都躲不掉不要在这上面浪费精力。3.2 Bagging为什么主要压方差假设有m个独立同分布、方差为σ²的模型取平均之后整体方差会变成σ²除以m。模型之间的相关性越低这个压缩效果越明显。Bagging通过自助采样让每棵树看到的样本不同通过随机特征让每次分裂的属性不同本质上都是为了把树和树之间的相关性按下去。所以随机森林类方法特别适合那种“单棵树很生猛、但也很激进”的基模型。决策树如果完全不剪枝、长到很深偏差低而方差高这恰好是Bagging的主场。反过来如果基模型本身已经又稳又准Bagging的提升空间就很有限。3.3 Boosting为什么主要压偏差Boosting每一轮都在做什么训练集没有变但模型在逐步往目标函数上贴。拿GBDT来说下一棵树拟合的是当前模型的负梯度方向翻译成人话就是“现在哪里误差还大我就集中火力补哪里”。这意味着Boosting是一个不断压缩偏差的过程模型从最初的欠拟合状态被一步一步推向拟合。这也解释了为什么Boosting在偏差主导的场景里收益最大——比如你手里只有很浅的决策树它们单看都太“笨”了但经过多轮迭代之后可以被补成强模型。3.4 偏差和方差的此消彼长模型调复杂偏差下降方差上升调简单方差下降偏差上升。这就是机器学习中最经典的跷跷板。单个模型能做的只是在某个中间位置寻找相对合脚的平衡点。集成学习高明的地方在于它提供了一条绕开跷跷板的路径不强行在单个模型上找最优平衡而是用多个“偏科”的模型互相搭配。Bagging在保住低偏差的前提下把方差压下来Boosting在保住低方差的前提下把偏差降下去。理解到这个程度你回头看调参会清楚自己每一步到底在动哪一头。4. 随机森林的“双随机”机制与使用心得4.1 样本随机加特征随机随机森林是Bagging配CART树的产物但它的“随机”有两层。第一层是样本随机就是我们前面讲的自助采样。每棵树都有自己的训练集样本不完全一样学到的规律自然有差异每棵树大约有36.8%的样本没参与训练这些袋外样本可以用来做内置评估相当于白送一个验证集省时间。第二层是特征随机。每次构造树节点分裂时不是从全部特征里找最优切分而是先随机抽一小撮特征作为候选再从那几个候选特征里挑最优。分类任务里候选特征个数通常取特征总数的平方根回归任务里常用三分之一左右。第二层随机常常被忽略但其实特别关键。如果某个强特征一直存在几乎每棵树都会优先拿它分裂最后所有树都长得像兄弟姐妹Bagging的方差压缩效果就会大打折扣。只有双随机同时起作用才能既保证每棵树有一定质量又保证树和树之间足够有差异。4.2 树该长多深随机森林里的树通常主张“长得很深不做太多剪枝”。原因是单棵树已经通过随机扰动控制住了相互之间的相关性方差主要靠大量树的平均来吸收如果一上来就把每棵树限制得很浅树与树之间的差异确实会更明显但单棵树的偏差也同时变大了集成效果未必更好。我的实际操作习惯是先把树的深度放开看看随机森林的整体表现确认过拟合明显之后再去限制叶节点最小样本数或树深度而不是一开始就剪枝成型。在大多数开源机器学习库里随机森林的默认参数通常已经比较能打优先调“特征随机范围”往往比调深度性价比更高。4.3 随机森林在实战里的定位随机森林在结构化数据上一直是非常稳的选择。它对异常值相对不敏感因为单棵树的极端结果会被平均过程冲淡它也不需要精细到苛刻的调参几百棵树组合起来线上表现通常不会太差。它附带的一大福利是直接输出特征重要性。不过要注意这个重要性排序只能当参考数值型特征在分裂中更容易占到便宜基数高的离散型特征重要性也容易被抬高。要真正判断一个特征要不要留最好跑个简单的剔除验证再说。不要让特征重要性完全替你做决策。5. Boosting家族如何一步一步修正错误5.1 AdaBoost给错题不断加权Boosting早期的代表是AdaBoost。它给每个样本挂一个权重一开始大家的权重都相等第一轮训练完成后把分错样本的权重调大把分对样本的权重调小下一轮的弱学习器在优化目标时就更努力去迁就那些高权重样本相当于盯住错题本反复练。每轮还会根据当前错误率给弱学习器算一个话语权错误越少话语权越大最后把所有模型按话语权加权投票。这套思路非常优雅但也有一个致命弱点异常值会变成“最扎眼的错题样本”权重一轮轮被放大模型可能被极少数异常点绑架。如果你的数据噪声大、异常值多用AdaBoost要多留个心眼。5.2 GBDT和梯度下降的关系GBDT比AdaBoost又进了一步。它不再纠结于修改样本权重而是把“当前模型的损失”看成一座山坡每新增一棵树就往损失下降最快的方向迈一步。那个方向就是负梯度。为什么要在概念上区分“负梯度”和“残差”因为残差只是平方损失下的一个特例。换成绝对损失、Huber损失或对数损失时“下一步往哪走”就不能用简单的残差描述了但负梯度永远有意义。所以GBDT本质上是函数空间里的梯度下降每一棵树就是一步梯度的更新。这也是GBDT能覆盖回归、分类、排序等各种任务的原因——你只需定义好损失函数优化路径会自动浮现出来。5.3 工业界梯度提升实现为什么能赢得默认首选后来工业界的梯度提升实现在GBDT框架上做了几件重要的事利用二阶导数加速收敛类似牛顿法的思路加入正则项控制单棵树复杂度支持列采样和行子采样进一步降低过拟合还能自动处理缺失值。最关键的提升是效率。在大规模数据面前传统逐个枚举切分点的做法太慢于是有的实现用直方图算法简化计算有的用单边采样只对梯度大的样本认真训练有的把互斥特征捆绑起来降维。这些手段让Boosting在千万级样本上也能快速迭代慢慢成了结构化数据处理里的默认选择。5.4 Boosting的过拟合风险与常见对策Boosting不是迭代越多越好。训练后期模型往往会开始背训练集中的噪声泛化表现反而下跌。几个我实际踩过之后的经验把学习率设低一些轮数设高一些配合早停判断什么时候加不动了。通过限制每棵树的叶子数、最小样本数压住单棵树的复杂度。想让方差再小一点就开行采样、列采样。训练之前先做异常值诊断。Boosting对异常值比Bagging敏感得多极端点尤其明显。如果你发现训练集误差持续下降、验证集误差却在反弹基本就是迭代过头了。早停不是玄学是Boosting用户最常用、也最值得用的防过拟合手段。6. 基学习器的多样性“好而不同”才是集成的灵魂6.1 多样性从哪里来要让基学习器之间“不同”手段其实不少。按我的习惯可以归成几类样本扰动用自助采样让每份训练数据不一样。特征扰动随机挑选特征子空间让每棵树看问题的角度不同。算法扰动混用线性模型、树模型、近邻、核方法等模型类型本身就不一样。参数扰动同一个算法给不同深度、不同学习率、不同随机种子。输出扰动对标签做形式变换比如排序转换、少量翻转。在实际项目里同质集成最常用的是样本扰动加特征扰动异构集成则主要靠算法扰动。具体选哪几种要在“增加差异”和“保持质量”之间找平衡并不是扰动越多越好。6.2 “好而不同”是最容易被忽略的那句话集成学习最容易被忽略的边界条件就是“好而不同”四个字。“好”强调每个基学习器要基本可靠。如果硬塞进一个效果很差的模型它每次投票都相当于投错误票反而给整体拖后腿。“不同”强调错误要互补。两个模型准确率一模一样如果错也错在同样的样本上集成后准确率还是原来那个数白忙一场。判断到底“不同”到什么程度最直接的方法就是看两个模型在验证集上的预测差异。如果它们的预测相关性接近1集成几乎没有意义如果错误分布比较散集成收益立刻就能看到。我见过不少人把高度相关的模型硬拼在一起完了还奇怪效果为什么没涨根子就在这儿。6.3 异构集成的两个实操提醒第一小心概率输出的分布差异。不同模型预测出来的分数不在一个量纲上树模型的概率输出通常比较陡集中在两头线性模型的概率输出相对平贴近中间。如果直接把它们的原始概率平均量纲偏激的那个会暗中主导结果。稳妥的做法是先把每个模型的预测按样本排序转成统一的分位秩再去做平均或者让第二层模型自己去学权重。第二Stacking的第二层模型别搞得太复杂。第二层的数据量其实就是原始样本量没有海量特征用线性模型或者很浅的树通常已经够用。第二层越复杂越容易把第一层留下的噪声一并学了进去得不偿失。常见的一个大坑我已经提过再强调一次生成第一层元特征时一定要用K折交叉验证让第二层看到的预测是“从没见过该样本”时得到的预测。这一步只要偷懒Stacking基本必翻车。这一节把基础框架搭完后面就能挨个拆具体算法了。下一节我们先拿Bagging和随机森林开刀把它从理论到参数掰开揉碎练到可以直接上手用的程度。