使用机器学习构建简单金融风控反欺诈模型(一)EDA+XGBOOST

使用机器学习构建简单金融风控反欺诈模型(一)EDA+XGBOOST

ReLuQ

ReLuQ

交叉学科就像交叉特征一样有趣

已关注

7 人赞同了该文章

本篇带来的是如何在金融风控领域应用我们的机器学习来识别一些风险
仅仅作探索性的尝试,可以当作是一个简单的baseline吧

概念

首先了解一下什么是金融风控中的反欺诈,由于笔者目前就职于的公司是一家大型的P2P金融互联网公司(top的那几家),那么我们公司有理财,信贷,保险,货基等等业务。就拿借贷来说,简而言之就是借钱给有需要的人,类似于银行贷款。

风险来源

现在市面上有这么一些人,俗称黑产灰产。他们会做啥呢?骗贷。他们会使用一些渠道获得一些人的真实信息或者说伪造一些身份信息来进行接待尝试,一旦企业的风控部门没有识别出来这是一个可疑的危险的账号所发起的借贷行为而真真正正的借出了这笔钱,那么可能这笔钱他就再也拿不回来了。

我们知道现在的审核一般会有要求三要素,四要素等等。简单来说就是公民个人的重要信息比如姓名,身份证号,银行卡,地址,电话号码这些。而黑灰产所要做的就是使用虚假我这说其他人的这些信息来进行骗贷行为。而对于公司的风控部门,需要的就是通过一些数据方法和手段识别这些信息是否可靠,这笔贷款是否可借。

大概类似这种(简单点就是使用虚假信息骗别人借钱给他):

图片来源于网络,侵删

进入正题吧

当然啦,风控是十分宽泛的概念,需要的技术有很多,我们不谈金融行业的经验和知识,但从书绝学的角度,可能涉及到关联模型,知识图谱,用户画像,机器学习等等。一个完善的只能风控可能类似这样:

图片来源于网络,侵删

我们今天不会涉及那么全面深入,仅仅举一个例子,来将机器学习应用到风控中。

OK我们的任务是,通过以往的借贷以及还款情况数据,来预测一个新来的用户是否可能存在潜在的逾期或者干脆不还钱的行为

数据集

借贷数据这种非常非常敏感又核心的数据拿到真实数据那是不存在的,因此。本片所使用的数据来源于美国一家非常著名的P2P公司Lending Club所开源出来的,可以通过链接:

LendingClub Statistics​www.lendingclub.com图标

下载这些数据,笔者所使用的是他们提供的2018年Q1,Q2,Q3,Q4四个季度的借贷及还款情况数据。那么接下来就是正题了。让我们先看一下数据长啥样吧!

数据格式

首先还是老做法,用pandas打开四个季度的CSV文件,并且我们看一下他们的columns的情况以防止出现一些错误,比如说列明不同的情况:

import pandas as pd

df1 = pd.read_csv("20181.csv",skiprows=1,low_memory=False)
df2 = pd.read_csv("20182.csv",skiprows=1,low_memory=False)
df3 = pd.read_csv("20183.csv",skiprows=1,low_memory=False)
df4 = pd.read_csv("20184.csv",skiprows=1,low_memory=False)

比较一下列:

df1.columns.tolist() == df2.columns.tolist() == df3.columns.tolist() == df4.columns.tolist()

OUT:True 

我们发现四个文件所包含的列是相同的,那么接下来我们把四个dataframe纵向拼接到一起,并看一看合成的数据集的基础信息:

train = pd.concat([df1,df2,df3,df4],axis = 0)
train.info()
train.head()

我们可以看到一共包含了495250条数据,145列,其中107列类型为float64,其余为Object

从图中我们就可以看到,id,member_id两列数据值均为NAN,思考应该是为了脱敏。那么这两列可以说是没用的数据,直接删掉。

train = train.drop(["id","member_id"],axis=1,inplace=True)

好啦,其实上面异步可以不做的2333,因为我们现在首先要做的就是处理缺失值,首先对于某一行或者某一列数据全都是NAN的情况,想都别想直接删了:

train = train.dropna(how="all",axis=1)
train = train.dropna(how="all",axis=0)

接下来处理处理缺失值,如何做呢?对于缺失值的处理,自古以来又比较多的方法,但是也需要分情况使用:

  1. 缺失值占多数或半数:直接删除
  2. 数据缺失不多,可以采用众数或平均数或0补全
  3. 数值型离散数据确实不多的,可以采用一些回归方法来根据其他特征预测缺失的特征,比如random forest就可以被用来做这项任务

