news 2026/9/16 7:13:51

CART决策树客户流失预测实战:从原理到工程落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CART决策树客户流失预测实战:从原理到工程落地

简介:面向电信运营商量化分析与数据挖掘学习者,提供基于分类回归树(CART)决策树算法的客户流失预测完整项目,适合电信行业数据分析师、机器学习初学者及毕业设计参考。项目通过通话时长、在网时长等关键行为特征,利用CART分类树与基尼不纯度分裂机制构建预测模型,并围绕准确率、召回率、精确率等指标给出完整评估流程,可帮助理解从数据清洗、特征探索到模型训练与评价的实战链路。资源压缩包约22MB,共9个文件,包含程序脚本、交互式分析笔记本、数据可视化图表、两份CSV源数据及说明文档,可运行复现从数据探索到模型评价的全过程;其中直方图直观展示客户行为分布,项目说明文档便于快速上手。已有336人学习下载,资源目录结构清晰,便于按脚本、笔记与图像分模块研读。

1. 运营商客户流失预测:为什么先选CART决策树当基线

一家省公司每月几千万用户里流失一两个百分点,对收入的影响就可能以千万计。流失预测的直觉是用最复杂模型去抓最细微的信号,但实际落地时,业务侧要解释“为什么是他”,运营侧要根据规则圈人,算法侧还要面对套餐、通话、流量、投诉这些混合类型的字段。CART决策树在这类任务里的位置,有点像SQL在数据仓库里的位置:不是处处最强,但几乎处处可用。它可解释、能直接输出 if-then 规则,连续值和类别值不必单独做标准化,训练成本和维护成本都低。用它把运营商客户流失预测的完整流程跑通,是数据团队进入这个场景门槛最低的方案。新手能快速看到可落地的树形规则,老手也能把它当作对比 LightGBM、XGBoost 时不会失控的基线模型。

2. CART决策树原理与选型理由:从基尼不纯度到可解释规则

2.1 二叉树结构与基尼指数:CART 分裂的依据

CART 全称 Classification And Regression Tree,1984 年由 Breiman 等人提出,核心是“递归二分”。每个节点只做一次判断,把样本切到左子树或右子树,然后对子节点重复同样操作。分类任务的不纯度度量用的是基尼指数,而不是信息熵。对二分类问题,若某节点正样本占比为 p,则基尼指数为Gini = 2 * p * (1 - p)。基尼为 0 时节点最纯,正负样本各半时基尼最大等于 0.5。

CART 每次分裂时遍历候选特征和切分阈值,挑出能让“分裂后左右子节点基尼加权和”下降最多的那组条件。这个选型让它天然是二叉树,和信息增益的 ID3、信息增益率的 C4.5 相比,少了对数运算,计算更快,也不容易因为多叉分裂造成子节点样本过少。在客户流失场景里,特征维度往往几十个,样本量几十万到上百万,基尼系数的速度优势会被明显放大。

import numpy as np def gini(labels): unique, counts = np.unique(labels, return_counts=True) p = counts / counts.sum() return 1 - (p ** 2).sum() print(gini([0, 0, 1, 1])) # 0.5,最不纯 print(gini([0, 0, 0, 1])) # 0.375 print(gini([0, 0, 0, 0])) # 0.0,完全同质

代码说明:gini函数接收一批标签,先统计每个类别占比,再按基尼公式计算。基尼值越小,节点里样本类别越一致。CART 在内部就是这样反复做节点基尼下降的计算,只是 sklearn 用 Cython 做了加速。理解这个函数,后面看feature_importances_就不会只把它当黑盒数字。

2.2 为什么流失预测场景更适合 CART:可解释性与混合特征

客户流失预测不是纯算法竞赛,产出物最终要给运营策略用。比如“近三个月费用波动超过 20% 且投诉次数不小于 2 的用户,流失概率 63%”这类规则,业务方可以直接拿去圈名单。逻辑回归能给出系数,但要解释交互效应很费劲;深度模型给出一堆 embedding,运营没法拿着特征向量去沟通。CART 的整条路径就是业务规则,这是它在这个场景的核心竞争力。

