银行客户流失预测(Python 完整实战)

银行客户流失预测:决策树 vs SVM vs 神经网络(Python 完整实战)

最后更新 :2026-07-22 | 数据来源 :Kaggle Churn Modelling 数据集(10000 条匿名化真实银行客户记录) | 技术栈 :Python + scikit-learn + pandas + matplotlib ==代码+数据:pan.quark.cn/s/b4dc33652...

核心要点

本文基于 Kaggle 公开的银行客户流失数据集(10000 条记录,14 个字段),对比了决策树、SVM、神经网络三种机器学习算法在客户流失预测任务上的表现。实验结论如下:

  • 决策树准确率最高(78.77%),SVM 次之(75.71%),神经网络最低(74.36%)
  • Age(年龄)是与客户流失相关性最强的特征,相关系数 0.34
  • 原始数据正负样本比为 4:1(79.6% 未流失 vs 20.4% 流失),经平衡处理后训练集为 4074 条
  • 德国客户流失率(32%)约为法国和西班牙(16%)的两倍
  • 15 折交叉验证平均准确率为 76.12%,最高单折达 80.07%

什么是银行客户流失预测

银行客户流失预测是指利用机器学习算法,基于客户的历史行为数据(如信用分数、年龄、账户余额、活跃度等特征),预测该客户在未来是否会关闭账户或停止使用银行服务的过程。其核心目标是在客户实际流失之前识别出高风险客户,使银行能够采取针对性的挽留措施。

根据 HubSpot State of Service Report 的数据,获取一个新客户的成本是保留一个现有客户成本的 5 倍。因此,通过机器学习提前识别流失风险客户,对银行利润有直接影响。

**客户流失(Customer Churn)**是指客户终止与企业的商业关系的行为。在银行业语境下,流失通常指客户关闭账户、转移存款或停止使用银行的核心服务。流失分为自愿流失(客户主动退出)和非自愿流失(因违约等原因被动终止),机器学习预测主要针对前者。

数据集与特征说明

本实验使用 Kaggle 平台公开的 Churn Modelling 数据集,包含 10000 条银行客户记录,每条记录有 14 个字段。数据来源为国外匿名化处理后的真实银行客户数据。

字段 含义 是否用作特征 数据类型
RowNumber 行号 整数
CustomerId 用户编号 整数
Surname 用户姓名 文本
CreditScore 信用分数(300-850) 连续
Geography 用户所在国家/地区 类别
Gender 用户性别 类别
Age 年龄 连续
Tenure 当了本银行多少年用户 整数
Balance 账户余额 连续
NumOfProducts 使用产品数量 整数
HasCrCard 是否有本行信用卡 二值
IsActiveMember 是否活跃用户 二值
EstimatedSalary 估计收入 连续
Exited 是否已流失(0=未流失, 1=流失) 标签 二值

RowNumber、CustomerId、Surname 三个字段与流失行为无因果关系,在预处理阶段直接剔除。剩下 10 个字段中 9 个作为特征,Exited 作为预测标签。

数据分布与流失特征分析

客户流失的总体分布

在 10000 条客户记录中,未流失 7963 条(79.6%),流失 2037 条(20.4%)。正负样本比例约为 4:1,属于严重不均衡数据。如果不处理样本不平衡问题,模型会倾向于把所有样本都预测为"未流失",因为光靠猜"不流失"就能拿到接近 80% 的准确率。

哪些地区的客户流失率更高

三个地区的客户数量大致均衡,但流失率差异显著:

  • 德国客户流失率约 32%,远高于法国和西班牙
  • 法国和西班牙的流失率均在 16% 左右
  • Geography 因此成为一个有强区分力的特征

哪些特征与流失最相关

通过计算各特征与流失标签(Exited)的 Pearson 相关系数,可以得到特征重要性排序:

特征 与 Exited 的相关系数 含义
Age +0.34 年龄越大,流失倾向越强
IsActiveMember -0.16 活跃用户流失概率更低
Balance +0.12 余额越高,流失倾向略强
Gender -0.11 女性流失率略高于男性

Age 是与流失正相关性最强的特征。这些数据洞察在后续决策树的特征分裂顺序中得到了验证------树的根节点就是 Age。

数据预处理的三个步骤

原始数据不能直接输入模型,需要经过量化、离散化、样本平衡三步处理:

第一步:量化(类别特征转数值)

**量化(Quantification)**是指将文本类别的分类变量转换为整数编码的过程。scikit-learn 的模型只接受数值输入,因此 Geography(France/Spain/Germany)和 Gender(Female/Male)需要先转为整数。

