ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

决策树连续变量切分全解析:从阈值搜索到上线验证

决策树连续变量切分全解析:从阈值搜索到上线验证

最近有个做风控建模的朋友跟我吐槽,说他的表格里全是年龄、月收入、征信查询次数、授信额度使用率这种连续变量。他原本以为决策树处理连续变量很简单,不就是手动分箱嘛,比如把年龄切成“30岁以下”“30到40岁”“40岁以上”,分好喂进去就行。结果真上手才发现两件事:第一,分箱边界到底放在哪里,完全凭感觉,没有依据;第二,就算勉强分了箱,树的预测效果和可解释性依然一塌糊涂。

这其实是很多人初学决策树时的共同困惑。教科书上讲的例子大多是“天气是否晴朗”“是否有房”这类离散特征,一遇到身高、体重、温度、收入这种连续特征,就不知道该让树怎么“分叉”了。这篇文章就围绕“连续变量”这个具体场景,把决策树的切分逻辑、搜索过程、实战陷阱和上线经验完整讲一遍。想搞懂树上连续特征怎么切分、阈值为什么老变、上线后怎么解释,这篇文章可以直接当参考。

1. 连续特征带来的是什么性质的难题

1.1 分类树对离散特征的处理逻辑

先回顾一下决策树处理离散特征时的基本思路。假设一个特征是“是否有房”,取值只有两种情况:有房、无房。分类树在这个节点上只要做一个判断:有房往左走,无房往右走。走完之后,左子节点和右子节点的样本在这一个维度上就是完全“分开”的,互不掺和。

这个“分开”的质量怎么衡量?用不纯度。最常见的两个指标是信息熵和基尼指数。信息熵的计算公式是:

(Entropy = -\sum_{i=1}^{c} p_i \log_2(p_i))

基尼指数是:

(Gini = 1 - \sum_{i=1}^{c} p_i^2)

其中 (p_i) 是第 (i) 个类别在当前节点样本中的占比。当节点里只有一类样本时,不纯度是 0,这个节点就是纯的。决策树的每个节点,本质上都在找那个“能让子节点最纯”的特征和取值组合。

离散特征因为取值有限,遍历起来非常直接:有房试一次,无房试一次,算算各自带来的不纯度下降,挑下降最多的方案。整个过程就像在几个现成的盒子里挑一个装着最干净的盒盖,不需要太多想象力。

1.2 “按每个取值分叉”这条路为什么走不通

问题从连续特征开始变得麻烦。收入这个特征,一个几千人的样本集里可能就有几百种不同的取值。如果按照离散特征的老办法,每个取值都分一个叉,那树会瞬间膨胀成一个怪物,每个子节点可能只剩下几个人甚至一个人。

这种做法的本质是“按取值切分”,它有一个致命问题:取值数量的多少直接决定了树的复杂度。连续变量的取值可以无限多,训练集里出现了多少种收入,树就会长出多少根枝。这已经不是拟合规律了,这就是在背答案。基于该严格的数学逻辑,在实际项目中根本跑不动,即便跑动了,预测新数据时只要出现一个训练集里没见过的收入,树就直接懵掉。

所以,选项为“每一个取值一个叉”在连续变量场景中彻底失效,它把离散特征的问题直接推到了你面前:如果不能在每一个取值上分叉,那应该在哪里分?

1.3 一个关键转向:把连续特征整体“二分”

CART决策树给出的答案非常朴素:不搞多叉,只在连续特征上做二分。也就是每个节点只问一个类似“收入是否小于等于 17850”的问题。如果为真走左边,为假走右边。

这意味着一个连续特征在树的某一层上,只会被使用一次,且只会切出一刀。至于这一刀切在哪里,后面会细说。更重要的是:这个特征并不会因为这一刀就“用完”了。下一次在更深的节点上,同一个收入特征完全可以再被拿出来,换一个更大的或更小的阈值再切一刀。这样一层层切下去,连续特征就被逐渐“细化”成了若干个阶梯式的区间,而不是一次性被分成几十个细碎的小块。