另一个现实原因是运营商特征表非常杂:入网时长、套餐档位、缴费渠道、投诉类别、流量使用量,类型从连续值到高基数类别都有。对 CART 来说,连续值特征只需要排序后找切分点,不需要做标准化;类别特征做编码后也能参与二分;特征之间的交互由树结构自动组合。相比线性模型要做独热、归一化、共线性检查,CART 的数据准备工作量小得多,尤其适合第一版建模。

2.3 CART 分类树与决策树回归:流失概率的两种输出方式

CART 另一面是决策树回归,分裂准则从基尼换成误差平方和最小化,叶子输出不再是类别占比而是均值。客户流失预测主要用分类树,把“是否流失”作为标签,叶子节点输出的是流失样本的占比,也就是predict_proba的分数。这个分数有鲜明特点:它只会取有限几种值,等于每个叶子内的正样本比例,所以概率分布呈阶梯状,而不是逻辑回归那样的光滑曲线。

如果目标从“是否流失”改成“未来流失时间”,决策树回归可以输出平均留存天数;工程上还有一种做法是先用分类树算概率,再用回归树对高概率样本做流失时间预估,两个模型串联。但大多数运营场景里,分类树概率已经够用,回归树更多用在资源排期时判断“先挽留谁”。

3. 流失定义与特征工程:CART 能学到什么取决于喂什么数据

3.1 流失标签怎么定:观察窗口、表现窗口与沉默阈值

流失定义不对,再好的算法也是白搭。用“用户注销”做标签最大的问题是滞后:注销前三个月行为已经明显恶化,但标签体系里没有任何信号;用“单月无通话”做标签则太吵,一个用户可能只是换号过渡期。常见做法是滑窗法:以 T 月为观察点,取 T-2 到 T 月的特征;然后看 T+1 到 T+3 的表现窗口,如果连续 90 天没有通话、没有流量消耗、也没有缴费行为,就标记为流失。

口径判断标准标签数量适用场景
单月沉默30 天无行为偏多,噪声大不适合单独作为标签
连续 90 天沉默90 天无通话、流量、缴费适中,业务可解释优先推荐
状态注销正式销户或欠费停机超过宽限期偏少且滞后只用来做离线复盘

标签定义直接影响 CART 的树结构。表现窗口拉长,标签更干净,但预测结果天然滞后;标签定义越短,决策边界会跟着行为波动,树的深度会被迫加大去拟合噪声。我一般会先用 90 天沉默口径跑一版,再对比单月口径看 AUC 和规则稳定性,多数情况下 90 天口径在运营侧更吃得开。

3.2 运营商用户特征体系与构造方法

CART 能自动做特征选择和切分,但好特征仍然来自业务理解。拉数的时候我习惯按四类维度铺开,先多造特征,后面用决策树排序去筛。

特征类别字段示例构造要点CART 视角下的价值
基本属性入网时长、年龄、套餐品牌入网时长用“月”计,年龄做分箱基础分群,切出高危群体
消费行为ARPU、通话时长、流量、费用波动率费用波动率=最近三个月 ARPU 标准差/均值行为突变的用户往往是流失前兆
交互信号投诉次数、投诉类别、账单查询次数投诉按天聚合,时间窗口选最近 90 天服务体验恶化的重要先导指标
渠道与合约缴费渠道、是否宽带捆绑、合约剩余月份高基数渠道做归并或频率编码合约到期时间附近流失率会陡增

费用波动率这类特征是流失预测里的“硬通货”。它把一个用户日常消费的稳定性压缩成单值,当波动率超过 0.3 时,CART 通常会自动在树的前两层命中。合约剩余月份也很有用:如果剩余月数为 0,说明用户已经进入可转网窗口,流失概率会显著上升。

3.3 类别特征编码与连续值切分的注意事项

sklearn 的 CART 不支持类别特征原生输入,必须编码。这里有个常见坑:对“套餐档位”“缴费渠道”这类无序类别直接用LabelEncoder编码成 0、1、2、3,CART 会拿它们和数值阈值比较,等于强行给类别排了顺序,模型会学到不存在的“档位大小关系”。正确做法是用OneHotEncoder转成多列;如果某个类别特征基数特别高,比如渠道有 300 个值,独热后矩阵会膨胀,可以先按样本占比合并低频类别,或用目标编码压缩成单列。

