风控模型指标KS值详解与代码实现

1.KS是啥

一般的机器学习模型,常用来衡量模型的指标包括准确率,召回率,AUC,F1等。在风控领域,还有个常用的指标是KS值。

KS值的全称为Kolmogorov-Smirnov,中文名叫做洛伦兹曲线,Ks经常被用于模型风险区分能力进行评估, 指标衡量的是好坏样本累计分部之间的差值。好坏样本累计差异越大,Ks指标越大,那么模型的风险区分能力越强。

K-s曲线的数据来源和本质是与ROC曲线是一致的,只不过ROC曲线是将真正类率和假正类率作为横纵轴,K-s曲线则是把真正率和假正率都当作是纵轴,横轴为选定的阈值。

2.KS值理论分析

关于KS值相关的理论分析,知乎上已经有一篇文章介绍得比较详细,博主自认为短时间内无法完成一篇作品超过上文,因此不再打算再介绍理论部分,感兴趣的同学直接见参考文献1查阅原文。

为了方便查阅,将参考文献中计算KS值的步骤摘录出来。

计算KS值的步骤如下
1.对变量进行分箱(binning),可以选择等频、等距,或者自定义距离。
2.计算每个分箱区间的好账户数(goods)和坏账户数(bads)
3.计算每个分箱区间的累计好账户数占总好账户数比率(cum_good_rate)和累计坏账户数占总坏账户数比率(cum_bad_rate)。
4.计算每个分箱区间累计坏账户占比与累计好账户占比差的绝对值,得到KS曲线。也就是: ks=|cum_good_rate - cum_bad_rate|
5.在这些绝对值中取最大值,得到此变量最终的KS值。

3.代码实现计算KS值

下面我们来重点讲讲如何代码实现。以python为例,实现ks值的计算。

3.1 使用np.searchsorted方法

import numpy as np
import pandas as pd
from scipy.stats import ks_2samp


def ks_calc_2smap(data):
    good = data.loc[data['y_label'] == 1, 'pred']
    bad = data.loc[data['y_label'] == 0, 'pred']
    badvalues = bad.values
    goodvalues = good.values
    n1 = badvalues.shape[0]
    n2 = goodvalues.shape[0]
    badvalues = np.sort(badvalues)
    goodvalues = np.sort(goodvalues)
    values = np.concatenate([goodvalues, badvalues])

    cdf1 = n1 - np.searchsorted(goodvalues, values, side='right') / n1
    cdf2 = n2 - np.searchsorted(badvalues, values, side='right') / n2
    print("goodvalues: ", goodvalues)
    print("badvalues: ", badvalues)
    print("values: ", values)
    print("predict good and really good is: ", n1 - np.searchsorted(goodvalues, values, side='right'))
    print("predict good and really bad is: ", n2 - np.searchsorted(badvalues, values, side='right'))
    print()

    kslist = np.absolute(cdf1 - cdf2)
    ks = np.max(kslist)
    print("kslist is: ", kslist)

    index = np.where(kslist==ks)[0][0]
    print(f"index is: {index}")
    print(f"score is: {values[index]}")

    return ks



