Press "Enter" to skip to content

大数据风控—机器学习在个人征信判别上的应用

 

一、背景及目标

 

风控领域是新兴的机器学习应用场景之一。本文对提供的银行征信数据,包括用户的基本属性、银行流水记录、顾客逾期行为、信用卡账单记录,用户浏览行为记录以及放款信息等数据进行汇总分析,运用决策树DTC、随机森林RFC等算法对用户是否逾期进行建模估计。我将对完整的流程进行详细的演示,希望能够对大家有所帮助。

 

一个完整的模型开发如图所示。开发一个优秀的模型就好比炼制一颗仙丹,在这里我们将炼丹也分解为五大部分:材料准备(灵芝,五倍子,茯苓等)—材料比例调制—炉中炼丹—出炉检验,淘汰次品。开发模型中的“获取数据和数据清洗”相当于炼丹过程中的材料准备,一旦材料选择不合适、或者缺斤少两,那最后炼出的仙丹一定不好,所以说在开发一个模型时,“数据获取和数据清洗”是至关重要的,原始数据选择不恰当,后面的操作都是白费。另外,“特征工程”相当于炼丹的“材料比例调制”,也是入炉前的最后一道调制工序,也是很重要的,一旦调制比例不准确,再优秀的炼丹师也不能炼好丹,正所谓“巧妇难为无米之炊”。入炉后,我们只需要控制好火候、压力、湿度等等参数就可以完成整个炼丹过程,对应于我们模型开发中的调参环节。以上简单用炼丹和模型开发进行对比解释,希望能让大家更好的理解本文。

 

二、让我们开启整个模型开发过程吧!

 

1.获取数据

 

–首先导入需要的包

 

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import Binarizer #二值化
from sklearn.feature_selection import VarianceThreshold  #方差特征提取
from sklearn.feature_selection import mutual_info_classif as MIC  # 互信息法提取特征
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score as cvs
from sklearn.feature_selection import RFE   #包装法筛选变量
from imblearn.over_sampling import SMOTE   # 解决样本不均衡包
from sklearn.model_selection import train_test_split
from sklearn.metrics import precision_score,recall_score,f1_score,classification_report
from sklearn.tree import DecisionTreeClassifier as DTCimport graphviz
from sklearn import tree
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.model_selection import GridSearchCV

 

