
简介针对客户流失预测的Python课程期末实验报告内容围绕电信用户流失数据共7044条记录、20个字段展开系统比较随机森林、支持向量机、逻辑回归、K近邻、朴素贝叶斯等多种机器学习算法的效果适合初学者参考也可作为高校期末报告模板。压缩包内共1个文件为doc格式文档大小1.42MB便于直接阅读和修改。这份报告已吸引1465人学习下载。报告包含实验目的、运行环境、各字段含义说明、缺失值处理、数据标准化、特征工程、模型训练与评估指标准确率、召回率、F1值等完整流程从导入数据到结论均有清晰步骤和对应代码并对比多个模型后给出最优选择读者据此可快速复现客户流失预测模型同时借鉴其报告结构完成自己的课程设计或期末作业。1. 客户流失预测模型为什么业务方总说“预测得不准”客户流失预测模型在简历上看起来是一行漂亮的项目经历落到真实业务里却是另一回事。我接过一个订阅制 SaaS 的项目Python 建模跑到测试集 AUC 0.85上线后却被销售骂了半个月——模型报“高危”的客户电话打过去人家刚续完费反而漏掉的名单里出了大客户流失。问题不在算法而在标签定义、特征窗口和评估方式没对齐业务。这篇文章想讲透的不只是怎么用 Python 建模而是从数据准备、特征工程、模型选型、阈值设置到避坑排查的完整落地路径。每一步都给可复现的代码、参数和踩坑记录适合刚入行的数据分析师也适合想把模型从“跑得通”推到“用得上”的工程师。2. 数据准备用 Python 把客户行为数据变成可训练的特征矩阵2.1 流失标签怎么定先明确“流失”再写代码建模的第一步不是找算法是定标签。很多项目翻车就是在这里业务说“流失就是客户不来了”但“不来了”是三天不登录还是三十天不登录是到期未续费还是长期零消费不同定义训练出来的模型完全不是一个东西。我一般会先和业务确认一个可量化的口径订阅制产品用“到期后 N 天未续费”非订阅制用“连续 N 个月无消费或无活跃记录”。N 的取值要参考业务周期不能拍脑袋。定好口径后用 Python 构造标签。常见做法是选一个观察时间点看该时间点之后的一段窗口内客户有没有发生“续费”或“活跃”行为# 定义流失标签观察点为 2023-01-31未来 60 天内没有续费记录视为流失 import pandas as pd # 假设 subscriptions 表字段user_id, renew_date, amount # 先取每个用户最后一次续费日期 last_renew subscriptions.groupby(user_id)[renew_date].max().reset_index() obs_date pd.Timestamp(2023-01-31) # 观察点之后 60 天仍未续费 流失 last_renew[churn] ( last_renew[renew_date] obs_date pd.Timedelta(days60) ).astype(int)这段代码的核心逻辑是“未来发生的事决定标签”但注意陷阱特征只能使用观察点之前的数据标签却要看观察点之后的数据。这就是时间窗口的错位设计也是防止数据泄漏的第一道闸门。churn列里 1 表示流失0 表示留存。60这个参数要按业务调月付产品我一般用 30 或 60 天年付产品要用 90 甚至 365 天否则会把正常续费周期内的客户误标成流失模型学出来的规律全是错的。2.2 特征工程的四个方向RFM、使用频率、服务交互、账户属性标签定了特征决定模型上限。流失预测的特征没有统一公式但按方向梳基本逃不出这四类RFM 价值特征、使用行为特征、服务交互特征、账户属性特征。RFM 是三个字段的组合——最近一次消费距今多少天Recency、一段时间内消费了多少次Frequency、累计消费金额Monetary它在流失预测里几乎必做因为这三个数字直接指向客户粘性和价值。用 Python 聚合特征时最忌讳的是拿全量历史算。比如今天建模却用了上周的数据算“最近活跃”这就是把未来信息塞进了样本。我一般会统一把特征计算截止到观察日# 从使用记录聚合 RFM 特征只使用 obs_date 之前的数据 usage usage[usage[use_date] obs_date].copy() rfm usage.groupby(user_id).agg( recency(use_date, lambda x: (obs_date - x.max()).days), frequency(use_date, count), monetary(amount, sum) ).reset_index() # 服务交互特征过去 30 天工单或客服会话次数 tickets tickets[tickets[created_at] obs_date].copy() ticket_cnt tickets.groupby(user_id).agg( ticket_30d(ticket_id, lambda x: (x obs_date - pd.Timedelta(days30)).sum()) ).reset_index() # 账户属性特征注册时长、套餐类型、行业 acct accounts[[user_id, signup_date, plan_type, industry]].copy() acct[tenure_days] (obs_date - acct[signup_date]).dt.daysrecency的计算用的是(obs_date - x.max()).days意思是“观察日距离最近一次使用的天数”天数越大客户越久没动流失风险越高。frequency和monetary直接反应使用强度。这里有个细节如果客户在同一窗口期内已经被标记为流失他的recency可能因为窗口截断而失真所以特征聚合之后要做一次分布检查看有没有极端值。四类特征全部聚合完后合并成一张宽表索引是user_id列是特征最后一列是标签。合并时用outer连接注意处理那些有账户但没有使用记录的客户——他们往往就是潜在的流失对象不能直接丢掉。features acct.merge(rfm, onuser_id, howleft) features features.merge(ticket_cnt, onuser_id, howleft) features features.merge(last_renew[[user_id, churn]], onuser_id, howleft) # 无使用记录的客户填充 0 或较大值 features[recency].fillna(999, inplaceTrue) features[frequency].fillna(0, inplaceTrue) features[monetary].fillna(0, inplaceTrue)2.3 按时间切分数据防止“用未来预测过去”流失预测的样本切分和一般分类问题不一样。用train_test_split(random_state42)随机切分短期内看起来效果不错实则是把同一时期的客户同时放进了训练集和测试集模型在测试集上的表现虚高。流失预测的生产场景永远是“用过去预测未来”所以必须按时间切分前 12 个月训练后 3 个月验证。# 按观察月份切分保证训练集的时间严格早于测试集 features[obs_month] features[obs_date].dt.to_period(M) train_feats features[features[obs_month] 2023-02] test_feats features[features[obs_month] 2023-02] X_train train_feats.drop(columns[user_id, churn, obs_month, obs_date]) y_train train_feats[churn] X_test test_feats.drop(columns[user_id, churn, obs_month, obs_date]) y_test test_feats[churn]这里如果把train_test_split换成时间切分模型的 AUC 通常会掉 3 到 5 个百分点属于正常现象。掉得比这多说明特征里混进了未来信息或者业务本身随季节波动大。按时间切分后还要再做一个动作检查训练集和测试集的标签比例。如果两个集合的流失率差异超过两倍说明业务发生了结构变化比如产品改版、价格调整需要先做时间衰减采样或重新选择时间窗口否则模型上线第一天就会水土不服。3. 模型选型与训练从逻辑回归到 LightGBM 的落地选择3.1 为什么先跑逻辑回归而不是直接上 LightGBM数据准备工作完成后很多人习惯直接上 XGBoost 或 LightGBM理由是树模型精度高。但如果这是企业里的第一个流失模型我的建议是先跑逻辑回归。原因有三个第一逻辑回归训练快几分钟能迭代一轮适合验证特征工程是否正确第二逻辑回归的系数可以直接解释能拿给业务看“哪个特征在驱动流失”方便建立信任第三树模型在特征数量少、样本量几千到几万的流失场景里精度优势其实有限反而更容易过拟合。逻辑回归不是用来当最终模型的而是当基线。它的作用是在你花费大量时间调 LightGBM 参数之前先用它确认特征方向和标签定义有没有硬伤。我见过一个项目逻辑回归 AUC 只有 0.6换了 LightGBM 刷到 0.75看起来是模型进步了实际上是逻辑回归对极端值敏感RFM 里有几个客户消费金额是正常值的五十倍归一化之后把梯度带偏了。所以先看特征分布再谈模型选型。3.2 用 Python 训练基线模型Pipeline 与关键参数逻辑回归对缺失值和量纲敏感所以要把缺失值填充、标准化、训练三个步骤包进同一个 Pipeline。这样做的好处是预测阶段对线上数据执行同样的处理流程不会出现训练和推理两套代码的荒谬问题。from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression pipe_lr Pipeline([ (imputer, SimpleImputer(strategymedian)), (scaler, StandardScaler()), (clf, LogisticRegression(class_weightbalanced, max_iter1000, C0.5)) ]) pipe_lr.fit(X_train, y_train) y_prob_lr pipe_lr.predict_proba(X_test)[:, 1]class_weightbalanced是流失预测里必设的参数。流失样本在数据集里通常只占 10% 到 20%不处理的话逻辑回归会倾向把所有客户预测成“不流失”因为这样整体准确率最高。C0.5是正则化强度的倒数值越小正则越强。流失预测的特征维度不高正则太强容易欠拟合太弱又容易过拟合一般从 1.0 开始调每次减半试到 0.1 左右。max_iter1000是因为数据经过标准化后通常几百次迭代能收敛但如果特征之间存在严重共线性比如频次和金额高度相关需要加大迭代次数或先做特征去重。跑完基线后打印逻辑回归系数看一眼。系数最大的几个特征如果全是业务常识之外的字段比如“用户 ID 的哈希值”这种先怀疑数据错误不要怀疑业务。3.3 树模型的增益与代价LightGBM 的调参顺序基线确认无误后再上 LightGBM。它处理非线性关系、特征交互、缺失值的能力都比逻辑回归强在客户流失这类结构化数据场景里几乎是我的第一选择。但 LightGBM 的坑在于参数多新手最爱一上来把n_estimators调到几千然后看着训练集 AUC 0.99 沾沾自喜测试集一测直接崩掉。import lightgbm as lgb model_lgb lgb.LGBMClassifier( n_estimators300, learning_rate0.05, num_leaves31, max_depth5, min_child_samples20, subsample0.8, colsample_bytree0.8, class_weightbalanced, random_state42, verbose-1 ) model_lgb.fit( X_train, y_train, eval_set[(X_test, y_test)], callbacks[lgb.early_stopping(stopping_rounds50)] ) y_prob_lgb model_lgb.predict_proba(X_test)[:, 1]说几个参数在我实际调参时的影响learning_rate先定在 0.05如果追求精度可以调到 0.01但训练轮数要翻倍。num_leaves是 LightGBM 最重要的参数它控制树的复杂度流失预测特征量不多时 31 是安全起点最大不要超过2^max_depth否则容易过拟合。min_child_samples用于限制叶子节点最少样本数流失场景样本少我一般调到 20 到 50防的是叶子节点里只有几条样本学出来的规律全是噪声。subsample和colsample_bytree是行采样和列采样0.8 是常用值如果验证集波动大可以降到 0.7。early_stopping是保命用的模型在验证集上连续 50 轮不提升就停省得手动找最佳轮数。调参顺序也有讲究。先固定learning_rate和n_estimators调num_leaves和max_depth看验证集 AUC 是否提升稳定后再降min_child_samples或加subsample防过拟合。不要在第一步就同时动五个参数否则改崩了都不知道是哪个参数闯的祸。4. 评估与阈值别盯准确率看召回率和提升度4.1 类别不平衡下准确率的陷阱客户流失数据天然是不平衡的。假设留存率 90%流失率 10%模型把所有客户都预测成“留存”准确率也能到 90%。所以我在项目里从来不看准确率只看 AUC、召回率、精确率和提升度。AUC 衡量的是模型排序能力——流失概率高的客户是否排在前面但它不告诉你阈值设在哪。召回率和精确率是一对矛盾召回率高意味着把更多潜在流失客户找出来了但也会误伤把不会流失的客户名单推给销售精确率高名单干净但会漏掉真正流失的客户。业务场景决定你要偏重哪一边。如果挽留成本低发一张优惠券就能做动作拉高召回如果挽留成本高销售一对一电话拜访拉高精确率。这一点必须提前和业务对齐否则你调出来的模型在技术指标上漂亮业务用起来却想骂人。4.2 用 Python 画 PR 曲线和提升度曲线评估阶段我一般不看 ROC 曲线而看 PR 曲线。原因很简单ROC 曲线在类别不平衡时会给人“模型很好”的假象PR 曲线对正样本的识别能力更敏感。代码也简单from sklearn.metrics import precision_recall_curve, auc precision, recall, _ precision_recall_curve(y_test, y_prob_lgb) pr_auc auc(recall, precision) print(fPR AUC: {pr_auc:.4f})PR 曲线越靠近右上角说明模型在保持高精确率的同时召回率也高。但光看曲线还不够实战里更有价值的是一张提升度表格把测试集客户按预测概率从高到低分成 10 组看每组实际流失客户占比。比如模型预测概率最高的前 10% 客户里实际流失率是整体流失率的 4 倍那这份名单递给销售才有意义。import numpy as np df_test pd.DataFrame({y_true: y_test.values, y_prob: y_prob_lgb}) df_test[decile] pd.qcut(df_test[y_prob], 10, labelsFalse, duplicatesdrop) lift_table df_test.groupby(decile)[y_true].agg([mean, count]).reset_index() lift_table[lift] lift_table[mean] / y_test.mean() print(lift_table.sort_values(decile, ascendingFalse))decile是把预测概率等分成十等份mean是每组的实际流失率lift是实际流失率除以整体流失率。如果第一组的lift在 3 以下说明模型排序能力弱效果可能还不如人工按“最近 30 天未登录”这种规则筛。如果第一组能到 5 以上这份名单就值得交给业务做定向挽留。4.3 阈值怎么定把业务成本写进决策模型输出的是概率业务要的却是“做不做动作”。默认阈值 0.5 在流失场景基本不可用因为流失率本身低模型输出的概率分布整体都偏小0.5 会把几乎所有客户都判定为“留存”。正确的做法是画一张“阈值-收益”曲线把挽留成本和客户价值放进去。from sklearn.metrics import precision_recall_curve precision, recall, thresholds precision_recall_curve(y_test, y_prob_lgb) # 假设挽留一个客户的平均成本 50 元客户平均价值 500 元 cost_retain 50 value_save 500 # 每个阈值下的预期收益 挽留下来的价值 - 挽留成本 # 召回率近似等于“流失客户中被找到的比例”精确率近似“名单里确实流失的比例” benefit recall * value_save - precision * cost_retain best_idx np.argmax(benefit) best_threshold thresholds[best_idx] print(f最优阈值: {best_threshold:.3f})这里的逻辑不复杂召回率高意味着找到了更多流失客户能挽救更多价值精确率高意味着名单准确少花冤枉钱。把value_save和cost_retain换成你们业务的真实数字就能算出理论收益最大的阈值。注意这三组数都不是模型参数是业务参数得找财务或运营确认。5. 避坑客户流失预测常见的 5 个翻车点5.1 现象训练集 AUC 很高上线后完全失效训练集 AUC 0.95测试集 0.88上线后业务反馈名单完全不准。原因最常见的是特征里混进了未来数据。比如用“是否已续费”做特征——这个信息在预测时点根本不存在模型上线后拿不到自然失效。解决的方法是严格做时间切分把特征计算截止到观察日并且用线上真实能拿到的字段反推验证。每次特征表生成后我会开会跟数据工程师过一遍每个字段的逻辑口径看到有“截至今天”这种口味的字段就格外警觉。5.2 现象模型预测“要流失”的客户下个月还在这有两种可能。第一种是标签定义本身有误比如把“到期后 30 天未续费”定义为流失但年付客户的续费周期本来就不是 30 天被模型误判成了高风险。解决方法是把标签定义按套餐类型拆开。第二种是业务侧看到名单后做了挽留动作客户因为优惠被劝住了——这在逻辑回归里叫“干预导致结果改变”不是模型错了而是模型已经改变了业务结果这时候要和业务确认名单下发机制不要干巴巴地看模型“准不准”。5.3 现象模型报告“缺失值被填充后导致分数异常”LightGBM 本身能处理缺失值不需要填充但逻辑回归不行所以 Pipeline 里给逻辑回归做了中位数填充。问题是流失场景里“缺失”本身携带业务含义比如一个客户没有工单记录他不一定是“没有交互”而是“从未使用过服务”这是很强的流失信号。遇到这种情况我会额外加一个“是否有记录”的 0/1 特征而不是简单填充。填充只解决算法崩溃问题特征语义要单独设计。5.4 现象流失样本太少模型学不到东西有些小型业务一个季度的流失客户就两三百个模型学不出来。常见做法是下调观察窗口把月粒度数据改成周粒度让每期样本增加或者做样本加权给近期样本更高的权重因为老样本的业务环境可能已经变了。还有一个容易忽略的思路不一定要用监督学习。可以先跑一个无监督的客户分群把“近期活跃度骤降但还没流失”的群体标记出来作为风险名单的补充和模型结果交叉使用。5.5 现象销售说名单没法用模型输出和业务动作脱节这是最容易被忽视的一类问题。模型输出 80% 流失概率销售问“所以呢我该说什么话术客户为什么可能要流失”所以建模不是到predict_proba就结束了。我会在模型最后加一层规则解释器把 SHAP 值最大的特征翻译成人话比如“这个客户连续 45 天没有登录且账单金额环比下降 60%可能对调价不满”。名单上每一条都要能看懂、能回应业务才愿意用。6. 进阶用 SHAP 把模型输出变成业务能用的行动清单6.1 SHAP 值怎么读模型上线后业务方问得最多的不是“哪个模型”而是“为什么名单里有他”。LightGBM 这类树模型是典型黑匣子但 Shapley 值可以给每个客户算出每个特征对预测结果的贡献度。SHAP 值的核心概念是把预测概率拆解成一个个特征的贡献之和正值把预测推向流失负值推向留存。import shap explainer shap.TreeExplainer(model_lgb) shap_values explainer.shap_values(X_test) shap.summary_plot(shap_values, X_test, max_display10)summary_plot输出的是全样本的全局解释红色表示该特征取值高蓝色表示低。一眼看去如果recency的红色带子全挤在流失概率高的一侧说明“最近没使用”确实是流失的高危信号。max_display10控制显示的 Top 特征数特征太多了图看不明白十个足够用于业务沟通。6.2 把预测结果落到客户运营动作上全局解释用于汇报落地还要落到单个客户。我会给模型输出表加两列Top1 风险原因和 Top2 风险原因。做法是对每个客户取 SHAP 值绝对值最大的两个特征再根据特征取值生成描述文本# 对每个客户生成可读的风险原因 risk_features [recency, frequency, monetary, ticket_30d, tenure_days] # 创建一个客户级 SHAP 贡献矩阵 shap_df pd.DataFrame(shap_values, columnsX_test.columns, indexX_test.index) def gen_reason(row_idx, n2): vals shap_df.loc[row_idx].abs().sort_values(ascendingFalse) top_cols vals.head(n).index reasons [] for col in top_cols: direction 偏高 if shap_df.loc[row_idx, col] 0 else 偏低 reasons.append(f{col} 数值{direction}) return ; .join(reasons) X_test[risk_reason] [gen_reason(i) for i in range(len(X_test))] X_test[risk_score] y_prob_lgb X_test[[risk_score, risk_reason]].head(20)这段代码把黑匣子变成了销售能直接读的一句话比如recency 数值偏高; frequency 数值偏低翻译过来就是“很久没用了用得也不勤”。业务拿到这份名单至少知道电话里第一句该聊什么。我自己做项目时会额外要求名单里加一列“建议动作”按分数阈值分成高风险的“立即电话挽留”、中风险的“发优惠券触达”、低风险的“日常维系”这样模型就从“预测工具”变成了“运营策略的一部分”。回顾这几年做过的流失项目最大的教训就是模型永远只是决策支持不是决策本身。阈值、标签、特征都要回到业务成本上去对齐技术指标再漂亮业务用不上就是白做。如果你正打算在自己的数据上跑这个流程别一开始就追求完美模型先把基线模型、时间切分和业务指标体系做扎实。希望帮到你。本文还有配套的精品资源点击获取