def run():
    data = {'y_label':[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
            'pred':[0.5, 0.6, 0.65, 0.55, 0.6, 0.8, 0.35, 0.2, 0.1, 0.4, 0.3, 0.7]}
    data = pd.DataFrame(data)
    ks = ks_calc_2smap(data)
    print(f"ks is: {ks}")

运行run方法,输出如下

goodvalues:  [0.5  0.55 0.6  0.6  0.65 0.8 ]
badvalues:  [0.1  0.2  0.3  0.35 0.4  0.7 ]
values:  [0.5  0.55 0.6  0.6  0.65 0.8  0.1  0.2  0.3  0.35 0.4  0.7 ]
predict good and really good is:  [5 4 2 2 1 0 6 6 6 6 6 1]
predict good and really bad is:  [1 1 1 1 1 0 5 4 3 2 1 0]

kslist is:  [0.66666667 0.5        0.16666667 0.16666667 0.         0.
 0.16666667 0.33333333 0.5        0.66666667 0.83333333 0.16666667]
index is: 10
score is: 0.4
ks is: 0.833333333333333

逐步解释一下代码思路:
1.根据data中的label,分成good, bad两部分人。
2.对good, bad两拨人的分数排序,并将其进行concatenate操作。
3.重点理解cdf1 = n1 - np.searchsorted(goodvalues, values, side='right') / n1这一行代码。
goodvalues是一个有序数组,np.searchsorted方法的作用是,遍历values中的每一个值,假设这个值为score, score在goodvalues中如果插入,将位于哪个index。那么这个index之后的人分数都将大于score,将会被预测为好人,所以goodvalues这一组人中被预测为好人的数量为n1 - index,即cdf1那行代码。
4.cdf2那行代码同理。
5.ks的值为|cdf1 - cdf2|中的最大值。
6.模型对应的最佳阈值为values[index],此时能取到最大的ks值。

2.使用透视表crosstab

创建透视表crosstab方法用来求ks值非常方便,也很直观,下面我们来看看。

def ks_calc_cross(data):
    crossfreq = pd.crosstab(data['pred'], data['y_label'])
    print(crossfreq, "\n")
    print(crossfreq.cumsum(axis=0), "\n")
    crossdens = crossfreq.cumsum(axis=0) / crossfreq.sum()

    crossdens['gap'] = abs(crossdens[0] - crossdens[1])
    print(crossdens, "\n")
    print("ks value is: ", crossdens['gap'].max(), "\n")

    ks = crossdens[crossdens['gap'] == crossdens['gap'].max()]
    print("ks is: ", ks, "\n")
    print("ks value max score is: ", ks.index.values[0])


def run2():
    data = {'y_label':[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
            'pred':[0.5, 0.7, 0.6, 0.55, 0.6, 0.8, 0.35, 0.2, 0.1, 0.4, 0.3, 0.7]}
    data = pd.DataFrame(data)
    ks_calc_cross(data)

代码输出为

y_label  0  1
pred         
0.10     1  0
0.20     1  0
0.30     1  0
0.35     1  0
0.40     1  0
0.50     0  1
0.55     0  1
0.60     0  2
0.70     1  1
0.80     0  1 

y_label  0  1
pred         
0.10     1  0
0.20     2  0
0.30     3  0
0.35     4  0
0.40     5  0
0.50     5  1
0.55     5  2
0.60     5  4
0.70     6  5
0.80     6  6 

y_label         0         1       gap
pred                                 
0.10     0.166667  0.000000  0.166667
0.20     0.333333  0.000000  0.333333
0.30     0.500000  0.000000  0.500000
0.35     0.666667  0.000000  0.666667
0.40     0.833333  0.000000  0.833333
0.50     0.833333  0.166667  0.666667
0.55     0.833333  0.333333  0.500000
0.60     0.833333  0.666667  0.166667
0.70     1.000000  0.833333  0.166667
0.80     1.000000  1.000000  0.000000 

ks value is:  0.8333333333333334 

ks is:  y_label         0    1       gap
pred                            
0.4      0.833333  0.0  0.833333 

ks value max score is:  0.4

crosstab是计算分组频率的特殊透视表,我们先试用crosstab计算分组频率,然后通过cumsum方法,即可求得在各个阈值的累积频率然后完成ks值的计算。

3.3 ks_2samp方法

当然如果我们不想自己实现的话(大部分情况下也不会自己实现),可以直接调用scipy中的ks_2samp方法即可完成ks值的计算。

def run3():
    data = {'y_label':[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
            'pred':[0.5, 0.7, 0.6, 0.55, 0.6, 0.8, 0.35, 0.2, 0.1, 0.4, 0.3, 0.7]}

    data = pd.DataFrame(data)
    ks = ks_2samp(data[data['y_label'] == 1]['pred'], data[data['y_label'] != 1]['pred'])
    print(ks)
    print(ks.statistic)
Ks_2sampResult(statistic=0.8333333333333334, pvalue=0.025974025974025972)
0.8333333333333334

关于ks_2samp方法,可以多说几句,其输出了两个值,一个是statistic,就是我们想求的ks值,还有一个pvalue。

这个pvalue其实对应的是KS检验,KS检验用来检测单样本是否服从某一分布,或者两样本是否服从相同分布。
我们的原假设为:两个数据服从同一分布
我们的拒绝假设为:两个数据不服从同一分布

通过指定置信度水平,pvalue如果低于0.01或者0.05,此时可以拒绝原假设,即我们拒绝假设,此时两个数据来自于不同分布。

参考文献

1.https://zhuanlan.zhihu.com/p/79934510

猜你喜欢

转载自blog.csdn.net/bitcarmanlee/article/details/130814450
今日推荐