ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

决策树原理与sklearn实战:从分裂标准到剪枝调参完全指南

决策树原理与sklearn实战:从分裂标准到剪枝调参完全指南 刚接触机器学习那会儿我做得最多的模型就是决策树。不是因为它最简单而是因为它最“讲道理”。你训练完一个神经网络它给你一个准确率数字但你很难说清楚它到底依据什么做的判断决策树不一样它会把整个判断过程掰开揉碎摆在你面前——先看什么特征、阈值划在哪、分到哪条分支全都明明白白。这篇文章就围绕决策树展开讲清楚它的核心原理、sklearn实操、调参避坑以及和随机森林的对比。不管你是刚入门机器学习的新手还是已经在用但总觉得哪里不对劲的实践者这篇都能给你一些实在的东西。我自己最早接触决策树是在一个收入预测的任务上用的是sklearn版本代码不复杂但真正把树的生长逻辑搞清楚之后后面的调参和模型优化就顺了很多。这篇文章不堆公式我会用大白话把信息熵、基尼系数这些概念讲透然后直接上代码最后再把那些教科书里不会写的坑一个个列出来。1. 决策树原理从生活直觉到算法定义1.1 用“猜人游戏”建立直觉模型你肯定玩过这样的游戏一个人心里想好一个角色其他人通过提问来猜。你不会上来就问“你是不是关羽”而是先问“这个人是不是男性”“是不是古人”“是不是武将”。每问一个问题候选范围就被砍掉一大块多问几个回合之后答案自然浮出水面。决策树干的就是这件事。它把“猜人游戏”里的每一个问题称作一次“分裂”每次分裂都要从众多候选特征里挑一个最优的、最能区分数据的特征来问。比如判断一个用户会不会流失树可能会先问“最近90天登录次数是否小于3次”如果是再接着问“是否使用过付费功能”一步步往下走直到走到某个纯度高到不需要再分的节点就给出最终结论。这种“逐层细化、不断过滤”的思维方式本质上是将复杂问题拆解成一系列简单判断的叠加。比起那些黑箱模型它的可解释性天然强出几个量级。这也解释了为什么在风控、医疗诊断、信用评估这些对可解释性有硬要求的场景决策树和它的变体至今仍是主流方案之一。1.2 决策树的三个核心构件要真正搞懂决策树得先搞清楚它的解剖结构。整棵树由三个基本构件组成根节点整个树的起点包含全部训练样本它负责第一次、也是全局最重要的一次分裂。根节点选哪个特征作为首个分裂特征往往能反映数据里区分度最高的属性。内部节点每个内部节点都代表对某个特征的判断比如“年龄是否大于30”“income是否超过50k”。节点向下分裂出子节点子节点继续分裂形成层层递进的分支结构。叶子节点终端节点不再分裂给出最终的预测结果。分类任务里叶子节点是类别标签回归任务里叶子节点是目标变量的均值或中位数。每个节点分裂时会遵循“局部最优”的原则——在当前节点的数据范围内挑选一个让纯度提升最大的特征和阈值。注意是局部最优不是全局最优。这意味着决策树的生长过程是一个贪心策略的迭代过程。提示理解“贪心”这一点很关键。很多人在调参时觉得多分裂几层一定会更好但实际上每一步只追求局部最优累积起来很容易过拟合。回头我会详细讲怎么用剪枝解决这个问题。1.3 决策树到底能解决哪些问题大多数人对决策树的认知停留在“分类器”上这其实低估了它。决策树家族能覆盖的问题远比你想象中广分类问题垃圾邮件识别、鸢尾花分类、用户流失预警、疾病诊断。这是决策树最经典的应用领域。回归问题收入预测、房价估算、销量预测。只需把叶子节点的输出从类别改成数值均值一棵分类树就变成了回归树。特征重要性评估训练完一棵树你可以直接得到每个特征的importances属性它告诉你在整个分裂过程中哪些特征贡献最大。做特征工程时这一步能帮你快速删掉噪音特征。另外决策树还是随机森林、GBDT、XGBoost、LightGBM这些集成模型的基学习器。换句话说你学会了决策树再往上走学梯度提升树时至少能听懂底层在跑什么。2. 核心机制分裂标准与树的生长逻辑决策树最核心的问题就一个每次分裂时该选哪个特征、该选哪个阈值说白了就是要找一种“问法”让数据在分裂之后能最大程度地变纯。怎么量化“纯”这个概念不同年代、不同版本的决策树算法给出了三套不同的答案。2.1 信息熵与ID3第一次让树学会“选择”ID3算法由Quinlan在1986年提出它引入“信息熵”来衡量集合的混乱程度。熵这个概念来自信息论理解起来可以这样类比一个完全均匀的骰子各面概率都一样你很难预测它下一次掷出什么这时的熵最高一个被做了手脚的骰子某一面概率占绝对优势你猜中的概率大幅提升熵就低了。用公式表示就是熵等于所有类别概率乘以其对数的负累加。当数据落到某个节点如果各类别比例非常均衡熵值接近最大如果某一类占比压倒性高熵值接近0。ID3在每次分裂时计算分裂前后的熵差这个差值叫信息增益哪个特征带来的增益最大就选哪个特征分裂。信息增益的计算逻辑很直白分裂前的熵减去分裂后各子节点熵的加权平均。一个lemma某个特征能把数据切得非常纯那它的信息增益就大。但ID3有个明显的缺陷——它特别偏爱取值很多的特征。一个极端例子如果数据里有个“用户ID”列每个用户取值都不一样按它分裂的话每个叶子节点只剩一条样本纯度直接拉满信息增益爆表。但这种分裂毫无泛化能力属于典型的过拟合。2.2 增益率与C4.5修正偏好问题C4.5是Quinlan在1993年推出的改进版核心改动是引入了“增益率”这个概念。增益率不是直接用信息增益而是拿信息增益除以特征的固有值这个固有值会惩罚取值多的特征。通俗地解释一个特征取值越多它把数据切得越碎固有值也就越高信息增益一除以这个惩罚项优势就被拉回来了。C4.5还额外支持连续特征处理做法是把连续值排序后尝试所有相邻点作为候选阈值取增益率最高的那个。C4.5在学术界地位很高但工程实践中用得反而不如CART多一个重要原因是它只能做分类不能做回归另一个原因是它的实现复杂涉及到多路分裂计算开销比二叉分裂大不少。你只要记住ID3的问题是偏爱离散取值多的特征C4.5用增益率修正了它但适用范围受限。2.3 基尼系数与CART工程上最常用的选择你打开sklearn会发现DecisionTreeClassifier默认的criterion参数是“gini”也就是基尼系数。这是CART算法的默认分裂标准也是目前工业界实际应用最广的一套。基尼系数的本质是从一个集合里随机抽两个样本它们的类别不一致的概率。如果集合里全是同类任意抽两个类别都一致基尼系数为0如果各类均匀分布不一致概率就高基尼系数也高。基尼系数越低说明集合越纯。和熵相比基尼系数不需要算对数只涉及平方运算计算速度更快而且在分类上的效果和熵极其接近。CART之所以能在工程上胜出还有个重要原因是它强制生成二叉树每次分裂只切一次要么左要么右不会像ID3那样多路开花。二叉结构配合后续的剪枝策略训练和调优都方便得多。2.4 回归树用方差最小化逼近真实曲线回归树的分裂目标和分类树不同它不再看类别纯度而是看数值分布的一致性。sklearn里的DecisionTreeRegressor就是按照这个逻辑工作的。具体过程是对于某个候选特征和阈值把数据切成了左右两份计算左右两份的目标变量方差然后加权求和目标是最小化这个加权方差。方差小说明两边各自内部的数值都比较聚拢用每一边的均值代表整体时误差就比较小。你可能听说过“决策树如何逼近真实曲线”这类讨论。回归树逼近曲线的逻辑是用无数个分段常数去拟合连续曲线。每一个叶子节点输出的就是该区间内所有训练样本目标值的平均。叶子越多、层级越深这些“小阶梯”就越细拟合出来的曲线就越贴近真实分布——但同时也越容易出现过拟合。如果拿一个带正弦波的数据集来做回归树实验你会看到层数从1加深到10时预测曲线从一条平坦直线变成一条剧烈抖动的折线。这个过程非常直观能帮你看清“拟合能力增强”和“过拟合”之间那条微妙的分界线。3. sklearn实操从鸢尾花到收入预测光懂原理不写代码等于白学。这部分我会用sklearn把分类和回归两条线都过一遍代码尽量简洁保证你复制下来就能跑同时会把每个关键参数背后的意义解释清楚。3.1 环境准备与数据约定实操之前先确认环境。你只需要三个东西Python 3.7以上、scikit-learn、matplotlibpandas如果没有也装一下处理表格数据方便。pip install scikit-learn pandas matplotlib头歌平台上的两个经典实验一个是鸢尾花分类一个是收入预测本质上都是sklearn的决策树标准用法。区别在于鸢尾花是分类任务收入预测如果目标值是连续金额那就是回归任务。实战里很多人容易把这俩搞混DecisionTreeClassifier和DecisionTreeRegressor是两个类输入输出的数据类型也不一样。3.2 鸢尾花分类20行代码跑通全流程鸢尾花数据集是机器学习的Hello World150条样本4个特征花萼长宽、花瓣长宽3个类别。直接用默认参数的决策树就能拿不错的准确率。from sklearn.datasets import load_iris from sklearn.tree import DecisionTreeClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score # 加载数据 iris load_iris() X, y iris.data, iris.target # 划分训练集和测试集random_state固定保证结果可复现 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) # 预测与评估 y_pred clf.predict(X_test) print(准确率:, accuracy_score(y_test, y_pred))这段代码虽然是标准操作但有两个容易被新手忽略的细节。第一个train_test_split里的stratifyy参数意思是按类别比例做分层采样。鸢尾花数据每类50条很均衡不分层问题不大但如果类别不均衡不分层会导致切出来的训练集和测试集类别分布偏差很大直接影响评估结果。第二个random_state参数。决策树虽然看起来是确定性算法但遇到特征值完全相同的样本时sklearn会用随机数决定分裂偏好。不固定随机种子你跑两次可能得到不同的树调参的时候容易产生幻觉——以为某个参数有效其实只是随机波动。默认参数下这棵树的准确率通常在0.95以上。但注意准确率并不能说明模型一定好。你可以用clf.tree_.max_depth看一眼默认树有多深。对30个训练样本量级的叶节点来说默认树往往长得很深这棵树极有可能已经过拟合了。准确率高只是因为测试集恰好没踩到过拟合的坑。3.3 收入预测回归树的关键参数设置再来看回归场景。收入预测的任务可以抽象成根据一个人的年龄、教育年限、工作时长、职业等特征预测他的收入数值。这类问题在头歌平台上有对应的sklearn实验核心代码结构如下。from sklearn.tree import DecisionTreeRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score # 假设df是已经清洗好的DataFrameincome是目标列 # X df.drop(income, axis1) # y df[income] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42 ) # 创建回归树先限制一下最大深度避免一开始就过拟合 reg DecisionTreeRegressor(max_depth6, min_samples_leaf10, random_state42) reg.fit(X_train, y_train) y_pred reg.predict(X_test) print(MSE:, mean_squared_error(y_test, y_pred)) print(R2:, r2_score(y_test, y_pred))这里和分类有两个关键差异评价指标不同。回归任务里准确率没有意义要用MSE、RMSE、MAE或者R2去衡量预测值和真实值之间的偏差。叶子节点的输出含义不同。回归树的每个叶子节点存的是落在该区域所有样本的目标均值预测时直接取那个均值。建议你做一个对比实验分别训练max_depth2、5、10、20的回归树观察训练集R2和测试集R2的差值。你会看到深度越深训练集R2一路飙升甚至接近1.0而测试集R2可能先升后降。这个先升后降的拐点就是你在这个数据集上的“最佳复杂度”。3.4 可视化看决策逻辑让模型开口说话我始终觉得决策树最迷人的地方在于它能把决策过程画出来。sklearn提供了plot_tree函数一行代码就能把整棵树的判断逻辑可视化。import matplotlib.pyplot as plt from sklearn.tree import plot_tree plt.figure(figsize(20, 10)) plot_tree( clf, feature_namesiris.feature_names, class_namesiris.target_names, filledTrue, roundedTrue, max_depth3 # 只画前3层否则树太深图会很挤 ) plt.show()图上每个节点会有四行信息分裂判断条件比如 petal width (cm) 0.8当前节点的基尼系数节点包含的样本数和每个类别的样本数如果是叶子节点会给出最终预测类别我每次拿到一棵新树第一件事就是把它画出来用眼睛先扫一遍。这一步能迅速暴露很多问题比如某个特征在多个深层节点反复出现说明前面的特征选择不够干净某个叶子节点的样本数只有一两根说明树被切得太碎了比如树的左右子节点样本数极度不均衡说明分裂阈值选得很偏。可视化不仅是给别人讲解用的更是你自己调参的重要诊断工具。你调完一次参画个图看看结构变没变心里立刻有数。4. 防止过拟合剪枝策略与参数调优实战决策树有一个绕不开的毛病如果不加限制它会一直生长到每个叶子节点都只剩极少数样本甚至每个叶子节点都完全纯。这种树在训练集上的表现堪称完美一旦遇到新数据就原形毕露。解决这个问题的核心思路就是剪枝。4.1 为什么要剪枝决策树天然过拟合很多人不理解为什么决策树容易过拟合。说直白点其他算法的过拟合可能还需要点运气决策树的过拟合是必然的——只要你给它足够的深度。原因我之前提过树的生长过程是贪心的、只看局部最优的。每一层都选当下最能分纯数据的特征和阈值累积到最后树已经为训练数据里的每一个噪音都量身定制了一套规则。之前的“用户ID”例子最能说明问题这种特征切出来的树在训练集上完美无缺放到新数据上就彻底失效。这就好比一个人把复习资料上的每一个错题都背到滚瓜烂熟连题号顺序都记住了但考试的时候题目稍微改一个数字他就不会做了——不是因为他笨是因为他记住了答案而不是学会了规律。4.2 预剪枝vs后剪枝两个方向的取舍剪枝有两种思路方向完全相反。预剪枝在树生长过程中就提前刹车。具体手段是设置max_depth、min_samples_split、min_samples_leaf等限制条件让树在长到某个深度或节点样本数小于某个阈值时不再继续分裂。预剪枝的优点是效率高训练过程中直接裁掉了不必要的计算量缺点是“目光短浅”有些现在看起来导致纯度提升很小的分裂如果再往下长两层就能带来很大收益但预剪枝没有回头路可走。sklearn里默认是预剪枝策略你设置的那几个参数就是刹车的力度。后剪枝先把树长完整再从下往上逐层评估每个节点的子树如果剪掉这棵子树后整体误差没有变差就取而代之用一个叶子节点顶上。sklearn里可以通过ccp_alpha参数实现成本复杂度剪枝这是后剪枝的实现方式。后剪枝在理论上比预剪枝更稳重因为它基于完整的树结构做判断。代价是训练过程必须先跑完一整棵完整的树计算量比预剪枝大不少。注意实战中90%以上的场景用预剪枝就够了。调max_depth和min_samples_leaf这两个参数通常能在计算代价不高的前提下拿到相当好的效果。后剪枝可以做锦上添花的优化不建议一上来就花太多时间。4.3 核心参数速查表sklearn的DecisionTreeClassifier和DecisionTreeRegressor有一大堆参数但真正需要你操心的是下面这几个。我把它们列成一张表每个参数的含义、作用和经验值都写清楚参数作用经验值/说明criterion分裂标准分类用gini回归用squared_error默认值即可不必过度折腾max_depth最大深度最常用的剪枝参数一般从3开始尝试按数据量逐步加深min_samples_split内部节点最小样本数默认2太小建议调大8~20之间试min_samples_leaf叶子节点最小样本数这是最被低估的参数建议设10以上能大幅抑制过拟合max_features每个节点最多考虑的特征数默认None取全部数据特征很多时设成sqrt(n_features)random_state随机种子必须固定否则结果无法复现ccp_alpha成本复杂度剪枝参数值越大剪枝越狠先跑路径后选最佳值一看这张表你会发现真正影响模型质量的主要就是前四个。不要迷信调参的复杂度决策树这个算法本身并不复杂你只需要关注树的复杂度和叶子节点的纯度这两件事就够了。4.4 实战调参流程与经验分享一套我用得比较顺手的调参流程不一定最优但足够稳第一步先固定random_state随便拿一组初始参数跑通拿到一个基线结果。第二步调max_depth从3到15挨个试画训练集和测试集性能曲线找到测试集性能开始下降的拐点。第三步在最佳深度附近调min_samples_leaf用交叉验证验证通常min_samples_leaf从1、3、5、10、20这个序列里选。第四步如果还过拟合再上ccp_alpha做后剪枝。这里有一个非常重要的经验不要只用准确率或者MSE来评估要看训练误差和测试误差的差值。这个差值才是过拟合程度最直接的度量。很多新手只看测试集分数如果分数还行就万事大吉这其实是自欺欺人。你的目标是让这个差值越小越好而不是单方面把训练集分数刷高。另外决策树的性能对数据尺度不敏感特征不需要归一化或标准化。这是它跟逻辑回归、SVM相比的一个显著优势也让它非常适合做特征类型混杂的表格数据的基线模型。5. 决策树 vs 随机森林单棵树的边界与森林的价值写到这里决策树本身的东西基本讲完了。但“随机森林和决策树区别”这个话题几乎是每篇讲决策树的文章都绕不开的延伸内容我也单独说清楚。5.1 决策树的三大软肋单棵决策树有几个结构性的弱点不是调参能完全解决的第一高方差。决策树对训练数据的变化极其敏感训练数据稍微换一批树的整体结构可能大变样。这意味着单棵树的预测结果不够稳定换个数据切分可能得到完全不同的结论。第二贪心搜索的局部最优问题。每步只选当前节点上最好的分裂但不同特征组合起来可能比单个最优特征更强。单棵树考虑不了这种组合效应。第三深度与泛化的矛盾。树浅了拟合不足树深了过拟合找到一个恰到好处的复杂度平衡点有时候要靠运气和反复试验。这三个软肋决定了单棵决策树在真实工业场景里的上限不会太高。它更适合做基线、做特征探索、做快速验证而不是直接作为最终模型上场。5.2 随机森林如何补短板随机森林的思路其实特别朴素一棵树不稳定那就种几千棵树让它们投票决定最终结果。它做了两件关键的事来保证“每棵树都不太一样”样本随机采样Bootstrap每棵树训练时从全部样本里随机有放回地抽样出一部分。有的样本在一棵树里可能出现多次在另一棵树里可能完全没出现。特征随机选择每个节点分裂时不把全部特征都纳入候选而是随机抽一个特征子集从子集里找最优分裂。这两个“随机”凑在一起保证了森林里每棵树都有自己的视角不会变成同一个模子刻出来的复制品。最后聚合时分类任务用投票法回归任务用平均值。因为每棵树的偏差和方差都相似但彼此之间的相关性被显著降低了求平均之后方差大幅下降整体预测自然更稳。在征对大量真实数据集的对比中随机森林的精度通常明显优于单棵树而且几乎没有增加太多调参难度。这也是为什么在实际项目里我很少直接上线一棵单树但经常把随机森林作为默认起点。5.3 选型建议什么时候用树什么时候用森林做选择之前先想清楚你的需求是什么。如果项目要求可解释性第一比如风控审批、医疗辅助诊断、信贷评分这些场景你需要把判断依据清晰讲给业务方或者监管听那必须用单棵决策树或者带解释性的规则模型。树浅一点深度控制在3到5层规则一列谁都看得懂。如果项目追求精度优先数据量又还可以行数在上万级别用随机森林。大多数表格数据任务里随机森林的默认参数已经能拿到不错的效果你只需要随机森林里做有限调参性价比很高。如果数据维度极高、特征极稀疏或者样本量极小这时候决策树和随机森林的表现都会受限得考虑上逻辑回归或者线性模型。不过这是另一个话题了。提示你一定要记住随机森林并不是决策树的终极形态。在它之后还有梯度提升树GBDT、XGBoost、LightGBM性能往往更强但调试的复杂度和理解门槛也更高。决策树的学习就是那个地基地基打不牢盖多高的楼都会晃。我个人在实际项目里最常用的一条经验是拿到一张陌生的表格数据先用五折交叉验证跑一棵max_depth3的决策树把每个特征的重要性打出来快速感知哪些字段有区分度然后用随机森林做主力模型最后如果业务方需要解释规则再回头调优那棵浅树。这条路我走了很多个项目基本没踩过大坑。最后再分享一个小技巧每次训练完决策树顺手把测试集里预测错的样本打印出来一个样本一行挨个看它们的真实特征值。你会发现很多错误是数据结构本身造成的——比如缺失值没有处理、类别变量编码带来了隐含顺序、某些特征天然冗余。这些东西比调参更值得你花时间很多时候你还没开始调max_depth问题就已经解决了一半。
返回列表