哈喽,大家好~
今儿咱们来聊聊随机森林 vs GBDT,包括其中的原理、推导、偏差-方差角度、优化方式、计算复杂度、正则化策略等等~
首先,随机森林是并行的Bagging思想。
训练若干棵深树,基于自助采样和特征随机子空间来去相关化,最后平均/投票聚合。主要做方差下降,相对偏差较大但鲁棒、稳定。
GBDT是串行的Boosting思想。
逐步学习若干棵浅树(弱学习器),每一棵都拟合上一步的残差/负梯度,最后加和得到强学习器。主要做偏差下降(bias reduction),若控制不好容易过拟合。
目标函数与优化方面。
随机森林不显式最小化全局损失,而是在每棵树内部用启发式分裂准则(如Gini/熵/平方误差)局部最优;总模型是简单平均。
GBDT显式在函数空间做梯度下降(或近似Newton),将任意可导损失(平方误差、逻辑损失、指数损失等)作为目标进行迭代优化。
模型结构与正则化方面。
随机森林:大量深树 + 强随机性,天然抗过拟合;调参简单,默认配置常常表现稳健。
GBDT:大量浅树 + 学习率+ 子采样+ 结构约束;调参更敏感但上限更高。
并行化与规模。
随机森林天生并行(树与树之间独立);适合快速训练与大规模特征下的基线模型。
传统GBDT是顺序依赖(树与树之间串行),但单棵树训练可并行;工业版如XGBoost、LightGBM通过工程优化显著提升速度与规模。
可解释与校准。
两者都能提供特征重要性。GBDT在逻辑损失下的概率输出(若有良好正则)通常校准性更好;RF概率往往偏粗。
随机森林
随机森林由Breiman提出,核心是Bagging + 随机子空间 + 决策树弱偏好(高方差)的组合。
给定训练数据集 。
Bagging(Bootstrap Aggregating)
通过自助采样(有放回)从原始数据生成 个子样本集 ,对每个子样本集训练一棵决策树 ,最后做平均或投票:
回归:
分类(硬投票):
分类(软投票,概率平均):
方差分析:若每棵树的预测方差为 ,两两相关系数为 ,则平均后的方差为
增大 可将第二项压低至近0,但若 较大则方差下界为 。因此需要降低树间相关性——随机子空间就是为此服务。
决策树与分裂准则
分类树常用基尼系数或信息增益:
-
基尼: 其中 为结点 内类别 的样本比例。 -
信息熵:
回归树常用平方误差:
对每个候选切分 (如特征 与阈值 ),选择最大化不纯度下降(或损失下降):
其中 可为 、 或 。
随机子空间
在每个结点分裂时随机抽取 个特征候选( )。这显著降低树与树之间的相关性 ,提升Bagging方差下降的收益。常见默认:
-
分类: -
回归:
Out-of-Bag(OOB)估计
对于每棵树,由于bootstrap采样通常会留下约 的样本未被抽到(Out-of-Bag样本),可以用这些OOB样本对该树(或森林聚合)做验证,从而得到近似的无偏泛化误差估计。
OOB也可用于调参与早期诊断。
偏差-方差与复杂度
单棵深树:低偏差,高方差。
RF通过平均:保留较低偏差的同时显著降方差。
训练复杂度(粗略):每棵树 ,整体 ;可并行化到 台机器/线程。
聚合预测(回归/概率平均),方差分解,分裂准则不纯度下降 ,OOB近似泛化误差。RF没有显式的全局损失最小化目标函数。
GBDT
GBDT将决策树作为基学习器(弱学习器),在函数空间上沿负梯度方向迭代逼近最优。
给定损失函数 ,模型为加性形式:
其中 是第 棵树, 为步长系数, 为学习率。
函数空间梯度下降:
在第 步,给定当前模型 ,计算负梯度(伪残差)作为新的拟合目标:
训练一棵回归树 去拟合 。
随后做线搜索确定 (或在叶子上做二阶近似求解),更新:
回归(平方误差)情形:
若 ,则
即传统的拟合残差。线搜索的闭式解为
(若 完美拟合残差,步长为1;实践中常用 )
二分类情形:
设 ,用对数损失
梯度与Hessian:
负梯度是 ,即观测标签减当前预测概率。
以叶子为单位的Newton近似(常见于XGBoost/LightGBM类实现),对第 棵树的某个叶子 ,叶子输出值可近似为:
其中 是二阶正则(L2)项。更新:
备注:sklearn的GradientBoostingClassifier使用一阶方法+线搜索思想;XGBoost/LightGBM使用二阶近似,工程更高效。
正则化与防过拟合:
学习率 :越小越保守,需更多树数 才能达到同等训练误差,但泛化更好。经典经验:小 +大 优于大 +小 。
子采样:随机采样训练样本的子集训练每棵树(Stochastic GBDT),可视为近似引入噪声,提升泛化并加速。
结构约束:max_depth、min_samples_leaf、max_leaf_nodes控制树复杂度。
正则项:对叶子权重施加L1/L2(在XGBoost等实现中常见)。
早停:在验证集上监控指标随 变化,选择最优迭代轮次。
随机森林 vs GBDT
偏差-方差:
-
RF:偏差较高、方差显著下降。对高噪声数据更加稳健。 -
GBDT:偏差快速下降,若正则不足(大深度、大学习率),则后期可能方差上升(过拟合)。
优化目标:
-
RF:没有全局损失最小化,单棵树内做局部最优分裂,最后平均。 -
GBDT:显式最小化损失函数,在函数空间做梯度下降/二阶近似。
并行化:
-
RF:天然并行(树与树独立)。 -
GBDT:树与树串行,工程化优化靠近似与系统设计。
超参敏感性:
-
RF:更省心,默认配置往往表现不错;OOB能快速诊断。 -
GBDT:对学习率、树深、树数、子采样较敏感,需要验证集与早停。
对噪声与异常值:
-
RF:更鲁棒,不容易被异常点拖偏整体模型。 -
GBDT:若损失为对数损失或平方误差,异常点影响较大;可用Huber、Fair之类更鲁棒的损失(在部分库中支持)。
概率校准与可解释:
-
RF概率偏粗,需要时可加Platt/Isotonic校准。 -
GBDT逻辑损失下往往更接近概率(但仍需视正则而定)。 -
两者都有特征重要性,但GBDT常与单调约束、PDP/ICE等解释方法搭配更自然。
何时选谁:
-
快速建立强基线、数据多噪声、多类型特征:RF。 -
追求SOTA、能认真做验证与调参、数据非线性复杂:GBDT(工业常用XGBoost/LightGBM/CatBoost)。
完整案例
我们构造一个非线性+噪声的二分类数据集:用make_moons产生2个强信息维度,再添加8个高斯噪声维度。
将数据分为训练/验证/测试。
我们在案例中,对比RandomForestClassifier与GradientBoostingClassifier在以下方面的表现:决策边界、特征重要性对比。
RF的OOB误差曲线 vs GBDT的验证集对数损失曲线(同一子图双轴)。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.metrics import accuracy_score, roc_auc_score, log_loss
from matplotlib.colors import ListedColormap
import warnings
warnings.filterwarnings('ignore')
np.random.seed(42)
# 1) 生成非线性+噪声特征的数据
n_samples = 3500
X_2d, y = make_moons(n_samples=n_samples, noise=0.25, random_state=42) # 两个非线性信息特征
noise_dims = 8
X_noise = np.random.normal(size=(n_samples, noise_dims)) * 1.0
X = np.hstack([X_2d, X_noise]) # 总特征维度=10,前2维是强信息
# 划分训练/验证/测试:60%/20%/20%
X_train_full, X_test, y_train_full, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)
X_train, X_valid, y_train, y_valid = train_test_split(X_train_full, y_train_full, test_size=0.25, stratify=y_train_full, random_state=42)
# 现在:训练60%,验证20%,测试20%
# 2) 训练RF和GBDT
# RF:使用OOB估计,并记录随树数量变化的OOB误差曲线
rf = RandomForestClassifier(
n_estimators=10, # 我们会用warm_start逐步增加
max_features='sqrt',
bootstrap=True,
oob_score=True,
n_jobs=-1,
random_state=42,
warm_start=True
)
# 为收集OOB曲线,逐步增加树的数量
rf_estimators_grid = list(range(10, 401, 10)) # 10 -> 400,每10步
rf_oob_errors = []
for n_trees in rf_estimators_grid:
rf.set_params(n_estimators=n_trees)
rf.fit(X_train, y_train)
# oob_score_为OOB精度
oob_error = 1.0 - rf.oob_score_
rf_oob_errors.append(oob_error)
# 最终RF模型(400棵树)
rf_final = rf
# GBDT:使用对数损失,收集staged预测以计算验证集log loss曲线
gbdt = GradientBoostingClassifier(
loss='log_loss', # deviance的等价名称
learning_rate=0.05,
n_estimators=400,
max_depth=3,
subsample=0.8,
random_state=42
)
gbdt.fit(X_train, y_train)
# 收集GBDT在验证集上的staged log loss
gbdt_valid_logloss = []
gbdt_train_logloss = []
for y_proba_valid, y_proba_train in zip(gbdt.staged_predict_proba(X_valid), gbdt.staged_predict_proba(X_train)):
gbdt_valid_logloss.append(log_loss(y_valid, y_proba_valid))
gbdt_train_logloss.append(log_loss(y_train, y_proba_train))
# 3) 测试集评估
def evaluate_model(name, model):
y_proba_test = model.predict_proba(X_test)
y_pred_test = np.argmax(y_proba_test, axis=1)
acc = accuracy_score(y_test, y_pred_test)
auc = roc_auc_score(y_test, y_proba_test[:, 1])
ll = log_loss(y_test, y_proba_test)
print(f"[{name}] Test Accuracy={acc:.4f}, ROC-AUC={auc:.4f}, LogLoss={ll:.4f}")
evaluate_model("RandomForest", rf_final)
evaluate_model("GBDT", gbdt)
# 4) 可视化
plt.figure(figsize=(14, 10))
cmap_bg = plt.cm.Spectral
color_pos = '#e41a1c' # 红
color_neg = '#377eb8' # 蓝
color_rf = '#4daf4a' # 绿(RF)
color_gbdt = '#984ea3' # 紫(GBDT)
# 决策边界需要2D网格。我们用前两个信息特征绘制,其他特征固定为训练集均值
idx_plot = [0, 1]
X_means = X_train.mean(axis=0)
def make_meshgrid(X2d, h=0.02, pad=0.8):
x_min, x_max = X2d[:, 0].min() - pad, X2d[:, 0].max() + pad
y_min, y_max = X2d[:, 1].min() - pad, X2d[:, 1].max() + pad
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
return xx, yy
def model_proba_on_grid(model, xx, yy, X_means, idx_plot):
# 构造全特征矩阵:在网格点处给前两维赋值,其他维用均值
grid_points = np.c_[xx.ravel(), yy.ravel()]
X_grid = np.tile(X_means, (grid_points.shape[0], 1))
X_grid[:, idx_plot[0]] = grid_points[:, 0]
X_grid[:, idx_plot[1]] = grid_points[:, 1]
proba = model.predict_proba(X_grid)[:, 1]
return proba.reshape(xx.shape)
xx, yy = make_meshgrid(X_train[:, idx_plot])
# 图1:RF的决策边界
plt.subplot(2, 2, 1)
Z_rf = model_proba_on_grid(rf_final, xx, yy, X_means, idx_plot)
plt.contourf(xx, yy, Z_rf, alpha=0.85, cmap=cmap_bg)
plt.scatter(X_train[y_train==0][:, 0], X_train[y_train==0][:, 1], s=20, c=color_neg, edgecolor='k', alpha=0.9, label='Train 0')
plt.scatter(X_train[y_train==1][:, 0], X_train[y_train==1][:, 1], s=20, c=color_pos, edgecolor='k', alpha=0.9, label='Train 1')
plt.title("随机森林:决策边界(前两特征)", fontsize=12, fontweight='bold')
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.legend(loc='upper right', frameon=True)
# 图2:GBDT的决策边界
plt.subplot(2, 2, 2)
Z_gbdt = model_proba_on_grid(gbdt, xx, yy, X_means, idx_plot)
plt.contourf(xx, yy, Z_gbdt, alpha=0.85, cmap=cmap_bg)
plt.scatter(X_train[y_train==0][:, 0], X_train[y_train==0][:, 1], s=20, c=color_neg, edgecolor='k', alpha=0.9, label='Train 0')
plt.scatter(X_train[y_train==1][:, 0], X_train[y_train==1][:, 1], s=20, c=color_pos, edgecolor='k', alpha=0.9, label='Train 1')
plt.title("GBDT:决策边界(前两特征)", fontsize=12, fontweight='bold')
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.legend(loc='upper right', frameon=True)
# 图3:特征重要性对比(前10维全部展示)
plt.subplot(2, 2, 3)
rf_imp = rf_final.feature_importances_
gbdt_imp = gbdt.feature_importances_
indices = np.arange(len(rf_imp))
width = 0.38
plt.bar(indices - width/2, rf_imp, width=width, color=color_rf, alpha=0.85, label='RF')
plt.bar(indices + width/2, gbdt_imp, width=width, color=color_gbdt, alpha=0.85, label='GBDT')
plt.xticks(indices, [f'F{i}' for i in range(X.shape[1])], rotation=0)
plt.ylabel("Feature Importance")
plt.title("特征重要性对比(RF vs GBDT)", fontsize=12, fontweight='bold')
plt.legend(loc='upper right', frameon=True)
# 图4:学习动态对比
# RF:OOB error vs #trees
# GBDT:Valid LogLoss vs #trees
plt.subplot(2, 2, 4)
x_rf = rf_estimators_grid
y_rf = rf_oob_errors
line1, = plt.plot(x_rf, y_rf, color=color_rf, lw=2.5, label='RF OOB Error')
plt.xlabel("#Trees / #Estimators")
plt.ylabel("RF OOB Error", color=color_rf)
plt.tick_params(axis='y', labelcolor=color_rf)
plt.title("学习动态:OOB误差 vs 验证LogLoss", fontsize=12, fontweight='bold')
# 叠加GBDT验证曲线(右轴)
ax2 = plt.gca().twinx()
x_gbdt = np.arange(1, len(gbdt_valid_logloss)+1)
y_gbdt = gbdt_valid_logloss
line2, = ax2.plot(x_gbdt, y_gbdt, color=color_gbdt, lw=2.5, label='GBDT Valid LogLoss')
ax2.set_ylabel("GBDT Valid LogLoss", color=color_gbdt)
ax2.tick_params(axis='y', labelcolor=color_gbdt)
# 合并图例
lines = [line1, line2]
labels = [l.get_label() for l in lines]
plt.legend(lines, labels, loc='upper right', frameon=True)
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
随机森林的决策边界:
背景色为模型对正类的预测概率等高线(颜色越亮/偏暖代表概率越大),训练样本散点覆盖其上,不同颜色表示不同类别。
随机森林由多棵深树投票/平均,决策边界通常呈现块状/台阶状的非线性形态,边界变化可能相对粗糙,但对噪声较鲁棒。
即使加入了8个噪声维度,RF通过随机子空间与多数投票,仍能学到稳定边界;其平均化的机制让单个树的过拟合不会直接导致整体过拟合。
GBDT的决策边界:
同样是正类概率的等高线,但由GBDT的加法模型给出。
GBDT通过逐步拟合负梯度/残差,可以在复杂区域提升边界的细腻程度。一般情况下,你会看到GBDT边界在曲线难度较高的区域更贴合真实分布(例如月亮数据的弯曲部分)。
不过若学习率过大或树太深,边界可能呈现过拟合的局部硬折线,需要适当正则与早停。
特征重要性对比:
展示全部10个特征的importance,左侧为RF,右侧为GBDT,颜色鲜明对比。
由于前2个特征为informative(make_moons),通常RF与GBDT都会为这两维给出最高重要性。
噪声特征的权重在两者中都应较低,但分布可能略有差异:RF因为随机子空间,在部分种子下可能对某些噪声特征给出相对不为零的重要性;GBDT则可能更集中于少数关键特征,尤其是在浅树+小学习率配置下。
学习动态对比:
RF:随树的数量(#trees)增加的OOB误差曲线(左轴,绿色)。
GBDT:随迭代轮次的验证集LogLoss曲线(右轴,紫色)。
RF 随着树数增加,OOB误差逐步下降并趋于平稳,体现平均化收敛的特征;一旦达到足够多的树,继续增加对性能提升有限(但也不太会过拟合)。
GBDT,验证LogLoss在初期迅速下降(偏差快速下降),随后可能在某个拐点后趋于震荡或回升(过拟合开始);最优点常在某个中等迭代数,适合做早停。
总结
随机森林与GBDT是两类极为成功的树模型集成方法,其本质差异在于并行平均 vs 串行提升、方差下降 vs 偏差下降、启发式分裂 vs 函数空间优化。
随机森林 往往是一个高质量、快稳的基线:极具鲁棒性、无需过多调参、OOB可做快速泛化估计。
GBDT 在合适的正则与早停设置下,往往能在复杂非线性问题上拿到更高的SOTA指标;但需在学习率、树深、树数、子采样等超参上更为谨慎。
最后

