资讯详情

资讯详情

客户流失判断数据建模实战:从逻辑回归到LightGBM与SHAP可解释性

1. 客户流失判断项目的整体设计思路1.1 为什么客户流失判断值得单独做一个数据建模项目客户流失判断说白了就是回答一个很朴素的问题哪些客户快要走了这个问题在几乎所有有订阅制、会员制或者持续性服务关系的行业里都存在。比如SaaS产品、在线教育、健身工作室、云服务、内容付费平台甚至传统行业的长期供货合同都有“客户悄悄不续费了”这件事。我做过好几个和客户流失相关的项目最大的感受是这件事的难点从来不在模型本身而在于你怎么定义“流失”以及你手上到底有哪些能用的数据。很多新手一上来就想着用XGBoost还是LightGBM结果数据标签都没搞干净跑出来的AUC看着不错一上线发现完全没法用。这个项目的核心目标可以拆成三层第一层描述性分析搞清楚当前流失率是多少流失客户有什么共同特征。第二层预测性建模给定一个客户当前的状态预测他在未来某个时间窗口内流失的概率。第三层可解释与行动不光要给概率还要告诉业务方“为什么这个客户会走”以及“应该对谁做什么动作”。适合看这个内容的人我觉得有三类一是刚接触数据建模、想找一个完整项目练手的同学二是业务侧的产品或运营想理解流失模型到底能做什么、不能做什么三是已经做过一些模型、但想把整个流程做得更规范的中级选手。1.2 整体方案选型为什么我倾向于“逻辑回归打底 树模型提效 可解释性收尾”在方案设计上我踩过的一个大坑是一开始就追求高精度直接上复杂模型结果业务方问“这个客户为什么被判定为高流失风险”我答不上来。后来我调整了策略形成了一套比较稳的组合拳。第一步用逻辑回归做基线。逻辑回归的好处是系数直接可解释你能清楚看到“近30天登录次数每减少1次流失 odds 增加多少”。这对后续和业务方沟通极其重要。而且逻辑回归对特征工程的要求比较明确能帮你快速发现数据里的问题。第二步用梯度提升树比如LightGBM或XGBoost做效果提升。树模型能自动捕捉非线性关系和特征交互比如“登录次数少且客单价高”的组合风险逻辑回归需要手动构造交叉特征树模型可以自己学。实测下来在同样的特征集上树模型比逻辑回归的AUC通常能高3到8个百分点。第三步用SHAP或特征重要性做可解释性收尾。树模型虽然准但黑盒感强。SHAP值可以给出每个样本每个特征的贡献度这样你就能告诉业务方“这个客户流失风险高主要是因为最近两周活跃度骤降其次是工单投诉次数增加。”这套组合的逻辑是先保证能解释再追求准最后把准的部分也解释清楚。而不是反过来。1.3 项目整体流程图文字版整个项目我一般分成六个阶段每个阶段都有明确的交付物业务理解与标签定义和业务方对齐“流失”的口径确定观察窗口和预测窗口。数据采集与清洗从用户表、行为表、交易表、工单表中抽取数据处理缺失值和异常值。特征工程构造RFM类特征、趋势类特征、交互类特征。模型训练与评估逻辑回归基线树模型调优交叉验证。可解释性分析SHAP值、特征重要性、单样本解释。输出与落地输出客户流失概率列表给出分层运营建议。下面我会逐个阶段展开把每个环节的细节、坑和实操技巧都讲清楚。2. 核心细节解析与实操要点2.1 流失标签的定义整个项目最容易被忽视但最关键的一步我见过太多项目模型跑得飞起结果一问“流失是怎么定义的”回答是“就是客户不活跃了”。这种模糊定义会导致后面所有工作都白做。流失标签的定义需要明确三个要素观察窗口Observation Window你用什么时间段的数据来构造特征。比如用2024年1月1日到2024年3月31日的行为数据。预测窗口Prediction Window你预测未来多长时间内会流失。比如预测客户在2024年4月1日到2024年6月30日之间是否会流失。流失判定规则在预测窗口内满足什么条件算流失。比如“连续60天没有登录且没有续费”或者“订阅到期后30天内未续费”。这三个要素确定后你的数据集才能被正确切分。我一般会做一个时间轴图来和业务方确认确保双方理解一致。注意观察窗口和预测窗口不能重叠否则会造成数据泄露。比如你用4月的数据预测4月的流失那模型学到的就是“已经流失的人不活跃”而不是“不活跃的人会流失”。2.2 数据采集需要哪些表每张表的关键字段是什么客户流失判断通常需要以下几类数据数据类别典型表名关键字段用途客户基础信息用户表注册时间、渠道、套餐类型、客户等级构造静态特征行为数据登录日志、点击流登录时间、功能使用记录、会话时长构造活跃度特征交易数据订单表、续费表订单金额、购买频次、最近购买时间构造RFM特征服务数据工单表、投诉表工单类型、处理时长、满意度构造体验特征营销数据触达表、优惠券表触达次数、点击率、核销率构造互动特征在实际操作中我建议先用SQL把这几张表按客户ID和时间聚合到“客户-观察窗口”粒度。比如每个客户在观察窗口内的登录次数、平均会话时长、订单总金额、工单数量等。一个常见的坑是行为表的数据量可能非常大直接全量拉取会拖垮数据库。我的做法是先用采样或者预聚合的方式把每天的数据汇总成客户日粒度再基于日粒度做窗口聚合。这样数据量能减少一到两个数量级。2.3 特征工程从原始字段到模型可用特征的完整思路特征工程是客户流失模型里最耗时间但也最出效果的部分。我一般把特征分成四大类第一类RFM类特征。这是最经典的客户价值特征Recency最近一次登录/购买距观察窗口结束的天数。Frequency观察窗口内的登录次数/购买次数。Monetary观察窗口内的总消费金额/平均客单价。第二类趋势类特征。这类特征对流失预测特别有用因为流失往往是一个“走下坡路”的过程最近7天登录次数 vs 前7天登录次数变化率。最近30天消费金额 vs 前30天消费金额变化率。活跃天数的标准差波动越大风险越高。第三类交互类特征。比如登录次数 × 平均会话时长。工单数量 / 登录次数投诉密度。优惠券核销率 × 最近购买间隔。第四类静态特征。注册渠道、套餐类型、客户等级等。这类特征虽然不随时间变化但在冷启动阶段很有用。实操心得趋势类特征的计算一定要小心时间边界。比如“最近7天”是相对于观察窗口结束日而言的不是相对于今天。我一般会在SQL里用date_sub(window_end, 6)这样的方式明确边界避免歧义。2.4 样本不平衡处理流失客户通常只占5%到20%客户流失是一个典型的类别不平衡问题。如果流失率是10%你直接训练模型模型可能会倾向于把所有客户都预测为“不流失”因为这样准确率也有90%。但这显然没有意义。我常用的处理方式有三种调整类别权重在逻辑回归里用class_weightbalanced在树模型里用scale_pos_weight。这是最简单也最推荐的方式因为它不改变原始数据分布。过采样少数类比如SMOTE。但我个人不太喜欢SMOTE因为它会生成合成样本在业务解释上容易出问题。如果要用一定要在交叉验证的每一折内部做而不是先过采样再切分。欠采样多数类适合数据量特别大的情况但会丢失信息。实测下来调整类别权重 选择合适的评估指标是最稳的组合。评估指标我一般看AUC、KS和RecallTopK而不是准确率。2.5 模型评估为什么准确率会骗人应该看什么在流失预测里准确率几乎是最没用的指标。假设100个客户里只有10个会流失你全预测不流失准确率90%但一个流失客户都没抓到。我通常看这几个指标指标含义为什么重要AUC模型区分正负样本的整体能力不受阈值影响适合比较模型KS最大区分度风控和流失领域常用直观RecallTopK预测概率最高的K个客户中实际流失的比例直接对应运营动作的覆盖面LiftTopK相比随机抽取TopK的流失率提升倍数业务方最容易理解比如业务方说“我一个月只能触达500个客户”那你就看Top500的Recall和Lift。如果Top500能覆盖60%的流失客户Lift是6倍那这个模型就很有价值。3. 实操过程与核心环节实现3.1 数据准备与SQL聚合的完整示例假设我们有一张用户行为表user_behavior字段包括user_id、action_date、login_count、session_duration。我们要构造每个客户在观察窗口内的聚合特征。-- 观察窗口2024-01-01 到 2024-03-31 -- 预测窗口2024-04-01 到 2024-06-30 -- 流失定义预测窗口内连续60天未登录 WITH observation_agg AS ( SELECT user_id, SUM(login_count) AS total_login, AVG(session_duration) AS avg_session_duration, COUNT(DISTINCT action_date) AS active_days, -- 最近7天登录次数 SUM(CASE WHEN action_date 2024-03-25 THEN login_count ELSE 0 END) AS login_last_7d, -- 前7天登录次数 SUM(CASE WHEN action_date BETWEEN 2024-03-18 AND 2024-03-24 THEN login_count ELSE 0 END) AS login_prev_7d, -- 最近一次登录距窗口结束的天数 DATEDIFF(2024-03-31, MAX(action_date)) AS recency_days FROM user_behavior WHERE action_date BETWEEN 2024-01-01 AND 2024-03-31 GROUP BY user_id ), label AS ( SELECT user_id, CASE WHEN DATEDIFF(2024-06-30, MAX(action_date)) 60 THEN 1 ELSE 0 END AS is_churn FROM user_behavior WHERE action_date BETWEEN 2024-04-01 AND 2024-06-30 GROUP BY user_id ) SELECT o.*, COALESCE(l.is_churn, 1) AS is_churn -- 预测窗口内完全没有行为的也视为流失 FROM observation_agg o LEFT JOIN label l ON o.user_id l.user_id;这段SQL的核心逻辑是先在观察窗口内做聚合再在预测窗口内判断是否流失最后按user_id拼接。注意COALESCE(l.is_churn, 1)这个处理因为有些客户在预测窗口内完全没有行为LEFT JOIN后会是NULL这些客户大概率是流失了。3.2 特征构造的Python实现拿到聚合后的数据接下来用Python做特征衍生和清洗。import pandas as pd import numpy as np df pd.read_csv(churn_dataset.csv) # 趋势特征最近7天登录变化率 df[login_change_rate] ( df[login_last_7d] - df[login_prev_7d] ) / (df[login_prev_7d] 1) # 加1避免除零 # 活跃度特征 df[login_per_active_day] df[total_login] / (df[active_days] 1) # 会话质量特征 df[session_per_login] df[avg_session_duration] / (df[total_login] 1) # 处理无穷值 df.replace([np.inf, -np.inf], np.nan, inplaceTrue) # 缺失值填充数值型用中位数类别型用众数 num_cols df.select_dtypes(include[np.number]).columns for col in num_cols: df[col].fillna(df[col].median(), inplaceTrue) # 类别型特征做One-Hot编码 df pd.get_dummies(df, columns[channel, plan_type], drop_firstTrue)这里有几个细节值得说除零问题构造比率特征时分母加1是最简单的处理方式。也可以用np.where做条件判断但加1更简洁。无穷值处理除以0会产生inf必须替换成NaN再填充否则模型会报错。缺失值填充我一般先用中位数填充数值型因为中位数对异常值不敏感。但更好的做法是加一个“是否缺失”的指示特征因为缺失本身可能就有信息量。3.3 模型训练逻辑回归基线与LightGBM调优逻辑回归基线from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.metrics import roc_auc_score, classification_report X df.drop([user_id, is_churn], axis1) y df[is_churn] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) lr LogisticRegression(class_weightbalanced, max_iter1000, random_state42) lr.fit(X_train_scaled, y_train) y_pred_proba lr.predict_proba(X_test_scaled)[:, 1] print(LR AUC:, roc_auc_score(y_test, y_pred_proba))逻辑回归的系数可以直接看coef_df pd.DataFrame({ feature: X.columns, coef: lr.coef_[0] }).sort_values(coef, ascendingFalse) print(coef_df.head(10)) print(coef_df.tail(10))正系数表示该特征增加会提高流失概率负系数表示降低流失概率。比如recency_days的系数通常是正的说明越久没登录越容易流失。LightGBM调优import lightgbm as lgb from sklearn.model_selection import StratifiedKFold params { objective: binary, metric: auc, boosting_type: gbdt, learning_rate: 0.05, num_leaves: 31, max_depth: 6, min_child_samples: 50, scale_pos_weight: (y_train 0).sum() / (y_train 1).sum(), subsample: 0.8, colsample_bytree: 0.8, reg_alpha: 0.1, reg_lambda: 0.1, random_state: 42 } skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) auc_scores [] for train_idx, val_idx in skf.split(X_train, y_train): X_tr, X_val X_train.iloc[train_idx], X_train.iloc[val_idx] y_tr, y_val y_train.iloc[train_idx], y_train.iloc[val_idx] dtrain lgb.Dataset(X_tr, labely_tr) dval lgb.Dataset(X_val, labely_val, referencedtrain) model lgb.train( params, dtrain, num_boost_round1000, valid_sets[dval], callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)] ) y_val_pred model.predict(X_val, num_iterationmodel.best_iteration) auc_scores.append(roc_auc_score(y_val, y_val_pred)) print(LightGBM CV AUC:, np.mean(auc_scores), -, np.std(auc_scores))这里的关键参数解释scale_pos_weight处理类别不平衡设置为负样本数除以正样本数。learning_rate和num_boost_round学习率小一点迭代次数多一点通常效果更稳。我用0.05的学习率配合early stopping。min_child_samples叶子节点最小样本数设大一点可以防止过拟合尤其是流失预测这种噪声比较多的场景。subsample和colsample_bytree行采样和列采样增加模型泛化能力。3.4 模型可解释性SHAP值分析import shap explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_test) # 全局特征重要性 shap.summary_plot(shap_values, X_test, plot_typebar) # 单样本解释 shap.force_plot( explainer.expected_value, shap_values[0, :], X_test.iloc[0, :], matplotlibTrue )SHAP值的好处是它不仅能告诉你哪些特征重要还能告诉你每个特征对每个样本的预测是正向还是负向贡献。比如某个客户被判定为高流失风险SHAP图会显示主要是recency_days高和login_change_rate低导致的。实操心得SHAP在树模型上的计算量比较大如果测试集有几十万条建议先采样几千条做全局分析单样本解释再单独算。另外SHAP的force_plot在Jupyter里交互效果很好但导出成静态图会有点乱建议用waterfall_plot做单样本展示。4. 常见问题与排查技巧实录4.1 模型AUC很高但业务方不买账问题出在哪这是我最常遇到的情况。AUC 0.85看起来不错但业务方说“你推荐的Top100客户我打电话过去人家明明很活跃”。排查下来通常有几个原因原因一标签定义和业务感知不一致。比如你用“60天未登录”定义流失但业务方认为“30天没下单”就是流失。这种情况下模型学到的和你实际想预测的不是一回事。解决办法是重新对齐标签定义最好让业务方参与标注一批样本看看他们的判断和你的标签重合度有多高。原因二特征里有“未来信息”泄露。比如你不小心把预测窗口内的某个字段带进了特征模型就会“作弊”。排查方法是检查所有特征的计算时间范围确保它们都只用了观察窗口内的数据。原因三评估指标选错了。AUC高不代表TopK的精度高。如果业务方只关心Top100那你就应该看Top100的Recall和Lift。我一般会做一个累积增益图Cumulative Gain Chart直观展示TopK的效果。4.2 特征重要性排名和业务直觉相反怎么处理有时候模型告诉你“工单数量”是最重要的特征但业务方觉得“登录频率”才是关键。这种情况不一定是模型错了可能是特征相关性工单数量和登录频率可能高度相关模型只是选了其中一个。可以用相关性矩阵检查一下。非线性关系登录频率和流失可能是U型关系极高和极低都容易流失。树模型能捕捉到但特征重要性只反映整体贡献。数据偏差如果工单数据只覆盖了一部分客户那它的重要性可能被高估。我的处理方式是把SHAP的依赖图Dependence Plot画出来看看特征和预测值的关系是不是单调的。如果不是就和业务方解释“这个特征的影响不是简单的越多越好”。4.3 常见问题速查表问题现象可能原因排查方法解决方案模型AUC低于0.7特征区分度不够检查单特征AUC增加趋势类特征、交互特征训练集AUC高测试集AUC低过拟合对比训练/测试曲线增加正则化、减少特征、增加样本预测概率集中在0.5附近特征尺度差异大检查特征分布标准化、归一化TopK的Lift很低标签定义模糊重新对齐业务口径调整流失定义SHAP计算超时测试集太大检查数据量采样计算某些客户预测概率为NaN特征有缺失或无穷值检查数据清洗步骤填充缺失、替换无穷值4.4 几个我踩过的坑和对应的避坑技巧坑一用未来数据构造特征。有一次我做了一个“最近30天平均订单金额”的特征结果发现这个“最近30天”是相对于数据拉取日而不是观察窗口结束日。这导致模型用到了预测窗口的信息AUC虚高到0.95上线后直接崩了。避坑技巧所有时间窗口的边界都必须显式指定最好在SQL里用常量定义不要用CURRENT_DATE。坑二忽略客户生命周期阶段。新注册客户和老客户的流失模式完全不同。新客户可能因为“没找到使用场景”而流失老客户可能因为“需求变化”而流失。如果混在一起建模模型会很难学。避坑技巧可以按客户生命周期分段建模或者在特征里加入“注册天数”和“历史流失次数”。坑三模型更新频率太低。客户行为模式会随时间变化半年前训练的模型可能已经不准了。避坑技巧建立定期重训机制比如每月或每季度重新训练一次同时监控模型在线上的AUC和KS变化。坑四只给概率不给行动建议。业务方拿到一个流失概率列表不知道该怎么办。避坑技巧在输出里加上“主要流失原因”和“建议动作”。比如“该客户因活跃度下降被判定为高风险建议发送召回优惠券”。5. 从模型输出到业务落地的最后一公里5.1 客户分层与运营策略的对应关系模型输出的是每个客户的流失概率但业务方需要的是可执行的策略。我一般会把客户分成四层风险等级流失概率范围客户特征建议动作高风险 0.7活跃度骤降、近期无交易人工外呼、大额优惠券中高风险0.4 - 0.7活跃度下降、有投诉记录自动化触达、中等优惠中低风险0.2 - 0.4活跃度平稳、偶尔互动内容推荐、会员权益提醒低风险 0.2活跃度高、交易频繁维护关系、交叉销售这个分层不是固定的需要根据业务方的触达能力和成本来调整。比如如果人工外呼团队只有5个人那高风险客户可能只能覆盖Top200这时候就要看Top200的Recall够不够。5.2 模型监控与迭代上线后要看哪些指标模型上线不是终点而是起点。我一般会监控以下几类指标模型稳定性指标PSIPopulation Stability Index监控特征分布和预测分布是否发生漂移。PSI大于0.2就要警惕大于0.5建议重训。模型效果指标定期回捞预测窗口结束的客户计算实际流失率和预测流失率的差异。如果差异持续扩大说明模型需要更新。业务指标触达后的转化率、召回成本、客户生命周期价值变化。这些指标虽然不直接反映模型好坏但能说明模型是否产生了业务价值。5.3 一个完整的输出示例假设模型跑完我一般会输出这样一张表给业务方user_idchurn_probrisk_leveltop_reasonsuggested_actionU0010.89高风险最近30天登录下降80%人工外呼50元券U0020.72高风险工单投诉2次未解决客服优先回访U0030.55中高风险最近购买间隔超过60天自动化邮件20元券U0040.31中低风险会话时长下降30%内容推荐U0050.08低风险活跃度稳定交叉销售推荐这张表的好处是业务方不需要理解模型细节直接看risk_level和suggested_action就能执行。top_reason则给了他们和客户沟通时的切入点。5.4 关于特征工程的一些进阶思路如果你已经把基础特征做完了想进一步提升模型效果可以尝试以下几个方向方向一序列特征。把客户的行为序列当成文本用RNN或Transformer提取特征。比如把最近30天的每日登录次数当成一个序列用LSTM编码成一个向量。这个方法效果通常不错但工程复杂度高适合数据量大的场景。方向二图特征。如果客户之间有社交关系或推荐关系可以构造图神经网络特征。比如一个客户的邻居流失率很高那他自己流失的概率也会增加。方向三外部数据。比如客户的行业景气度、地区经济指标等。这类数据获取成本高但在B2B场景下可能很有用。方向四自动化特征工程。比如Featuretools可以自动生成聚合和转换特征。适合快速迭代但生成的特征太多需要做特征选择。我个人在实际操作中的体会是基础特征做扎实比追求花哨的特征更重要。很多项目把RFM和趋势特征做好AUC就能到0.8以上再往上提升的边际收益递减不如把精力放在业务落地和模型监控上。最后再分享一个小技巧如果你不确定某个特征该不该加可以先单独用这个特征训练一个逻辑回归看它的单特征AUC。如果单特征AUC低于0.55那它单独作用很弱但可能和其他特征组合后有用。如果单特征AUC高于0.7那它很可能是强特征要重点检查有没有数据泄露。这个快速筛查方法帮我省了很多时间。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →