第2章:随机森林原理:Bagging、决策树与集成学习

这一章,我们聊聊随机森林的根基。很多人一上来就调参,却忽略了它背后的逻辑。我个人觉得,搞懂原理比记住一百个参数更重要。毕竟你知道了它为什么好,才知道怎么用。

2.1 集成学习:三个臭皮匠的智慧

先问一个问题:如果让你猜一个瓶子里有多少颗糖,你是自己猜准,还是问100个人取平均值准?

答案是后者。这就是集成学习的核心思想——把多个“弱”模型组合成一个“强”模型。单个模型可能犯错误,但一群模型同时犯错的概率就低得多。

核心观点:集成学习不是简单投票,而是用策略让“众包”产生1+1>2的效果

集成学习主要有两派:

  • Bagging(自举汇聚):并行训练多个模型,然后投票或平均。随机森林就是用的这个套路。
  • Boosting(提升):串行训练,后一个模型纠正前一个的错误。XGBoost是这派的代表。

你看,这俩路子完全不同。随机森林讲究“民主决策”,而Boosting是“层层纠偏”。我当时做用户流失预测时测试过,数据噪声大时Bagging更鲁棒,数据有偏时Boosting调参好了更强。你得根据场景选。

2.2 Bagging:为什么随机采样这么管用?

Bagging全名叫Bootstrap Aggregating。名字听着玄乎,其实流程特别简单:

  1. 从原始数据里有放回地抽样,生成M个子数据集。
  2. 在每个子数据集上训练一个模型。
  3. 预测时,分类任务用投票,回归任务用平均值。

我问你,为什么不直接训练一棵大树?非要弄这么多份数据?

原因有两个。第一,降低方差。单个决策树容易过拟合,一换数据集结果就变。但多棵树取平均,波动就被平滑了。第二,稳定性。如果某棵树的训练集里恰好有异常值,其他树的训练集里可能没有,投票时那棵“坏树”就被淹没了。

特性单棵决策树Bagging集成
方差高,容易过拟合低,泛化强
训练速度快慢(但可并行)
对异常值敏感度非常敏感鲁棒性好

我有一个经验值:基模型之间的相关性越低,Bagging效果越好。如果所有模型都一样,那投票就是自欺欺人。随机森林在此基础上又加了“随机特征选择”,就是为了进一步降低模型间的相关性。

技巧:训练集大小可以设成和原始数据差不多,有放回抽样通常能抽到约63.2%的不重复样本,剩下的叫“袋外数据”(OOB),可以直接用来验证模型——省得再做一套验证集。

2.3 决策树:随机森林最小单元

随机森林里的“树”,就是决策树。它长什么样?我给你拆开看看。

2.3.1 树是怎么长起来的?

决策树的本质是一堆 if-else 规则的嵌套。每次分裂选一个特征和一个阈值,让分出来的两个子集“纯度”尽可能高。衡量纯度的方法很多:

  • 基尼系数:默认选项,计算快。值越小越纯。
  • 信息增益:基于熵,对特征取值多的列有偏好。
  • 增益率:给信息增益加了惩罚,防止特征取值太多导致过拟合。

我一般在做分类任务时用基尼系数就够了,计算快,结果跟信息增益差不了多少。但做文本分类时,特征特别稀疏,我建议用信息增益,它会更倾向选择有区分度的特征。

注意:决策树非常容易过拟合。只要让树长到足够深,它就能记住训练集里每一个样本——包括噪声。所以剪枝很关键。随机森林通过“多棵树取平均”自然解决了这个问题,但你如果单用一棵决策树,必须限制深度或叶节点最小样本数。

2.3.2 为什么决策树是随机森林的基石?

决策树有个天生的好处:非线性拟合能力强。它不假设特征之间存在线性关系,也不需要做特征缩放。比如你有一个特征取值是“红色、蓝色、绿色”,树可以直接按某个类别划分,而像SVM就得先独热编码。

