决策树算法从入门到实战:原理、sklearn调参、回归预测与随机森林对比

发布时间:2026/11/4 5:28:34
决策树算法从入门到实战:原理、sklearn调参、回归预测与随机森林对比 决策树这个算法我前前后后在不同项目里用过不下几十次。从最早用Excel手动算信息增益到后来用sklearn调参调到凌晨三点再到现在带新人时反复给他们讲“为什么你的树长得跟灌木丛一样”踩过的坑比很多人看过的教程都多。这篇文章不打算写成教科书而是把我这些年对决策树的全部理解——包括它怎么工作、为什么这么设计、实际用的时候哪些参数是命门、遇到问题怎么排查——一次性讲透。无论你是刚接触机器学习的学生还是已经工作几年但一直对决策树“会用不会调”的开发者看完都能有收获。尤其是那些正在做头歌平台决策树实验、或者想搞明白随机森林和决策树到底差在哪的朋友这篇文章就是为你写的。1. 决策树到底在解决什么问题1.1 从“猜人物”游戏说起决策树的本质其实就是一个“二十问”游戏。你心里想一个人物我来问问题每个问题只能用是或否回答我通过一系列问题逐步缩小范围最终猜出你想的是谁。比如我先问“这个人是男性吗”如果是再问“他是运动员吗”如果是再问“他踢足球吗”——每一步都在把可能性空间一分为二。机器学习里的决策树干的就是这件事。给它一堆带标签的数据它自动找出“该问什么问题”“先问哪个问题”“什么条件下给出什么结论”。跟人玩游戏不同的是机器不靠直觉它靠数学指标来决定哪个特征最适合用来切分数据。这里有个关键点很多人一开始会忽略决策树既可以做分类也可以做回归。做分类时叶子节点输出的是类别标签比如“会买”“不会买”做回归时叶子节点输出的是一个数值比如“房价多少万”。两者的树结构生长逻辑类似但分裂准则和叶子输出方式不同。热词里提到的“决策树分类器”和“决策树如何逼近真实曲线”其实分别对应分类和回归两种用法。1.2 为什么决策树这么受欢迎我总结下来决策树在实际项目中受欢迎主要有三个原因。第一可解释性极强。你训练一个深度神经网络最后得到一堆权重没人看得懂。但决策树你可以直接画出来从根节点到叶子节点就是一条条if-then规则。业务方问你“为什么这个用户被判定为高风险”你直接把路径一指“因为他年龄小于25、月收入低于5000、且近三个月有逾期记录。”这种解释力在很多场景下比模型精度还重要。第二对数据预处理要求低。线性模型需要归一化、需要处理缺失值、需要把类别变量做独热编码决策树基本上拿到原始数据就能跑。它不需要你告诉它“特征之间要独立”也不需要你担心异常值把模型带偏——极端值只会影响分裂点的选择不会像线性回归那样把整个超平面拉歪。第三它是集成学习的基础。随机森林、梯度提升树GBDT、XGBoost、LightGBM这些在工业界大杀四方的算法底层都是决策树。你把单棵决策树搞明白了这些集成方法就是“怎么把很多棵树组合起来”的问题。热词里“随机森林和决策树区别”之所以被频繁搜索就是因为很多人学到随机森林时发现绕不开决策树这个基础。1.3 单棵树的能力边界不过我得先把丑话说在前面单棵决策树在绝大多数任务上精度都不如逻辑回归、SVM或者集成树模型。它的方差很大——训练数据稍微变一点树结构可能完全不一样。你可以把它理解成一个“偏科天才”在某些结构化数据、规则清晰的场景下表现很好但面对复杂非线性关系时容易过拟合。所以实际项目中我很少直接拿单棵决策树上线。要么用它做特征筛选看哪些特征在树的上层被频繁使用要么用它做快速原型验证要么直接上随机森林。但无论如何理解单棵决策树是理解所有树模型的前提。2. 决策树的核心原理拆解2.1 分裂准则信息增益、基尼指数与方差决策树最核心的问题就是在每个节点上选哪个特征、以什么阈值来切分数据这个选择需要一把“尺子”来衡量切分的好坏。常用的尺子有三把。信息增益Information Gain基于信息熵。熵是衡量数据混乱程度的指标熵越高说明类别越混杂。信息增益 切分前的熵 - 切分后的加权熵。增益越大说明这次切分让数据“纯”了很多。ID3算法用的就是这个准则。但它有个缺点倾向于选择取值多的特征。比如“身份证号”这个特征每个值对应一个样本切完之后每个子节点都纯了但显然没有泛化能力。信息增益率Information Gain Ratio就是为了修正上面那个问题。它在信息增益的基础上除以特征本身的熵惩罚取值多的特征。C4.5算法用的是这个。基尼指数Gini Index是另一个衡量纯度的指标。基尼指数越小数据越纯。CART算法Classification and Regression Trees用的是基尼指数。相比信息熵基尼指数不需要算对数计算更快实际效果差异不大。sklearn的DecisionTreeClassifier默认用的就是基尼指数。对于回归树分裂准则通常是均方误差MSE或平均绝对误差MAE。目标是让每个叶子节点内样本标签的方差最小。热词里“决策树如何逼近真实曲线”说的就是回归树它用一系列水平线段阶跃函数去拟合任意曲线树越深阶跃越多逼近越精细但过拟合风险也越大。2.2 树的生长与剪枝决策树生长是一个递归过程在当前节点选最优特征和阈值把数据分成两份然后对每份递归执行同样的操作直到满足停止条件。停止条件通常包括节点样本数小于某个阈值、树达到最大深度、节点内所有样本属于同一类别、没有特征可用了。但这样长出来的树往往过拟合。因为它会一直分裂到每个叶子节点都纯为止把训练数据里的噪声也学进去了。解决办法就是剪枝。预剪枝Pre-pruning是在生长过程中提前停止比如限制最大深度、限制叶子节点最小样本数、限制分裂所需的最小增益。优点是计算快缺点是可能欠拟合——有些分支虽然当前增益小但后续分裂可能带来很大提升预剪枝会错过。后剪枝Post-pruning是先让树长到足够大然后自底向上考察每个子树如果把它替换成叶子节点后验证集精度不降反升就剪掉。优点是效果通常更好缺点是计算量大。sklearn的DecisionTreeClassifier只支持预剪枝通过ccp_alpha参数可以实现代价复杂度剪枝本质上是一种后剪枝的近似。2.3 连续值与缺失值处理连续值特征不能像离散值那样直接枚举所有取值来分裂。常见做法是先把该特征的所有取值排序取相邻值的中点作为候选分裂点然后像离散值一样计算每个候选点的增益选最好的。sklearn内部就是这么做的。如果数据量很大还会用分位数来减少候选点数量。缺失值处理是决策树的一个优势。很多算法遇到缺失值直接报错但决策树可以处理。基本思路有两种一是把缺失值单独作为一个分支“缺失”本身可能携带信息二是用替代分裂surrogate split找一个跟原特征相关性高的特征来替代。sklearn的DecisionTreeClassifier不支持缺失值需要提前填充但XGBoost和LightGBM原生支持缺失值处理这也是它们在工业界更受欢迎的原因之一。3. 用sklearn实操决策树分类3.1 环境准备与数据加载先确保你的环境里有sklearn。如果没装一条命令搞定pip install scikit-learn pandas numpy matplotlib我习惯用鸢尾花数据集做入门演示因为它干净、经典、结果可解释。头歌平台上“决策树进行鸢尾花分类-sklearn版”这个实验用的也是这个数据集。from sklearn.datasets import load_iris from sklearn.tree import DecisionTreeClassifier, plot_tree from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, classification_report import matplotlib.pyplot as plt iris load_iris() X, y iris.data, iris.target X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy )这里有个细节stratifyy保证训练集和测试集的类别比例一致。如果不用这个参数随机切分可能导致某个类别在测试集里一个样本都没有评估结果就不可信了。3.2 训练第一棵树并可视化clf DecisionTreeClassifier(random_state42) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(f准确率: {accuracy_score(y_test, y_pred):.4f}) print(classification_report(y_test, y_pred, target_namesiris.target_names))默认参数下这棵树会一直长到叶子节点全纯。在鸢尾花数据集上准确率通常能到95%以上。但别高兴太早这是因为它把训练数据里的细节都记住了。可视化一下plt.figure(figsize(20, 10)) plot_tree(clf, feature_namesiris.feature_names, class_namesiris.target_names, filledTrue, roundedTrue) plt.show()你会看到每个节点里都有gini值、samples数、value分布和class。gini0说明这个节点已经纯了。filledTrue让不同类别显示不同颜色非常直观。3.3 关键参数逐个拆解sklearn的DecisionTreeClassifier有十几个参数但真正影响模型行为的核心参数就几个。我按重要性排序讲。criterion分裂准则可选gini或entropy。默认gini。大多数情况下两者差异很小gini计算快一点。如果你做特征筛选entropy可能稍微好一点因为它对不纯度的惩罚更平滑。max_depth树的最大深度。这是控制过拟合最直接的参数。默认None表示不限深度。我一般从3开始试逐步增加到5、7、10看验证集精度什么时候开始下降。鸢尾花数据集上深度3-4就够了。min_samples_split一个节点至少要有多少个样本才允许分裂。默认2。调大这个值可以防止树在样本很少的节点上继续分裂。我通常设成5到20之间具体看数据量。min_samples_leaf叶子节点最少要有多少个样本。默认1。这个参数比min_samples_split更有效因为它直接限制了每个叶子的“证据量”。我一般设成3到10。max_features每次分裂时考虑的最大特征数。默认None考虑所有特征。设成sqrt或log2可以增加随机性这在随机森林里是必须的但单棵树里用得少。ccp_alpha代价复杂度剪枝参数。默认0不剪枝。值越大剪掉的子树越多。可以用cost_complexity_pruning_path找到合适的值。path clf.cost_complexity_pruning_path(X_train, y_train) ccp_alphas path.ccp_alphas # 用交叉验证选最佳alpha from sklearn.model_selection import cross_val_score scores [] for alpha in ccp_alphas: clf_temp DecisionTreeClassifier(random_state42, ccp_alphaalpha) score cross_val_score(clf_temp, X_train, y_train, cv5).mean() scores.append(score) best_alpha ccp_alphas[scores.index(max(scores))] print(f最佳ccp_alpha: {best_alpha:.6f})3.4 用网格搜索找最优参数组合手动调参效率低我一般直接用GridSearchCVfrom sklearn.model_selection import GridSearchCV param_grid { max_depth: [3, 4, 5, 6, 7, 10], min_samples_split: [2, 5, 10, 20], min_samples_leaf: [1, 3, 5, 10], criterion: [gini, entropy] } grid GridSearchCV( DecisionTreeClassifier(random_state42), param_grid, cv5, scoringaccuracy, n_jobs-1 ) grid.fit(X_train, y_train) print(f最佳参数: {grid.best_params_}) print(f最佳交叉验证分数: {grid.best_score_:.4f}) print(f测试集分数: {grid.score(X_test, y_test):.4f})n_jobs-1表示用所有CPU核心并行搜索。在鸢尾花这种小数据集上几秒就跑完了。如果是几万条数据、几十个特征可能要跑几分钟但比手动试快得多。4. 决策树做回归与收入预测实战4.1 回归树与分类树的区别回归树和分类树在结构上几乎一样区别在于分类树用基尼指数或信息熵衡量分裂质量回归树用MSE或MAE分类树的叶子输出是多数类回归树的叶子输出是均值。sklearn里用DecisionTreeRegressor。热词里“头歌决策树进行收入预测-sklearn版”就是一个典型的回归任务——根据年龄、教育年限、工时等特征预测收入数值。4.2 收入预测完整流程我用一个模拟的收入数据集来演示。实际项目中你可以替换成自己的数据。import numpy as np import pandas as pd from sklearn.tree import DecisionTreeRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score np.random.seed(42) n 1000 age np.random.randint(20, 60, n) education_years np.random.randint(6, 20, n) hours_per_week np.random.randint(20, 60, n) # 构造一个非线性关系 income (age * 0.5 education_years * 2.5 hours_per_week * 0.8 np.where(education_years 15, 10, 0) np.random.normal(0, 3, n)) df pd.DataFrame({ age: age, education_years: education_years, hours_per_week: hours_per_week, income: income }) X df[[age, education_years, hours_per_week]] y df[income] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) reg DecisionTreeRegressor(random_state42, max_depth5, min_samples_leaf10) reg.fit(X_train, y_train) y_pred reg.predict(X_test) print(fMSE: {mean_squared_error(y_test, y_pred):.4f}) print(fMAE: {mean_absolute_error(y_test, y_pred):.4f}) print(fR2: {r2_score(y_test, y_pred):.4f})这里我故意把max_depth设成5、min_samples_leaf设成10就是为了防止过拟合。如果不设这两个参数回归树会把每个叶子节点的MSE降到0但测试集上的R2可能是负的。4.3 回归树如何逼近真实曲线热词里“决策树如何逼近真实曲线”这个问题我用一个一维例子来解释最清楚。import matplotlib.pyplot as plt np.random.seed(42) X_1d np.sort(np.random.uniform(0, 10, 200)).reshape(-1, 1) y_1d np.sin(X_1d).ravel() np.random.normal(0, 0.1, 200) fig, axes plt.subplots(1, 3, figsize(18, 5)) for ax, depth in zip(axes, [2, 4, 8]): reg DecisionTreeRegressor(max_depthdepth, random_state42) reg.fit(X_1d, y_1d) X_plot np.linspace(0, 10, 1000).reshape(-1, 1) y_plot reg.predict(X_plot) ax.scatter(X_1d, y_1d, s10, alpha0.5, label数据) ax.plot(X_plot, y_plot, r-, linewidth2, labelf树深度{depth}) ax.plot(X_plot, np.sin(X_plot), g--, linewidth1, label真实曲线) ax.legend() ax.set_title(f决策树回归 (max_depth{depth})) plt.tight_layout() plt.show()深度2时树只能用4个阶跃去拟合正弦曲线非常粗糙。深度4时16个阶跃已经比较接近了。深度8时阶跃更多拟合更精细但开始把噪声也学进去了。这就是回归树逼近曲线的本质用阶梯函数去近似连续函数深度越大阶梯越多逼近能力越强但泛化能力先升后降。5. 常见问题与排查技巧实录5.1 树太深导致过拟合怎么办这是最常见的问题。症状是训练集准确率接近100%测试集准确率低很多。解决办法按优先级排序第一限制max_depth。从3开始试逐步增加观察验证集精度变化。第二增大min_samples_leaf。这个参数比min_samples_split更有效因为它直接保证每个叶子有足够的样本支撑。第三用ccp_alpha做后剪枝。第四减少特征数量用feature_importances_看哪些特征重要把不重要的去掉。我一般会画一条“深度-精度”曲线来辅助决策train_scores, test_scores [], [] depths range(1, 21) for d in depths: clf DecisionTreeClassifier(max_depthd, random_state42) clf.fit(X_train, y_train) train_scores.append(clf.score(X_train, y_train)) test_scores.append(clf.score(X_test, y_test)) plt.plot(depths, train_scores, o-, label训练集) plt.plot(depths, test_scores, s-, label测试集) plt.xlabel(max_depth) plt.ylabel(准确率) plt.legend() plt.grid(True) plt.show()训练集和测试集曲线分叉的地方就是过拟合开始的深度。5.2 树太浅导致欠拟合怎么办症状是训练集和测试集精度都低。解决办法增加max_depth、减小min_samples_leaf、减小min_samples_split、增加特征数量做特征工程。如果加了深度还是不行说明单棵树的表达能力到顶了该上随机森林或梯度提升树了。5.3 特征重要性怎么解读clf.feature_importances_返回每个特征的重要性分数加起来等于1。但要注意这个重要性是基于不纯度减少量计算的对于取值多的特征有偏好。更可靠的做法是用permutation importancefrom sklearn.inspection import permutation_importance perm_imp permutation_importance( clf, X_test, y_test, n_repeats10, random_state42 ) for i, feat in enumerate(iris.feature_names): print(f{feat}: {perm_imp.importances_mean[i]:.4f} f± {perm_imp.importances_std[i]:.4f})Permutation importance的思路是把某个特征的值随机打乱看模型精度下降多少。下降越多说明该特征越重要。这个方法没有取值多的偏好更公平。5.4 决策树和随机森林到底怎么选这是热词里被搜得最多的问题之一。我的经验是维度单棵决策树随机森林精度一般高可解释性强弱训练速度快慢与树数量成正比过拟合风险高低调参难度低中适合场景规则提取、快速原型追求精度、生产环境如果业务方要求“给我一条条规则”用单棵树。如果只关心预测精度用随机森林。如果既要精度又要解释性可以用随机森林的feature_importances_做特征筛选再用筛选后的特征训练一棵浅树。5.5 头歌平台实验常见报错头歌平台上做决策树实验时最常见的报错是“模块未找到”和“参数不匹配”。前者通常是sklearn版本问题建议用pip show scikit-learn确认版本头歌环境一般预装的是0.24以上。后者常见于plot_tree函数老版本没有这个函数需要用export_graphviz配合graphviz。另外头歌的评测脚本对输出格式有严格要求比如要求打印准确率保留4位小数少一位都可能判错。我建议先在本地跑通再提交本地和头歌的环境差异主要在sklearn版本和matplotlib后端。6. 从单棵树到集成树的进阶思路6.1 Bagging与随机森林的核心思想单棵树的方差大但如果我训练很多棵树每棵看不同的数据子集和特征子集然后投票方差就会降下来。这就是BaggingBootstrap Aggregating的核心。随机森林在Bagging基础上加了一个“随机特征选择”每次分裂时不是考虑所有特征而是随机选一个子集。这进一步降低了树之间的相关性让集成效果更好。sklearn里用RandomForestClassifier主要参数是n_estimators树的数量、max_features每次分裂考虑的特征数、max_depth等。from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators100, max_depth5, max_featuressqrt, random_state42, n_jobs-1 ) rf.fit(X_train, y_train) print(f随机森林准确率: {rf.score(X_test, y_test):.4f})在鸢尾花数据集上随机森林通常比单棵树高1-3个百分点。在更复杂的数据集上差距会更大。6.2 梯度提升树的直观理解如果说随机森林是“很多棵树并行投票”那梯度提升树就是“很多棵树串行纠错”。第一棵树预测第二棵树学第一棵树的残差第三棵树学第二棵树的残差以此类推。每棵树都很浅通常深度3-5但组合起来精度极高。XGBoost和LightGBM就是这类算法的工程优化版本。从单棵树到集成树核心思想就一句话一棵树不够那就多来几棵但要让它们“各有各的视角”。随机森林让每棵树看不同的数据和特征梯度提升让每棵树专注于纠正前面的错误。6.3 实际项目中我的选型逻辑数据量小几千条、特征少几十个、要求可解释单棵决策树深度控制在3-5。数据量中等几万到几十万、特征中等、追求精度随机森林或LightGBM。数据量很大百万级以上、特征很多LightGBM或XGBoost配合特征工程。需要在线学习或增量更新Hoeffding Treesklearn里有。需要极低延迟推理把树模型转成if-then规则硬编码或者用ONNX Runtime加速。最后分享一个我踩过的坑决策树对特征尺度不敏感但对特征的相关性很敏感。如果两个特征高度相关树会倾向于选其中一个导致另一个的重要性被低估。解决办法是在训练前做相关性分析把相关系数大于0.9的特征对删掉一个。这个细节很多教程不会讲但在实际项目中经常影响特征重要性解读的准确性。

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询