这个策略很聪明。它把“无限取值”的问题转化成了“有限切分”的问题——树的每一层只增加一个阈值判断,训练和预测的计算量都可控。同时它也保留了连续信息的排序特性,比如“收入 18000 的人”和“收入 55000 的人”之间的大小关系,一直通过不同的切分阈值被保留在树里面。

很多初学者在理解上过不去的坎,就是把“连续特征”和“连续切分”画等号,总觉得树应该把一条连续曲线给完整还原出来。其实树从来不做这件事,树只在每个节点找一个经过优化的局部阈值,通过层层叠加逼近规律。

2. 从排序到扫描:最优切分点到底怎么找出来

2.1 候选切分点的来源:相邻取值的中点

确定了二分这个方向之后,下一个问题就是:收入那么多不同的值,切在哪“一刀”效果最好?

经典做法是排序加扫描。假设你有一个已经按收入从小到大排好序的样本列表,比如这样五六个样本:

样本收入是否购买
A5000否
B6500是
C8000否
D12000是
E20000是
F35000是

排序之后,真正可能成为切分点的位置,只可能发生在相邻两个样本的取值之间。为什么?如果切在一个没有任何样本取值覆盖的空白区间里,那么阈值往左挪一点或往右挪一点,左右两边包含的样本集合完全一模一样,评判结果当然也一模一样。换句话说,空白区域里的切分点都没有意义,真正有区分度的切分点,必然存在于两个相邻取值的中间位置。

于是候选切分点就是每对相邻收入的中值:5000 和 6500 之间是 5750,6500 和 8000 之间是 7250,8000 和 12000 之间是 10000,12000 和 20000 之间是 16000,20000 和 35000 之间是 27500。

这段说透了一件事:你不需要尝试几百个取值。对一个唯一值的连续特征,候选切分点最多只有 n-1 个。这个数量是有限的、可控的。

2.2 用基尼不纯度给每个断点打分

有了候选切分点,接下来就是“打分”环节。对每个候选点,把样本分成左右两组,分别计算左右两组的不纯度,再按样本量占比做加权,得到一个最终得分。得分越低,说明这个切分点让左右两边变得越“纯”。

拿上面 10000 这个切分点举例。切在 10000 时,左边是样本 A、B、C,对应是否购买的结果是:否、是、否。这组样本的基尼指数是:

(1 - (\frac{1}{3})^2 - (\frac{2}{3})^2 = 1 - \frac{1}{9} - \frac{4}{9} = \frac{4}{9})

右边是 D、E、F,全部是“是”,基尼指数为 0。因为右边是 3 个样本,左边也是 3 个样本,加权不纯度是:

(\frac{3}{6} \times \frac{4}{9} + \frac{3}{6} \times 0 \approx 0.222)

再看 16000 这个点。左边是 A、B、C、D,是否购买是:否、是、否、是。基尼指数是:

(1 - (\frac{2}{4})^2 - (\frac{2}{4})^2 = 1 - 0.25 - 0.25 = 0.5)

右边是 E、F,全部是“是”,基尼指数 0。加权不纯度是:

(\frac{4}{6} \times 0.5 + \frac{2}{6} \times 0 \approx 0.333)

这么一对比,10000 的加权不纯度 0.222 明显优于 16000 的 0.333。算法会把所有候选切分点都扫一遍,选出得分最低的那个作为当前节点在这个特征上的最佳切分点。

这个过程,就是网上众多博客里常说的“对连续特征排序后求最优分割点”。它并不神秘,本质上就是在一个有序数组上寻找能让左右子节点标签分布差异最大的那个位置。

2.3 关于复杂度与去重:两个常被忽略的工程细节

过去我在项目里遇到连续变量,一开始还担心计算量会爆掉。实际上计算量完全可控:排序一次是 O(n log n),扫描一遍是 O(n),再加不纯度计算也是 O(n)。整体复杂度比训练一个深度网络的任何一个 epoch 都要低得多,完全不需要担心。