OK,笔者比较粗暴,对于缺失值在10W以上的数据,全部删除,10W以下的在分情况处理:

drop_list = (train.isnull().sum()>100000)
for i in drop_list.keys():
    if drop_list[i] == True:
        train.drop([i],axis=1,inplace=True)

接下来我们把数值型特征先放一边,看一看Object类型的,先看看这些变量的唯一取值有多少:

for col in train.select_dtypes(include=['object']).columns:
    print ("Column {} has {} unique instances".format( col, len(train[col].unique())) )

我么可以看到如下:

一共25个,仔细分析一下其中的数据,我们发现emp_title这个字段的取值有129450,我们查看网站上提供的字段说明可以知道这是借款人目前的职业,怪不得这么多,应该还有一些重复比如(teacher和Teacher),这么多的值依赖作数据清洗修复需要大量的时间而来存在太多类型没办法进行一些编码(太过稀疏),因此我们目前不使用这个特征,删除之

之后我们关注到字段int_rate,这个字段的是这笔贷款还款的利息是多少,他共有110个值,我们需要对他做去百分号处理,因为原始数据是以类似5.0%这样的格式显示的(过程略,用replace("%","").astype("float"))即可

接下来是addr_state字段,这个字段表明的是借款人来自于哪一个国家,依照敏感这是一个比较有意义的字段(不是地图炮哈),我们可以将其作one-hot编码使用它

看到另一个字段loan_status,这个字段实际上也就是这笔借款目前的状态啦,它包含这样几种取值:

  1. Current:分期还款流程中
  2. Fully Paid :已经还清
  3. Late (31-120 days) :逾期超过31天
  4. Charged Off:已注销
  5. Late (16-30 days) :逾期超过16天
  6. In Grace Period:在宽限期
  7. Default:默认

由于我们需要预测的是用户是否会不还钱或者逾期,因此我们的正样本实际上就是Late (31-120 days)和Late (16-30 days),我们新建一列label存储这个字段:

def getlabel(x):
   if x == "Late (16-30 days)" or x == "Late (31-120 days)":
       return 1
   else:
       return 0

train["label"] = train.loan_status.apply(lambda x:getlabel(x))

在看看其他的字段比如 zip_code,这个字段表明借款人所在地的邮政编码,由于我们已经有了国家信息了,邮政编码对于地球来说是个太过于细化的概念并且他又将近900中取值很难做编码处理,而聚类编码有看上去没耗费时间有没有太大作用,因此删除

字段earliest_cr_line 是一个有用的字段,我无法准确的翻译他的意思,大致就是说借款人银行流水(?)或者征信(?)记录是从多久开始有的,很容易思考如果记录从很早以前开始那么可能这个人年龄较大或者说一直有良好的财政进出,相反可能这个人在银行视角里不是那么的清晰,那么风险也可能会比较大。

他的数据是类似于这样的格式:

月份-年份

同时数据集里还有几个表示时间的字段也是用这种格式记录的,他们有:

  1. last_pymnt_d:最近一次还款时间
  2. next_pymnt_d:下一次还款时间
  3. last_credit_pull_d:这笔贷款最后一次被lending club评估的时间(不知道这么理解对不对)
  4. issue_d:这笔贷款发起的时间

那么我们如何处理时间数据?

由于笔者对于这份数据的很多字段也搞不清,因此在模糊测试阶段使用了如下几种方式:

  1. 将时间中的月份和年份拆分开并单独存储成一列,后进行one-hot编码
  2. 将事件中的年份计算到今年(2019)的距离
  3. last_credit_pull_d,last_credit_pull_d,last_pymnt_d三个数据两辆做差值求出相差得时间间隔

大致代码如下:

首先对缺失值,我们填充以出现最多的值,用last_credit_pull_d举例:

train.last_credit_pull_d = train.last_credit_pull_d.fillna("Feb-2019")

拆分月份和年份:

train["last_credit_pull_d_month"] = train.last_credit_pull_d.apply(lambda x:x.split("-")[0])
train["last_credit_pull_d_year"] = train.last_credit_pull_d.apply(lambda x:x.split("-")[1])

月份转化成数字:

def getmonth(x):
    if x == "May":
        return 5
    if x == "Oct":
        return 10
    if x == "Aug":
        return 8
    if x == "Jul":
        return 7
    if x == "Apr":
        return 4
    if x == "Nov":
        return 11
    if x == "Dec":
        return 12
    if x == "Sep":
        return 9
    if x == "Mar":
        return 3
    if x == "Jan":
        return 1
    if x == "Feb":
        return 2
    if x == "Jun":
        return 6

