随机森林 Python 实战:从决策树到参数调优与特征重要性

发布时间:2026/10/21 2:51:15
随机森林 Python 实战:从决策树到参数调优与特征重要性 简介面向机器学习初学者的随机森林算法Python实现与配套数据集打包资源围绕sonar数据集的二分类任务完整演示随机森林分类器的构建、训练、预测与评估流程同时呈现调参与准确率对比的思路适合想用代码验证算法原理的Python学习者。压缩包合计4个文件包含2个Python脚本、1个CSV格式数据集和1个txt阅读说明整体仅34KB轻量易携带。两份Python代码分别来自网络下载和作者自行整理编写均配有详尽中文注释并已在Python2.7环境下调试通过CSV数据集规模较小便于快速跑通全流程也方便读者通过调整n_estimators、max_depth等参数直观感受随机森林在不同设置下的效果差异。目前已有15741人学习下载对希望快速理解随机森林基础实现与调参方向的学习者具有较高的参考价值尤其适合作为算法入门的辅助材料。1. 随机森林 Python 实现先看懂它为什么不容易翻车随机森林在我接手过的不少表格类项目里都是第一个能稳定出分的模型。它不像 XGBoost 那样有一堆超参数要博弈也不像单棵决策树那样稍微换个数据分布就过拟合到让人怀疑人生。它的核心逻辑很简单用有放回抽样和特征随机化训练出一批相关性低但各自有判断力的树然后让它们投票。这份随机森林代码和数据集资源目标就是让刚接触机器学习的读者能把从数据到模型、再到参数调优和结果解释的完整链路跑通而不是只会在 sklearn 里调用一下函数就完事。适合正在做课设、入门 Kaggle 或者准备面试代码手撕的人。2. 环境准备与数据解读正式建模前先搞清楚手里的牌2.1 依赖版本与运行环境不是所有 sklearn 版本行为都一样我一般会先确认 Python 版本再装依赖。随机森林在 sklearn 里的接口从 0.22 到 1.3 基本稳定但个别参数名字和默认行为有变化比如n_estimators在 0.22 之后就默认是 100而更早的版本默认是 10。如果你照着旧教程写代码很容易在模型表现上产生困惑。python --version pip install scikit-learn pandas numpy matplotlib seaborn跑完上面的命令后建议在脚本里打印一下版本避免后续排查问题时怀疑环境不一致import sklearn import pandas as pd import numpy as np print(sklearn version:, sklearn.__version__) print(pandas version:, pd.__version__) print(numpy version:, np.__version__)如果你用的虚拟环境里已经装过不同版本的包最好用pip freeze | grep scikit-learn确认一下。这里不强制要求 GPU随机森林是纯 CPU 计算数据集规模在几万行级别时普通笔记本完全扛得住。数据量如果到了百万级才需要考虑用RandomForestClassifier的并行参数n_jobs去压榨多核。2.2 数据说明与探索性检查特征缺失和类别不均衡是第一个坑这份资源里附带的数据集是一份脱敏后的二分类模拟数据我用make_classification按典型的信贷风控场景生成方便你把注意力放在算法本身上不用纠结数据版权问题。生成的逻辑如下from sklearn.datasets import make_classification import pandas as pd # 生成 2000 条样本12 个特征其中 8 个有效特征、4 个冗余特征 X, y make_classification( n_samples2000, n_features12, n_informative8, n_redundant4, n_classes2, weights[0.8, 0.2], # 类别比例 80% : 20%模拟信贷违约场景 random_state42 ) # 把特征名改成可读的字段名 feature_names [ income_level, # 收入水平连续性已标准化 credit_use_ratio, # 信用卡额度使用率 loan_amount, # 贷款金额 repayment_history, # 历史还款次数 debt_ratio, # 负债比 education_years, # 受教育年限 age, # 年龄 job_stability, # 工作稳定性评分 monthly_income, # 月收入 family_size, # 家庭人口 asset_value, # 资产价值 overdue_days, # 历史逾期天数 ] df pd.DataFrame(X, columnsfeature_names) df[target] y df.to_csv(random_forest_demo_dataset.csv, indexFalse) print(df.shape) print(df[target].value_counts())这段代码会把生成的数据保存成 CSV也就是这份资源里可以直接拿来用的数据集文件。你可以先跑一遍然后重点观察两个点第一n_features12里包含 4 个冗余特征这是为了模拟真实业务里特征之间往往有相关性第二weights[0.8, 0.2]制造了类别不均衡。这两个点会直接影响后面你调参时的判断。看数据分布的时候不要只盯着平均值。我一般会打印出每个特征的缺失率、标准差和唯一值数量# 检查缺失率和基本统计量 missing_rate df.isnull().mean() print(缺失率\n, missing_rate[missing_rate 0]) desc df.describe().T print(描述统计\n, desc[[mean, std, min, max]])如果真实业务数据里出现缺失随机森林在 split 节点时默认不会处理缺失值需要你自己用均值/中位数填充或者用SimpleImputer包一层。常见做法是先用中位数填充连续特征因为随机森林对特征尺度不敏感但缺失值的处理方式仍然会影响树的切分结果。2.3 训练集/验证集划分stratify 参数为什么不能省二分类任务里如果直接train_test_split(X, y, test_size0.2)极端情况下可能导致验证集里只有一类样本。对于随机森林这种依赖类别分布投票的模型验证集类别失衡会让评估指标失真。from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, # 保证训练集和测试集里正负样本比例一致 random_state42 ) print(训练集类别比例, y_train.mean()) print(测试集类别比例, y_test.mean())stratifyy会按照原始y的类别比例来分层抽样。对于二分类它确保两边的正样本占比都接近 0.2。这也是我在项目里固定random_state42的习惯方便对比不同实验之间的效果。很多初学者会忽略这个参数导致后面调参时发现验证集 AUC 忽高忽低其实不是模型不稳定而是数据切分不稳定。有一点要提醒如果你的数据集是时间序列比如预测用户未来几个月是否逾期绝对不能随机切分必须按时间先后切分否则会造成未来信息泄漏到训练集里。这份模拟数据是截面数据随机切分没有问题但换成真实业务数据时你要多想一步。3. 从单棵决策树到随机森林核心代码这样写才不容易翻车3.1 基线决策树先知道单棵树的误差在哪里直接上手随机森林之前我会先训练一棵默认参数的决策树做基线。这样做的好处是能直观感受到单棵树的高方差特性后续再看随机森林的提升时心里有数。from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score, roc_auc_score # 使用默认参数不限制深度让树尽量生长 dt_clf DecisionTreeClassifier(random_state42) dt_clf.fit(X_train, y_train) # 训练集评估 train_pred dt_clf.predict(X_train) train_auc roc_auc_score(y_train, dt_clf.predict_proba(X_train)[:, 1]) # 测试集评估 test_pred dt_clf.predict(X_test) test_auc roc_auc_score(y_test, dt_clf.predict_proba(X_test)[:, 1]) print(f决策树 训练集 AUC: {train_auc:.4f}) print(f决策树 测试集 AUC: {test_auc:.4f})这段代码跑出来训练集 AUC 通常接近 1.0而测试集 AUC 就会掉下来这就是过拟合的典型表现。predict_proba(X_train)[:, 1]取的是正类概率ROC AUC 比准确率更能反映排序能力尤其在类别不均衡时。如果你发现测试集 AUC 和训练集 AUC 差距不大那反而说明数据模式太简单或者树深度不够。基线决策树存在的意义不是让你真去用它而是给随机森林一个参照系。后面的随机森林如果测试集 AUC 没有明显提升你就该怀疑是不是特征工程有问题而不是模型能力不够。3.2 随机森林核心实现bootstrap 采样与特征随机选择在代码里如何体现随机森林相比单棵决策树改动就两个地方每棵树的训练样本是原始数据的有放回抽样bootstrap每棵树做节点分裂时只在随机的特征子集里找最优特征。sklearn 里这些都封装好了但你要明白参数对应的就是这个过程。from sklearn.ensemble import RandomForestClassifier rf_clf RandomForestClassifier( n_estimators100, # 树的数量通常 100~300 够用 max_depth10, # 限制单棵树最大深度防止过拟合 min_samples_leaf4, # 叶节点最少样本数避免树太碎 max_featuressqrt, # 分类任务默认取 sqrt(特征数) bootstrapTrue, # 有放回抽样默认开启 oob_scoreTrue, # 计算袋外分数作为内部验证 n_jobs-1, # 使用所有 CPU 核心并行训练 class_weightbalanced, # 自动调整类别权重 random_state42 ) rf_clf.fit(X_train, y_train)这里几个参数是随机森林的核心我挨个说明一下。n_estimators100表示训练 100 棵决策树。树越多预测越稳定但训练时间和内存占用线性增长100 只是一个常规起点实际项目里我会用 OOB 分数曲线判断继续加树有没有收益。max_depth10限制每棵树的生长深度默认是 None 即完全生长但完全生长的树在高维数据上容易过拟合。min_samples_leaf4强制叶节点至少包含 4 个训练样本这个值能明显控制单棵树的复杂度。max_featuressqrt在分类里默认值是sqrt(n_features)也就是每次分裂只随机看约 3~4 个特征这是随机森林“随机”二字的另一半体现。bootstrapTrue开启了自助采样每棵树大约只用 63.2% 的样本剩下的作为袋外数据用来算oob_score。class_weightbalanced在构建每棵树时自动为少数类样本加大权重这个在处理正负样本 4:1 的数据时很有用。随机森林的预测结果来自所有树的投票。对于分类任务predict返回投票结果predict_proba返回各类别的平均概率。这种平均机制就是它比单棵决策树稳定的根本原因——单棵树的错误方向各有不同投票能把多数噪音抵消掉。3.3 预测与评估混淆矩阵和概率输出比单看准确率可靠训练完成后评估这一步最容易草草了事。很多人只看准确率但在正负样本比例 4:1 的情况下准确率很容易虚高。比如一个瞎猜的模型把所有样本都预测成多数类准确率也有 80%。所以我的习惯是同时看混淆矩阵、精确率、召回率和 AUC。from sklearn.metrics import confusion_matrix, classification_report import numpy as np y_pred rf_clf.predict(X_test) y_prob rf_clf.predict_proba(X_test)[:, 1] # 混淆矩阵 cm confusion_matrix(y_test, y_pred) print(混淆矩阵行真实列预测:) print(cm) # 分类报告 print(\n分类报告:) print(classification_report(y_test, y_pred, target_names[正常, 违约])) # AUC auc_score roc_auc_score(y_test, y_prob) print(f\n测试集 AUC: {auc_score:.4f}) # 特征重要性 importance pd.Series(rf_clf.feature_importances_, indexfeature_names) print(\n特征重要性 Top5:) print(importance.sort_values(ascendingFalse).head(5))y_prob rf_clf.predict_proba(X_test)[:, 1]这行很关键因为 AUC 计算必须用概率而不是硬分类结果。混淆矩阵会让你直观看到模型是把多少正样本漏掉了以及把多少负样本误伤了。在信贷场景里漏掉一个违约客户远比多审一个正常客户代价高所以我会更关注召回率。feature_importances_输出每个特征在所有树分裂时带来的不纯度减少量的归一化结果它能告诉你模型主要靠什么特征做判断。但要注意这个值反映的是“模型内部的使用程度”不代表因果贡献。如果你有两个高度相关的特征重要性会在它们之间被稀释分摊看起来单个都不高但删掉其中任何一个模型都会变差。4. 参数调优与特征重要性别让默认参数背锅也别迷信默认参数4.1 网格搜索与交叉验证关键参数应该优先调哪个随机森林默认参数在 sklearn 里已经能跑出不错的效果但想要压榨出更好的泛化性能需要调几个关键参数。我调参的优先级是max_depthmin_samples_leafmax_featuresn_estimators。其中n_estimators通常是最不需要花时间调的因为只要树够多OOB 误差就会稳定下来而max_depth和min_samples_leaf直接控制单棵树的复杂度对防止过拟合影响最大。from sklearn.model_selection import GridSearchCV from sklearn.model_selection import StratifiedKFold param_grid { n_estimators: [100, 200, 300], max_depth: [6, 10, 14], min_samples_leaf: [2, 4, 6], max_features: [sqrt, log2, 0.6] } cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) grid_search GridSearchCV( estimatorRandomForestClassifier(class_weightbalanced, random_state42), param_gridparam_grid, scoringroc_auc, # 以 AUC 作为调参目标 cvcv, n_jobs-1, verbose1 ) grid_search.fit(X_train, y_train) print(最佳参数:, grid_search.best_params_) print(最佳交叉验证 AUC:, grid_search.best_score_)这段代码在 12 个特征、2000 条样本的数据集上运行大约需要几十秒到几分钟不等。StratifiedKFold手动指定分层交叉验证是为了配合class_weightbalanced时不至于让每一折的类别比例失稳。scoringroc_auc表示用 AUC 而不是准确率来挑选参数因为 AUC 对类别不均衡更不敏感。运行完之后你得到的最佳参数往往不是网格里所有参数的极端值而是在一个中等位置徘徊。找到最佳参数后我建议再用测试集做一次最终评估但千万不能回头再根据测试集结果调整参数否则测试集就变成了训练集的一部分。4.2 特征重要性的局限与可视化用排序条形图快速汇报特征重要性是随机森林最容易被拿去写报告的东西但使用时有边界。它只能告诉你模型在训练时“用得多”不能告诉你“删掉有没有影响”。import matplotlib.pyplot as plt # 按重要性排序 importance_sorted importance.sort_values(ascendingTrue) plt.figure(figsize(8, 6)) plt.barh(importance_sorted.index, importance_sorted.values) plt.xlabel(Feature Importance) plt.title(Random Forest Feature Importance) plt.tight_layout() plt.savefig(feature_importance.png, dpi150) plt.show()这段代码会生成一个横向条形图适合直接放进项目文档或汇报 PPT 里。横向条形图比纵向更适合展示特征名因为特征名通常较长。我在项目里看过一个反直觉的现象某个特征重要性排第一但把该特征从数据里删掉之后AUC 只掉了 0.002。这说明存在一个与它高度相关的替代特征重要性被高估了。反过来也有重要性不高但删掉后 AUC 明显下降的特征它可能是和其他特征组合后才有作用。遇到这种情况更可靠的验证方式是做特征消融实验每次删掉一个特征重新训练同一组参数对比 AUC 变化。虽然耗时但结论扎实。4.3 OOB 分数快速验证省一次交叉验证的土办法交叉验证在数据量大的时候很费时间随机森林有个内置的袋外分数可以快速预览模型表现。每棵树训练时没用到的样本约 36.8%会被用来做一次验证所有树的结果汇总后算出的 AUC 或准确率和十折交叉验证的结果通常很接近。# 用 OOB 判断 n_estimators 是否够用 import matplotlib.pyplot as plt oob_scores [] tree_range range(10, 201, 10) for n in tree_range: rf_temp RandomForestClassifier( n_estimatorsn, max_depth10, min_samples_leaf4, max_featuressqrt, bootstrapTrue, oob_scoreTrue, class_weightbalanced, n_jobs-1, random_state42 ) rf_temp.fit(X_train, y_train) oob_scores.append(rf_temp.oob_score_) plt.figure(figsize(8, 4)) plt.plot(list(tree_range), oob_scores, markero) plt.xlabel(n_estimators) plt.ylabel(OOB Score) plt.title(OOB Score vs n_estimators) plt.tight_layout() plt.savefig(oob_curve.png, dpi150) plt.show()这段代码通过逐渐增加树的数量画出 OOB 分数曲线。你会在图上看到当树的数量增加到某个值后曲线变得平坦说明再增加树的数量没有明显收益。这时候就可以确定n_estimators的合理取值然后专注去调其他参数。OOB 分数的意义在于不需要额外划分验证集也不用把训练时间翻几倍去做交叉验证。我在初步探索参数空间时都会先用 OOB 快速排除明显不好的参数组合等范围缩小后再上网格搜索。需要注意OOB 分数在树的数量很小时波动较大所以要看曲线的末尾趋势不要只看前几个点。5. 避坑随机森林实战中常见的五个翻车现场5.1 现象准确率很高但线上完全没法用某次在一个模拟项目里模型训练完准确率 0.92AUC 也接近 0.9但是把模型部署到测试环境后业务方反馈预测结果明显偏向某一类。检查之后发现数据预处理阶段用全量数据计算了中位数填充缺失值再做的训练集测试集切分。中位数里包含了测试集的信息这叫数据泄漏。原因填充缺失值或标准化时用了全量数据的统计量。解决先把数据切分成训练集和测试集再在训练集上拟合填充器或标准化器然后只对训练集和测试集分别做 transform。用 Pipeline 能少踩这个坑。from sklearn.impute import SimpleImputer from sklearn.pipeline import Pipeline pipeline Pipeline([ (imputer, SimpleImputer(strategymedian)), (classifier, RandomForestClassifier(n_estimators100, random_state42)) ]) pipeline.fit(X_train, y_train)这里把填充和分类器封装在一起交叉验证时每一折都会独立拟合填充器才不会把验证集信息泄漏到训练过程里。5.2 现象n_estimators 从 50 调到 500 效果变化不大新手最常见的操作是一上来就把n_estimators拉满认为树越多越好。实际上当单棵树的复杂度没有控制好时加再多的树也只是在平均同一个有偏的错误。我见过有人把n_estimators调了两天AUC 波动不超过 0.01真正的问题出在别的参数上。原因n_estimators只减少方差不减少偏差。如果单棵树本身拟合能力弱或者数据特征质量差加树解决不了根本问题。解决先用 OOB 曲线确定树的合理数量然后把精力放到max_depth和min_samples_leaf上。这两个参数直接影响单棵树的拟合精度和稳定性。5.3 现象特征重要性分布极其平均如果你发现所有特征重要性都在 0.05~0.1 之间没有一个明显的头部特征这不一定说明数据没有信息。很可能是你用了大量高度相关的特征或者把类别型特征用整数编码塞进去了随机森林被迫在它们之间均匀分摊重要性。原因相关特征和稠密编码会把重要性稀释。解决先用相关性矩阵筛掉冗余特征类别特征用有序编码或 one-hot 处理后重新训练观察。特征重要性平均还有一个常见原因是max_features设置太小每棵树的随机特征子集覆盖不到所有有效特征。尝试把max_features调大比如改成 log2 或具体的比例 0.6。5.4 现象训练耗时随数据量爆炸式增长随机森林虽然是并行算法但如果单棵树很深每棵树都要反复扫描大量样本来找最优切分点计算量会暴涨。特征数量几十个、样本量几十万时默认参数的随机森林已经可能跑得让人崩溃。原因max_depthNone加上min_samples_split2树完全生长切分次数太多。解决限制树的深度max_depth15把min_samples_leaf调大到 4~8这会让每棵树的体积显著缩小再用n_jobs-1利用全核并行。如果数据量到了百万条建议先用抽样或者改用直方图提升算法。5.5 现象模型对少数类的召回率非常低即使在跑完默认随机森林后整体 AUC 看着还行看混淆矩阵却发现少数类几乎完全没被预测出来。因为随机森林的 bootstrap 抽样在类别不平衡时多数类更容易被抽到少数类样本在每棵树里的存在感很低。原因多数类和少数类样本数差距过大投票结果被多数类主导。解决用class_weightbalanced或者用class_weightbalanced_subsample在每次 bootstrap 抽样时根据实际抽到的样本比例来调整权重。如果这样还不行就考虑对少数类做 SMOTE 过采样或者把预测阈值往下调用概率而不是默认 0.5 来决策。# 调低决策阈值用概率输出替换硬分类 from sklearn.metrics import precision_recall_curve y_prob_test rf_clf.predict_proba(X_test)[:, 1] precisions, recalls, thresholds precision_recall_curve(y_test, y_prob_test) # 找一个召回率 0.8 对应的阈值 threshold thresholds[np.argmax(recalls 0.8)] y_pred_adjust (y_prob_test threshold).astype(int)这里用精确率-召回率曲线反推出要达成目标召回率所需的概率阈值。随机森林的predict默认以 0.5 为阈值但在不平衡数据上这个阈值几乎总是次优的所以实际项目里我更依赖predict_proba加自定义阈值。6. 把随机森林的预测结果解释给业务听一份可交付的最小解释方案模型训完参数调好最后一步往往是给不懂机器学习的同事讲清楚“模型为什么把这个人判成高风险”。随机森林本身是个黑匣子但我们可以从两个角度把解释做出来。第一个角度是全局解释用特征重要性做条形图说明“收入水平”“负债比”“逾期天数”是模型最主要的三张底牌。第二个角度是局部解释抽取一个被预测为违约的样本查看它的特征值在整棵树的决策路径中被哪些条件推动。sklearn 里可以用decision_path辅助但更直观的做法是把该样本的特征值和总体均值做对比sample X_test[5:6] sample_prob rf_clf.predict_proba(sample)[:, 1][0] for i, col in enumerate(feature_names): sample_val sample[0, i] mean_val X_train[:, i].mean() diff sample_val - mean_val if abs(diff) 0.5: print(f{col}: 样本值 {sample_val:.2f} vs 训练集均值 {mean_val:.2f}差 {diff:.2f})这样业务方看到的是“这个人逾期天数比平均水平高出很多收入水平低于平均所以模型给高风险”而不是一句话“模型说的”。如果你需要更精确的局部解释可以安装 shap 库用 TreeExplainer 对随机森林做 SHAP 值分解但 shap 包的安装偶尔会和 sklearn 版本冲突我在本地环境里调试过两次才跑通所以除非交付文档有硬性要求否则优先用上面的对比方法。我自己的习惯是每次做随机森林模型都会强制走一遍完整流程生成或加载数据检查类别比例分层切分先跑 OOB 基线再调参最后输出一个带特征重要性和阈值选择的简版报告。最近一次做模拟信贷项目我就是在 OOB 曲线稳定在 100 棵树后调了max_depth和min_samples_leaf最终测试集 AUC 比默认参数提升了 0.03而且没有额外算力成本。从那以后我每次都先跑 OOB 曲线看趋势再决定要不要继续加树而不是拍脑袋把数值调大。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询