项目要求
项目与数据链接: 银行用户流失预测
通过对提供的165034条用户的相关数据进行分析,建立预测模型,预测不同用户流失的概率,从而采取相关措施降低流失率。
预测用户的流失率,看起来是一个回归问题,但是由于用户只有流失和不流失两种可能,因此该问题也可以看成一个二分类问题,在分类的过程中间接的获取用户流失的概率。而对于二分类的问题,首先想到的就是逻辑回归,SVM,其次便是决策树和随机森林等模型。
因此该报告通过逻辑回归,决策树以及随机森林三个模型,对问题和数据进行多方面的分析和改进。
算法设计与分析
一 数据分析
训练集给出的数据包括
名称 | 内容 |
---|---|
id | 数据编号 |
CustomerId | 银行用户编号 |
Surname | 用户的姓氏 |
CreditScore | 用户的信用评分 |
Geography | 用户所在的国家 |
Gender | 用户的性别 |
Age | 用户的年龄 |
Tenure | 用户在银行的年限 |
Balance | 用户的银行余额 |
NumOfProducts | 用户拥有的银行产品数量 |
HasCrCard | 用户是否拥有信用卡 (1: 是, 0: 否) |
IsActiveMember | 用户是否是活跃会员 (1: 是, 0: 否) |
EstimatedSalary | 用户的估计薪水 |
Exited | 用户是否已流失 (1: 是, 0: 否) |
观察之后很明显可以推测id, CustomerId, Surname对结果的影响很小甚至无影响,因此将这三个属性从训练集和测试集中删除。
相关性分析
同时通过对每个属性中不同类别的用户流失的占比进行分析,兼顾卡方检验,点二列相关系数,寻找可能对用户流失存在影响的属性。
下面展示不同年龄段用户的数量以及用户Exited的占比:
![]() |
![]() |
![]() |
             各个年龄段用户数量                                 各个年龄段流失用户数量                              各个年龄段用户流失占比