连续值特征不需要标准化。CART 分裂时把特征值排序后取相邻样本中点作为候选阈值,量纲变化不改变排序顺序,做 MinMaxScaler 反而白白增加一层维护成本。缺失值方面,sklearn 的树模型不支持缺失值透传,常见做法是SimpleImputer用中位数填充连续特征、用众数填充类别特征;缺失率超过一半的特征直接丢弃,不然模型会把“缺失”这个特殊值当作强信号来用,线上数据一旦不缺失就会打脸。

4. Python + scikit-learn 实现客户流失预测 CART 模型

4.1 数据装载与训练测试集划分

用一个典型的数据文件telecom_churn.csv演示,字段包含用户 ID、入网时长、近三月通话时长、流量、ARPU、费用波动率、投诉次数、合约剩余月份、是否宽带捆绑,以及标签churn_label

import pandas as pd import numpy as np from sklearn.model_selection import train_test_split df = pd.read_csv("telecom_churn.csv") print(df.shape, df["churn_label"].mean()) # 流失率一般在 5%~15% feature_cols = [c for c in df.columns if c not in ("user_id", "churn_label")] X = df[feature_cols].copy() y = df["churn_label"].astype(int).values X = X.fillna(X.median()) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42 )

逻辑说明:stratify=y保证训练集和测试集里流失样本占比一致,否则随机切分可能让测试集流失率偏离整体,评估结果失真。random_state=42固定切分结果,方便复现和对比参数。缺失值统一用中位数填充,简单有效;类别列如果有 object 类型,这里要先做独热编码,再进入模型训练。

4.2 训练第一颗 CART 树:max_depth=4 让模型先“看得懂”

第一版不要追求精度,先把树训练出来看规则。

from sklearn.tree import DecisionTreeClassifier model = DecisionTreeClassifier( criterion="gini", max_depth=4, min_samples_leaf=50, random_state=42, ) model.fit(X_train, y_train) train_acc = model.score(X_train, y_train) test_acc = model.score(X_test, y_test) print("train acc", train_acc) print("test acc ", test_acc)

参数说明:criterion="gini"指定用基尼系数,是 CART 默认准则;max_depth=4把树限制到 4 层,最多 16 个叶子,每一条路径都可以人工阅读;min_samples_leaf=50要求每个叶子至少有 50 个样本,防止小分支噪声被当成规律。第一次建模我基本固定这两组参数,把注意力放在特征和标签上。如果train_acc明显高于test_acc三分以上,说明树在过拟合,后面调剪枝参数时优先加大min_samples_leaf

4.3 用混淆矩阵和 AUC 评估:流失场景不能只看准确率

在流失率接近 10% 的数据里,一个把所有用户都判为不流失的模型也能拿到 90% 准确率,但这个模型毫无价值。要看的是在预测流失的用户里有多少命中,以及真实流失的用户被抓住多少。

from sklearn.metrics import confusion_matrix from sklearn.metrics import roc_auc_score from sklearn.metrics import precision_score, recall_score y_pred = model.predict(X_test) y_prob = model.predict_proba(X_test)[:, 1] cm = confusion_matrix(y_test, y_pred) print(cm) print("precision=%.3f recall=%.3f auc=%.3f" % ( precision_score(y_test, y_pred), recall_score(y_test, y_pred), roc_auc_score(y_test, y_prob), ))

混淆矩阵的四格分别对应实际非流失被预测为非流失、实际非流失被预测为流失、实际流失被预测为非流失、实际流失被预测为流失。第二类是运营成本,第四类是流失漏召回的损失。recall表示真实流失中被模型抓住的比例,在挽留场景里通常优先保障它;auc衡量模型把流失用户排到非流失用户前面的能力,不依赖具体阈值。AUC 在 0.75 以上说明模型有可用信号,0.85 以上在大多运营商场景已经有明显业务价值。

4.4 输出树规则与决策树排序出的特征重要性