使用 pandas 的 factorize 函数实现:

python 复制代码
def quantification(data_path, output_path):
    df = pd.read_csv(data_path)
    df["Geography"] = pd.factorize(df["Geography"])[0]
    df["Gender"] = pd.factorize(df["Gender"])[0]
    df.to_csv(output_path, index=False)

转换后的编码映射:France=0, Spain=1, Germany=2;Female=0, Male=1。

第二步:离散化(连续变量分箱)

**离散化(Discretization)**是指将连续数值变量按分位数划分为若干离散区间的过程。本实验对 CreditScore、Age、EstimatedSalary 按四分位数(Q1/Q2/Q3)分成 4 档,Balance 额外将 0 值单独作为一档(共 5 档)。

离散化的目的是控制决策树的复杂度。连续变量精度过高时,树会变得极其庞大且容易过拟合。分箱后既保留了数值的相对大小信息,又限制了树的分裂维度。

python 复制代码
def discretization(data_path, output_path):
    df = pd.read_csv(data_path)

    def _quartile_labels(series):
        q1, q2, q3 = series.quantile([0.25, 0.5, 0.75])
        labels = []
        for v in series:
            if v < q1:      labels.append(0)
            elif v < q2:    labels.append(1)
            elif v < q3:    labels.append(2)
            else:           labels.append(3)
        return labels

    df["CreditScore"] = _quartile_labels(df["CreditScore"])
    df["Age"] = _quartile_labels(df["Age"])
    df["EstimatedSalary"] = _quartile_labels(df["EstimatedSalary"])

    # Balance 单独处理:0 值单独作为一类
    nonzero = df.loc[df["Balance"] != 0, "Balance"]
    q1, q2, q3 = nonzero.quantile([0.25, 0.5, 0.75])
    balance_labels = []
    for v in df["Balance"]:
        if v == 0:     balance_labels.append(0)
        elif v < q1:   balance_labels.append(1)
        elif v < q2:   balance_labels.append(2)
        elif v < q3:   balance_labels.append(3)
        else:          balance_labels.append(4)
    df["Balance"] = balance_labels

    df.to_csv(output_path, index=False)

分箱规则汇总:

特征 0 1 2 3 4
CreditScore <Q1 Q1-Q2 Q2-Q3 >Q3 -
Age <Q1 Q1-Q2 Q2-Q3 >Q3 -
EstimatedSalary <Q1 Q1-Q2 Q2-Q3 >Q3 -
Balance =0 <Q1 Q1-Q2 Q2-Q3 >Q3

第三步:样本平衡(正负样本等量过滤)

原始数据正负样本比例 4:1(7963 未流失 vs 2037 流失),直接训练会导致模型偏向多数类。本实验采取的下采样策略是:从多数类(未流失)中抽取与少数类(流失)等量的样本,使最终训练数据的正负比达到 1:1。

python 复制代码
def filtering(data_path, output_path):
    df = pd.read_csv(data_path)
    ones = int(df["Exited"].sum())       # 流失样本数:2037
    zeros = len(df) - ones               # 未流失样本数:7963

    df_new = pd.DataFrame(columns=FEATURE_COLUMNS + [TARGET_COLUMN])
    flag_0 = 0  # 已取的流失样本数
    flag_1 = 0  # 已取的未流失样本数

    for i in range(len(df)):
        row = df.iloc[i]
        if row["Exited"] == 0 and flag_1 < ones:
            df_new = pd.concat([df_new, pd.DataFrame([row[...]])], ignore_index=True)
            flag_1 += 1
        if row["Exited"] == 1 and flag_0 < zeros:
            df_new = pd.concat([df_new, pd.DataFrame([row[...]])], ignore_index=True)
            flag_0 += 1

    df_new.to_csv(output_path, index=False)

过滤后最终得到 4074 条记录(流失 2037 + 未流失 2037),正负样本完全平衡。按 4:1 划分训练集和测试集:训练集 3259 条,测试集 815 条。

决策树模型:准确率最高的选择

什么是决策树

**决策树(Decision Tree)**是一种基于树形结构的监督学习算法,通过一系列 if-else 规则对特征空间进行递归划分。每个内部节点表示一个特征测试,每个分支代表一个测试结果,每个叶子节点代表一个分类结果。决策树的核心优势是可解释性强,树结构可以直接可视化。

本实验使用 scikit-learn 的 DecisionTreeClassifier,配置参数如下:

参数 作用
criterion gini 使用 Gini 系数作为分裂标准
max_depth 8 限制树的最大深度,防止过拟合
min_samples_split 200 节点至少包含 200 个样本才允许分裂
python 复制代码
dt_model = DecisionTreeClassifier(
    criterion="gini", max_depth=8, min_samples_split=200
)
dt_model.fit(feature_train, target_train)
predict_results = dt_model.predict(feature_test)
score = dt_model.score(feature_test, target_test)

决策树的实验结果

决策树在测试集上的准确率为 78.77%,是三种模型中最高的。

树的第一层分裂节点是 Age(离散化后的年龄段),与相关性分析中 Age 与 Exited 相关性最强(r=0.34)的结论一致。年龄较大的客户群体被优先分离出来,进入更深层的判断。

决策树的混淆矩阵

混淆矩阵显示,模型在流失和未流失两类上的预测表现较为均衡,没有出现严重偏向某一类的情况。

决策树的 ROC 曲线

ROC 曲线衡量了模型在不同阈值下的区分能力。曲线越靠近左上角,模型的判别能力越强。蓝色实线是模型的 ROC 曲线,黑色虚线是随机猜测的基准线。

SVM 模型

什么是 SVM

**支持向量机(Support Vector Machine, SVM)**是一种通过寻找最大间隔超平面来进行分类的监督学习算法。对于非线性可分问题,SVM 使用核函数将数据映射到高维空间,使其在高维空间中线性可分。本实验使用 RBF(径向基函数)核。

python 复制代码
svm_model = SVC(kernel="rbf", C=1.0, gamma="scale", random_state=42)
svm_model.fit(feature_train, target_train)

SVM 的实验结果

SVM 的准确率为 75.71%,低于决策树 3.06 个百分点。

神经网络模型

什么是神经网络

**多层感知机(Multilayer Perceptron, MLP)**是一种前馈人工神经网络,由输入层、若干隐藏层和输出层组成。每个神经元使用激活函数引入非线性,通过反向传播算法调整权重。本实验使用 scikit-learn 的 MLPClassifier,配置两个隐藏层(100 和 50 个神经元),ReLU 激活函数,Adam 优化器。

python 复制代码
nn_model = MLPClassifier(
    hidden_layer_sizes=(100, 50),
    activation="relu",
    solver="adam",
    alpha=0.001,
    learning_rate_init=0.001,
    max_iter=500,
    random_state=42,
)
nn_model.fit(feature_train, target_train)

神经网络的实验结果

神经网络的准确率为 74.36%,是三种模型中最低的。

三种模型对比:哪个准确率最高

模型 准确率 训练速度 可解释性 适用场景
决策树 78.77% 强(可视化树结构) 离散特征、小数据
SVM 75.71% 中等数据、非线性
神经网络 74.36% 大数据、复杂模式

决策树在这个数据集上表现最好的原因:一是经过离散化处理后,所有特征都变成了小范围的整数编码,决策树对此类离散特征天然友好;二是数据量相对较小(训练集仅 3259 条),神经网络和 SVM 的参数空间更大,在小数据上容易欠拟合或陷入局部最优。

K 折交叉验证:如何更稳健地评估模型

什么是 K 折交叉验证

**K 折交叉验证(K-Fold Cross-Validation)**是一种模型评估方法,将数据集均匀分成 K 份,每次用其中 K-1 份训练、剩余 1 份测试,循环 K 次后取平均准确率。分层 K 折(Stratified K-Fold)在此基础上保证每折中正负样本比例与原始数据一致。交叉验证可以减少单次 train-test split 的随机性影响。

本实验分别测试了 K=5、K=10、K=15 三种情况:

python 复制代码
def cross_validation(dt_model, feature, target, n_splits):
    skfold = StratifiedKFold(n_splits=n_splits, shuffle=False)

    for k, (train_index, test_index) in enumerate(skfold.split(feature, target), 1):
        f_train = feature[train_index]
        f_test = feature[test_index]
        t_train = target[train_index]
        t_test = target[test_index]

        dt_model.fit(f_train, t_train)
        score = dt_model.score(f_test, t_test)

交叉验证结果

K=15 时的各折准确率波动情况:

三种 K 值的交叉验证结果汇总:

K 折数 平均准确率 最高准确率 最低准确率 波动范围
5 75.58% 77.91% 72.76% 5.15%
10 76.00% 78.62% 69.85% 8.77%
15 76.12% 80.07% 69.12% 10.95%

随着 K 增大,平均准确率略有上升(从 75.58% 到 76.12%),因为训练集占比更大。但最低准确率也在下降(K=15 时最低仅 69.12%),说明某些折的数据划分对模型不太友好,波动性增大。

完整代码结构与复现方法