真正需要留意的反而是两个容易踩坑的细节。

第一是重复值处理。当样本量大时,连续特征往往会出现大量重复取值。比如风控场景里的“征信查询次数”,它名义上是连续变量,实际上取值可能只有 0 到 20 这几十个整数。如果不做去重,排序后同一个值会对应好几条样本,切分点扫描时会把相同取值的样本反复计算,既浪费又容易被同一个值附近的少量噪声带偏。一般的实现都会先做一个“压缩”,把相同取值聚合在一起,只对取值发生变化的相邻位置计算切分点。这会带来两个好处:计算量减少,且切分点的稳定性更高。

第二是不要忽略总样本数很小的情况。连续特征在几十个样本的统计学习中不敏感,切分点往往落在线性关系明显的位置;一旦样本量上来,比如到了几万或几十万,连续特征的最优切分点会变得非常“细腻”,它能把目标变量差异细微的两个区间精确切开。这种细腻在某些任务里是好事,在另一些任务里则是过拟合的温床。所以连续特征使用起来,需要结合后面的剪枝策略综合考虑,而不只是盯着切分点本身。

3. 连续特征实战中的高发问题:剪枝、缺失值与阈值抖动

3.1 连续特征天然容易诱发过拟合

连续特征和过拟合的关系,在我遇到的建模项目里几乎是一对铁搭档。之前有个模拟项目,特征表里有一个“最近一次还款距离今天的天数”,取值从 0 到 90 天都有,分布非常密。树在默认参数下训练,深度蹭蹭往上涨,最后在验证集上表现很拉胯。

原因不难理解。连续变量在每个节点上都会暴露出新的“高分辨率切分机会”,树只要放任不管,就会一路追着噪声去。比如某个 73 到 74 天之间的样本恰好有三个坏样本,树就可能在这个位置切上一刀。这个切分在训练集上很精妙,但对新数据毫无意义。

实操上怎么管?主要是两类手段:

  • 限制每次切分之后最少样本数。让叶子节点至少保留一定数量的样本,可以避免树在个别离群值上继续深挖。
  • 限制树的最大深度。连续特征有时只需要几层就能把主规律抓完,更深层往往是在拼命适配训练集的个体差异。

我自己的习惯是先跑一个不设上限的满树,观察它在验证集上的误差曲线,然后再把深度和叶子节点调小。注意,连续特征多的场景,树的误差曲线会呈现明显的先降后升形态,那个“最低点”附近的参数组合是首选参考,而不是直接把满树拿上线。

3.2 缺失值与代理切分:样本落到空分支怎么办

真实业务数据里,连续特征最容易出现缺失。收入字段空着,年龄字段空着,这在真实表格里太常见了。决策树处理连续特征缺失时有一个不错的逻辑:当某个样本在当前节点缺少该特征值时,就根据缺失样本的自变量替代字段继续下钻。

具体来说,树在训练时不仅会记录“收入≤8000”这个主切分,还会记录一些与主切分行为相似的其他特征切分,比如“年龄≤28”或者“查询次数≤5”。这些被统称为代理切分。当新样本缺失收入时,树就尝试用代理切分来决定它往左走还是往右走。

这里有一个普遍的误解:有些人以为缺失值直接丢到左边或者右边,其实不是。树会尽量利用其他变量的信息来模拟原来的分裂效果。这也解释了,为什么在一个特征缺失率很高的项目里,加一些和它相关性强的辅助特征,反而能提升树的整体表现——那些辅助特征成了缺失样本在树里的“向导”。

但代理切分也不是万能药。如果缺失比例太高,比如超过 60%,那树在计算这个特征的切分点时,其实只用了不到一半的样本,切分点的代表性会大打折扣。实际上,遇到这种情况我会先做缺失原因判断,如果缺失本身代表一种业务状态,比如“收入为空”可能意味着无固定收入来源,那么值得单独建一个“是否缺失”的特征,让树显式地利用这个信号。

3.3 阈值抖动:换了随机种子,切分点就变

