小白也能搞懂风控模型分数(附案例和代码)

欢迎各位同学学习python金融风控评分卡模型和数据分析微专业课

腾讯课堂报名入口

网易云课堂报名入口

小白也能搞懂风控模型分数(附案例和代码)

 

目前在个人信用风控建模领域主要使用的模型有两种:

  1. 基于线性回归分析的评分卡模型,常见于银行背景的放贷机构;

  2. 基于大数据的机器学习模型,如随即森林、XGBoost等,常见于互联网金融领域。

 

这两种建模方法无论哪种都需要将模型结果映射到信用分数空间,最终呈现给用户一个能够表征信用的分数,比如国内的芝麻分,美国的FICO分。

 

那么问题来了,模型结果和分数同样都是数值,为什么不直接呈现模型结果?为什么还要进一步转化为分数?为什么使用分数校准?

 

本文的目的就是要让你知其然也知其所以然。

 

深入到业务中,带你一步步搞懂分数映射产生的背景以及分数校准背后的业务需求。在了解了业务之后,你就会发现分数的意义和映射方法的内在逻辑。

 

 1.分数映射的产生——一款产品一个模式


先从最简单的情况入手,假设我们现在成立了一家小信贷公司,推出一款借贷产品,详情如下: 

小白也能搞懂风控模型分数(附案例和代码)

(该产品数值仅作示意,不代表真实情况)

 

此时我们要解决的第一个问题是:如何让这款产品盈利?

 

信贷业务能够盈利的关键是还款人的利息能够覆盖坏账损失(不能收回的各种应收款项)。

 

也就是说要尽可能的把钱借给信用良好、按时还款的用户以保证产品盈利。

 

在产品参数确定后,逾期率是保证产品盈利的唯一影响因素。

 

这里要提一下关键的一点:如何定义逾期率?

 

在信贷领域通常逾期超过60天的用户基本上就不会再还钱了,自然的我们就可以通过逾期超过60天的比例来衡量用户的好坏程度。

 

在机器学习建模时,有时为了增加坏账样本的数量,也会将逾期超过30天的比例定义为逾期率。

1.逾期率多久可以忍受

根据信贷业务盈利的逻辑,我们可以得到下面这个盈亏平衡的公式:

小白也能搞懂风控模型分数(附案例和代码)

其中:

L —— 表示额度

InterestRate —— 表示贷款年化利率

FundsRate —— 表示资金成本年化利率

pd 表示逾期率(Probability of Default)

OperatingCost —— 表示运营成本

 

NewCost —— 表示拉新成本,指获取单个新客户所支付的金额。拉新成本的定价要结合营销方案具体问题具体分析。

 

举个简单的例子,若从渠道购买流量,购买金额除以该渠道的新客户数量就是该渠道的新客成本。

 

再比如举办拉新奖励活动,成功拉取一个新用户所需要支付的奖励金额也是拉新成本。

 

总之拉新成本的计算要围绕实际引流方案、投入来确定。

 

将产品详情中数值带入公式后计算得到 pd = 0.015 ,即盈亏平衡点。

2.如何确定模型稳定性是否发生了变化

 

什么是阈值cutoff?

 

我们先看一看真实的模型打分结果长什么样子,这里用的是XGBoost模型:

小白也能搞懂风控模型分数(附案例和代码)

 

 

label —— 样本真实类别,0表示正常还款, 1 表示逾期

predict —— 模型预测值

对于二分类问题的模型,预测结果是一个0~1的小数,最终该观测点被判定为哪个类别是由阈值所决定的:p >= cutoff 表示1, p < cutoff 表示0。

 

这里有个小问题:逾期率和模型结果的值域都是[0, 1],那么模型预测值等于逾期率这个假设成立吗?

 

我们做一个简单的统计,如下:

 小白也能搞懂风控模型分数(附案例和代码)

从均值、分位点的结果上看他们并不能直接划等号。

 

理论上来说Linear Regression模型的输出概率可以认为是真实概率,而其他分类器的输出概率并不反映真实概率。

 

接下来我们需要将模型预测结果先排序,再分箱计算各个桶的逾期率,找到满足盈亏平衡点要求的预测结果阈值,低于阈值通过贷款申请,反之则拒绝贷款申请。

 

