菜菜 ML:随机森林实战
3790 字约 13 分钟
2026-05-20
1. 随机森林概述
基本思想: 随机森林的核心思想是"集体智慧",它通过构建多个决策树,然后集成它们的预测结果来得到更可靠的预测。这背后的哲学是:"三个臭皮匠,顶个诸葛亮" - 多个相对简单的模型通过合作可以达到更好的效果。
随机采样训练数据(Bootstrap Sampling): 对于每棵树,随机森林会从原始数据集中随机抽取样本(有放回抽样)来训练。比如有1000条数据,每棵树可能随机抽取800条来训练。这样每棵树看到的数据都略有不同,增加了模型的多样性。
1.1 集成算法概述
集成学习(ensemble learning)是当前非常流行的机器学习算法。它不是一个独立的机器学习算法,而是通过在数据上构建多个模型,集成所有模型的建模结果。基本上所有的机器学习领域都可以看到集成学习的身影。
在现实中集成学习有着广泛的应用:
- 市场营销模拟建模
- 统计客户来源、保留和流失
- 预测疾病风险和病患者的易感性
在现在的各种算法竞赛中,随机森林、梯度提升树(GBDT)、Xgboost等集成算法的身影也随处可见。
集成算法的组成部分
- 多个模型集成成为的模型叫做集成评估器(ensemble estimator)
- 组成集成评估器的每个模型都叫做基评估器(base estimator)
主要的集成算法类型
- 装袋法(Bagging)
- 提升法(Boosting)
- Stacking
装袋法(Bagging)
装袋法的核心思想是构建多个相互独立的评估器,然后对其预测进行平均或多数表决原则来决定集成评估器的结果。代表模型就是随机森林。
提升法(Boosting)
基评估器是相关的,是按顺序一一构建的。其核心思想是结合弱评估器的力量一次次对难以评估的样本进行预测,从而构成一个强评估器。代表模型有Adaboost和梯度提升树。
1.2 sklearn中的集成算法
sklearn中的集成算法模块包含:
| 类 | 功能 |
|---|---|
| ensemble.AdaBoostClassifier | AdaBoost分类 |
| ensemble.AdaBoostRegressor | Adaboost回归 |
| ensemble.BaggingClassifier | 装袋分类器 |
| ensemble.BaggingRegressor | 装袋回归器 |
| ensemble.ExtraTreesClassifier | Extra-trees分类(超树,极端随机树) |
| ensemble.ExtraTreesRegressor | Extra-trees回归 |
| ensemble.GradientBoostingClassifier | 梯度提升分类 |
| ensemble.GradientBoostingRegressor | 梯度提升回归 |
| ensemble.IsolationForest | 隔离森林 |
| ensemble.RandomForestClassifier | 随机森林分类 |
| ensemble.RandomForestRegressor | 随机森林回归 |
| ensemble.RandomTreesEmbedding | 完全随机树的集成 |
| ensemble.VotingClassifier | 用于不合适估算器的软投票/多数规则分类器 |
2. RandomForestClassifier详解
2.1 重要参数
2.1.1 criterion(分裂标准)
- 用来决定不纯度的计算方法
- 可选值:
- "gini":使用基尼系数(默认)
- "entropy":使用信息熵
- 这个参数帮助找出最佳节点和最佳分枝,不纯度越低,决策树对训练集的拟合越好
- MSE
- Friedman_MSE
- MAE mean absolute error
2.1.2 n_estimators
n_estimators 是指随机森林中决策树的数量。简单来说,它决定了我们要"种植"多少棵树来组成这片"森林"。每一棵决策树都会对数据进行预测,最终随机森林会将所有树的预测结果进行整合:
- 对于分类问题,采用投票的方式,以多数树的预测结果为准
- 对于回归问题,取所有树预测值的平均值作为最终结果
n_estimators越大,模型的效果往往越好。但是相应的,任何模型都有决策边界,n_estimators达到一定的程度之后,随机森林的精确性往往不在上升或开始波动,并且,n_estimators越大,需要的计算量和内存也越大,训练的时间也会越来越长。对于这个参数,我们是渴望在训练难度和模型效果之间取得平衡。n_estimators的默认值在现有版本的sklearn中是100
随机森林用了什么方法,来保证集成的效果一定好于单个分类器?
随机森林的本质是一种装袋集成算法(bagging),装袋集成算法是对基评估器的预测结果进行平均或用多数表决原则来决定集成评估器的结果。在刚才的红酒例子中,我们建立了25棵树,对任何一个样本而言,平均或多数表决原则下,当且仅当有13棵以上的树判断错误的时候,随机森林才会判断错误。单独一棵决策树对红酒数据集的分类准确率在0.85上下浮动,假设一棵树判断错误的可能性为0.2(ε),那20棵树以上都判断错误的可能性是:
:
2%E9%9A%8F%E6%9C%BA%E6%A3%AE%E6%9E%97/image.png
其中,i是判断错误的次数,也是判错的树的数量,ε是一棵树判断错误的概率,(1-ε)是判断正确的概率,共判对25-i次。采用组合,是因为25棵树中,有任意i棵都判断错误。
random_state & splitter
random_state:- 用来设置分枝中的随机模式
- 默认None
- 在高维度时随机性会表现更明显
- 设置固定值可以保证每次生成相同的树
splitter:- 控制决策树中的随机选项
- 可选值:
- "best":虽然随机,但会优先选择更重要的特征进行分枝
- "random":更加随机,可能会包含更多不必要信息
2.1.3 剪枝参数
max_depth:- 限制树的最大深度
- 最常用和最有效的剪枝参数
- 建议从3开始尝试
min_samples_leaf:- 限定叶子节点最少的样本数
- 一般搭配max_depth使用
- 建议从5开始使用
min_samples_split:- 限定分枝最少的样本数
- 防止过拟合
- 默认值为2
max_features:- 限制分枝时考虑的特征个数
- 默认值是auto(特征数量的平方根)
- 可以用来限制高维度数据的过拟合
min_impurity_decrease:- 限制信息增益的大小
- 信息增益小于设定数值的分枝不会发生
2.2 重要属性和接口
主要属性
feature_importances_:特征重要性评估estimators_:所有训练好的基评估器classes_:标签类别n_classes_:标签类别数量n_features_:特征数量
核心接口
fit(X, y):训练模型predict(X):预测类别predict_proba(X):预测每个类别的概率score(X, y):返回模型准确率apply(X):返回每个样本所在的叶子节点
2.3 随机森林的工作原理
- 对训练集进行有放回抽样(bootstrap)
- 每个样本集训练一个决策树
- 每个决策树在分裂时只考虑随机选择的部分特征
- 所有决策树投票决定最终结果(分类用多数投票,回归用平均)
bootstrap & oob_score
要让基分类器尽量都不一样,一种很容易理解的方法是使用不同的训练集来进行训练,而袋装法正是通过有放回的随机抽样技术来形成不同的训练数据,bootstrap就是用来控制抽样技术的参数。
在一个含有n个样本的原始训练集中,我们进行随机采样,每次采样一个样本,并在抽取下一个样本之前将该样本放回原始训练集,也就是说下次采样时这个样本依然可能被采集到,这样采集n次,最终得到一个和原始训练集一样大的,n个样本组成的自助集。由于是随机采样,这样每次的自助集和原始数据集不同,和其他的采样集也是不同的。这样我们就可以自由创造取之不尽用之不竭,并且互不相同的自助集,用这些自助集来训练我们的基分类器,我们的基分类器自然也就各不相同了。 bootstrap参数默认True,代表采用这种有放回的随机抽样技术。通常,这个参数不会被我们设置为False。
然而有放回抽样也会有自己的问题。由于是有放回,一些样本可能在同一个自助集中出现多次,而其他一些却可能被忽略,一般来说,自助集大约平均会包含63%的原始数据。因为每一个样本被抽到某个自助集中的概率为:
Probability=1−(1−n1)n
当n足够大时,这个概率收敛于1-(1/e),约等于0.632。因此,会有约37%的训练数据被浪费掉,没有参与建模,这些数据被称为袋外数据(out of bag data,简写为oob)。除了我们最开始就划分好的测试集之外,这些数据也可以被用来作为集成算法的测试集。也就是说,在使用随机森林时,我们可以不划分测试集和训练集,只需要用袋外数据来测试我们的模型即可。当然,这也不是绝对的,当n和n_estimators都不够大的时候,很可能就没有数据掉落在袋外,自然也就无法使用oob数据来测试模型了。
如果希望用袋外数据来测试,则需要在实例化时就将oob_score这个参数调整为True,训练完毕之后,我们可以用随机森林的另一个重要属性:oob_score_来查看我们的在袋外数据上测试的结果
bootstrap采样的特点:
- 每个样本集大约包含原始数据的63.2%
- 剩余约36.8%的数据称为袋外数据(Out-Of-Bag,OOB)
- 可以用OOB数据评估模型性能,通过设置
oob_score=True
3. RandomForestRegressor
3.1 重要参数
基本与分类器相同,主要区别在于criterion参数:
- criterion可选值:
- "mse":均方误差(默认)
- "friedman_mse":弗里德曼均方误差
- "mae":平均绝对误差
3.2 实例:用随机森林回归填补缺失值
基本思路:
- 对于含有缺失值的特征,将其作为目标变量
- 其他特征和原始目标变量作为特征矩阵
- 使用随机森林预测缺失值
# 基本代码框架
from sklearn.datasets import load_boston
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestRegressor
from sklearn.impute import SimpleImputer
import numpy as np
import pandas as pd
def fill_missing_rf(data, missing_features):
"""使用随机森林填补缺失值"""
# 对每个含缺失值的特征进行填补
for feature in missing_features:
# 构建训练集
known_features = data[feature].notnull()
train_data = data[known_features]
test_data = data[~known_features]
# 训练随机森林回归器
regressor = RandomForestRegressor(n_estimators=100, random_state=0)
regressor.fit(train_data.drop(feature, axis=1), train_data[feature])
# 预测缺失值
predictions = regressor.predict(test_data.drop(feature, axis=1))
data.loc[~known_features, feature] = predictions
return data4. 机器学习中调参的基本思想
4.1 泛化误差的构成
泛化误差 = 偏差² + 方差 + 噪声
- 偏差:模型的预测值与真实值的差异
- 方差:模型预测结果的波动程度
- 噪声:数据本身的随机性导致的误差
4.2 调参的基本原则
- 首先寻找最重要的参数进行调整
n_estimators:森林中树的数量max_depth:树的深度限制max_features:特征选择数量
- 使用网格搜索或随机搜索来寻找最优参数组合
from sklearn.model_selection import GridSearchCV
param_grid = {
'n_estimators': [10, 50, 100, 200],
'max_depth': [None, 10, 20, 30],
'max_features': ['auto', 'sqrt', 'log2']
}
rf = RandomForestClassifier()
grid_search = GridSearchCV(rf, param_grid, cv=5)
grid_search.fit(X_train, y_train)- 注意过拟合和欠拟合的平衡
- 过拟合:在训练集表现很好,测试集表现差
- 欠拟合:在训练集和测试集都表现欠佳
4.3 调参流程
- 评估基线模型性能
- 选择最重要的参数进行粗调
- 在最佳参数周围进行精调
- 使用交叉验证评估模型
- 进行模型集成或者特征工程以进一步提升性能
5. 实例:随机森林在乳腺癌数据上的调参
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV, cross_val_score
# 加载数据
data = load_breast_cancer()
X, y = data.data, data.target
# 基准模型
rfc = RandomForestClassifier(random_state=42)
scores = cross_val_score(rfc, X, y, cv=5)
print(f"基准模型得分: {scores.mean():.3f} (+/- {scores.std() * 2:.3f})")
# 参数网格
param_grid = {
'n_estimators': [100, 200, 300],
'max_depth': [10, 20, 30, None],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4]
}
# 网格搜索
grid_search = GridSearchCV(rfc, param_grid, cv=5, n_jobs=-1)
grid_search.fit(X, y)6. 附录
6.1 Bagging vs Boosting 对比
| 特征 | Bagging | Boosting |
|---|---|---|
| 评估器 | 相互独立,同时运行 | 相互关联,按顺序依次构建,后建的模型会在先建模型预测失败的样本上有更多权重 |
| 抽样数据集 | 有放回抽样 | 有放回抽样,但会赋予数据权重,每次抽样会给预测失败的样本更多权重 |
| 决定集成结果 | 平均或多数服从少数原则 | 加权平均,表现更好的模型有更大权重 |
| 目标 | 降低方差,提高整体稳定性 | 降低偏差,提高整体精确度 |
| 过拟合倾向 | 能够一定程度缓解过拟合 | 可能会加剧过拟合 |
| 基评估器效果 | 不需要非常有效 | 效果越好越好 |
| 代表算法 | 随机森林 | AdaBoost, GBDT |
6.2 RandomForestClassifier参数完整列表
| 参数名 | 说明 | 默认值 |
|---|---|---|
| n_estimators | 森林中树的数量 | 10 (在0.22版本将改为100) |
| criterion | 分裂标准:"gini"或"entropy" | "gini" |
| max_depth | 树的最大深度 | None |
| min_samples_split | 分裂所需最小样本数 | 2 |
| min_samples_leaf | 叶节点最小样本数 | 1 |
| min_weight_fraction_leaf | 叶节点最小权重比例 | 0.0 |
| max_features | 最大特征数 | "auto" |
| max_leaf_nodes | 最大叶节点数 | None |
| min_impurity_decrease | 最小不纯度减少 | 0.0 |
| bootstrap | 是否使用bootstrap采样 | True |
| oob_score | 是否使用袋外样本评估 | False |
| n_jobs | 并行任务数 | None |
| random_state | 随机种子 | None |
| verbose | 日志详细程度 | 0 |
| warm_start | 是否使用之前的解 | False |
| class_weight | 类别权重 | None |
6.3 随机森林调参建议
首要调整参数:
# 优先调整n_estimators n_estimators_range = range(10, 200, 10) cv_scores = [] for n_estimators in n_estimators_range: rf = RandomForestClassifier(n_estimators=n_estimators) scores = cross_val_score(rf, X, y, cv=5) cv_scores.append(scores.mean())防止过拟合的参数调整:
# 调整max_depth和min_samples_leaf param_grid = { 'max_depth': [3, 5, 7, 9], 'min_samples_leaf': [3, 5, 7, 9] }特征选择相关参数:
# 调整max_features param_grid = { 'max_features': ['auto', 'sqrt', 'log2', None] }
6.4 实践技巧
交叉验证的使用:
- 使用K折交叉验证评估模型
- 推荐使用5-10折
- 对于小数据集可以使用留一交叉验证
特征重要性分析:
feature_importance = pd.DataFrame({ 'feature': feature_names, 'importance': rf.feature_importances_ }).sort_values('importance', ascending=False)并行计算:
- 使用n_jobs参数开启并行
- n_jobs=-1使用所有CPU核心
这就是随机森林在sklearn中实现的主要内容。通过了解这些参数和使用方法,我们可以更好地利用随机森林解决实际问题。记住,调参是一个需要经验和耐心的过程,要多尝试不同的参数组合,并始终关注模型的泛化能力。