另一个好处是可解释性。虽然随机森林几百棵树堆一起,解释性变差了,但单棵决策树可以画出来——你跟业务方交流时,拿出一个简单的树结构,说“年龄小于30且收入大于5万的用户流失概率高”,对方立刻就能理解。

我曾经有个项目,需要向风控部门解释模型为什么拒绝了一笔贷款。我用单棵决策树做了个“轻量级解释器”,虽然准确率降了两三个点,但合规那边直接通过了。这就是模型可解释性的力量。

2.4 随机森林:Bagging + 决策树 + 随机特征选择

把上面两块拼一起,就是随机森林了。那它跟最基础的Bagging决策树有什么不同?

核心区别:每次分裂时,不是从所有特征里选最佳切分点,而是随机选一个子集(通常为sqrt(total features)或log2(total features))。

为什么会这样?你想想看,如果数据里有一个“强特征”,所有树都会优先用它分裂。那每棵树长得都差不多,Bagging的效果就被削弱了。随机选特征,就是为了强迫不同的树从不同角度学东西。

一句话总结:随机森林 = Bagging(行采样)+ 决策树(基模型)+ 随机特征选择(列采样)

顺便提一下,随机森林的随机有两层:一是随机选样本(行),二是随机选特征(列)。这两层随机让随机森林几乎成了“开箱即用”的模型。我记得有一次拿了一堆乱七八糟的数据,什么字段都有,用默认参数跑随机森林,效果居然比调了半天的逻辑回归还好——当然,后来我调了调,又提升了一些。

下面这个SVG图展示了随机森林从训练到预测的完整流程:

随机森林训练与预测流程 原始训练集 (N样本, M特征) 行采样(有放回) 列采样(随机特征) 子集1 子集2 子集3 ... 子集M 训练 决策树1 决策树2 决策树3 ... 决策树M 投票 / 平均 最终预测 (分类/回归)

2.5 用代码感受一下

光看原理不过瘾?我给你一段scikit-learn的代码,跑跑就明白了:

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 造一份分类数据
X, y = make_classification(n_samples=1000, n_features=20,
                           n_informative=8, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# 创建随机森林模型
clf = RandomForestClassifier(
    n_estimators=100,       # 100棵树
    max_depth=10,           # 每棵树最多10层
    max_features='sqrt',    # 每次分裂随机选sqrt个特征
    oob_score=True,         # 用袋外数据评估
    random_state=42
)

clf.fit(X_train, y_train)

# 看一下袋外分数和测试集准确率
print("OOB score:", clf.oob_score_)
y_pred = clf.predict(X_test)
print("Test accuracy:", accuracy_score(y_test, y_pred))

这里有几个参数,我重点讲一下:

  • n_estimators:森林里树的棵树。我一般从100开始试。太少稳定性差,太多耗时增加但提升不大。
  • max_features:每次分裂考虑的特征数。分类默认sqrt,回归默认全部特征。但如果你特征特别多,可以设小一点,比如log2。
  • oob_score=True:利用没被抽到的样本做验证,省得再切一份验证集出来。
调试技巧:可以先把n_estimators设小(比如10),快速迭代特征工程和预处理。确定特征之后,再加大树的数量去冲上线。

2.6 避坑指南

我曾经在一个信贷评分项目中用随机森林,跑出来准确率高达99%,心里还挺美。结果一上线,坏账率反而上升了——你知道为什么吗?

后来我发现:数据存在严重的类别不平衡。正样本(违约用户)只占5%。模型学到的全是“怎么识别不违约的人”,对真正的违约用户毫无分辨力。这个坑我踩得很痛。

所以给你几个我觉得实用的建议:

  • 检查类别分布:用clf.classes_看看每类的样本数,如果严重失衡,考虑调class_weight='balanced'或者用SMOTE过采样。
  • 不要迷信默认参数:sklearn的默认参数是通用型,但不是最优的。比如max_depth默认None,会在高维数据上过拟合。
  • 看OOB误差曲线:画一下n_estimators从1到500的OOB误差变化,能找到最经济的树数量。通常一两百棵就稳定了,再增加就是浪费算力。