```python

def get_cutoff(data, bin_size, fpd):

    """

 

    第一步:确定分箱箱数    

    第二步:按照predict分数排序,划分bins    

    第三步:计算各个bins的逾期率,找到逾期率满足盈亏平衡点的分界线 

 

  :param data: Dataframe类型,包含 label(是否逾期), predict(模型预测值)两列数据

    :param bin_size: 分箱个数

    :param pd: 盈亏平衡点的逾期率

    :return:

    """

    bin_width = round((max(data['predict']) - min(data['predict'])) / bin_size, 6)

    bin_lst = [min(data['predict']) + bin_width * i for i in range(bin_size + 1)]

    bin_lst[0], bin_lst[-1] = 0, 1

    data['bin'] = pd.cut(data['predict'], bin_lst)

    bin_group = data.groupby('bin')['label'].agg([ 'mean']).reset_index()

    print(bin_group)

    cutoff = None

    for i in range(20):

        if bin_group.iloc[i]['mean'] > fpd:

            cutoff = bin_group.iloc[i]['bin'].left

            break

    return cutoff

```

 

将上一环节计算出的盈亏平衡点0.015作为参数代入函数,返回cutoff值。

小白也能搞懂风控模型分数(附案例和代码)

 

 

 

根据打印的分箱结果,可以看到在(0.0299, 0.0327) 处,逾期率为0.015512,是第一个逾期率超过盈亏平衡点0.015的分箱。

 

因此把cutoff定为 0.0299,以保证拒绝掉不满足盈亏平衡点的用户。

如何向客户展示信用评估结果?

 

至此,你的小公司将面临几种选择:

 

A. 直接告诉用户贷款申请结果呢?通过或拒绝

这样做不是不可以,但给用户的感觉有点霸道了(是不是很像申请信用卡的感觉,等了半个月被通知审核失败),这不是一个追求用户体验的互金小公司该有的服务态度。

 

B.把模型预测值所在的分箱逾期率作为信用评估结果反馈给用户呢?

这样做也不是不可以,但设身处地的站在用户角度想一想会怎样?

  1. 用户会不会产生疑问:

  2. 这小数点后这么多位的数值到底是个啥?

  3. 高了好还是低了好?

  4. 从小到大我只见过1星到5星,0分到100分......

 

通常真实逾期率在小数点2位以后有所差别,显然把逾期率反馈给用户是不够专业的。这还没考虑是不是暴露了商业机密的问题,比如客群质量,定价等。

把模型预测值映射到一个分数区间,比如350~950,分数越高信用越好。

 

很自然的我们就走到了这个最佳选择,至于分数为什么定义在这个区间,我个人理解一是为了跟国际上主流的个人信用评分区间接轨;二是为了拉开用户之间的分数差距。

 

如何映射分数

 

最简单最容易想到的是采用尺度变化,将模型预测结果线性的映射到350~950,然后找到cutoff 对应的分数X,告诉用户分数高于X就可以拿到贷款。

 

等一下!如果过段时间模型更换了怎么办?

 

模型换了,cutoff随之变化,分数X也跟着变了。这时就会有一部分用户疯狂的呼叫客服问“为啥我的评分变高了,反而不能贷款?”

 

同时每次更换模型,后台、前端都需要进行相应的逻辑、页面修改,每次模型发布需要多个环节协作完成,是一种高耦合的工作方式。

 

于是你赶紧召集团队成员讨论解决方案,有人提出一个又简单又好用的方案:分段尺度变换,将cutoff 固定为 680分(本文假定的)然后分成两段作尺度变换。

 

总结分段尺度变换的优点:

 

  1. 模型切换用户无感

    无论模型的cutoff如何调整,用户感知不到差别,只要分数超过680都可以成功申请到贷款;

  2. 解耦了模型团队与开发团队

    也就是说当模型人员校准好评分后,后台开发只需要设定680分通过,从此以后无论模型人员怎么更换模型,后台开发都不用再重新修改代码。

 

```python

 

predict_desc = table.describe()['predict']

# 这里用几倍标准差确定上下界根据经验设定,是为了避免outlier值使得分数过于集中在某个范围

 

upper = min(predict_desc['mean'] + 5 * predict_desc['std'], 1)

lower = max(predict_desc['mean'] - 3 * predict_desc['std'], 0)

def get_score_linear(predict, upper, lower, cutoff):

    """

 

将模型打分结果尺度变换到350~950的分数区间

 

 :param predict: float, 模型打分

 :param upper: 模型打分上界

 :param lower: 模型打分下界

 :param cutoff: 680分位置对应的模型打分

 :return:

    """

   if predict > upper:

        return 350

   elif predict > cutoff:

        return 680 - int((680 - 350) * (predict - cutoff) / (upper - cutoff))

   elif predict == cutoff:

        return 680

   elif predict > lower:

        return 680 + int((cutoff - predict) * (950 - 680) / (cutoff - lower))

   else:

        950 ```从此,客服终于可以静静了。 随着业务的发展,你开始思考如何扩大利润,现有的业务模式是否存在不合理的地方,业务逐渐向精细化的方向发展。

 

 

2
客群分层下的分数映射

老用户分层

 