–导入原始数据(用户的基本属性、银行流水记录、顾客逾期行为、信用卡账单记录,用户浏览行为记录以及放款信息等数据,6个txt文件,数据下载链接:链接:https://pan.baidu.com/s/1qbF-st4mm-GNV1cqmBt04Q 提取码:tqes)

 

df_bank_detail = pd.read_table("bank_detail.txt",
                               names=["用户ID","时间戳","交易类型","交易金额","工资收入标记"],
                               sep=",")
df_user_info = pd.read_table("user_info.txt",
                              names=["用户ID","性别","职业","教育程度","婚姻状态","户口类型"],
                              sep=",")
df_overdue = pd.read_table("overdue.txt",
                            names=["用户ID","样本标签"],
                            sep=",")
df_BillDetail = pd.read_table("bill_detail.txt",
                               names=["用户ID","账单时间戳","银行ID","上期账单金额","上期还款金额","信用卡额度",
                                       "本期账单余额","本期账单最低还款额","消费笔数","本期账单金额","调整金额",
                                       "循环利息","可用金额","预借现金额度","还款状态"],
                               sep=",")
df_BrowseHistory = pd.read_table("browse_history.txt",
                                  names=["用户ID","时间戳","浏览行为数据","浏览子行为编号"],
                                  sep=",")
df_loanTime = pd.read_table("loan_time.txt",names=["用户ID","放款时间"],sep=",")

 

浏览下原始数据,结果如下:

 

–打印出所有数据表具有的用户数量。

 

print(
   {
 'userinfo表的用户id数':len(df_user_info["用户ID"].unique()),
   'bank_detail表的用户id数':len(set(df_bank_detail["用户ID"])),
   "overdue表的用户ID数": len(set(df_overdue["用户ID"])),
    "bill_detail表的用户ID数": len(set(df_BillDetail["用户ID"])),
    "browse_history表的用户ID数":len(set(df_BrowseHistory["用户ID"])),
    "loan_time表的用户ID数":len(set(df_loanTime["用户ID"]))
   }
)

 

由以上数据导入,可以看出不同的原始数据表有着不同用户ID的数量,需要我们对所有数据表取交集,获取信息完整的用户作为有效的原始客户数据。

 

2.数据清洗

 

-获取有效的用户数据(通过比较放款时间和时间戳,获取银行账单记录表、信用卡账单记录表、客户浏览记录表的有效数据)

 

# 银行账单记录的“时间戳”小于放款时间,即为有效用户
df_user1 = pd.merge(left=df_bank_detail,right=df_loanTime,how="left",on="用户ID")
df_user1_  = df_user1[df_user1["时间戳"]<=df_user1["放款时间"]]
df_user1_ = df_user1_[["用户ID"]].drop_duplicates(subset="用户ID",keep='first')
#信用卡账单记录的“时间戳”小于放款时间,即为有效用户
df_user2 = pd.merge(left=df_BillDetail,right=df_loanTime,how="left",on="用户ID")
df_user2_ = df_user2[df_user2["账单时间戳"]<=df_user2["放款时间"]]
df_user2_ = df_user2_[["用户ID"]].drop_duplicates(subset="用户ID",keep="first")
#客户浏览记录的“时间戳”小于放款时间,即为有效用户
df_user3 = pd.merge(left=df_BrowseHistory,right=df_loanTime,how="left",on="用户ID")
df_user3_ = df_user3[df_user3["时间戳"]<=df_user3["放款时间"]]
df_user3_ = df_user3_[["用户ID"]].drop_duplicates(keep="first",subset="用户ID")

 

–以上获取了三种原始数据的有效用户df_user1_,df_user2_,df_user3_,下面我们需要对所有的表进行取交集,得到具有完整信息数据的有效用户,如下:

 

df_ = pd.merge(left=df_user1_,right=df_user2_,how="inner",on="用户ID")
df_ = pd.merge(left=df_ ,right=df_user3_,how='inner',on="用户ID")
df_

 

到这里,我们就得到了5735个有效的用户信息,接下来围绕着这5735个数据进行分析。

 

-首先对交易类型为0(即收入)的用户进行分组,并计算出对应用户的进账单数和进账金额;

 

-首先对交易类型为1(即支出)的用户进行分组,并计算出对应用户的支出单数和支出金额;

 

-首先对工资收入标记为(即有收入)的用户进行分组,并计算出对应用户的工资笔数和工资收入;

 

bank_detail_select = pd.merge(left=df_bank_detail,right=df_,how="inner",on="用户ID")
b1 = bank_detail_select[bank_detail_select["交易类型"]==0].groupby(['用户ID'],as_index=False)
b2 = bank_detail_select[bank_detail_select["交易类型"]==1].groupby(['用户ID'],as_index=False)
b3 = bank_detail_select[bank_detail_select["工资收入标记"]==1].groupby(['用户ID'],as_index=False)
c1=b1["交易金额"].agg({
 "进账单数":"count","进账金额":"sum"})
c2=b2['交易金额'].agg({
 '支出单数':'count','支出金额':'sum'}) 
c3=b3['交易金额'].agg({
 '工资笔数':'count','工资收入':'sum'}) 
c1.head()
c2.head()
c3.head()

 

-选择所有数据表与df_表共有的用户作为分析对象缺失值用0填充。

 

# 需要获取有效用户的进账,支出,工资情况,去交集
d1 = pd.merge(left=df_,right=c1,how="left",on="用户ID")
d1 = d1.fillna(0)
d2=pd.merge(left=df_, right=c2, how='left', on='用户ID')
d2=d2.fillna(0)
d3=pd.merge(left=df_, right=c3, how='left', on='用户ID')
d3=d3.fillna(0)
bank_train = d1.merge(d2)
bank_train = pd.merge(bank_train,d3)
bank_train

 

-同样的道理,对浏览记录表、信用卡账单记录、逾期记录表选取有效用户数据,方便后面分析

 

browse_history_select = pd.merge(left=df_,right=df_BrowseHistory,how='left',on="用户ID")
g1 = browse_history_select.groupby(['用户ID'],as_index=False)
h1 = g1['浏览行为数据'].agg({
 "浏览行为数据":"count"})
browse_train = pd.merge(df_,h1,how="left",on="用户ID")

 

 

 

bill_select = pd.merge(df_,df_BillDetail,how="left",on="用户ID")
bill_select.drop(["账单时间戳","银行ID","还款状态"],axis=1,inplace=True)
e1=bill_select.groupby(['用户ID'], as_index=False)
f1=e1['上期账单金额', '上期还款金额', '信用卡额度', '本期账单余额','本期账单最低还款额', '消费笔数', '本期账单金额', '调整金额', '循环利息', '可用金额', '预借现金额度'].agg(np.mean)
bill_train = pd.merge(left=df_, right=f1, how='left', on='用户ID')

 

 

 

overdue_train = pd.merge(df_,df_overdue,"left","用户ID")
user_train=pd.merge(left=df_, right=df_user_info, how='left', on='用户ID')
user_train

 

 

 

合并以上分析的所有有效数据,得到df_train.

 

df_train = user_train.merge(bank_train)
df_train = df_train.merge(bill_train)
df_train = df_train.merge(browse_train)
df_train = df_train.merge(overdue_train)

 

3.特征工程

 

-在上面,我们得到了xx条数据,25个特征,首先我们通过热力图判断【‘进账单数’, ‘进账金额’, ‘支出单数’, ‘支出金额’, ‘工资笔数’, ‘工资收入’】这六个特征是否具有相关性。

 

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
plt.rcParams["axes.unicode_minus"] = False
internal_chars=['进账单数', '进账金额', '支出单数', '支出金额', '工资笔数', '工资收入']
corrmat=bank_train[internal_chars].corr()   # 相关性结果数据表
plt.subplots(figsize=(10,10))
sns.heatmap(corrmat, square=True, linewidths=.5, annot=True); #热力图

 

 

 

由图得到的结论:

 

– 进账单数与进账金额的相关系数很高为0.99

 

– 支出单数与支出金额,支出单数的相关系数较高,为0.82,0.85

 

– 支出金额与支出单数相关系数很高为0.99

 

– 进账金额与支出单数,支出金额的相关系数为0.81,0.85

 

– 工资笔数与工资收入的相关系数为1

 

可见,收入,支出,工资三个指标的金额跟笔数是线性的关系,那幺后续将构建一个新的特征:笔均=金额/笔数,取工资笔均;

 

而且收入,支出是强相关(0.82),所以只取一个即可,支出笔均。

 

后续将用进账金额/进账单数,支出金额/支出单数,工资收入/工资单数得到进账笔均,支出笔均,工资笔均

 

internal_chars = ["上期账单金额","上期还款金额","信用卡额度","本期账单余额","本期账单最低还款额",
                  "消费笔数","本期账单金额","调整金额","循环利息","可用金额","预借现金额度"]
corrmat = df_train[internal_chars].corr()
plt.subplots(figsize=(15,10))
plt.xticks(rotation=0)
sns.heatmap(corrmat,square=False,linewidths=0.5,annot=True)

 

 

‘本期账单金额’与’本期账单余额’相关系数为0.85
‘上期账单金额’与’上期还款金额’相关系数为0.75
‘本期账单金额’与’上期还款金额’相关系数为0.64
‘信用卡额度’与’上期账单金额’和’上期还款金额’相关系数分别为0.54和0.52
‘本期账单金额’与’上期账单金额’相关系数为0.5

重新构建数据表

 

df_train['平均支出']=df_train.apply(lambda x:x.支出金额/x.支出单数, axis=1) # apply() 当函数的参数存在于字典或元组中时
df_train['平均工资收入']=df_train.apply(lambda x:x.工资收入/x.工资笔数, axis=1)
df_train['上期还款差额']=df_train.apply(lambda x:x.上期账单金额-x.上期还款金额, axis=1)
df_select=df_train.loc[:,['用户id', '性别', '教育程度', '婚姻状态', '平均支出',
                          '平均工资收入', '上期还款差额', '信用卡额度', '本期账单余额', '本期账单最低还款额', 
                          '消费笔数',  '浏览行为数据', '样本标签']].fillna(0)
df_select.head()

 

到这里,我们基于业务层面进行了特征选择与创造。接下来,我们将通过机器学习算法进行特征的选择。

 

基于机器学习(逻辑回归)筛选特征

 

X=df_select['上期还款差额'].values.reshape(-1,1)
transformer = Binarizer(threshold=0).fit_transform(X)
df_select['上期还款差额标签']=transformer

 

—x为特征,y为标签

 

x=df_select.drop(['用户id','上期还款差额','样本标签'],axis=1)
y=df_select['样本标签']

 

首先,当样本不均衡时,进行特征提取,发现,特征提取效果很差

 

LR_1 = LogisticRegression(penalty='l1', solver='liblinear', C=0.6, max_iter=1000, random_state=0)
LR_2 = LogisticRegression(penalty='l2', solver='liblinear', C=0.6, max_iter=1000, random_state=0)
score1=[]
score2=[]
for i in range(1,12,1):
    selector1 = RFE(LR_1, n_features_to_select=i, step=1)
    selector2 = RFE(LR_2, n_features_to_select=i, step=1)
    X_wrapper1 = selector1.fit_transform(x, y)
    X_wrapper2 = selector2.fit_transform(x, y)
    once1=cvs(LR_1, X_wrapper1, y, cv=5, scoring='f1').mean()
    once2=cvs(LR_2, X_wrapper2, y, cv=5, scoring='f1').mean()
    score1.append(once1)
    score2.append(once2)
plt.plot(range(1,12,1),score1, 'r')
plt.plot(range(1,12,1),score2, 'g')
plt.show()

 

当我们对样本进行均衡化后,提取效果明显提升。

 

over_samples = SMOTE(random_state=111)
over_samples_x, over_samples_y = over_samples.fit_sample(x,y)
LR_1 = LogisticRegression(penalty='l1', solver='liblinear', C=0.6, max_iter=1000, random_state=0)
LR_2 = LogisticRegression(penalty='l2', solver='liblinear', C=0.6, max_iter=1000, random_state=0)
score1=[]
score2=[]
for i in range(1,12,1):
    selector1 = RFE(LR_1, n_features_to_select=i, step=1)
    selector2 = RFE(LR_2, n_features_to_select=i, step=1)
    X_wrapper1 = selector1.fit_transform(over_samples_x, over_samples_y)
    X_wrapper2 = selector2.fit_transform(over_samples_x, over_samples_y)
    once1=cvs(LR_1, X_wrapper1, over_samples_y, cv=5, scoring='f1').mean()
    once2=cvs(LR_2, X_wrapper2, over_samples_y, cv=5, scoring='f1').mean()
    score1.append(once1)
    score2.append(once2)
plt.plot(range(1,12,1),score1, 'r')
plt.plot(range(1,12,1),score2, 'g')
plt.show()

 

至此,特征工程到此结束,从业务层面和算法方面进行特征提取。

 

4.模型开发与评估

 

4.1 决策树

 

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=999)
over_samples = SMOTE(random_state=111)
over_samples_x_train, over_samples_y_train = over_samples.fit_sample(x_train,y_train)
over_samples_x_test, over_samples_y_test = over_samples.fit_sample(x_test,y_test)
dtc=DTC(splitter='random', random_state=222).fit(over_samples_x_train, over_samples_y_train)
cvs(dtc, over_samples_x_train, over_samples_y_train, cv=10, scoring='f1').mean()

 