项目已整理为结构化的 Python 脚本,目录如下:

php 复制代码
银行客户流失/
  代码.py              # 主脚本,包含完整流程
  data/                # 数据文件
    Churn-Modelling-new.csv      # 原始数据
    Churn-Modelling-newT.csv     # 量化后数据
    Churn-Modelling-new-tree.csv # 离散化后数据
    final.csv                    # 平衡后最终训练数据
  images/              # 所有图片输出

核心函数说明:

函数 作用
quantification() Geography/Gender 文本转整数
discretization() 连续变量四分位分箱
filtering() 正负样本等量过滤
train_decision_tree() 决策树训练+混淆矩阵+ROC+树可视化
train_svm() SVM 训练+混淆矩阵
train_neural_network() 神经网络训练+混淆矩阵
cross_validation() K 折交叉验证+折线图

运行环境要求:Python 3.8+,依赖库为 scikit-learn、pandas、numpy、matplotlib。直接运行 python 代码.py 即可复现全部实验结果。

常见问题

银行客户流失预测用什么模型最好

在数据量为万级别、特征以离散型为主的银行客户流失预测任务中,决策树是性价比最高的选择。本实验中决策树准确率为 78.77%,高于 SVM(75.71%)和神经网络(74.36%),且训练速度快、可解释性强。如果数据量达到百万级或特征间存在复杂非线性交互,可考虑梯度提升树(XGBoost/LightGBM)或深度学习模型。

如何处理客户流失数据中的样本不平衡问题

常见的样本不平衡处理方法有三种:欠采样(从多数类中抽取等量样本)、过采样(对少数类进行复制或 SMOTE 合成)、以及调整类别权重(class_weight 参数)。本实验采用欠采样方法,将原始 4:1 的正负样本比平衡为 1:1,最终训练集为 4074 条记录。

决策树的 max_depth 和 min_samples_split 怎么选

max_depth 控制树的最大深度,值越大模型越复杂但容易过拟合;min_samples_split 规定节点至少包含多少样本才允许分裂,值越大树的分裂越保守。本实验经过对比测试,选择 max_depth=8、min_samples_split=200,在准确率和泛化能力之间取得了平衡。

为什么神经网络在小数据集上表现不如决策树

神经网络通常需要大量数据来学习有效的特征表示。本实验训练集仅 3259 条,对于包含两个隐藏层(100+50 神经元)的 MLP 来说数据量偏少,容易陷入欠拟合或局部最优。决策树的参数空间更小,在小数据上更容易找到有效的分裂规则。一般而言,数据量达到十万级以上时神经网络的优势才会显现。

总结

维度 决策树 SVM 神经网络
准确率 78.77% 75.71% 74.36%
训练速度
可解释性 强(可视化树结构)
适用场景 离散特征、小数据 中等数据、非线性 大数据、复杂模式

在银行客户流失预测这类任务中,数据量通常在万级别,特征以离散型为主,决策树是性价比最高的选择。它的训练速度快、可解释性强,业务人员能直接从树结构中看到"哪些特征导致了客户流失"。Age 是最重要的预测特征,其次是 IsActiveMember 和 Geography。

如果数据量达到百万级,或者特征之间存在复杂的非线性交互,可以考虑切换到梯度提升树(XGBoost/LightGBM)或深度学习模型。但在当前数据规模下,决策树已经给出了足够好的结果。

相关推荐
来一碗刘肉面1 小时前
栈的应用(表达式求值)
数据结构·算法
xiaowang1234shs2 小时前
怪兽轻断食技术深度测评:从断食计时引擎到AI识别算法的工程实践解析
数据库·人工智能·算法·macos·机器学习·p2p·visual studio
小果因子实验室2 小时前
量化研究--策略迁移算法1研究
算法
Web极客码3 小时前
如何用三段式确定性剪枝,为 LLM Agent 砍掉 35% 的 Token 成本?
服务器·人工智能·算法·机器学习
程序猿乐锅3 小时前
【数据结构与算法 | 第六篇】力扣1109,1094差分数组
java·算法·leetcode
wabs6664 小时前
关于图论【广度优先搜索的理论基础】
算法·图论·宽度优先
孤魂2334 小时前
逻辑回归算法
算法·机器学习·逻辑回归
weixin_377634844 小时前
【多模型预测】 如何合理融合多个预测分值
算法·机器学习·概率论·预测·agent预测
无风听海4 小时前
Claude Agent Skills 的四种设计模式;从渐进式披露到最小权限
java·算法·设计模式
37.2℃9954 小时前
专业的Claude Design解决方案
前端·算法