银行客户流失预测:决策树 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)或深度学习模型。但在当前数据规模下,决策树已经给出了足够好的结果。