敏锐的你发现,随着用户的复贷次数增多,原有盈亏平衡点的计算方式会使得拉新成本被计算了多次(用户复贷几次就多计算几次)。

 

这也就引出了用户分层的一种场景:区分新老客户,分别建模,分别计算盈亏平衡点。

 

传统的评分卡中A卡B卡也有异曲同工之处:

 

  • A卡用于贷前审批阶段对借款申请人的量化评估,是使用最广泛的;

  • B卡用于贷中阶段,增加了借款人的还款及交易行为,预测借款人未来的还款能力和意愿;

 

综合分析新老用户分别建模的特点:

 

  1. 模型更准确

    老客户建模引入了历史还款行为、交易行为,使得模型更准确。

  2. 老用户的成本更低

    拉新成本归属于边际成本,当新用户转化为老用户后,每多一次复贷都会将拉新成本摊平。那么就可以将拉新成本算在新用户上,老用户的成本得以降低,对逾期率有更大的容忍度;

  3. 提高复贷率,提高盈利

    修正老客户的盈亏平衡点,可以提高老客户的通过率,从而提高复贷率,提高盈利。

 

那么我们再重新计算以下老用户的盈亏平衡点: 

小白也能搞懂风控模型分数(附案例和代码)

 

 

 

从此,客服终于可以静静了。 随着业务的发展,你开始思考如何扩大利润,现有的业务模式是否存在不合理的地方,业务逐渐向精细化的方向发展。

 

 

2
客群分层下的分数映射

老用户分层

 

敏锐的你发现,随着用户的复贷次数增多,原有盈亏平衡点的计算方式会使得拉新成本被计算了多次(用户复贷几次就多计算几次)。

 

这也就引出了用户分层的一种场景:区分新老客户,分别建模,分别计算盈亏平衡点。

 

传统的评分卡中A卡B卡也有异曲同工之处:

 

  • A卡用于贷前审批阶段对借款申请人的量化评估,是使用最广泛的;

  • B卡用于贷中阶段,增加了借款人的还款及交易行为,预测借款人未来的还款能力和意愿;

 

综合分析新老用户分别建模的特点:

 

  1. 模型更准确

    老客户建模引入了历史还款行为、交易行为,使得模型更准确。

  2. 老用户的成本更低

    拉新成本归属于边际成本,当新用户转化为老用户后,每多一次复贷都会将拉新成本摊平。那么就可以将拉新成本算在新用户上,老用户的成本得以降低,对逾期率有更大的容忍度;

  3. 提高复贷率,提高盈利

    修正老客户的盈亏平衡点,可以提高老客户的通过率,从而提高复贷率,提高盈利。

 

那么我们再重新计算以下老用户的盈亏平衡点:

小白也能搞懂风控模型分数(附案例和代码)

 

 

 小白也能搞懂风控模型分数(附案例和代码)

 

 

 

带入公式计算得到盈亏平衡点 pd = 0.0217,在代码中求得cutoff = 0.047。

 

同理,采用分段尺度变换方法可以将模型结果进一步映射到信用分数空间,这里不再赘述。

 

此时的你又开始思考如何做大做强。 

为了吸引更多用户,留住优质客源,你跑断了腿找到一个新的资本方,愿意以更低的资金成本给你提供资金来源。

 

于是你终于可以上线一款新产品:更低的利率给更好的你。

 

至此业务进入了多产品轨道。

 

用户质量分层以匹配不同利率产品

 

除了按照新老客户性质分层,还可以按照用户质量来划分用户。

 

将老用户分为优质用户和次优用户,这样做的目的是为优质用户匹配优质产品,更低利率或更高额度。

 

按照这个思路就可以将用户分为以下四个层次: 

   

 A -- 老用户中的优质用户   资金成本0.02 利率 0.22 额度 5000    

 B -- 老用户中的次优用户   资金成本0.04 利率 0.34 额度 3000     

 C -- 新用户中的优质用户   资金成本0.02 利率 0.30 额度 3000    

 D -- 新用户中的次优用户   资金成本0.04 利率 0.34 额度 1000   

 

只以A、B类用户举例,同理带入公式计算盈亏平衡点:

小白也能搞懂风控模型分数(附案例和代码)

得到:

A类用户盈亏平衡点 pd = 0.01467

B类用户盈亏平衡点 pd = 0.02167

 

现实业务中可以将ABCD四类用户评级结果直接反馈给用户,比如我们常看到的黄金会员、钻石会员,本质上是类似的;

 

也可以按照分段尺度变换的思路将分数划分多段,例如:

小白也能搞懂风控模型分数(附案例和代码)

 

 

 

代码可以参考前面二分段尺度变换的代码,增加对应的判断分支即可。

 