树训练好后,我习惯先打印特征重要性,再用文本形式把树规则导出来。

from sklearn.tree import export_text importance = pd.Series( model.feature_importances_, index=feature_cols ).sort_values(ascending=False) print(importance.head(10)) print(export_text(model, feature_names=feature_cols, max_depth=4))

特征重要性的计算逻辑是:每次分裂带来的基尼下降量按节点样本量加权,再按特征累加,最后归一化。它衡量的是“这个特征在树里做出的总贡献”,不是业务意义上的因果权重。决策树排序最大的用途是特征筛选:跑一版全特征模型,取排序前 20 的字段,再单独训练一次,通常 AUC 下降不到 0.01,但训练和上线维护成本显著降低。export_text输出的缩进规则可以直接贴给业务,例如“若费用波动率大于 0.28 且投诉次数不小于 2,则流失概率 0.63”。

5. 调参、样本不平衡与剪枝:让 CART 在真实流失数据上更稳

5.1 预剪枝参数与后剪枝成本复杂度剪枝的组合

CART 的剪枝分两种:预剪枝在训练时提前停止分裂,后剪枝训练完成后再裁掉贡献不大的子树。预剪枝最常用的是max_depthmin_samples_leaf,它们的组合规律是:样本量越大,min_samples_leaf应设得越大。几十万样本时设 100 到 500 都很常见,目的是让叶子有足够统计置信度。

后剪枝对应 sklearn 里的ccp_alpha,即成本复杂度剪枝参数。ccp_alpha越大,被裁剪的节点越多。常见做法是结合网格搜索一起选参。

from sklearn.model_selection import GridSearchCV param_grid = { "max_depth": [3, 4, 5, 6], "min_samples_leaf": [50, 100, 200], "ccp_alpha": [0, 0.0001, 0.001, 0.01], } search = GridSearchCV( DecisionTreeClassifier(criterion="gini", random_state=42), param_grid, scoring="roc_auc", cv=5, ) search.fit(X_train, y_train) print(search.best_params_) print(search.best_score_)

逻辑说明:网格搜索用 5 折交叉验证,把每个参数组合在训练集上训练 5 次并取平均 AUC,选最优组合。评分不用准确率,还是那个原因:类别不平衡时准确率会误导。ccp_alpha=0表示不剪枝,一般搜索结果会落在 0.0001 到 0.001 之间。注意这里只能在训练集上做交叉验证,测试集要留到最后评估,否则调参过程会把测试集信息泄漏进模型。

5.2 不均衡样本:class_weight 与阈值移动的组合拳

运营商流失率经常只有 5% 到 10%,CART 会天然偏向多数类。两条路可以同时用:训练时给少数类加权,预测后再移动阈值。

class_weight="balanced"让 sklearn 按类别频率自动调整权重,少数的流失样本获得更高权重,树会更积极寻找流失特征。但训练阶段加权后,predict_proba输出的概率已经带着偏置,直接拿 0.5 做阈值并不合适。正确做法是在验证集上画一条“阈值-F1”曲线。

from sklearn.metrics import f1_score best_t, best_f1 = 0.5, 0 for t in np.arange(0.1, 0.9, 0.02): pred = (y_prob >= t).astype(int) f1 = f1_score(y_test, pred) if f1 > best_f1: best_t, best_f1 = t, f1 print("best threshold:", best_t, "best f1:", best_f1)

阈值移动的工程意义在于:它不改变树结构,只改变判定边界。如果运营目标是尽可能多地找出潜在流失用户,可以把阈值降到 0.3,接受多一些误伤;如果外呼资源有限,就提到 0.6,只打高概率客户。这种方式比反复调class_weight更直接,也更容易向业务方解释。

5.3 从概率分数到运营名单:分桶输出与对照组

模型最终输出的不是 0/1 标签,而是概率分数。我一般会按概率分三档:低风险、中风险、高风险,对应不同的运营动作。

test_out = pd.DataFrame({ "user_id": df.loc[X_test.index, "user_id"], "churn_prob": y_prob, }) test_out["risk_group"] = pd.cut( test_out["churn_prob"], bins=[-0.01, 0.2, 0.4, 1.0], labels=["low", "mid", "high"], ) print(test_out.groupby("risk_group", observed=True).size())

