模型评价指标KS和PSI

概述#

风控指标千千万,三句话概括版本:

Confusion Matrix -> Lift,Gain,ROC。

ROC -> AUC,KS -> GINI。

MSE独立出来。

image-20200525172747965

image-20200525172805312

ROC曲线#

回顾混淆矩阵的TPR(True Positive Rate)和FPR(False Positive Rate)#

混淆矩阵,横着的P、N是预测结果阳性还是阴性。竖着的是说预测是否正确。

Positive Negtive
T TP TN
F FP FN
  • TP:预测为正向(P),实际上预测正确(T),即判断为正向的正确率

  • TN:预测为负向(N),实际上预测正确(T),即判断为负向的正确率

  • FP:预测为正向(P),实际上预测错误(F),误报率,即把负向判断成了正向

  • FN:预测为负向(N),实际上预测错误(F),漏报率,即把正向判断称了负向

  • 准确率Accuracy=(TP+TN) / (TP+FP+TN+FN), 即预测正确的比上全部的数据

  • 精确率、查准率 Precision=TP / (TP+FP),即在预测为正向的数据中,有多少预测正确

  • 召回率、查全率 Recall=TP / (TP+FN),即在所有正向的数据中,有多少预测出来

TPR:在所有实际为阳性的样本中,被正确地判断为阳性之比率。(就是召回率,正样本被召回的比例)
【金融里面,分母是所有的good_cnt】

Recall=TPR=TPTP+FNRecall = TPR = \frac{TP}{TP+FN}

FPR:在所有实际为阴性的样本中,被错误地判断为阳性之比率。(就是漏掉的阴性或判断错的阳性,占总阴性的比例)
【金融里面,分母是所有的bad_cnt】

FPR=FPFP+TNFPR = \frac{FP}{FP + TN}

更多关于TPR: /02_ml/01_THEORY/00_theory.ipynb#TPR(True-Positive-Rate)

ROC曲线#

在一个二分类模型中,假设采用逻辑回归分类器,其给出针对每个实例为正类的概率,那么通过设定一个阈值如0.6,概率大于等于0.6的为正类,小于0.6的为负类。对应的就可以算出一组(FPR,TPR),在平面中得到对应坐标点。随着阈值的逐渐减小,越来越多的实例被划分为正类,但是这些正类中同样也掺杂着真正的负实例,即TPR和FPR会同时增大。阈值最大时,对应坐标点为(0,0),阈值最小时,对应坐标点(1,1)。

如下面这幅图,(a)图中实线为ROC曲线,线上每个点对应一个阈值。纵坐标TPR,横坐标FPR。

image-20200525174307499

(a) 理想情况下,TPR应该接近1,FPR应该接近0。ROC曲线上的每一个点对应于一个threshold,对于一个分类器,每个threshold下会有一个TPR和FPR。比如Threshold最大时,TP=FP=0,对应于原点;Threshold最小时,TN=FN=1,对应于右上角的点(1,1)。
(b) P和N得分不作为特征间距离d的一个函数,随着阈值theta增加,TP和FP都增加。

  • 横轴FPR:1-TNR,1-Specificity,FPR越大,预测正类中实际负类越多。
  • 纵轴TPR:Sensitivity(正类覆盖率),TPR越大,预测正类中实际正类越多。
  • 理想目标:TPR=1,FPR=0,即图中(0,1)点,故ROC曲线越靠拢(0,1)点,越偏离45度对角线越好,Sensitivity、Specificity越大效果越好。

ROC曲线理解#

主要是关于阈值的理解
roc_curve里面有一个阈值数组,里面的原理:

  • 先按照概率值逆序排序
  • 计算排序后所有前后之间的概率差
  • 得到的差值数组里面有很多为0的(排序后前后概率相同),丢掉
  • 源码里还根据二阶导数判断拐点
  • 剩下的作为阈值数组
  • 以上面的阈值数组下标为界,数所有label为1,0的样本【累计值】(cumsum)为tps和fps数组(有权加权)
  • 数组tps除以最后一个tps[-1]得到tpr,fpr同理。(这里并没有判断概率值是否大于或者小于0.5,因为排序后此处这个概率值就是阈值,概率值大于这个阈值的里面,所有的+就是TP,所有的-就是FP。对应tps[i]和fps[i])
  • 这样计算出来的tpr 、fpr数组比原始样本数少很多(因为阈值的原因),具体理解如下