train["last_credit_pull_d_month"] = train.last_credit_pull_d_month.apply(lambda x :getmonth(x))

差值处理:

train["loan_last_start"] = train["last_credit_pull_d_month"] - train["loan_month"] + 12 * (train["last_credit_pull_d_year"]-train["loan_year"])

PS:这里只是示例代码,其他时间字段按照同方法处理即可

再看这个字段:emp_length 它表明了借款人迄今为止工作了多少年,数据分布如下:

这也是一个很有用的维度因为一般来说工作越长的人金钱积累和收入都可能更高,偿还能力夜可能更好,我们将其转化为数值型:

train.emp_length = train.emp_length.fillna("10 years")
def getwork(x):
    if "+" in x:
        return 10
    else:
        if "<" in x:
            return 0
        else:
            return int(x.split(" ")[0])
train["work_long"] = train.emp_length.apply(lambda x : getwork(x))

然后我们将原有的不需要的字段全部删除,包括右边,职业,原始保存的时间格式等等

train.drop(["issue_d","last_credit_pull_d","last_pymnt_d","loan_status","issue_d"],axis=1,inplace=True)

我们看一下现在的数据情况

train.info()

可以看到目前一共99个特征维度,一个标签列。看上去整整齐齐也没有大规模的数据缺失。那么一下步我们就要进行一些少规模数据缺失的处理

由于在经过以上的处理后,我们所生下包含缺失值的列均为数字型,因此我们分情况将每一列的数据分布和取值都看一看,在决定使用众数,平均数还是0进行填充即可

补全代码都是通用的,类似这种:

train.colname = train.colname.fillna(train.colname.mean())

最后一步,one-hot编码,我们使用pandas.get_dummies()一步到位

train = pd.get_dummies(train)

最后我们的数据情况如下:

接下来就是初步的模型训练啦,模型选用强大的xgboost,参数现场是通用参数:

我们首先把数据集的标签和特征分开,在使用train_test_split随机划分为训练集和测试集:

y  = train.label
x = train.drop(["label"],axis=1,inplace=True)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=666)

构建XGB参数,并使用训练集进行训练,在测试集上测试

clf2 = xgb.XGBClassifier(n_estimators=500, max_depth=3, 
                            learning_rate=0.01, subsample=0.8, colsample_bytree=0.3,scale_pos_weight=3.0, 
                             silent=True, nthread=-1, seed=0, missing=None,objective='binary:logistic', 
                             reg_alpha=1, reg_lambda=1, 
                             gamma=0, min_child_weight=1, 
                             max_delta_step=0,base_score=0.5)

clf2.fit(x_train, y_train)
print (clf2.score(x_test, y_test))

最终结果:

总结

风控其实是一个很有意思的事情。

我认为只有金融,机器学习和安全结合在一起,才能做到更加安全的风控系统和更强的反欺诈能力。

安全方面的IP,Phone,User-Agent,请求频率配合业务方的真实线上数据;

风控和金融人员的专业经验结合机器学习和统计学习甚至深度学习;

才是风控越走越远的法门。

编辑于 2019-03-12

机器学习

大数据风控

业务安全

​赞同 7​​6 条评论

​分享

​收藏

文章被以下专栏收录

机器学习于安全风控领域的应用实践

 

机器学习于安全风控领域的应用实践

已关注

6 条评论

​切换为时间排序

写下你的评论...

 

 

发布

  • 吴三岁

    吴三岁8 小时前

    代码好多错误之处啊 运行不了 怎么回事

    ​赞​回复​踩​举报

  • ReLuQ

    ReLuQ (作者) 回复吴三岁6 小时前

    因为是简略版的代码 不是完整版的~

    ​赞​回复​踩​举报

  • 吴三岁

    吴三岁回复ReLuQ (作者)1 小时前

    你好 能发我一下完整版的代码吗 我想试一下

    ​赞​回复​踩​举报

  • 吴三岁

    吴三岁回复ReLuQ (作者)1 小时前

    谢谢

    ​赞​回复​举报

  • ReLuQ

    ReLuQ (作者) 回复吴三岁19 分钟前

    代码已经上传https://github.com/0FuzzingQ/fengkong可能有一些问题因为有地方是反复运行的,不过问题应该不大~写的比较丑

     

    ​赞​回复​举报

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值