随之而来的问题也就出现了:

 

  1. 如果产品越来越丰富,变换额度、利率,所需要计算的盈亏平衡点越来越多,那怎么办呢?

    再使用分段尺度变换这种简单粗暴的方法就显得越来越笨拙了,也增加了出错的概率。

  2. 当多个模型共同工作或切换备用模型时,如何保证分数尺度一致?

    这里尺度一致的意思是,两个模型分数相同时对应的逾期率是否一致。

 

3
 多产品、多客群、多模型下数映射的产生——分数校准

 

为了能够更好的进行风险定价,业务部门希望模型给出的分数能够准确的反映出真实的信用风险等级。

 

这一过程称为分数校准(Score Calibration)

 

也就是说,我们最终的目标是:建立信用评估分数与预测逾期率的函数关系

 

这里介绍一种常用的分数校准方法,通过该方法,可以由分数精准的计算出预测逾期率,反之亦可。

 

建立Odds与分数的函数关系

 

熟悉业务的同学会知道,建模时逾期样本非常少,更多的是信用良好的样本。

 

原因我认为有两点:

 

  1. 现实中好人是大多数的,这点不多解释。

  2. 模型长期筛选的作用,通常我们已经得到还款结果的样本是已经通过上一轮模型的一批样本,坏用户已经被上一轮模型过滤掉大部分了。

 

因此,逾期率与用户数量是符合幂律分布的。也就是说随着逾期率的升高,用户数量呈指数下降。

 

进而在设计Odds与分数的函数关系时,对Odds取了一个log,再结合简单的线性方程,加入截距A和斜率B进行线性拟合:
小白也能搞懂风控模型分数(附案例和代码)

 

 

 小白也能搞懂风控模型分数(附案例和代码)

 

 

 求解方程组计算得到A,B

小白也能搞懂风控模型分数(附案例和代码)

 

 

 小白也能搞懂风控模型分数(附案例和代码)

 

上面这个公式就是分数与逾期率的函数关系,我们可以进一步把分数、Odds、逾期率的对照关系计算出来:
仔细观察我们会发现,随着逾期率PD越来越小,Odds其实近似等于逾期率,这就更方便业务团队进行风险定价。

 

建立模型结果与Odds的函数关系

 

 小白也能搞懂风控模型分数(附案例和代码)

 

 

  •  对于机器学习模型来说这里变量x可以是一个或多个模型预测结果;

  •  对于评分卡来说变量x就是原始特征;

 

从模型结果到Odds是一个线性回归过程。

 

至此,我们就完成了一个标准的分数校准。

 

 

4
 总结

 

我们回顾一下,随着产品种类,模型数量的丰富,分数映射逐渐有了更多的要求以适应业务的需求和团队协作的高效。

 

总结一下每个阶段的优缺点:

小白也能搞懂风控模型分数(附案例和代码)

 

从均值、分位点的结果上看他们并不能直接划等号。

 

理论上来说Linear Regression模型的输出概率可以认为是真实概率,而其他分类器的输出概率并不反映真实概率。

 

接下来我们需要将模型预测结果先排序,再分箱计算各个桶的逾期率,找到满足盈亏平衡点要求的预测结果阈值,低于阈值通过贷款申请,反之则拒绝贷款申请。

 

```python

def get_cutoff(data, bin_size, fpd):

    """

 

    第一步:确定分箱箱数    

    第二步:按照predict分数排序,划分bins    

    第三步:计算各个bins的逾期率,找到逾期率满足盈亏平衡点的分界线 

 

  :param data: Dataframe类型,包含 label(是否逾期), predict(模型预测值)两列数据

    :param bin_size: 分箱个数

    :param pd: 盈亏平衡点的逾期率

    :return:

    """

    bin_width = round((max(data['predict']) - min(data['predict'])) / bin_size, 6)

    bin_lst = [min(data['predict']) + bin_width * i for i in range(bin_size + 1)]

    bin_lst[0], bin_lst[-1] = 0, 1

    data['bin'] = pd.cut(data['predict'], bin_lst)

    bin_group = data.groupby('bin')['label'].agg([ 'mean']).reset_index()

    print(bin_group)

    cutoff = None

    for i in range(20):

        if bin_group.iloc[i]['mean'] > fpd:

            cutoff = bin_group.iloc[i]['bin'].left

            break

    return cutoff

```

将上一环节计算出的盈亏平衡点0.015作为参数代入函数,返回cutoff值。

 转载:https://mp.weixin.qq.com/s/D8XpNznxw0W9pAFnbSix_A

 

欢迎各位同学学习python信用评分卡建模视频系列教程(附代码, 博主录制) :

腾讯课堂报名入口

网易云课堂报名入口

 

小白也能搞懂风控模型分数(附案例和代码)

 

上一篇:简单线性回归


下一篇:【Leetcode】486. Predict the Winner