说明:分桶阈值不是固定的,要根据业务资源和概率分布调整。如果高风险桶的样本量太大,外呼团队做不过来,就得把阈值继续上调,或者在高风险桶里再按费用波动率排名输出名单。每一批名单里要留一组随机用户做对照组,否则无法度量模型带来的增量挽留效果。

6. 验证决策树排序的稳定性与单规则命中率

模型上线前,我最后做三件小事:验证特征重要性稳定、计算单规则命中率、监控线上分数漂移。

第一个技巧是用不同随机种子多训练几次,看决策树排序出的头部特征是否一致。

def top_features(seed): clf = DecisionTreeClassifier( max_depth=4, min_samples_leaf=100, random_state=seed ).fit(X_train, y_train) imp = pd.Series(clf.feature_importances_, index=feature_cols) return imp.sort_values(ascending=False).head(5).index.tolist() for seed in [1, 42, 2024]: print(seed, top_features(seed))

如果前 5 个特征在不同种子下重合少于 3 个,说明模型正在吃一组不稳定的噪声特征,优先回去查特征质量,而不是继续调参。如果重合度高,这组特征就是可以沉淀下来的业务指标,后续做月度监控时只盯它们的变化就够。

第二个技巧是单规则命中率。从浅树里任选一条叶子路径,例如“合约剩余月份为 0 且费用波动率大于 0.25 且投诉次数不少于 1”,在测试集上统计落入该路径的人数和流失人数,计算路径流失率。如果路径流失率超过全量平均流失率的 3 倍,这条规则可以直接沉淀成运营白名单,即使整个树以后被替换,它依然有独立价值。这个验证方式是决策树相对其他模型最大的优势,其他模型很难给出这样的单规则解释。

第三个技巧是线上监控。把每天模型输出的churn_prob按 0.1 分箱统计样本占比,按月对比分布变化,如果某个分箱占比相对上月偏离超过 25%,说明用户近期行为发生了结构性变化,需要重新训练或排查上游特征数据。这套检验配合决策树排序稳定性的结果,就能在模型悄悄失效前发现预警信号。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 7:12:46

Python+OpenCV实现高效批量图像处理与智能抠图

1. 图像处理效率提升的核心痛点在数字内容爆炸式增长的今天,图像处理已成为设计师、自媒体从业者和电商运营人员的日常刚需。但传统单张处理的方式在面对上百张产品图、活动海报或文章配图时,往往让人陷入重复劳动的泥潭。我曾为一家电商代运营公司优化工…

作者头像 李华
网站建设 2026/9/16 7:12:03

Java Map核心解析与性能优化实战

1. Java集合框架中的Map核心解析作为Java集合框架中最常用的数据结构之一,Map在日常开发中扮演着关键角色。不同于List和Set这类单元素集合,Map采用键值对(Key-Value)存储机制,这种设计特别适合需要快速通过键查找值的…

作者头像 李华
网站建设 2026/9/16 7:12:01

从SOP到Dockerfile:构建可复制、可审计的容器镜像指南

我第一次看 Dockerfile 的时候,脑子里全是问号:这个 FROM 是干什么的?RUN 为什么要用 && 连成一长串?CMD 和 ENTRYPOINT 看起来都是启动命令,到底有什么区别?后来有一次在奶茶店等单,看…

作者头像 李华
网站建设 2026/9/16 7:10:19

OmniQuant:端侧大模型低比特量化的新思路与实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 7:10:11

一文读懂 SPI 总线:时序原理、电气特性与驱动开发全解析

文章摘要 SPI(Serial Peripheral Interface)是嵌入式开发中最常用的高速通信总线之一,也是大厂面试的高频考点。本文从通信时序、电气特性、驱动开发三个层面,系统梳理 SPI 的核心原理与工程实践,涵盖 CPOL/CPHA 四种模…

作者头像 李华
网站建设 2026/9/16 7:09:56

DPR、压缩与格式:解决移动端图片模糊的三大核心

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华