如果大家对二值分类模型熟悉的话,都会知道其输出一般都是预测样本为正例的概率,而事实上,ROC曲线正是通过不断移动分类器的“阈值”来生成曲线上的一组关键点的。可能这样讲有点抽象,还是举刚才雷达兵的例子。每一个雷达兵用的都是同一台雷达返回的结果,但是每一个雷达兵内心对其属于敌军轰炸机的判断是不一样的,可能1号兵解析后认为结果大于0.9,就是轰炸机,2号兵解析后认为结果大于0.85,就是轰炸机,依次类推,每一个雷达兵内心都有自己的一个判断标准(也即对应分类器的不同“阈值”),这样针对每一个雷达兵(样本输出),都能计算出一个ROC曲线上的关键点(一组FPR,TPR值),把大家的点连起来,也就是最早的ROC曲线了。

为方便大家进一步理解,本菇也在网上找到了一个示例跟大家一起分享【4】。下图是一个二分模型真实的输出结果,一共有20个样本,输出的概率就是模型判定其为正例的概率,第二列是样本的真实标签。

image-20200525195550264

  • 现在我们指定一个阈值为0.9,那么只有第一个样本(0.9)会被归类为正例,而其他所有样本都会被归为负例
  • 因此,对于0.9这个阈值,我们可以计算出FPR为0,TPR为0.1(因为总共10个正样本,预测正确的个数为1),那么我们就知道曲线上必有一个点为(0, 0.1)。
  • 依次选择不同的阈值(或称为“截断点”),画出全部的关键点以后,再连接关键点即可最终得到ROC曲线如下图所示。

image-20200525195618707

sklearn中的绘制原理:(SolveKS和roc_curve代码中)#

其实还有一种更直观的绘制ROC曲线的方法,这边简单提一下。:

  • 就是把横轴的刻度间隔设为1/N,纵轴的刻度间隔设为1/P,N和P分别为负样本与正样本数量。
  • 然后再根据模型的输出结果【概率降序】排列,
  • 依次遍历样本,从0开始绘制ROC曲线,每遇到一个正样本就沿纵轴方向绘制一个刻度间隔的曲线,
  • 每遇到一个负样本就沿横轴方向绘制一个刻度间隔的曲线,遍历完所有样本点以后,曲线也就绘制完成了。
  • 究其根本,其最大的好处便是不需要再去指定阈值寻求关键点了,每一个样本的输出概率都算是一个阈值了。当然,无论是工业界还是学术界的实现,都不可能手动去绘制,下面就来讲一下如何用Python高效绘制ROC曲线。

KS指标#

KS值定义#

模型的KS值#

  • 最理想的模型,是TPR尽量高而FPR尽量低(召回尽可能多的坏人,漏掉尽可能少的好人),然而任何模型在提高正确预测概率的同时,也会难以避免地增加误判率。
  • 我们训练出来的模型,一般不是直接给出是正类还是负类的结果,给的是为正类的概率,我们还需要选择一个阈值,实例通过模型得到的概率大于阈值,判断为正类,小于阈值判断为负类。也就是说阈值的不同,以上的各个指标的值也是不同的。每一个阈值对应一对TPR和FPR。把阈值看成自变量,以上TPR、和FPR看成因变量,在二维坐标系里面做FPTFPR|FPT-FPR|关系曲线,这就是KS曲线。

