5.6.3 项目二:客户流失预测(分类问题)

| 信息 | 说明 |
|---|---|
| 任务类型 | 二分类(流失/留存) |
| 核心挑战 | 数据不平衡(流失客户远少于留存) |
| 评估指标 | F1、AUC、召回率 |
| 涉及技能 | 不平衡处理、Pipeline、业务分析 |
读代码前先认识几个关键术语
Section titled “读代码前先认识几个关键术语”- Recall(召回率):真正会流失的客户里,我们抓住了多少。业务上如果特别怕漏掉流失客户,就要重点看它。
- Precision(精确率):被模型标成高风险的客户里,真正会流失的占多少。保留动作成本很高时,它很重要。
- F1:精确率和召回率的调和平均数。它适合做一个平衡指标,但会隐藏具体业务取舍。
- ROC(Receiver Operating Characteristic,受试者工作特征曲线):展示阈值变化时,召回率和误报率如何一起变化。
- AUC(Area Under the Curve,曲线下面积):把 ROC 曲线压缩成一个数字。AUC 越高,说明模型越能把流失客户排在留存客户前面。
- SMOTE(Synthetic Minority Over-sampling Technique,合成少数类过采样):为少数类人工合成新样本。它能缓解不平衡,但必须只在训练集或交叉验证训练折里使用。
class_weight(类别权重):不合成新样本,而是让模型更重视少数类错误。- Threshold(阈值):把“流失概率”转成“是否流失”的分界线。降低阈值通常会提高召回率,但也会增加误报。
先说一个很重要的学习预期
Section titled “先说一个很重要的学习预期”这题很容易让新人一上来就掉进“模型比较”里:
- 逻辑回归
- 随机森林
- SMOTE
- AUC
但更适合第一遍先练会的,其实不是谁分数更高,而是:
面对不平衡分类问题时,怎样把业务代价、指标选择、阈值决策和模型结果真正串起来。
只要这条线先立住,这题才会像真实项目,而不是“再做一道分类题”。
先建立一张地图
Section titled “先建立一张地图”这题最有价值的地方,不是“做一个二分类器”,而是第一次真正面对:
- 数据不平衡
- 阈值选择
- 业务代价不同
flowchart LR A["先看类别分布"] --> B["先做 baseline"] B --> C["再做 class_weight / SMOTE"] C --> D["再看 ROC / F1 / Recall"] D --> E["最后结合业务代价调阈值"]
style A fill:#e3f2fd,stroke:#1565c0,color:#333 style E fill:#e8f5e9,stroke:#2e7d32,color:#333所以这题真正练的是“怎么做分类决策”,不是单纯“怎么跑分类模型”。
这题你真正要练什么
Section titled “这题你真正要练什么”这个项目的核心不是“把分类器跑起来”,而是练习:
- 不平衡数据时为什么不能只看准确率
- 怎样在召回率、精确率和业务代价之间取舍
- 怎样把模型结果翻译成业务洞察
这题第一版最该先讲清什么
Section titled “这题第一版最该先讲清什么”第一次做这题时,最该先说明的不是模型名字,而是:
- 流失客户占比多少
- 为什么不能只看准确率
- 如果业务更怕漏掉将流失客户,应该偏向哪个指标
把这三件事说清楚,后面所有模型和阈值选择才有意义。
一个更适合新人的类比
Section titled “一个更适合新人的类比”你可以先把这题想成:
- 在客户真的离开之前,尽早拉一张“高风险名单”
这张名单的价值不在于:
- 每个人都判断得绝对完美
而在于:
- 你能不能在可接受的误报成本下,尽量少漏掉真正会流失的人
这就是为什么这题从一开始就不能只盯着准确率。
推荐推进顺序
Section titled “推荐推进顺序”- 先做一个不处理不平衡的 baseline
- 再做类别权重
- 再尝试 SMOTE 等方法
- 最后再比较 ROC、AUC、F1 和业务解释
这样你才知道“提升”到底来自哪里。
第一次做这题时,最稳的默认顺序
Section titled “第一次做这题时,最稳的默认顺序”如果你第一次做客户流失预测,建议按这个顺序:
- 先把业务目标说清楚
- 先看类别分布
- 先做原始 baseline
- 再做
class_weight - 最后再试 SMOTE
- 再决定阈值应该往召回率还是精确率偏
这样你会更清楚每一步提升到底来自:
- 模型
- 采样
- 还是阈值策略
步骤 1:模拟数据
Section titled “步骤 1:模拟数据”import pandas as pdimport numpy as npfrom sklearn.datasets import make_classification
# 生成不平衡的客户数据X, y = make_classification( n_samples=5000, n_features=15, n_informative=8, n_redundant=3, weights=[0.85, 0.15], # 85% 留存, 15% 流失 random_state=42)
feature_names = ['月消费', '通话时长', '流量使用', '客服通话次数', '合同时长', '账单争议', '套餐等级', '家庭成员数', '在网时长', '上月投诉', '流量超限次数', '国际漫游', '增值服务数', '账户余额', '设备更换']
df = pd.DataFrame(X, columns=feature_names)df['流失'] = y
print(f"数据形状: {df.shape}")print(f"流失比例: {df['流失'].mean():.1%}")print(f"流失客户: {df['流失'].sum()}, 留存客户: {(1-df['流失']).sum():.0f}")步骤 2:不平衡数据处理
Section titled “步骤 2:不平衡数据处理”
先看图里的阈值决策路径:类别不平衡会改变你该信任的指标和 cutoff。
from sklearn.model_selection import train_test_splitfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.metrics import classification_report, roc_auc_scoreimport matplotlib.pyplot as plt
X = df.drop('流失', axis=1)y = df['流失']X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
# 方法1: 类别权重rf_weighted = RandomForestClassifier(n_estimators=100, class_weight='balanced', random_state=42)rf_weighted.fit(X_train, y_train)y_pred = rf_weighted.predict(X_test)
print("带类别权重的随机森林:")print(classification_report(y_test, y_pred, target_names=['留存', '流失']))print(f"AUC: {roc_auc_score(y_test, rf_weighted.predict_proba(X_test)[:,1]):.4f}")这个固定随机种子的示例输出类似这样:
数据形状: (5000, 16)流失比例: 15.3%流失客户: 765, 留存客户: 4235
带类别权重的随机森林: precision recall f1-score support
留存 0.95 1.00 0.97 847 流失 0.97 0.73 0.83 153
accuracy 0.95 1000 macro avg 0.96 0.86 0.90 1000weighted avg 0.96 0.95 0.95 1000
AUC: 0.9681读这段输出时,要和上面的图一起看:accuracy 很高,但真正的业务问题在 流失 这一行。recall=0.73 说明模型仍然漏掉了一部分真实流失客户。如果漏掉流失客户代价更高,下一步不一定是“换更大的模型”,也可能是复查阈值、调整类别权重,或用 SMOTE Pipeline 做对比。
步骤 2.1 为什么不要一开始就上 SMOTE
Section titled “步骤 2.1 为什么不要一开始就上 SMOTE”更稳的顺序通常是:
- 先做原始 baseline
- 再试
class_weight - 最后再试
SMOTE
因为这样你才能分清:
- 提升是来自模型本身
- 还是来自采样策略
- 还是来自阈值调整
SMOTE 过采样
Section titled “SMOTE 过采样”# python -m pip install --upgrade imbalanced-learntry: from imblearn.over_sampling import SMOTE from imblearn.pipeline import Pipeline as ImbPipeline
smote_pipe = ImbPipeline([ ('smote', SMOTE(random_state=42)), ('classifier', RandomForestClassifier(n_estimators=100, random_state=42)), ]) smote_pipe.fit(X_train, y_train) y_pred_smote = smote_pipe.predict(X_test)
print("\nSMOTE + 随机森林:") print(classification_report(y_test, y_pred_smote, target_names=['留存', '流失']))except ImportError: print("请安装 imbalanced-learn: python -m pip install --upgrade imbalanced-learn")步骤 3:特征重要性与业务洞察
Section titled “步骤 3:特征重要性与业务洞察”# 特征重要性importance = rf_weighted.feature_importances_sorted_idx = np.argsort(importance)
plt.figure(figsize=(8, 8))plt.barh(range(len(sorted_idx)), importance[sorted_idx], color='coral')plt.yticks(range(len(sorted_idx)), np.array(feature_names)[sorted_idx])plt.xlabel('特征重要性')plt.title('客户流失预测——特征重要性')plt.grid(axis='x', alpha=0.3)plt.tight_layout()plt.show()
# 业务建议print("\n业务洞察:")top3 = np.array(feature_names)[np.argsort(importance)[-3:]]for i, feat in enumerate(reversed(top3), 1): print(f" {i}. {feat} 对流失预测最重要")步骤 4:ROC 对比
Section titled “步骤 4:ROC 对比”from sklearn.linear_model import LogisticRegressionfrom sklearn.metrics import roc_curve, roc_auc_scorefrom sklearn.preprocessing import StandardScalerfrom sklearn.pipeline import make_pipeline
models = { '逻辑回归': make_pipeline(StandardScaler(), LogisticRegression(class_weight='balanced', max_iter=1000)), '随机森林': RandomForestClassifier(n_estimators=100, class_weight='balanced', random_state=42),}
plt.figure(figsize=(8, 6))for name, model in models.items(): model.fit(X_train, y_train) proba = model.predict_proba(X_test)[:, 1] fpr, tpr, _ = roc_curve(y_test, proba) auc = roc_auc_score(y_test, proba) plt.plot(fpr, tpr, linewidth=2, label=f'{name} (AUC={auc:.4f})')
plt.plot([0, 1], [0, 1], 'k--', alpha=0.5)plt.xlabel('FPR')plt.ylabel('TPR')plt.title('客户流失预测 ROC 对比')plt.legend()plt.grid(True, alpha=0.3)plt.show()步骤 4.1 这一步最值得再补什么
Section titled “步骤 4.1 这一步最值得再补什么”如果想把这题做得更像真实业务项目,最值得补的是:
- 一张混淆矩阵
- 一张阈值 vs Precision / Recall / F1 曲线
- 一段“如果召回率优先,我会把阈值调到哪里”的解释
因为很多真实留存项目,重点不是默认阈值 0.5 下谁分数高,而是:
- 在可接受的误报成本内,尽量多抓高风险客户
项目交付时最好补上的内容
Section titled “项目交付时最好补上的内容”- 一张类别分布图
- 一张混淆矩阵
- 一张 ROC 曲线
- 一段“如果目标是尽量召回流失客户,我会怎么调阈值”的说明
一个更像真实项目的复盘顺序
Section titled “一个更像真实项目的复盘顺序”你可以用下面这个顺序来写项目复盘:
- 数据分布和业务目标
- baseline 模型结果
- 不平衡处理后的变化
- 指标权衡和阈值调整
- 特征重要性和业务建议
- 下一步如果上线,应该怎么监控
如果继续把这个项目往上做,最值得补什么
Section titled “如果继续把这个项目往上做,最值得补什么”更值得优先补的通常是:
- 阈值调优页
- 误判客户案例分析
- 不同业务目标下的指标切换说明
这样项目会从“一个分类任务”变成“一个更像真实业务决策系统”的作品。
做成作品集时,最值得展示什么
Section titled “做成作品集时,最值得展示什么”- 类别分布和任务目标
- baseline 与改进版的对比
- ROC 或 PR 曲线
- 一张阈值解释图
- 一段可执行的客户运营建议
项目检查清单
Section titled “项目检查清单”- 分析数据不平衡程度
- 尝试至少 2 种不平衡处理方法(类别权重、SMOTE)
- 用 F1 和 AUC 评估(不只看准确率)
- 分析特征重要性,给出业务建议
- ROC 曲线多模型对比
项目交付参考与讲解
- 先报告正类比例。如果流失客户很少,accuracy 可能很好看,但模型其实漏掉了大多数流失客户。
- 类别权重和 SMOTE 要在同一个验证方案下比较。如果使用过采样,必须放在训练流程内部,避免泄漏。
- F1 和 AUC 都有价值,但阈值选择要回到业务成本:漏掉流失客户和误报触达哪个代价更高。
- 特征重要性只能作为业务假设起点,不等于因果证明。每个重要特征都要说明它可能对应什么行动。
- 好的交付物应包含类别分布、baseline 对比、ROC 或 PR 曲线、阈值解释、混淆矩阵和留存建议。
版本路线建议
Section titled “版本路线建议”| 版本 | 目标 | 交付重点 |
|---|---|---|
| 基础版 | 跑通最小闭环 | 能输入、能处理、能输出,并保留一组示例 |
| 标准版 | 形成可展示项目 | 增加配置、日志、错误处理、README 和截图 |
| 挑战版 | 接近作品集质量 | 增加评估、对比实验、失败样本分析和下一步路线 |
建议先完成基础版,不要一开始就追求大而全。每提升一个版本,都要把“新增了什么能力、怎么验证、还有什么问题”写进 README。
学完这一页,至少保留这张证据卡:
- 项目目标
- 预测、分割、Kaggle,或端到端 ML 作品集目标
- 流水线
- 数据划分、预处理、模型、评估和报告工件
- 结果
- 指标表、图表、预测、失败样本和 README 说明
- 失败检查
- 运行不可复现、泄漏、过拟合、基线薄弱或缺少部署边界
- 期望产出
- 包含流水线、指标和失败复盘的 ML 项目文件夹