以及男女用户中流失的比例
Female | Male |
---|---|
27.96867% | 15.905529% |
对此可以初步得出关于年龄和性别的简单结论:
- Age 在30-40这个年龄段用户数量居多,但流失的人数却不及40-50年龄段用户,而且年龄在40以上的用户流失占比迅速增加,因此用户流失的可能性与年龄的大小存在联系。
- Gender 女性用户流失的概率可能比男性用户更大。
对于其他数据,因为篇幅有限,因此直接给出分析得到的结果:
- CreditScore 大部分用户的信誉评分都在500以上,且较为符合正态分布,但通过以50为区间长度进行分段并求出各个分段的流失人数占比后发现并无明显差别。同时计算该属性与是否流失之间的点二列相关系数,(该系数用于衡量一个二元变量和一个连续变量之间的关系,绝对值越接近1说明相关性越强),结果为 -0.027,说明该属性与是否流失之间的线性关系非常微小,因此初步推测该属性可能对预测结果无影响。
- Geography 提供的数据集中绝大部分都是France人,但相反的,其流失人数占比确是比其他两个国家的占比都小,为7%,Spain比其略大,为9% 左右。但Germany的用户数量虽然较少,但是流失率却接近40%,远远大于其他两个国家。可能是由于Germany用户不多存在一定偶然性,但任存在这种可能:Germany的用户流失的可能性远远大于其他两个地方。
- Tenure 用户在银行中的年限比较平均的分布在1-9年中,但并没有出现常识中的待得越久,流失越少的情况,而是分布较为随机,并没有明显趋势。之后通过对该属性与是否流失进行卡方独立性检验(卡方独立性检验是研究两类变量之间的关联性和依存性,或相关性、独立性、交互作用性的一种方法,得到的结果包含卡方统计量和p值,卡方统计量越大,相关性越强,P值越小,相关性越强,当P值小于0.05这个显著性水平时,则可认为两者存在较强烈的关系)。最后得到卡方统计量为265.46,P值为3.02e-51,远远小于0.05,因此该属性与结果之间存在显著联系。
- Balance 数据集中账户余额为0的用户占比为55%,也就是说为0和不为0的用户对半分。除去余额为0的用户后,计算余额与是否流失之间的点二列相关系数,结果为0.0066,说明两者之间几乎没有线性关系。因此将该属性分为两类,有余额和没有余额。计算两者流失占比,发现流失率并没有偏向于没有余额的用户,反而是在银行中存有余额的用户流失占比更高,达到了27%,比没有余额的占比多了11%。这其中可能是余额的不同也会照成一定的影响。
- NumOfProducts 由于数据集中47%的用户只有一个产品,51%的用户有两个产品,其他产品数量的用户太少,数据具有太多的随机性,因此难以信任。但是从只有一个产品的用户的流失率远大于两个产品的用户流失率可以推测:拥有的产品数量越多,用户越不容易流失。
- HasCrCard 虽然有有信用卡的人是没有信用卡的人的三倍,但两者用户中流失率接近相等,都在21%上下,推测这个属性对预测结果的影响可能比较小。
- IsActiveMember 通过比较活跃用户和非活跃用户的流失占比,发现非活跃用户的流失率为30%, 而活跃用户的流失率仅为12%,因此推测非活跃用户的流失可能性更大.
- EstimatedSalary 通过计算其与是否流失的点二列相关系数,得到结果0.0188,推测两者关系可能很小。同时将工资水平以20000为一个区间,共分成10段,计算每个区间内用户的流失率,发现10个区间的流失率都在20%上下,因此推测工资水平对用户是否流失的影响有限。
总结
通过上述分析,初步打算删除CreditScore, HasCrCard, EstimateSalary三个属性,从而选出可能与预测结果有关系的属性集:
Age | Gender | Geography | Tenure | Balance | NumOfProducts | IsActiveMember |
---|
其中Balance由于余额非0的用户中余额对流失率的影响很小,因此将该属性变为离散数据,具有余额则为1,没有余额则为0。
同时对于Geography属性拆分成France, Spain, Germany三列,每一列是一个二元属性,1代表属于这个国家。
因此,最终得到的属性共9个:
Age, Gender, Tenure, Balance, NumOfProducts, IsActiveMember, France, Spain, Germany。
二 逻辑回归
原理
逻辑回归的主要目的,就是找到这么一个合理的系数W,对于参数X
$$
\boldsymbol{z} = \boldsymbol{w_1x_1} + \boldsymbol{w_2x_2} + … + \boldsymbol{w_nx_n} = \boldsymbol{W^TX}
$$
而对于需要预测的结果为1的概率p,使用逻辑函数Sigmoid来计算:
$$
\boldsymbol{p=\frac{1}{1+e^{-z}}}
$$
因此,问题就回到了如何得到一个合理的系数W。
如果已知一个系数W和m个样本,那么就可以得到每个样本都按照实际的结果发生的总概率,在本题中就是训练集中所有客户流失情况都和实际一样的概率,即似然函数:
$$
\boldsymbol{L(W) = \prod_{i=1}^{m} p^{y_i}(1-p)^{1-y_i}}
$$
其中p是计算获得的流失的概率,$y_i$是实际的流失情况。
因此,这个合适的系数就是使得似然函数最大的系数,即极大化似然函数对应的参数W。
对此,我使用对数似然函数构造一个损失函数
$$
\boldsymbol{Loss(W) = -\frac{1}{m} \sum_{i=1}^{m} [y_i \log(p) + (1 - y_i) \log(1 - p)]}
$$
用梯度下降法求出使得损失函数最小的系数W。而梯度下降的方向就是损失函数的对于各个属性的偏导数:
$$
\boldsymbol{\frac{\partial Loss(W)}{\partial w_j} = \frac{1}{m} \sum_{i=1}^{m} (p^{i} - y^{i})x_j^{i}}
$$
$m$ 是样本用户数量。
$p^{i}$ 是第$i$个用户预测的流失概率。
$y^{i}$ 是第$i$个用户的真实流失情况(1或0)
$x_j^{i}$ 是第$i$个样本的第$j$个特征值。
这样,就得到了系数W的计算方法,不断迭代:
$$\boldsymbol{W^{k+1}=W^{k} - \alpha * \frac{\partial Loss(W{k})}{\partial W^{k}}}$$
$\alpha$是学习率,使得系数W以一个合适的速度进行迭代,防止速度过慢导致耗时增加或者速度过快导致偏离最优解。
实现
在对数据进行处理和掌握逻辑回归的原理后,使用python进行逻辑回归模型实现。
将整个实现分为6个部分:
- data_handle 数据处理函数,根据第一部分进行的数据分析对训练数据和测试数据进行相应处理。
- percentage 概率预测函数,根据提供的用户参数X和系数W,使用sigmod对用户流失的概率进行预测。
- loss 损失函数,根据当前系数W和训练数据,使用上述公式计算损失函数的值。
- gradient 梯度函数,使用上述公式计算当前系数W的各个属性的偏导数,返回一个数组。
- train 训练函数,不断的调用损失函数和梯度函数,更新系数W的值,直到达到一定次数。
- get_ans 预测函数,对测试集中的用户预测流失的概率,并输入到ans_logistic.csv文件中。
结果
由于我实现的逻辑回归的时间复杂度为O(mnt),其中m是样本数量,n是特征属性数量,t是迭代次数。
因此对于一次性训练所有训练集中的数据耗时很大。采用分批训练,将训练数据分为5组,分别求5组数据的预测概率最后取平均值。
但是实际训练的过程中发现,学习率为0.02的情况下,损失函数的值跳跃性很大,并没有像预期的逐渐收敛到某个值,初步分析是学习率过大导致更新步长过大,导致损失函数震荡。但经过测试发现,需要把学习率调的非常小,达到0.001,损失函数才不会震荡,但此时损失函数的变化非常小,每次训练步长维持在0.00005上下,这将导致训练次数需要很大,耗时很长。
为了解决这一问题,再次观察逻辑回归的原理和数据集,注意到逻辑回归本质上是系数W与属性X的线性相乘,如果属性X中某个属性$x_i$的值相比于其他属性过大时,将会导致相同的梯度下,该属性对结果造成的影响将会很大,导致步长过大,最终使得模型过度依赖于该属性。
因此将Age,Tenure, NumOfProducts进行归一化处理后,以0.02的学习率再次进行训练并预测。将前后两个结果的损失函数和测试结果在kaggle上的正确率进行对比:
![]() |
![]() |
归一化前正确率 | 归一化后正确率 |
---|---|
56.462% | 68.849% |
可以看到,无论是损失函数还是正确率,归一化后的数据都更优于未归一化的数据。
最后,不断调整合适的学习率,,学习率为0.1经过100次迭代后,测试集在kaggle上的正确率达到了72.635%。
三 CART决策树
由于逻辑回归最后的预测结果并没到达到预期的85%,因此考虑使用决策树模型进行预测。
原理
决策树的基本思路是每次在剩余特征属性中选择一个最佳的特征作为一个节点对数据进行分割,将数据分为2部分或多个部分,分割后的部分重复该过程,直到某个节点的数据集只属于一个类别,也即该数据集的用户全部流失或全部存在,或者节点数据集数量为0,或者只剩下一个特征属性,则把该节点作为叶子节点,并根据该节点的数据集的特征,对其进行分类。
分割的指标有信息增益,增益比和基尼系数。
前面两者生成的决策树是多叉树,而基尼系数生成的决策树是二叉树。考虑到逻辑回归训练时由于数据量太大而导致耗时远超预期,本决策树使用基尼系数作为划分指标。
基尼系数,我的理解是它评价的是一个数据集的纯度。由于我们的目标是把数据分割为易于分类的几个部分,而基尼系数的公式为:
$$ \boldsymbol{Gini(D) = \sum_{i=1}^{n} p(x_i)*(1-p(x_i)) }$$
D是数据集,$p(x_i)$是分类$x_i$出现的概率,n是分类数目,由于该问题是二分类,因此基尼系数为:
$$ \boldsymbol{Gini(D) = 2p(1-p) }$$
$p$是数据集D中用户流失的占比。
根据公式可以看出,如果一个数据集中流失用户的占比和不流失的占比一样,可以判断该数据集不够纯,这时基尼系数将达到最大;如果一个数据集中流失用户的占比远大于或远小于不流失的占比,说明该数据集纯度很高,基本只有一种类型,这时基尼系数将会非常小。因此基尼系数越小,数据纯度越高。
而根据属性X将数据分为$D_1$$D_2$两个部分时,基尼系数定义为:
$$
\boldsymbol{Gini(D|A) = \frac{|D_1|}{|D|}Gini(D_1) + \frac{|D_2|}{|D|}Gini(D_2)}
$$
对于一个属性X,分割数据集的方法便是先对数据集X的值进行唯一性排序,即把X所有出现过的值取出来进行排序,找到其中一个分割点,使得分割出来的两个数据集基尼系数最小。
实现
同样是使用python实现,将整个过程分为五个部分。
- data_handle 数据处理函数,处理流程与逻辑回归相同。
- gini_label 基尼指数计算函数,根据输入的数据集和特征属性,找到最优的分割点对数据进行分割。
- generate_decision_tree 决策树生成函数,采用递归的方法,不对生成子节点和叶子节点,最后返回整颗决策树。
- percentage 概率预测函数,根据输入的用户属性和决策树,找到该用户归属的叶子节点,返回该用户流失的概率。
- get_ans 结果输出函数,对于测试集中的每个用户,调用predict函数获取用户流失概率,并将结果输出到ans_decision.csv文件。
对于决策数生成函数generate_decision_tree,首先判断是否为叶子节点,若是叶子节点,直接返回该节点,若不是叶子节点,计算剩余的属性集中每个属性的基尼系数gini(D|X),并将其中最小的基尼系数用作当前节点分割的属性,将当前数据集分为两部分。然后从属性集中删除该属性,使用分割后的两个数据集调用generate_decision_tree函数并加入当前节点的左右子树。
对于决策树的结构,采用字典嵌套的结构来保存一颗决策树,每个节点的字典含有4个键值对
key | value | 解释 |
---|---|---|
info | ‘leaf’ 或 ‘non-leaf’ | 节点是否为叶子节点 |
label | 分割信息或概率 | 若是叶子节点,则为流失概率,若不是,则为一个二元列表,包含该节点分割的属性和分割点 |
left | 左子树 | 分割后的数据集构建出来的子树,若是叶子节点则没有该项 |
right | 右子树 | 分割后的数据集构建出来的子树,若是叶子节点则没有该项 |
而对于叶子节点的判断以及概率计算方法有三种:
- 数据集为空 由于数据集为空,因此设置流失概率为0.5。
- gini为0 即只有一种数据,这时该节点的用户流失概率为0或1。
- 属性集只剩下一种属性 流失概率为$\frac{流失人数}{该数据集总人数}$。
结果
使用所有训练数据集进行决策树的搭建后,对测试集的用户数据进行流失概率预测,将测试结果放在kaggle上提交,正确率达到**84.750%**,相比于逻辑回归有了很大的提升,但仍未达到目标85%,因此考虑对模型或者算法进行优化。
想到能不能用上述逻辑回归时相同的方法,将数据分成多批,生成多颗决策树,然后每颗决策树都得到一个用户流失概率,最后的结果便是多颗决策树的平均值。这与随机森林的思想相似。
四 CART决策树构建随机森林
实现
我的随机森林的实现并没有大的改动点,思路是基于上述的CART决策树,通过每次随机有放回的取出row_num个用户数据,并从属性集中随机无放回的取出column_num个属性构成一个数据集,并使用该数据集生成一颗CART决策树,该过程重复tree_num次,得到tree_num颗决策树构成随机森林。
因此,该随机森林的参数有三个,随机取出的数据数量row_num,随机取出的属性个数columm_num,决策树数量tree_num。
结果
由于每次构建决策树的过程都是随机的,因此最后得到的结果具有一定随机性,但经过多次参数调整和测试,在决策树数量为50,随机属性数量为8,每次选取数据数量为5000的参数下,正确率相比单颗决策树有微小的提升,大多数时候正确率在85%左右。
总结与体会
对于三个模型的底层实现,并没有想象中的那么困难,通过结合课堂知识和查找的资料,我顺利完成了模型的构建。对于这个过程的主要的工作,我认为在数据分析和处理方面。
在完成模型代码的编写后,我尝试对数据不进行任何删除或修改,仅将字符串转化为数字编码,在这种情况下,三个模型的准确率分别为**61%,78%,83%,处理后的正确率为72%,84%,85%**。可见每个模型对于数据的敏感性和鲁棒性还是有较大差别的。
结合上述内容,对这三个模型进行总结:
- 逻辑回归 逻辑回归的思路非常直接,但这也导致了其追求的是数据与结果之间的线性关系,对于非线性关系,可能性能并不会很理想。同时,逻辑回归对于数据的敏感性相比于其他两个模型就比较明显,归一化前后的两次预测中损失函数的变化就很直观的表明了这一点:如果一个属性相比于其他属性过大,将会导致模型过多的关注该属性,而忽略了其他属性。
- CART决策树 由决策树的原理可以看出,该模型并没有对数据于结果的关系进行假设,因此它对于非线性的关系也是可以处理的。但由于其分割数据集进行分类的特点,存在对数据的依赖性,很容易就造成过拟合
- 随机森林 因为是由多颗决策树共同决定预测的结果,决策树对于数据的依赖的问题得到了缓解,这点从数据处理前后预测准确率相差无几可以看出。随机性选取数据和属性的特点也增强了该模型的泛化能力。但需要调整的参数过多,想要找到一个最优的参数并不容易。而且其随机的特点导致根本不知道运行过程中的具体情况,每次运行都是一个不同的结果。
最后,三个模型测试集的测试结果在kaggle上的准确率如下表:
逻辑回归 | CART决策树 | 随机森林 |
---|---|---|
0.72635 | 0.84750 | 0.85449 |
参考资料
- 周志华. 机器学习: 第 3 章. 清华大学出版社, 2016.
- 决策树基本原理 https://zhuanlan.zhihu.com/p/112161073
- Bagging算法 https://zhuanlan.zhihu.com/p/355416998