运用决策树算法,得到交叉验证的准确率均值为0.7777491677294854

 

4.2 随机森林

 

随机森林算法选取了部分数据建立了多个决策树,即使有个别决策树会因为异常值的影响导致预测不准确,但预测结果是参考多个决策树得到的结果,降低了异常值带来的影响。因此随机森林的算法往往好于决策树。

 

rfc = RFC(n_estimators=200,random_state=90).fit(over_samples_x_train,over_samples_y_train)
score_pre = cvs(rfc,over_samples_x_train,over_samples_y_train,cv=5, scoring='f1').mean()
score_pre

 

0.8429840630249139

 

超参数n_estimators即是数目的棵树,对随机森林模型的精确性影响是单调的,n_estimators越大,模型的效果往往越好。但是相应的,任何模型都有决策边界,n_estimators达到一定的程度之后,计算量增大,随机森林的精确性往往不再上升或开始波动。

 

scorel = []
for i in range(0,200,10):
    rfc = RFC(n_estimators=i+1,
                                 n_jobs=-1,
                                 random_state=90)
    score = cvs(rfc,over_samples_x_train, over_samples_y_train,cv=5, scoring='f1').mean()
    scorel.append(score)
print('最⾼score:',max(scorel))
print('最优n_estimators:',(scorel.index(max(scorel))*10)+1)
#plt.figure(figsize=[20,5])
plt.plot(range(1,201,10),scorel)
plt.show()

 

这里我使用网格搜索法对超参数最佳取值进行分析。

 

param_grid = {
 'n_estimators':np.arange(0, 200, 10),
'max_depth':np.arange(1, 20, 1),
'criterion':['gini', 'entropy']}
rfc = RFC(n_estimators=150,random_state=90, n_jobs=-1)
GS = GridSearchCV(rfc,param_grid,cv=5, scoring='f1')
GS.fit(over_samples_x, over_samples_y)
GS.best_params_
GS.best_score_

 

网格搜索后,得到的准确率降低。与其这样,还不如使用函数的默认值。

 

至此,整个数据分析处理过程至此结束,希望对大家学习数据分析挖掘有一定的帮助。本人也是小白,文中如果存在异议,欢迎大家指正,共同进步。

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注