拓冰建站拓冰建站
首页 / 资讯中心 / 正文

决策树速查手册:分裂标准、剪枝调参与随机森林实战

做机器学习的人案头都应该有一份决策树cheatsheet。这玩意儿我用了好几年越用越觉得它是整个树模型家族的地基——你理解了决策树再看随机森林、梯度提升树甚至XGBoost里那些“神神叨叨”的参数本质上都是在解决决策树某一方面的短板。这份速查表不是教科书的目录搬运而是我调参、debug、上线模型这几年把踩过的坑和验证过的结论攒在一起形成的。内容覆盖决策树分类和回归的分裂标准、sklearn里那些参数到底该按什么顺序调、剪枝的正确姿势以及从单棵树走向随机森林时你到底在干什么。适合正在学机器学习的同学、准备面试的候选人以及工作中需要快速搭一个可解释模型的工程师。1. 决策树到底在解决什么问题1.1 一张图理解决策树的判断逻辑很多人第一次接触决策树会被“信息熵”“基尼系数”这些名词吓住。先忘掉公式用最朴素的方式理解它。决策树做的事情本质上就是把你“拍脑袋做决定”的过程固化下来。比如早高峰出门你要不要带伞会先看窗外有没有下雨根节点。没下再看天气预报APP说有雨没有内部节点预报有雨还要判断包里空间够不够、雨大不大。到最后得出一个结论带伞、不带伞、带什么类型的伞叶子节点。这一串 if-then 规则就是决策树的结构。它自己从数据里学出来的不是人写的。树学习的过程是在每一个节点上回答一个问题用什么特征、在什么阈值上切一刀能让分类结果最“干净”。所谓“干净”就是同一个分支里的人尽量属于同一个类别。学习完一棵树后你得到的东西非常直观一条从根到叶子的路径就是一条规则整棵树就是一组规则的集合。这个特质在工业界极其重要因为它意味着模型可以被人完整阅读、审核、解释。我做金融风控的时候监管问“你为什么拒绝这个用户”决策树可以直接说出“因为收入小于5000且历史逾期次数大于2”这是黑盒模型做不到的。1.2 分类、回归都能干的万能选手决策树不是只能做分类。它有两个形态分类树和回归树。分类树输出离散类别叶子节点存的是投票结果少数服从多数。比如判断用户是否流失叶子节点里80%样本是流失那这个叶子就输出“流失”。回归树输出连续值叶子节点存的是这个区域里样本目标值的均值。比如预测房价某个叶子里的样本均价是300万那落入这个叶子的新样本预测值就是300万。这导致回归树的预测曲线是“阶梯状”的——输出在一段区间内恒定跳到下一个区间才变。很多时候这看起来比线性回归粗糙但好处是它能抓住非线性关系比如收入对消费的影响可能是“低时平缓、中段陡增、高段回落”这样复杂的形态。按照我的经验决策树适合的场景有三个特征第一业务方要求你能解释预测理由第二特征和目标之间的关系复杂、有明显交互效应比如性别对收入有影响但只有在教育水平高时才显著第三任务不是超高维稀疏数据比如文本TF-IDF特征就不太适合裸决策树。不适合的场景也有需要平滑连续预测的比如轨迹拟合、特征维度上万的容易过拟合且训练慢、以及数据量大到需要分布式训练的情况。这些时候要么换集成模型要么换深度学习。2. 分裂、停止与剪枝决策树的三个齿轮一棵决策树从数据集长出来需要三个机制配合分裂标准决定怎么选特征停止条件决定什么时候不长了剪枝决定长出来后怎么修。这三个齿轮咬合得好树才能既不过拟合也不能用。2.1 怎么选分裂特征信息增益、增益率与基尼指数选分裂特征的实质是每一步都找一个特征和阈值让分裂后的子节点比分裂前“更纯”。先看分类树。经典的做法是用信息熵衡量纯度。熵的定义是Ent(D) -Σ p_k * log2(p_k)其中 p_k 是第 k 类样本在数据集 D 中占的比例。熵越大说明类别越混乱熵为0说明全是同一个类别。分裂之后的信息增益就是分裂前的熵减去各子节点熵的加权和Gain(D, a) Ent(D) - Σ (|Dv| / |D|) * Ent(Dv)信息增益越大说明这个特征让数据变纯的效果越好。这是最早 ID3 算法的思路。举一个经典例子。假设有14个样本目标是判断“今天是否打球”其中9个打、5个不打。初始熵约0.940。现在按“天气”切分晴天5个2打3不打、阴天4个4打0不打、雨天5个3打2不打。算下来信息增益约0.247。如果按另一个特征“风力”切分增益没这么高那就选择“天气”作为当前节点的分裂特征。但信息增益有一个毛病它偏爱取值多的特征。比如把“样本ID”作为特征每个ID只对应一个样本分裂后每个子节点纯度100%信息增益直接拉满。可这显然是过拟合。C4.5 算法引入增益率用特征的固有值做惩罚缓解了这个偏差。CART 则改用基尼指数基尼的定义是Gini(D) 1 - Σ p_k²基尼指数越小纯度越高。CART 每次分裂选择基尼指数下降最多的特征。实际使用中基尼和信息熵的结果通常非常接近sklearn 里默认用基尼主要因为它没有对数运算计算更快。再看回归树。分裂标准通常是均方误差 MSE。每次尝试一个切分点计算切分后左右两部分样本目标值的方差之和让这个总和最小。大白话说就是希望切完后每一块内部的数值都尽量接近误差最小。sklearn 回归树默认用的是 friedman_mse是MSE的一种优化变体训练效率更高。2.2 何时停止深度、样本量与不纯度如果不加限制决策树可以一直长到每个叶子只剩一个样本。这时候训练集准确率100%测试集一塌糊涂。所以必须有停止条件。实际工程里主要靠四个参数踩刹车max_depth限制树的最大深度。这是最重要的参数深度越大越容易过拟合。min_samples_split一个节点至少有多少样本才允许继续分裂。样本少了就不值得再分。min_samples_leaf叶子节点至少要有多少样本。这个参数能防止出现“一个叶子只装一个样本”的极端情况。min_impurity_decrease分裂前后不纯度下降必须达到某个阈值否则停止。适合追求精度时用但对样本量比较敏感需要反复试。这四个参数要联动看。只限定深度、不管叶子样本数树还是可能在最后一层分裂出很多碎片叶子只限定叶子样本数、不管深度又可能在深层持续分裂。常规做法是先固定 max_depth再调叶子样本数最后看情况决定要不要卡不纯度。2.3 剪枝预剪枝与后剪枝停止条件属于“预剪枝”——边长边限制。另一种思路是“后剪枝”先让树充分生长再把一些没用的分支剪掉。后剪枝在实践中往往效果更好因为它是在完整信息基础上做决策而不是过早砍掉可能后劲十足的分支。sklearn 里做后剪枝主要靠 ccp_alpha也就是成本复杂度剪枝。它的思路很简单给复杂度叶子节点数量加上一个惩罚系数公式是总损失 经验损失 ccp_alpha * 叶子节点数ccp_alpha 越大惩罚越重树就越小。sklearn 提供了路径函数 cost_complexity_pruning_path可以一次性算出不同 alpha 对应的树你再通过验证集挑一个最合适的。这部分操作我在第5节详细写代码。3. 常见决策树算法横向对比ID3、C4.5、CART面试和项目里最常被问到的三个决策树算法对比看这张表就够了。算法提出者分裂标准支持任务特征处理特点ID3Quinlan 1986信息增益分类只能离散特征简单偏爱取值多的特征C4.5Quinlan 1993增益率分类支持连续特征、缺失值比ID3健壮是ID3的升级版CARTBreiman 1984基尼指数分类/ MSE回归分类回归支持连续特征二叉树可做后剪枝实际工程里你几乎不会手写这些算法但理解它们的差异很重要。ID3 的问题是多分支生成而且天然偏向取值多的特征。C4.5 改进了这一点还支持连续特征的二分处理但实现复杂、训练慢。CART 始终保持二叉树结构基尼指数计算快而且同一套框架同时支持分类和回归所以 sklearn 里只有这个一个决策树实现也是随机森林、XGBoost 等模型的底子。顺带说一个面试常问的细节C4.5 的连续特征处理方式是把特征值排序然后尝试相邻值的中点作为切分点从中选增益率最高的位置。这一点理解透了你就明白为什么树模型不需要对连续特征做归一化——它每次只在排序后找切分点特征的绝对尺度没有意义。4. 手把手实操训练一棵拿得出手的决策树下面用 sklearn 走一遍完整的流程。我用的例子是乳腺癌数据集569个样本、30个特征适合演示分类。回归部分我会另外演示。4.1 五步快速搭建决策树第一步加载数据并划分训练集和测试集。分层抽样保证正负样本比例在两边一致。import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import classification_report, roc_auc_score data load_breast_cancer() X, y data.data, data.target X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy )第二步先不调任何参数训练一棵默认决策树。clf DecisionTreeClassifier(random_state42) clf.fit(X_train, y_train) train_acc clf.score(X_train, y_train) test_acc clf.score(X_test, y_test) print(ftrain acc {train_acc:.4f}, test acc {test_acc:.4f})运行结果基本是训练集100%、测试集90%出头。训练集100%不是模型好是典型的过拟合——树把所有样本的细节都记住了。这里训练集精度没有参考价值它只是在告诉你决策树有能力充分拟合训练数据。第三步加一个 max_depth 限制再训练。clf DecisionTreeClassifier(max_depth4, random_state42) clf.fit(X_train, y_train) print(ftrain acc {clf.score(X_train, y_train):.4f}, test acc {clf.score(X_test, y_test):.4f})限制到4层后测试集精度通常能到94%上下泛化能力反而更好。这就是决策树调参的主旋律你不是在追求训练集精度而是在控制模型复杂度。第四步在测试集上做完整评估。y_pred clf.predict(X_test) y_prob clf.predict_proba(X_test)[:, 1] print(classification_report(y_test, y_pred, target_namesdata.target_names)) print(fAUC {roc_auc_score(y_test, y_prob):.4f})第五步可视化。from sklearn.tree import plot_tree plt.figure(figsize(18, 8)) plot_tree( clf, filledTrue, feature_namesdata.feature_names, class_namesdata.target_names, max_depth3, fontsize8 ) plt.show()plot_tree 会让整棵树的结构一目了然。每个节点上能看到分裂特征和阈值、样本数、类别分布占比、基尼指数。可视化是检查决策树是否学到合理逻辑的最快手段。如果根节点就分出“半径均值小于某阈值全是良性”这种强规则说明模型抓住了核心区分点。4.2 关键参数速查表sklearn 决策树分类器和回归器大部分参数一致差异在 criterion 上。参数分类树可选值回归树可选值作用criteriongini / entropy / log_losssquared_error / friedman_mse / absolute_error / poisson分裂标准max_depthint 或 Noneint 或 None最大深度None表示不限制min_samples_splitint 或 floatint 或 float内部节点最少样本数浮点数表示比例min_samples_leafint 或 floatint 或 float叶子节点最少样本数max_featuresint / float / sqrt / log2同左每次分裂考虑的特征数max_leaf_nodesint 或 Noneint 或 None最多叶子数限制整体规模class_weightbalanced 或 dict无处理类别不均衡仅分类ccp_alphafloatfloat后剪枝复杂度惩罚系数这里有一个新手容易看漏的点min_samples_split 针对的是“内部节点”意思是这个节点在样本数达到阈值时才有资格继续分裂min_samples_leaf 针对的是“叶子节点”限制分裂后的子节点最少样本数。两者的侧重点不同min_samples_leaf 对抑制过拟合的效果往往更明显因为它直接保证了叶子不会太稀碎。4.3 决策树回归手把手演示回归树和分类树共用一套决策逻辑区别只在分裂标准和叶子输出。我用正弦波加噪声的数据演示。from sklearn.tree import DecisionTreeRegressor rng np.random.RandomState(0) X np.sort(5 * rng.rand(80, 1), axis0) y np.sin(X).ravel() y[::5] 3 * (0.5 - rng.rand(16)) # 加噪声 reg DecisionTreeRegressor(max_depth3) reg.fit(X, y) X_test np.linspace(0, 5, 200).reshape(-1, 1) y_pred reg.predict(X_test) plt.figure(figsize(8, 4)) plt.scatter(X, y, s20, alpha0.6, labelsample) plt.plot(X_test, y_pred, colorred, linewidth2, labelprediction) plt.legend() plt.show()把预测曲线画出来你会看到明显的“阶梯”。这就是回归树的特性它把特征空间切分成若干矩形区域每个区域里给一个常数预测值。max_depth 越大阶梯越细密拟合越灵活但也更容易把噪声一起记住。回归树调参的分类逻辑和分类树完全一致只是评估指标换成 MSE、MAE、R² 这类回归指标。5. 调参实战从过拟合到最优泛化5.1 先看懂过拟合的模样决策树的训练集精度动不动就接近100%这不代表模型强只能说明它把训练样本的背景噪声都背下来了。判断一棵树好不好我最先看两个数测试集精度和训练集与测试集之间的差距。如果训练集95%、测试集88%差7个点说明还有压缩空间如果差15个点说明限制得太松了。还有一个信号值得关注限制 max_depth 后测试集精度不降反升。这是好现象说明前面确实过拟合了砍掉一些分支后模型反而抓到更本质的模式。我见过很多同学调参时不敢把深度压低总觉得深度小了模型就“变笨”实际多跑几组对比就明白了。5.2 用 GridSearchCV 做一次正经调参调参不能靠感觉我用网格搜索加5折交叉验证。from sklearn.model_selection import GridSearchCV param_grid { criterion: [gini, entropy], max_depth: [2, 3, 4, 5, 8], min_samples_leaf: [1, 2, 4, 8], min_samples_split: [2, 5, 10], } grid GridSearchCV( DecisionTreeClassifier(random_state42), param_grid, cv5, scoringroc_auc, n_jobs-1, ) grid.fit(X_train, y_train) print(grid.best_params_) print(grid.best_score_)网格搜索选出来的参数组合不能直接盲信至少要做三件事确认第一看交叉验证标准差如果标准差很大说明模型在不同折上表现波动大换个 random_state 说不定选出的参数就变了第二把 best_params_ 拿回到测试集上验证一次确认没有用验证集泄露信息第三如果搜索空间很大可以考虑 RandomSearchCV用一定次数随机采样代替全网格效率高很多。基于我的实践决策树的调参顺序可以固定成一套流程先定 criterion然后调 max_depth 或 max_leaf_nodes再调 min_samples_leaf最后调 min_samples_split。每调一个参数固定住前面已经确定的值这样调试过程可控不会一次动多个变量出了问题都不知道是哪个引起的。5.3 成本复杂度剪枝的正确打开方式网格搜索属于预剪枝还有一种更优雅的方案先让树自由生长再用 ccp_alpha 做后剪枝。sklearn 提供了一个路径函数可以直接拿到不同 alpha 值对应的树效果。from sklearn.tree import DecisionTreeClassifier clf DecisionTreeClassifier(random_state42) path clf.cost_complexity_pruning_path(X_train, y_train) ccp_alphas, impurities path.ccp_alphas, path.impurities clfs [] for alpha in ccp_alphas: clf DecisionTreeClassifier(random_state42, ccp_alphaalpha) clf.fit(X_train, y_train) clfs.append(clf) train_scores [c.score(X_train, y_train) for c in clfs] test_scores [c.score(X_test, y_test) for c in clfs] plt.figure(figsize(8, 4)) plt.plot(ccp_alphas, train_scores, labeltrain) plt.plot(ccp_alphas, test_scores, labeltest) plt.xlabel(ccp_alpha) plt.ylabel(accuracy) plt.legend() plt.show()跑完这段代码你会看到一条经典的曲线alpha 增大时训练精度缓慢下降测试精度先升后降。选测试精度最高时对应的 alpha然后重新训练最终模型。后剪枝有一个额外好处剪完的树往往比同精度的预剪枝树规模更小、结构更干净对业务同学讲解也更省力。如果你的场景对模型可解释性要求高强烈建议把后剪枝纳入标准流程。6. 从决策树到随机森林一棵树永远打不过一群人6.1 为什么单棵树“不稳”单棵决策树有一个绕不开的毛病高方差。训练数据稍微变一点树的结构就可能大换血。根节点换一个特征整棵树的走向全变了。这种不稳定性让单棵树的预测结果不够可靠。举个例子你用同一份业务数据训练100棵决策树每棵树用不同的随机种子可能得到100个差异不小的结果。产品上线后数据稍微波动模型结论就可能翻盘。所以在对稳定性有要求的系统里我用单棵决策树的场景通常限定在快速做数据探查、给业务做简单规则、以及作为更复杂的集成模型的基线。6.2 Bagging 加特征随机才是随机森林随机森林的思路很直白既然一棵树不稳那就训练很多棵树让它们投票把个体波动平均掉。第一步是 Bagging也就是自助采样。每棵树从原始数据集里做有放回的抽样抽出一份和原数据等量的训练集。这样一来每棵树的训练数据都有所不同彼此之间形成差异。同时因为是有放回大约有37%的样本从来没被某棵树抽到这些样本叫“袋外样本”可以直接用来评估模型省掉一部分交叉验证。第二步是特征随机化。如果只在样本层面做扰动树的差异还是有限的——因为最强特征大概率会被所有树选为根节点大家长得太像。随机森林的做法是在每一个节点分裂时不考察全部特征只随机挑选一部分特征分类任务通常挑 sqrt(特征数) 个回归任务通常挑全部或三分之一从中选最优。这一步大大降低了树之间的相关性。相关度低投票后抵消噪声的效果才好。随机森林不是完全无罪它也有代价解释性不如单棵树。你很难把500棵树的投票结果讲成一条清晰的业务规则。所以我的经验是随机森林用于“追求预测效果且可以接受一定黑盒”决策树用于“必须交付透明规则”的场景。6.3 随机森林关键参数与 OOB 评估随机森林的调参重点和决策树不太一样。单棵树追求“结构最优”随机森林追求“多样性和稳定性的平衡”。参数建议值说明n_estimators100~500树的数量太少不稳定太多耗时且收益递减max_features分类 sqrt回归 1.0每棵树分裂时考虑的特征数是最关键的随机性来源max_depth视数据而定单棵树深度太深会让单棵树过拟合但不一定损害整体min_samples_leaf1~5限制叶子过稀能显著提升平滑度bootstrapTrue关闭后所有树用同一份数据多样性下降oob_scoreTrue使用袋外样本评估代替部分交叉验证实际操作中先设一个较大的 n_estimators比如200或300观察袋外分数是否随树数增加继续上升。如果到了200棵树还在涨继续加到500如果100棵以后就平稳了那就没必要堆算力。然后调整 max_features它比 max_depth 对随机森林整体效果的影响更大。max_features 调小了单树变弱但整体多样性提高调大了单树更强但大家趋于相似。这中间的平衡点只能在实际数据上摸。用随机森林做评估的时候我常直接看 oob_score_ 属性它是在训练过程中用袋外样本算出的精度和交叉验证结果高度相关但一分钱时间都不用花。7. 常见问题与排查技巧速查表7.1 实战高频问题排查表问题现象可能原因排查思路与对策训练集100%测试集很差过拟合缩小 max_depth调大 min_samples_leaf或使用 ccp_alpha 后剪枝特征很多树都长成“扫把”特征噪声多做特征筛选或用随机森林看特征重要性再缩减特征集类别不均衡少数类全被忽略树只看多数类收益大设 class_weightbalanced或对少数类加大样本权重分类效果好回归预测总是“平台状”回归树固有特性正常现象如果要平滑改用线性模型或梯度提升回归器训练集和测试集精度波动很大高方差使用集成方法随机森林或多重交叉验证再取平均一个高基数类别特征被反复选中类别编码有问题不要盲目 OneHot考虑使用目标编码或序数编码并限制深度树的结构几乎不变化特征区分度太差先做探索性数据分析寻找更有区分度的特征7.2 我一直沿用的几个避坑细节决策树和随机森林不需要做特征归一化。很多新入行的同学习惯先 StandardScaler 一把梭这在树模型上是无效操作——树只在排序后找切分点特征的均值和方差根本不影响分裂结果。但如果你打算做特征重要性分析千万别把不同量纲的特征混在一起就下结论重要性分数受量纲影响不大但受“候选特征数量”影响很大高基数特征往往被高估。类别特征不要无脑 OneHot。决策树做 OneHot 后每个虚拟变量都要参与分裂竞争类别一多矩阵稀疏分裂效率低更坑的是OneHot 会破坏类别之间的有序关系。如果类别本身有顺序比如“低/中/高”直接用 OrdinalEncoder 编码成0/1/2更合适如果没有顺序且类别多我更推荐先用随机森林训练一波把类别做成目标编码再喂进决策树。用 class_weight 处理类别不均衡时要搭配概率阈值调整。class_weightbalanced 会让少数类被更多识别但预测概率也会发生偏移。上线时如果对正类召回率有硬指标不要只看 accuracy多看一眼 ROC-AUC 和 precision-recall 曲线然后根据业务成本选择阈值。特征重要性只能作为线索不能作为因果证据。sklearn 的 feature_importances_ 基于不纯度减少量它偏向数值型特征和高基数特征而且和观测到的重要性可能差很多。如果特征重要性结果和业务常识冲突很大先别急着相信模型用排列重要性permutation importance交叉验证一遍通常会更接近真实情况。最后决策树和随机森林在我手里的定位始终是“基线模型里的王牌”。它们的价值不只是给你一个分数而是让你在几分钟内知道数据里到底有没有信号、哪些特征值得继续深挖、业务方能不能理解这个模型。理解了这一点这份cheatsheet才算真正消化了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门