image-20200522165407594

  • KS曲线实操的时候是可以把将概率的阈值从小到大进行排序,取10%的值为间隔,
  • 同理将10%*k(k=1,…9)处值作为阈值,计算不同的FPR和TPR,
  • 以10%*k(k=1,…9)为横坐标,同时分别以TPR和FPR为纵坐标画出两条曲线就是KS曲线。
  • KS值是KS曲线的最大值,也就是TPR和FPR差异的最大点
KS值=max(|TPR-FPR|)
  • KS值是在模型中用于区分预测正负样本分隔程度的评价指标。
  • 需要计算每一箱的KS,然后max是在所有分箱的KS上取最大值
  • 一般来说,KS大比较好。但是也不是越大越好,尤其征信行业
  • 业内认为AUC更能体现模型的【整体的】区分能力,但是KS关注的是区分能力的最大值。
    • 我们做的是拒绝模型,关注的是最大值的点取在哪里的会做一个截断,小于这个值的都拒绝了。关注在最大值之前误杀了多少人。(相比于AUC注重局部而不是全局)

KS(Kolmogorov-Smirnov)计算步骤:#

  • KS用于模型风险区分能力进行评估,指标衡量的是好坏样本累计分部之间的差值。

  • 好坏样本累计差异越大,KS指标越大,那么模型的风险区分能力越强。

  • KS的计算步骤如下:

      1. 计算每个评分区间的好坏账户数。
      1. 计算每个评分区间的累计好账户数占总好账户数比率(good%)和累计坏账户数占总坏账户数比率(bad%)。
      1. 计算每个评分区间累计坏账户占比与累计好账户占比差的绝对值(累计good%-累计bad%),然后对这些绝对值取最大值即得此评分卡的K-S值。
  • KS值:是和AUC强相关的,但是样本很小的时候KS大,AUC不一定大。

    • A卡的KS:

      • 714那种最差的一般也至少要25%,
      • 正常的p2p公司,客户质量稍微好一点会到30%-40%左右。
      • 最好那些有场景的分期产品最多也就不到50%,所以一般是在25%-50%之间。
    • B卡的KS:

      • 至少也有40%,最高80% 一般60%左右。
    • 各个数据集如dev和oft的KS差值不要太大,否则模型不稳定,跨时间稳定性差。

    • 一般dev和oft的KS差值只能在5%以内,比较求稳的公司要求在3%以内。

  • 正负样本:

    • 逾期样本/正常样本=1%-5%(也有能做到1/1000的) 5%个点就是比较高的了,是很不均衡的。
    • 要是坏账5%,会亏很多钱。badrte 3%以下才可能赚钱。
    • 欺诈样本/正常样本=1/10w 欺诈用户是极少的

https://www.zhihu.com/question/37405102/answer/106668941

KS和ROC的区别#

KS值对模型的评价不受样本不均衡问题的干扰,但仅限于模型评价。

模型评价时:#

ROC曲线#

描绘的是不同的截断点(判断好人坏人的阈值)时,以FPR和TPR为横纵坐标轴,描述随着截断点的变化,TPR随着FPR的变化。
纵轴:TPR=正例分对的概率 = TP/(TP+FN),其实就是查全率
横轴:FPR=负例分错的概率 = FP/(FP+TN)

作图步骤:

根据学习器的预测结果(注意,是正例的概率值,非0/1变量)对样本进行排序(从大到小)-----这就是截断点依次选取的顺序
按顺序选取截断点,并计算TPR和FPR—也可以只选取n个截断点,分别在1/n,2/n,3/n等位置
连接所有的点(TPR,FPR)即为ROC图

KS值#

作图步骤:

根据学习器的预测结果(注意,是正例的概率值,非0/1变量)对样本进行排序(从大到小)-----这就是截断点依次选取的顺序
按顺序选取截断点,并计算TPR和FPR —也可以只选取n个截断点,分别在1/n,2/n,3/n等位置
横轴为样本的占比百分比(最大100%),纵轴分别为TPR和FPR,计算|TPR-FPR|的ks值,可以得到KS曲线
TPR和FPR曲线分隔最开的位置就是最好的“截断点”,最大间隔距离就是KS值,通常>0.2即可认为模型有比较好的预测准确性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 模型预测会返回概率,两列,第一列是0的概率,第二列是1的概率
proba = lr_model.predict_proba(x)
proba

