随机森林的文档里,sample_weight 这个参数往往被一笔带过。很多人调模型时习惯性忽略它,觉得"随机森林靠的就是随机性和平均,权重这种精细活是提升树的事"。但如果你真正啃过随机森林的源码,或者在类别极不平衡的数据集上挣扎过,就会发现样本权重在整个随机森林的训练链条里贯穿始终——从 bootstrap 重采样、到每棵决策树内部的节点分裂、再到弱学习器的输出集成,权重在每一层都悄悄改变着模型的行为。
这篇文章我想把 "随机森林样本权重的计算-弱学习器" 这条线完整拆开,讲清楚三个问题:样本权重到底在随机森林的哪些环节起作用、每个环节权重是如何参与计算的、以及作为弱学习器的决策树是如何把这些权重"消耗"掉并最终影响投票结果的。内容会涉及具体的数学公式、sklearn 底层实现逻辑和一个手算实例,最后分享一些我在实际项目中踩过的坑。
1. 先厘清概念:随机森林里的"样本权重"到底指什么
1.1 bagging 自带的一层"隐式权重"
随机森林的核心是 bagging(Bootstrap Aggregating),也就是从原始训练集里有放回地抽取若干份子样本,每份子样本训练一棵决策树。为什么要强调这一点?因为"有放回抽样"本身就是一种权重行为的体现。
在标准 bagging 中,每个样本被抽中的概率是 1/N(N 是训练集样本量)。经过 m 次抽样之后,某个样本可能在某一棵树里出现了 3 次,在另一棵树里出现了 0 次。从"样本对最终模型的贡献度"角度看,这其实就是一种由随机性产生的样本权重——出现次数多的样本,对那棵树的拟合影响更大。
但这层权重是隐式的、不可控的。它的存在只是随机采样的副产品,而不是我们主动赋予的。所以实际应用中,当我们谈论"随机森林的样本权重"时,通常指的是另外两种显式的形态。
1.2 真正需要关注的三种权重形态
我把随机森林生命周期里的权重拆成了三类,理解它们之间的区别,后面所有内容才能串起来:
| 权重类型 | 作用阶段 | 控制方式 | 典型实现 |
|---|---|---|---|
| 采样权重 | 每棵树的训练子集构建 | bootstrap 抽样概率 | 直接改采样分布 |
| 分裂权重 | 决策树节点的特征划分 | 分裂准则中的加权计算 | 加权基尼系数、加权均方误差 |
| 投票权重 | 所有树输出集成 | 最终预测的加权平均 | 按验证精度加权、按样本加权 |
其中分裂权重是本文的主线,也是 sklearn 中 sample_weight 参数真正进入计算的地方。采样权重更多存在于原理层面,sklearn 对显式传入的 sample_weight 并不直接改变抽样概率,而是把权重向下传递给了决策树的分裂过程。这一点很多人理解错了——后面我会详述。
1.3 弱学习器在这里扮演什么角色
随机森林里的弱学习器是决策树(通常是 CART)。决策树天然适合做"带权重的学习器":它在分裂时逐个评估特征、逐个阈值地计算纯度增益,而这个纯度增益完全可以把样本权重直接乘进去。也就是说,决策树是一个"权重友好型"模型,它的每一步划分都能精确地体现出哪些样本更重要。
对比之下,如果你把基学习器换成线性模型,样本权重的作用方式就完全不同了——要么通过加权损失函数,要么通过过采样。而决策树不需要这些变通手段,它把权重当作每个样本的"质量",直接放进分裂准则的公式里。
这是理解随机森林样本权重的大前提:权重作用于决策树的局部(每个节点的分裂决策),而不是全局(整个模型的损失函数)。这种"局部性"也正是随机森林和提升树在权重机制上最大的差异。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 关键路径一:bootstrap 重采样中的权重兑现方式
2.1 从均匀采样到加权采样
严格意义上,标准随机森林的 bootstrap 采样是均匀的——每棵树的子样本量是 m ≈ N,每个样本被抽中的概率是 1 - (1 - 1/N)^N ≈ 0.632。也就是说大约 63.2% 的样本会出现在一棵树的训练集里,剩下约 36.8% 成为袋外样本(Out-of-Bag)。
但这个"均匀"只是默认行为。当你调整采样策略时,权重就开始介入了。例如:
- 类别不平衡时,可以让少数类样本有更高的被抽中概率;
- 时间序列数据中,可以让近期样本有更高的被抽中概率;
- 某些人工标注质量特别高的样本,可以赋予更高的权重。
实现方式有两种。一种是显式地在抽样前给每个样本一个概率 ( p_i ),按这个分布做有放回抽样;另一种就是 sklearn 中常见的方式——fit(X, y, sample_weight=w),模型拿到权重后不是用来改抽样概率,而是在后续分裂中直接使用。
2.2 sklearn 的 sample_weight 传递链路
很多人以为 sklearn 的随机森林传了 sample_weight 之后,bootstrap 抽样会变成加权抽样。实际上并非如此。我翻看过 sklearn 的源码,在 RandomForestClassifier.fit() 中,sample_weight 会被校验维度后,原封不动地传给每棵 DecisionTreeClassifier.fit() 方法的 sample_weight 参数。bootstrap 抽样本身依然遵循均匀采样(除非设置 class_weight='balanced_subsample')。
这意味着什么?意味着显式传入 sample_weight 时,权重主要影响的是决策树节点的分裂过程,而不是树的训练子集构成。
另外 class_weight='balanced_subsample' 是随机森林中一个比较特殊的参数。它和普通的 class_weight='balanced' 不同:'balanced' 是基于整个训练集的类别频率计算权重;而 'balanced_subsample' 是针对每一棵树的 bootstrap 子样本重新计算权重。这个细节很重要,尤其是在类别不平衡的场景下——'balanced_subsample' 使得每棵树的权重都在"局部"意义上达到平衡,比全局的 'balanced' 更灵活、更容易带来多样性的提升。
2.3 OOB 样本评估与权重的关系
既然涉及了 bootstrap,就不能不提袋外样本。标准随机森林中,每棵树大约有 36.8% 的样本没有参与该树的训练,这些样本被用来做 OOB 评估——相当于免费获得了一个验证集。
那么问题来了:如果给样本加了权重,OOB 评估会不会受影响?
答案是分层的。因为 sklearn 的 sample_weight 只影响分裂,不影响抽样,所以 OOB 样本的选择与否不受权重影响。但是由于每棵树训练时"重视"了某些样本,这些树的预测行为已经发生了改变,最终 OOB 误差反映出的是"加权训练后的模型"在袋外样本上的表现。
这在实践中有个很微妙的影响:如果你想用 OOB 误差来做模型选择,请注意你比较的是同一个加权策略下的不同超参数,而不要拿一个加权模型和一个未加权模型的 OOB 误差直接攀比——两者的"成本函数"都不一样,数值大小不具备绝对可比性。
3. 关键路径二:决策树分裂准则如何消耗权重
3.1 分类树:加权基尼系数的完整计算
决策树在分类任务中通常用基尼系数(Gini impurity)或信息熵来评估分裂质量。加入样本权重之后,所有涉及"类别比例"的计算都要换成"类别权重占比"。
以基尼系数为例,未加权时节点 ( D ) 的基尼不纯度是:
[
\text{Gini}(D) = 1 - \sum_{k=1}^{K} p_k^2
]
其中 ( p_k = \frac{\text{节点中第 } k \text{ 类样本数}}{\text{节点样本总数}} )。
加入样本权重后,( p_k ) 的计算变为:
[
p_k = \frac{\sum_{i \in D} w_i \cdot I(y_i = k)}{\sum_{i \in D} w_i}
]
也就是说,"该类样本的权重之和"除以"节点所有样本的权重之和"。当所有 ( w_i ) 相等时,这个公式自然退化成普通基尼系数的形式——这就是为什么权重是"扩展"而不是"推翻"原有算法。
分裂时,假设某个特征的一个阈值把节点 ( D ) 分成 ( D_L ) 和 ( D_R ),那么分裂后的加权基尼增益是:
[
\Delta \text{Gini} = \text{Gini}(D) - \frac{W_L}{W_{\text{总}}} \cdot \text{Gini}(D_L) - \frac{W_R}{W_{\text{总}}} \cdot \text{Gini}(D_R)
]
其中 ( W_L = \sum_{i \in D_L} w_i ),( W_R = \sum_{i \in D_R} w_i ), ( W_{\text{总}} = W_L + W_R )。决策树会遍历所有特征的候选阈值,选择加权基尼增益最大的那个划分。
3.2 回归树:加权方差与加权叶子值
回归树的分裂准则是均方误差(MSE)或均方绝对误差(MAE)。加入权重后,计算方式同样改动。
未加权时,节点 ( D ) 的 MSE 是:
[
\text{MSE}(D) = \frac{1}{n} \sum_{i \in D} (y_i - \bar{y}_D)^2
]
其中 ( \bar{y}_D ) 是节点内目标变量的均值。
加权后:
[
\text{MSE}(D) = \frac{\sum_{i \in D} w_i (y_i - \bar{y}{w})^2}{\sum{i \in D} w_i}
]
这里 ( \bar{y}{w} = \frac{\sum{i \in D} w_i y_i}{\sum_{i \in D} w_i} ),即加权均值。
也就是说,在回归树中样本权重不仅影响分裂点的选择,还直接决定叶子节点的预测值。如果你给某些"低噪声、高置信度"的样本赋予较高权重,最终树在叶子上的输出会向这些样本靠拢,整体回归曲线会更贴近可信区间的数据。
3.3 带权重的决策树训练代码演示
用 sklearn 验证这一点非常直观。先看一下,同一个数据集、同样的树深度,不同权重下的分裂结果是怎么变的:
python复制import numpy as np
from sklearn.tree import DecisionTreeClassifier, export_text
# 造一个简单的二维分类数据
X = np.array([
[1, 2], [1, 3], [2, 2], [2, 4],
[6, 5], [7, 5], [6, 7], [7, 8]
])
y = np.array([0, 0, 0, 0, 1, 1, 1, 1])
# 等权重
clf_uniform = DecisionTreeClassifier(max_depth=2, random_state=42)
clf_uniform.fit(X, y)
print("等权重树结构:")
print(export_text(clf_uniform, feature_names=["x1", "x2"]))
# 给前4个样本(类0)很高的权重
weights = np.array([5, 5, 5, 5, 1, 1, 1, 1])
clf_weighted = DecisionTreeClassifier(max_depth=2, random_state=42)
clf_weighted.fit(X, y, sample_weight=weights)
print("\n加权树结构:")
print(export_text(clf_weighted, feature_names=["x1", "x2"]))
你运行一下就会看到,等权重时树可能优先用 x1 在 6 附近分裂;而给类 0 样本高权重后,树可能变得更保守,甚至选择更"贴近类0分布"的分裂边界。这就是权重在决策树分裂阶段的直接体现。
4. 关键路径三:弱学习器输出阶段,权重如何影响投票
4.1 标准随机森林的等权投票机制
随机森林最终预测时,所有决策树的投票权重是相等的。分类任务中,每棵树投一票给它的预测类别,取多数;回归任务中,所有树的预测值做简单平均。这是 bagging 的初衷——通过降低方差来提升泛化能力,而不是通过调整各学习器的"话语权"来做集成。
所以如果你问"随机森林的样本权重在投票阶段是否起作用",标准答案是:不起直接作用。但这里的"不起直接作用"有个前提——你在前面训练阶段已经通过权重改变了每棵树的行为,所以投票结果虽然形式上等权,内容和没加权时已经完全不同了。
4.2 带权投票的扩展:什么时候需要打破等权
真实项目中,确实有打破等权投票的冲动,通常发生在以下几种场景:
- 树之间存在明显的性能差异:比如某些树在验证集上表现更好。此时按 OOB 精度给树加权,理论上会提升整体表现。
- 类别极端不平衡:少数类样本稀少,多数树可能连少数类的像样分裂都学不到。此时你可以按树对少数类的召回率加权投票。
- 样本对任务的重要性不同:某些样本是"坚决不能分错"的,而等权投票无法体现这种重要性。一种变通做法是,在训练时提高这些样本的权重,或者在投票后按树的预测置信度加权。
在 sklearn 中,标准 RandomForestClassifier 不直接支持加权投票。但你可以手动实现:训练完每棵树后用 tree.predict_proba(X) 提取预测概率,按树的 OOB 得分加权平均,再取 argmax。这个方法在不少 Kaggle 项目中被验证有效,但收益幅度通常不大——随机森林的多样性和平均机制已经把大多数收益吃掉了。
4.3 为什么"弱学习器"是权重发挥作用的必要前提
这可能是全文最关键的一个认知:随机森林之所以能让样本权重在分裂阶段发挥显著的调节作用,恰恰因为它的基学习器是"弱"的。
决策树是典型的低偏差、高方差模型。它对训练样本的细微变化非常敏感——训练集里换掉几个样本,树的形状可能就大不一样。这种敏感意味着:当你改变样本权重时,决策树的分裂选择会发生实质性的改变,而且这种改变不是温柔的微调,而是结构性的。
这就是"弱学习器"的核心意义。如果随机森林的基学习器换成逻辑回归或线性 SVM,样本权重只能通过加权损失函数温和地影响系数,模型表现受限;而一棵决策树可能因为一个高权重样本的存在,直接决定在哪个特征上、以什么阈值分裂。权重对弱学习器的影响是"非线性放大"的。
这也解释了为什么随机森林的调参中,min_samples_leaf、max_depth 这些限制树复杂度(即"保持弱")的超参数会和 class_weight 或 sample_weight 产生强交互。树越深越复杂,权重的影响越可能被过拟合;树越浅越"弱",权重则越能在稳定性和拟合性之间找到平衡。
5. 一个具体数字实例:手算加权 Gini 分裂
5.1 构造一个可手算的迷你数据集
原理讲多了容易飘,我用一组极小的数据完整走一遍加权 Gini 的计算过程。
假设 6 个样本,一个特征 ( x ),二分类标签 ( y ):
| 样本 | x | y | 权重 w |
|---|---|---|---|
| 1 | 1 | 0 | 2 |
| 2 | 2 | 0 | 2 |
| 3 | 3 | 1 | 1 |
| 4 | 4 | 1 | 1 |
| 5 | 5 | 0 | 2 |
| 6 | 6 | 1 | 1 |
总权重 ( W_{\text{总}} = 2+2+1+1+2+1 = 9 )。
节点总体的加权基尼系数:
类 0 的权重占比 = ( (2+2+2)/9 = 6/9 )
类 1 的权重占比 = ( (1+1+1)/9 = 3/9 )
[
\text{Gini}_{\text{根}} = 1 - (6/9)^2 - (3/9)^2 = 1 - 0.4444 - 0.1111 = 0.4444
]
如果等权,根节点的普通 Gini 是 ( 1 - (3/6)^2 - (3/6)^2 = 0.5 )。可以看到,给类 0 加权重后,节点不纯度下降——因为类 0 的"质量"占比大了,节点看起来更"纯"。
5.2 不加权 vs 加权:分裂点选择的差异
假设我们用阈值 ( t=2.5 ) 分裂,左节点样本 1、2,右节点样本 3、4、5、6。
- 左节点(样本 1, 2):y 都是 0,无权重重占比 = (2/2 = 1),加权基尼 = 0。
- 右节点(样本 3, 4, 5, 6):类 0 权重为样本 5 的 2,类 1 权重为样本 3、4、6 的 1+1+1=3。
右节点加权基尼:
[
1 - (2/5)^2 - (3/5)^2 = 1 - 0.16 - 0.36 = 0.48
]
分裂后的加权基尼增益为:
[
\Delta = 0.4444 - \frac{4}{9} \times 0 - \frac{5}{9} \times 0.48 = 0.4444 - 0.2667 = 0.1777
]
再换个阈值 ( t=4.5 ):左节点样本 1、2、3、4,右节点样本 5、6。
- 左节点:类 0 权重 2+2=4,类 1 权重 1+1=2,总权重 6。加权基尼 = ( 1 - (4/6)^2 - (2/6)^2 = 1 - 0.4444 - 0.1111 = 0.4444 )。
- 右节点:类 0 权重 2,类 1 权重 1,总权重 3。加权基尼 = ( 1 - (2/3)^2 - (1/3)^2 = 1 - 0.4444 - 0.1111 = 0.4444 )。
分裂增益:
[
\Delta = 0.4444 - \frac{6}{9} \times 0.4444 - \frac{3}{9} \times 0.4444 = 0.4444 - 0.4444 = 0
]
也就是说,加权情况下,阈值 4.5 根本不会选,增益为 0;阈值 2.5 的增益 0.1777 反而胜出。
比较一下等权的情况。等权时阈值 2.5 的分裂增益:
根 Gini 为 0.5。左节点全为类 0,Gini = 0;右节点类 0 有样本 5、类 1 有样本 3、4、6,普通基尼 = ( 1 - (1/4)^2 - (3/4)^2 = 1 - 0.0625 - 0.5625 = 0.375 )。
增益 = ( 0.5 - \frac{2}{6} \times 0 - \frac{4}{6} \times 0.375 = 0.5 - 0.25 = 0.25 )。
等权时阈值 4.5 的增益:左节点类 0 样本 1、2,类 1 样本 3、4。基尼 = ( 1 - (2/4)^2 - (2/4)^2 = 0.5 );右节点类 0 样本 5,类 1 样本 6,基尼 = 0.5。增益 = ( 0.5 - \frac{4}{6} \times 0.5 - \frac{2}{6} \times 0.5 = 0 )。
所以等权情况下,阈值 2.5 和 4.5 都会在树结构中选择 2.5(增益更大)。加权后的差异体现在哪里?差异在于——如果换一组阈值,比如 ( t=3.5 ),等权和加权的情况可能就有分歧了,权重越高,越倾向于选择能把高权重的类0样本干净分出来的阈值点。手算一遍最有价值的不是得出"哪个阈值更优",而是亲眼看到权重如何改变了分裂优先级。
5.3 从这个实例能看到什么本质规律
上面这个手算实例揭示了三个本质规律,值得单独点明。
第一,权重的作用是"染色"。它不改变特征的物理含义,也不改变样本的排列方式,但改变了每个样本在纯度计算中的"存在感"。高权重样本集内部的纯度,对不纯度的贡献比对低权重样本大得多。
第二,分裂点会被高权重样本"拉拽"。当某些样本权重很高时,树自然会把它们聚在一起,选择那些能让高权重组"干净"的特征阈值。直观地说,树在"讨好"重要的样本。
第三,加权 Gini 增益在不同节点之间的可比性。由于每棵树的训练数据不同(bootstrap 子样本),加上权重也不同,即便两棵树分裂时用了同一个特征和阈值,加权增益的具体数值也可能不同。这既是随机森林多样性的来源之一,也让"树的可解释性"变得更加微妙——你不能简单地说"这个特征在第 3 棵树上重要、在第 5 棵树上不重要",因为每棵树面临的权重环境不同。
6. 进阶思考:从"样本权重"到"弱学习器协同",以及几个实操建议
6.1 自适应随机森林中的显式权重迭代
如果说标准随机森林的样本权重是"隐式参与"的,那自适应随机森林(Adaptive Random Forest, ARF)则把权重搬到了聚光灯下。
ARF 是一种面向流式数据(streaming data)的在线随机森林变体。它会在数据到达时增量更新,并用一个叫做 ADWIN 的漂移检测器监控性能变化。一旦检测到概念漂移,它就会利用样本权重——具体来说,是用误分类成本来计算每个样本的权重——来触发树的替换或重训。
这里的逻辑链条是:新样本到达 → 在树中走到叶子 → 预测错误 → 计算该样本的权重 → 权重用来决定是否保留该叶子、是否分裂、是否重训整棵树。可以看到,在 ARF 中权重从"影响分裂的局部参数"升级成了"驱动模型结构更新的全局燃料"。
这对理解"随机森林样本权重"非常有启发:权重并不是一个静态的量,它的本质是"样本对当前模型的价值信号"。标准随机森林里这个信号只在训练时起作用,而在自适应框架里,它会持续反馈到模型维护过程中。
6.2 不同库的样本权重实现对比
前面所有分析基于 sklearn 的实现逻辑。实际工程中你可能会用到不同框架,它们的权重处理方式有差异,这里列一个对比表供参考:
| 框架 | 权重参数 | 作用机制 | 备注 |
|---|---|---|---|
| scikit-learn | sample_weight / class_weight |
直接传入分裂准则,加权 Gini / 加权 MSE | 不影响 bootstrap 抽样分布 |
R randomForest |
classwt / sampsize |
修改抽样权重或抽样量 | 无显式分裂权重 |
| XGBoost | weight(DMatrix) |
梯度提升中的二阶导加权,直接影响损失 | 不属于随机森林,但权重理念类似 |
| Spark MLlib | weightCol |
样本权重会参与分裂与叶子值计算 | 支持显式加权 |
| H2O | weights_column |
分裂阶段加权,不改变抽样 | 与 sklearn 类似 |
不同框架之间最大的差异在于"是否同步改变采样分布"。R 的 randomForest 包里,classwt 确实会让少数类有更大的被采样概率,这跟 sklearn 的机制就是两码事了。所以如果你在不同框架之间迁移代码,千万不能默认权重行为一致,最好是阅读对应版本的文档或者做一个小实验验证。
6.3 我踩过的坑与调参经验
讲到这里,分享几个我在实际项目中积累的实操经验,都是踩过坑换来的。
第一个坑:把 sample_weight 当成了"过采样按钮"。之前做一个反欺诈模型,正负样本比接近 1:100,我给正样本设了 50 倍权重,以为这样模型会像过采样 + 欠采样一样稳定。结果训练出来的树在验证集上表现不错,但在 OOB 样本上表现波动极大。原因就在于,权重只在分裂阶段起作用,bootstrap 抽样依然是均匀的,所以很多树可能压根没在训练集里见到足够的正样本,即使见到了,权重也只能在局部起作用,无法从根本上解决样本缺失问题。后来我改用 class_weight='balanced_subsample' 配合适度的 sample_weight,效果明显更稳。
第二个经验:权重范围不要跨太大。如果某类样本权重设成 100,其他类 1,树的节点分裂会被极端主导,很容易过拟合到少数类的个别样本上。一个比较稳妥的做法是把权重范围控制在 1 到 10 之间,让权重"引导"模型而不是"逼迫"模型。
第三个心得:在回归任务里,样本权重是处理噪声的好工具。之前做房屋价格预测,发现某些偏远地区的样本波动极大,严重干扰了树的划分。后来根据历史交易数据的可靠程度(比如成交周期、是否有明显异常值)给样本设置了权重,噪声大的样本权重调低。测试下来,验证集 RMSE 降低了大约 8%,而且模型对偏远地区数据的预测不再忽高忽低。回归树中加权 MSE 的好处是,权重不仅影响分裂方向,还会改变叶子节点的预测值——高权重样本会把叶子输出拉向自己,这个机制在某些业务场景下非常实用。
第四个提醒:注意特征重要性的解释偏差。加了权重之后,特征重要性(基于不纯度减少)会被高权重样本所在的区域放大。比如某特征只在少数高权重样本上剧烈变化,这个特征的重要性可能被严重高估。在做特征筛选时,最好结合 permutation importance 交叉验证,避免被权重带偏。
7. 写在最后的一点体会
真的把"随机森林样本权重"这一条线完整走下来,会发现它并不是一个孤立的参数,而是一整套和"弱学习器"深度绑定的机制。权重之所以能在随机森林中高效工作,依赖的是决策树对数据扰动的高度敏感性,以及 bagging 平均化对这些敏感性的"事后消化"。
我在实际使用中最喜欢的一个组合是:max_features 设为 'sqrt',min_samples_leaf 适当调大,然后配合 class_weight='balanced_subsample' 或显式的 sample_weight。这样既能保留随机森林的多样性,又能让权重在分裂阶段建立"正确的偏见"。如果你的任务里样本重要性天然不均,千万别把权重参数放着落灰,它会成为你手里最灵活的一根调音棒。
当然,权重不是万能的。它在类别不平衡问题上的效果,往往需要配合阈值调整、数据增强等手段才能真正发挥威力。建议你下次调随机森林时,专门做一组"有权重 vs 无权重"的对照实验,除了看 AUC 或准确率,更要观察混淆矩阵的变化——那才是权重影响最直接的地方。