这一点实操中很少人提前告诉你:连续特征的最优切分阈值,在不同随机种子下可能巨大不同。

同一份训练数据,换个随机种子重新跑一次模型,树的结构可能变,连那个“收入≤17850”的阈值都可能变成“收入≤17201”。阈值跳来跳去,不是模型坏了,而是连续特征的信息被切分点映射成了一组很敏感的条件。

为什么敏感?因为切分点本质上是从相邻样本的标签分布里挤出来的。只要样本集有细微改动,某个小区域的标签比例一变,最优切分点就可能整体平移。这个现象在数据量越小、连续变量分布越平缓时越明显。

碰到这种情况,我有一招:看稳定性而不是看某个固定阈值。如果两个随机种子跑出来的模型预测结果在整体准确率上保持一致,那阈值抖动就是模型内部的“表示切换”,不影响交付。反之,如果阈值稍微一抖,业务指标就剧烈变化,那就说明这个特征的区分能力本身不稳定,需要回到特征工程层面去修正,而不是逐次数值差异。

提示:上线前不要迷信某一个“稳定阈值”,要用一批候选阈值做敏感性测试。衡量标准不是阈值本身变不变,而是阈值上下浮动 10% 后,模型预测分布是否依然一致。

4. 当目标变量也连续:回归树的切分逻辑与混合特征处理

4.1 从基尼到方差:回归树怎么选切分点

前面讲的例子一直围绕分类问题,目标变量是“买或不买”。但在很多连续变量场景里,目标变量本身也是连续的,比如预测房屋价格、预测用电量。这时决策树的切分逻辑要换一把尺子。

分类树用的是不纯度下降,比如基尼或者熵;回归树用的则是方差下降。原理非常直接:在根节点上所有标签值的方差是固定的,切分后左右两组的方差应该尽量小,且两组均值差异尽量大。加权方差下降最大的切分点就是最优切分点。

举例来说,如果全体样本的价格方差是 5000,切在某个点上后,左侧样本价格方差变成 800,右侧样本价格方差变成 1200,加权之后总方差降到了 2200,这说明这个切分点让左右两组样本各自抱团,数据内部的一致性大大提升。

一个需要注意的点是:在连续目标下,切分阈值的选择对“平滑度”非常敏感。阈值偏一点,左右两组的均值就会变化,预测结果就会跳变。实操中做回归树时,我会把叶子节点的最小样本数调得比分类树更大,保证每个叶子节点有足够样本把均值“磨平”,否则预测结果会出现锯齿状的剧烈抖动。

4.2 连续+离散混合特征:为何排序信息会成为胜负手

现实项目里很少只存在连续变量,更多情况是连续变量和离散变量混合。比如一个人力资源数据集:年龄连续、性别离散、学历有序、岗位类别无序。

这种场景下,树对连续特征的第一依赖就是“排序信息”。连续特征天生自带顺序,年龄 20 岁到 30 岁的样本,天然形成一个从低到高的梯度。树可以利用这个梯度捕捉单调关系,比如“年龄越大,离职概率越低”这种趋势。而离散特征中的无序类别,像“部门 A/部门 B/部门 C”,只能在“是否等于某个部门”这种二元组合上去试,搜索空间完全不同。

实操中经常发现:在混合特征集上,连续特征会被树优先选中作为顶层切分,因为它的切分点可以在数轴上自由滑动,寻找到的边界往往更“贴合”数据分布。这既是优势也是隐患——如果连续特征太多,离散特征几乎没有出场机会。这时候可以考虑限制连续特征参与顶层分裂的次数,或者对极重要的离散特征做编码转换,让树更容易感知到它的信息。

4.3 归一化、分箱与异常值:三类预处理到底做不做

这是决策树和很多其他机器学习模型分道扬镳的地方。

归一化对决策树没用,因为树的切分只依赖相对大小比较,不依赖特征的绝对量纲。收入从元换算成万元,排序不变,最优切分点的位置换算比例地变一下,树的结构和预测结果完全一样。以前我见过一个项目组对着树做标准化,说实话纯属白费功夫。