# train_thresholds 是阈值,每一个阈值对应roc曲线上的一点
train_fpr,train_tpr,train_thresholds = roc_curve(y,proba[:,1])
train_ks_arr = abs(train_fpr-train_tpr)
train_ks = train_ks_arr.max()
print('train KS:',train_ks)

# 验证集的ks
oft_fpr, oft_tpr,oft_thresholds = roc_curve(oft_y, lr_model.predict_proba(oft_x)[:,1])
oft_ks_arr = abs(oft_fpr - oft_tpr)
oft_ks = oft_ks_arr.max()
print('oft KS:',oft_ks)

# 最大值ks对应的下标(画图用)
i = train_ks_arr.tolist().index(train_ks)
j = oft_ks_arr.tolist().index(oft_ks)

import matplotlib.pyplot as plt
from matplotlib import pyplot as plt
plt.plot(train_fpr,train_tpr,label='train roc')
plt.plot(train_fpr,abs(train_fpr-train_tpr),label='train ks')
plt.scatter(train_fpr[i],abs(train_fpr-train_tpr)[i])

plt.plot(oft_fpr, oft_tpr,label='out of time roc')
plt.plot(oft_fpr, abs(oft_fpr-oft_tpr),label='out of time ks')
plt.scatter(oft_fpr[j],abs(oft_fpr-oft_tpr)[j])

plt.plot([0,1],[0,1],'p-.')
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.legend(loc='best')
plt.title('ROC Curve')
plt.show()

image-20200522172344375

PSI群体稳定性指标#

PSI(Population Stability Index)的定义#

群体稳定性指标PSI(Population Stability Index)是衡量模型的预测值与实际值偏差大小的指标。

  • PSI用于评估模型在训练集和时间外样本集上的稳定性指标。
  • 给予的假设是:如果模型是稳定和有效的,那么在几个数据集上人群的分布也应该是稳定的
  • 风控行业常用PSI指标衡量模型或者特征的稳定性,同时也是一种模型效果监控的指标。
PSI = sum[(实际占比-预期占比)* ln(实际占比/预期占比)]

计算举例:#

  • 比如训练一个logistic回归模型,预测时候会有个概率输出p。

  • 以dev为基准,dev上的输出设定为p1,将这个概率值从小到大排序后10等分(实际中等频分箱优于等距分箱)。

  • 现在用这个模型去对新的样本(val或oft)进行预测,预测结果叫p2,按p1的区间也划分为10等分。

  • 实际占比就是p2上在各区间的用户占比,预期占比就是p1上各区间的用户占比。【如果模型是有效的,那么根据p1的区间划分出来的人群占总比和p2划分出来的各个区间的人群占总比应该是大体一致的

  • 意义就是如果模型跟稳定,那么p1和p2上各区间的用户应该是相近的,占比不会变动很大,也就是预测出来的概率不会差距很大。

  • 仔细想想,PSI就像是两个分布直方图,求了差值后再求和!越小说明模型在不同数据集上预测结果趋于一致,越稳定!

一般认为PSI小于0.1时候模型稳定性很高,一般认为0.2以下还ok。0.1-0.25一般,大于0.25模型稳定性差,建议重做。
分箱每一箱的样本要大致相同,否则若某一箱太少,造成PSI计算时里面的占比会波动,带来不准确

  • PS:除了按概率值大小等距十等分外,还可以对概率排序后按数量十等分,两种方法计算得到的psi可能有所区别但数值相差不大。

