ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

CART决策树客户流失预测实战:从原理到工程落地 简介面向电信运营商量化分析与数据挖掘学习者提供基于分类回归树CART决策树算法的客户流失预测完整项目适合电信行业数据分析师、机器学习初学者及毕业设计参考。项目通过通话时长、在网时长等关键行为特征利用CART分类树与基尼不纯度分裂机制构建预测模型并围绕准确率、召回率、精确率等指标给出完整评估流程可帮助理解从数据清洗、特征探索到模型训练与评价的实战链路。资源压缩包约22MB共9个文件包含程序脚本、交互式分析笔记本、数据可视化图表、两份CSV源数据及说明文档可运行复现从数据探索到模型评价的全过程其中直方图直观展示客户行为分布项目说明文档便于快速上手。已有336人学习下载资源目录结构清晰便于按脚本、笔记与图像分模块研读。1. 运营商客户流失预测为什么先选CART决策树当基线一家省公司每月几千万用户里流失一两个百分点对收入的影响就可能以千万计。流失预测的直觉是用最复杂模型去抓最细微的信号但实际落地时业务侧要解释“为什么是他”运营侧要根据规则圈人算法侧还要面对套餐、通话、流量、投诉这些混合类型的字段。CART决策树在这类任务里的位置有点像SQL在数据仓库里的位置不是处处最强但几乎处处可用。它可解释、能直接输出 if-then 规则连续值和类别值不必单独做标准化训练成本和维护成本都低。用它把运营商客户流失预测的完整流程跑通是数据团队进入这个场景门槛最低的方案。新手能快速看到可落地的树形规则老手也能把它当作对比 LightGBM、XGBoost 时不会失控的基线模型。2. CART决策树原理与选型理由从基尼不纯度到可解释规则2.1 二叉树结构与基尼指数CART 分裂的依据CART 全称 Classification And Regression Tree1984 年由 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_countsTrue) 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 月的特征然后看 T1 到 T3 的表现窗口如果连续 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、3CART 会拿它们和数值阈值比较等于强行给类别排了顺序模型会学到不存在的“档位大小关系”。正确做法是用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_size0.2, stratifyy, random_state42 )逻辑说明stratifyy保证训练集和测试集里流失样本占比一致否则随机切分可能让测试集流失率偏离整体评估结果失真。random_state42固定切分结果方便复现和对比参数。缺失值统一用中位数填充简单有效类别列如果有 object 类型这里要先做独热编码再进入模型训练。4.2 训练第一颗 CART 树max_depth4 让模型先“看得懂”第一版不要追求精度先把树训练出来看规则。from sklearn.tree import DecisionTreeClassifier model DecisionTreeClassifier( criteriongini, max_depth4, min_samples_leaf50, random_state42, ) 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)参数说明criteriongini指定用基尼系数是 CART 默认准则max_depth4把树限制到 4 层最多 16 个叶子每一条路径都可以人工阅读min_samples_leaf50要求每个叶子至少有 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_, indexfeature_cols ).sort_values(ascendingFalse) print(importance.head(10)) print(export_text(model, feature_namesfeature_cols, max_depth4))特征重要性的计算逻辑是每次分裂带来的基尼下降量按节点样本量加权再按特征累加最后归一化。它衡量的是“这个特征在树里做出的总贡献”不是业务意义上的因果权重。决策树排序最大的用途是特征筛选跑一版全特征模型取排序前 20 的字段再单独训练一次通常 AUC 下降不到 0.01但训练和上线维护成本显著降低。export_text输出的缩进规则可以直接贴给业务例如“若费用波动率大于 0.28 且投诉次数不小于 2则流失概率 0.63”。5. 调参、样本不平衡与剪枝让 CART 在真实流失数据上更稳5.1 预剪枝参数与后剪枝成本复杂度剪枝的组合CART 的剪枝分两种预剪枝在训练时提前停止分裂后剪枝训练完成后再裁掉贡献不大的子树。预剪枝最常用的是max_depth和min_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(criteriongini, random_state42), param_grid, scoringroc_auc, cv5, ) search.fit(X_train, y_train) print(search.best_params_) print(search.best_score_)逻辑说明网格搜索用 5 折交叉验证把每个参数组合在训练集上训练 5 次并取平均 AUC选最优组合。评分不用准确率还是那个原因类别不平衡时准确率会误导。ccp_alpha0表示不剪枝一般搜索结果会落在 0.0001 到 0.001 之间。注意这里只能在训练集上做交叉验证测试集要留到最后评估否则调参过程会把测试集信息泄漏进模型。5.2 不均衡样本class_weight 与阈值移动的组合拳运营商流失率经常只有 5% 到 10%CART 会天然偏向多数类。两条路可以同时用训练时给少数类加权预测后再移动阈值。class_weightbalanced让 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, observedTrue).size())说明分桶阈值不是固定的要根据业务资源和概率分布调整。如果高风险桶的样本量太大外呼团队做不过来就得把阈值继续上调或者在高风险桶里再按费用波动率排名输出名单。每一批名单里要留一组随机用户做对照组否则无法度量模型带来的增量挽留效果。6. 验证决策树排序的稳定性与单规则命中率模型上线前我最后做三件小事验证特征重要性稳定、计算单规则命中率、监控线上分数漂移。第一个技巧是用不同随机种子多训练几次看决策树排序出的头部特征是否一致。def top_features(seed): clf DecisionTreeClassifier( max_depth4, min_samples_leaf100, random_stateseed ).fit(X_train, y_train) imp pd.Series(clf.feature_importances_, indexfeature_cols) return imp.sort_values(ascendingFalse).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%说明用户近期行为发生了结构性变化需要重新训练或排查上游特征数据。这套检验配合决策树排序稳定性的结果就能在模型悄悄失效前发现预警信号。本文还有配套的精品资源点击获取
返回列表