分箱则不一定。分箱本质上是在提前预设切分点,比如把年龄切成几段再送进树。这个操作在树身上其实是在“帮倒忙”和“帮正忙”之间摇摆。如果分箱方式正好符合业务先验,比如监管要求里规定的年龄段,那分箱有意义。如果只是拍脑袋随便分,树会失去对更细分切点的搜索空间,反而降低拟合能力。所以我的建议是:先跑一遍原始连续特征,再看业务规则需不需要硬编码约束,再决定要不要额外上分箱特征。

异常值需要留意。因为切分点是对相邻样本的中点,一个远超常规分布的巨大离群值会在排序后孤零零地站在末尾。它要么把切分点拉向一个极端位置,要么被一个从它前面切过去的阈值直接隔离出去。实践中折合为:对尾部异常值做截断或者单独打标,比直接删除有效得多。

5. 上线之后:切分点的解释、验证与稳定性检查

5.1 把切分点翻成业务语言

决策树能长期活跃在业务端,靠得是强大的可解释性。连续特征的切分点一旦产生,就能直接翻译成业务规则。比如“月收入≤8000”可以被描述成“低收入群体的坏账率显著高于其他群体”。这个动作的成本很低,但价值非常高,因为业务同事不一定理解模型指标,但他们一定看得懂“收入 8000”这个数字。

实际落地时有一个技巧:把连续特征的切分点转写成分位数区间。比如不要只说“收入≤17850”,而是说“收入落在样本分布的第 40 百分位数以内”。百分位数比绝对数值更能抵御未来人群收入整体上涨带来的偏移。尤其是观察周期比较长的业务,去年的人群收入分布和今年很可能不再一致,固定绝对阈值很容易失效。

5.2 同一特征被多次切分:阶梯效应与可解释性

前面提过,连续特征允许在树的多个深度被反复切分。这会产生一种特殊的可解释性模式:阶梯效应。

比如收入特征在第一层被切成 8500 以下和以上;到了第二层,“8500 以上”那部分又会被进一步切成 8500 到 17000 和 17000 以上;第三层可能还会再切。表现在模型预测上,就变成收入每升高一段,预测概率就跳一次台阶。

这个阶梯在展示规则时非常直观,但也带来一个麻烦:如果切得太多太碎,阶梯就变成了折线,每一级的业务含义都不清晰。此时我建议做一次后处理:把多个连续切分点在图上画出来,观察台阶高度。如果某一级台阶的预测变化幅度小于业务认为“可感知”的最小值,就让这个切分点并入相邻区间,这样树的规则会变得简洁得多。

5.3 阈值稳定性验证的实操建议

上线前,我会对连续特征的切分阈值专门做一次稳定性检查。具体做法不复杂:

  • 从训练集中抽取多个不同种子下的子样本,分别重训树模型。
  • 记录同一个特征在不同模型里的切分点位置,选分布最密集的区间作为主阈值区间。
  • 用主阈值区间上下浮动一定比例,各出一版预测结果,计算与基准结果的一致性指标。

如果一致性很高,说明这个切分点受随机波动影响小,可以放心交付。如果一致性很差,我会回看该特征的缺失率和分布形态,通常会发现它本身区分度就不强,或者存在大量异常值。这类特征即使在单颗树上看着不错,上线后也很容易被后续的数据漂移打回原形。

说实话,连续变量在决策树里一直是被低估的“细节大户”。表面上看,切分点就是一串“小于等于某值”的判断;实际上,从候选点生成、不纯度计算到剪枝策略选择、阈值稳定性验证,每一步都有反直觉的坑。这篇文章把这些环节按“找切分点—防过拟合—混合场景—上线验证”的顺序串了一遍,也是我自己从最早用连续特征手足无措,到后来能快速定位问题的一套经验路径。希望你下次遇到年龄、收入、温度这类连续变量时,能把树“咔嚓”那一刀切得明明白白。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进