应用:

  • 样本外测试
    • 针对不同的样本测试一下模型稳定度,比如训练集与测试集,也能看出模型的训练情况,我理解是看出模型的方差情况。
  • 时间外测试
    • 测试基准日与建模基准日相隔越远,测试样本的风险特征和建模样本的差异可能就越大,因此PSI值通常较高。至此也可以看出模型建的时间太长了,是不是需要重新用新样本建模了。
  • 模型监控
    • 模型部署上线后,模型的拒绝率越高,线上的KS越低,也就无法体现模型的真实效果,所以常用PSI值监控线上模型与线下模型的差异,从侧面展示模型真实效果与预期效果的偏差。
  • 特征评估
    • 将PSI上面第一步的十等分逻辑换成特征取值的分布,对特征进行分箱
    • 在val、oft,或者跨时间段计算PSI
    • 可以评估这个特征随着时间的推移,他的分布是否稳定,考虑是否能将特征代入模型。

如下图:

  • 先把左边的概率或者score十等分,然后在actual(基准数据集,如dev)上面将样本进行划分
  • 对上面dev上各个箱内样本计算各个分箱内的用户站占体用户的比值得到Actual列
  • 同样的上面分箱阈值对Expected数据集进行划分,得到其上各个箱内样本数,计算出各个箱内占总比Expected列
  • 两列相减,乘以 相除的对数值得到index列
  • sum所有10箱即可得到总体模型的PSI

image-20200525193701620

https://www.cnblogs.com/webRobot/p/9133507.html

toad底层的PSI实现#

1
2
3
4
5
6
7
8
9
10
11
12
13
# test 和base 分别是模型在两个数据集上的概率预测输出
toad.metrics.PSI(test, base)
# 底层实现是先求test 和base 概率值的value_counts,normalize使得返回的数值被归一化。counts成了一个权重
# 这里的test_prop是一个series,index是之前test的概率,value是归一化后的counts
test_prop = pd.Series(test).value_counts(normalize = True, dropna = False)
base_prop = pd.Series(base).value_counts(normalize = True, dropna = False)

# 然后两个series相减,只会index相同的相减,也就是概率相同的相减,别的都是Nan
# 相除也一样
# 得到一个极其稀疏的series,绝大部分都是nan(因为概率相同的很少)
# 最后相乘的时候只会乘这些在test和base上概率相同的部分
# 再求和
psi = np.sum((test_prop - base_prop) * np.log(test_prop / base_prop))

Gini指数#

·GINI系数:也是用于模型风险区分能力进行评估。(使用较少)

  • GINI统计值衡量坏账户数在好账户数上的的累积分布与随机分布曲线之间的面积,好账户与坏账户分布之间的差异越大,GINI指标越高,表明模型的风险区分能力越强。
  • GINI系数的计算步骤如下:
      1. 计算每个评分区间的好坏账户数。
      1. 计算每个评分区间的累计好账户数占总好账户数比率(累计good%)和累计坏账户数占总坏账户数比率(累计bad%)。
      1. 按照累计好账户占比和累计坏账户占比得出下图所示曲线ADC。
      1. 计算出图中阴影部分面积,阴影面积占直角三角形ABC面积的百分比,即为GINI系数。

https://www.zhihu.com/question/37405102/answer/106668941

捕获率capture rate#

我们是低分拒绝模型,希望低分段内坏人尽量多,好人尽量少误伤!

计算步骤:

  • (1)将用户分数降序排列
  • (2)对排序后的用户进行等频分箱
  • (3)计算【累计到每一箱的累计负样本数】 【所有负样本】的比值。(cumsum/sum)

用于衡量在低分区间捕捉坏客户的能力,希望模型能尽可能的在更靠前的箱内,捕捉出更多比例的坏人

分段 2018/12/31
capture 5% 13.5%
capture 10% 24.2%
capture 20% 48.8%

一些表现比较好的B卡,前20%用户中可以捕获到7 80%的坏人。

image-20200525195244038

减少过拟合方法:#

  • 更多的数据
  • 对变量做筛选(模型更简单)
  • 分箱。因为分箱的时候,减少了过拟合(变量变得更稳定,降低噪音)

https://www.jianshu.com/p/c61ae11cc5f6