当前位置:   article > 正文

performance metrics

performance metrics

目录

1、混淆矩阵

混淆矩阵原理

混淆矩阵的python代码

2、准确率(Accuracy)、精确率(Precision)、灵敏度(Sensitivity)、召回率(Recall)、特异度(Specificity)、F1 Score等指标

3、ROC曲线、AUC、GINI系数

ROC曲线原理

AUC原理

GINI系数

ROC曲线的Python代码

4、KS曲线

KS曲线原理

KS曲线的python代码

5、CAP曲线和AR值

CAP曲线和AR值的原理

CAP曲线和AR值的python代码

6、模型稳定性PSI

PSI原理

PSI的python代码

7、Lift和Gain图

Lift原理

Gain原理

Lift和Gain画图

Lift和Gain的python代码

8、总结


1、混淆矩阵

混淆矩阵是最简单、最基础的分类的评估指标,在这里只讲二分类的混淆矩阵,多分类与二分类类似。

混淆矩阵原理

真实值是positive,模型认为是positive的数量(True Positive=TP)

真实值是positive,模型认为是negative的数量(False Negative=FN):这就是统计学上的第一类错误(Type I Error)

真实值是negative,模型认为是positive的数量(False Positive=FP):这就是统计学上的第二类错误(Type II Error)

真实值是negative,模型认为是negative的数量(True Negative=TN)

混淆矩阵的python代码

from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import numpy as np

def plot_confusion_matrix(y, y_hat, labels = [1,0]):
    # y是真实的标签,y_hat是预测的标签,labels表示label的展示顺序
    cm = confusion_matrix(y,y_hat,labels = labels)
    plt.figure(figsize=(5, 5))
    plt.imshow(cm, interpolation='nearest',cmap=plt.cm.binary)
    plt.xticks(np.array(range(len(labels))), labels, rotation=90)  # 将标签印在x轴坐标上
    plt.yticks(np.array(range(len(labels))), labels)  # 将标签印在y轴坐标上

    plt.title('Confusion Matrix')  # 标题
    plt.xlabel('predict label')  # x轴
    plt.ylabel('true label')  # y轴

    ind_array = np.arange(len(labels))
    x, y = np.meshgrid(ind_array, ind_array)

    for x_val, y_val in zip(x.flatten(), y.flatten()):
        c = cm[y_val][x_val]
        plt.text(x_val, y_val, "%s" % (c,), color='red', fontsize=20, va='center', ha='center')

    plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

图形结果:


2、准确率(Accuracy)、精确率(Precision)、灵敏度(Sensitivity)、召回率(Recall)、特异度(Specificity)、F1 Score等指标

使用较多的指标通常为精确率和召回率。

精确率描述模型预测结果准确度的衡量指标,表示模型预测结果有多准。

召回率描述模型预测结果全面性的衡量指标,表示模型预测结果涵盖有多全。

一般来说:

阈值越大,召回率(TPR)越低,精确率(PPV)越高,假阳性率(FPR)越低。

当阈值为0时,召回率(TPR)等于1,假阳性率(FPR)等于1(因为所有的样本都预测为正样本,所以真阳性率为1,假阳性率也为1)

当阈值为1时,召回率(TPR)等于0,假阳性率(FPR)等于0(因为所有的样本都被预测为负样本,所以真阳性率为0,假阳性率为0)

假设:全行下个月信用卡用户共1,000,000,其中实际逾期的客户数为10,000;模型预测出15,000个客户会出现逾期,其中8000个客户为真实逾期用户。

从而:精确率=TP/(TP+FP)=8000/15000=0.53

召回率=TP/(TP+FN)=8000/10000=0.8


3、ROC曲线、AUC、GINI系数

ROC曲线原理

ROC的全称是Receiver Operating Characteristic Curve,中文名字叫“受试者工作特征曲线”,顾名思义,其主要的分析方法就是画这条特征曲线。

ROC曲线的横轴是FPR(假阳性率、误诊率)、纵轴是TPR(真阳性率、灵敏度)。

这条曲线代表的是在不同的阈值下,FPR和TPR的一个变化曲线,通常,我们希望FPR尽可能的小,而TPR尽可能的大,所以曲线越靠近左上角,模型的效果越好。

如下图,模型效果A>B>C

AUC原理

AUC(Area Under Curve)被定义为ROC曲线下的面积,取值范围一般在0.5和1之间。使用AUC值作为评价标准是因为很多时候ROC曲线并不能清晰的说明哪个分类器的效果更好,而作为一个数值,对应AUC更大的分类器效果更好。

AUC = 1,是完美分类器,采用这个预测模型时,存在至少一个阈值能得出完美预测。绝大多数预测的场合,不存在完美分类器。

0.5 < AUC < 1,优于随机猜测。这个分类器(模型)妥善设定阈值的话,能有预测价值。

AUC = 0.5,跟随机猜测一样(例:丢铜板),模型没有预测价值。

AUC < 0.5,比随机猜测还差;但只要总是反预测而行,就优于随机猜测。

GINI系数

Gini coefficient 是指绝对公平线(line of equality)和洛伦茨曲线(Lorenz Curve)围成的面积与绝对公平线以下面积的比例,即gini coefficient = A面积 / (A面积+B面积) 。

用在评判分类模型的预测效力时,是指ROC曲线曲线和中线围成的面积与中线之上面积的比例。因此Gini coefficient与AUC可以互相转换:

gini =A/ (A+ B) = (AUC - C) / (A+ B) = (AUC -0.5) /0.5=2*AUC -1

ROC曲线的Python代码

from sklearn.metrics import auc,roc_curve
import matplotlib.pyplot as plt

def plot_roc_curve(y, y_hat_proba, pos_label = 1):
    # This is a function to plot a roc curve
    # y指的是真实值,y_hat_proba指的是预测的概率结果

    fpr, tpr, thresholds = roc_curve(y, y_hat_proba, pos_label=pos_label)
    AUC = auc(fpr, tpr)
    lw = 2
    plt.figure(figsize=(8, 8))
    plt.plot(fpr, tpr, color='darkorange',
             lw=lw, label='ROC curve (area = %0.4f)' % AUC)  ###假正率为横坐标,真正率为纵坐标做曲线
    plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')
    plt.title('Receiver operating characteristic example')
    plt.legend(loc="lower right")
    plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

图形结果:


4、KS曲线

KS曲线原理

KS(Kolmogorov-Smirnov)曲线(洛伦兹曲线)的纵轴是表示TPR和FPR的值,就是这两个值可以同时在一个纵轴上体现,横轴就是阈值,表示模型能够将正、负客户区分开的程度越大。两条曲线之间相距最远的地方对应的阈值,就是最能划分模型的阈值,即KS=max(TPR-FPR),KS值越大,模型的区分度越好。
说明K-S曲线的做法:
(1)把模型对样本的输出概率(predict_proba)从大到小排序,计算对应不同阈值时,大于等于阈值的样本数占总样本的百分比percentage

(2)计算阈值取每个概率时对应的TPR和FPR值,分别画(percentage, TPR)和(percentage, FPR)的曲线

(3) K-S曲线上的KS值,即max(TPR−FPR),即两条曲线间的最大间隔距离。

(有的资料横轴是阈值,都是一样的道理)

KS曲线的python代码

from sklearn.metrics import roc_curve
import matplotlib.pyplot as plt

def KS_cal(y,y_hat_proba):
    # 计算ks值

    fpr, tpr, _ = roc_curve(y, y_hat_proba)
    diff = np.subtract(tpr, fpr)
    ks = diff.max()

    return ks

def plot_ks(y,y_hat_proba):
    # 画ks曲线

    fpr, tpr, thresholds = roc_curve(y, y_hat_proba)
    diff = np.subtract(tpr, fpr)
    ks = diff.max()

    y_len = len(y)

    # 计算比例,这样计算比较快
    # 也可以自己划分样本的比例,自己计算fpr,tpr
    y_hat_proba_sort = sorted(y_hat_proba, reverse=True)
    cnt_list = []
    cnt = 0
    for t in thresholds:
        for p in y_hat_proba_sort[cnt:]:
            if p >= t:
                cnt += 1
            else:
                cnt_list.append(cnt)
                break
    percentage = [c/float(y_len) for c in cnt_list]

    if min(thresholds)<=min(y_hat_proba_sort):
        percentage.append(1)

    # 以下为画图部分
    best_thresholds = thresholds[np.argmax(diff)]
    best_percentage = percentage[np.argmax(diff)]
    best_fpr = fpr[np.argmax(diff)]

    lw = 2
    plt.figure(figsize=(8, 8))
    plt.plot(percentage, tpr, color='darkorange',
             lw=lw, label='True Positive Rate')
    plt.plot(percentage, fpr, color='darkblue',
             lw=lw, label='False Positive Rate')
    plt.plot(percentage, diff, color='darkgreen',
             lw=lw, label='diff')
    plt.plot([best_percentage, best_percentage], [best_fpr, best_fpr+ks],
             color='navy', lw=lw, linestyle='--',label = 'ks = %.2f, thresholds = %.2f'%(ks,best_thresholds))

    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('percentage')
    plt.title('Kolmogorov-Smirnov')
    plt.legend(loc="lower right")
    plt.show()
  • 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
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

图形结果:


5、CAP曲线和AR值

CAP曲线和AR值的原理

CAP(Cumulative Accuracy Profile)的步骤如下:首先,对于所有预测的结果,按照其概率probability值降序排列起来,然后我们顺序依次把样本划分到观察集中。假设所有的样本数量为T,真实的正样本数量为Tp,真实的负样本的数量为Tn,Tp+Tn=T。每次都划分一个样本到观察集中,观察集中的样本个数为O,观察集中包含的真正的正样本数为Op。

在最理想的情况下,存在一个阈值\epsilon,大于\epsilon的样本全部为真实正样本,小于\epsilon的全部为真实负样本,所以在这种情况下,probability>\epsilon时,每增加一个样本到预测的正样本中,都是真实的正样本,直到阈值\epsilon,此时O=Op=Tp,然后再往里加样本时,所有的样本都是负样本,所以将一直保持O=Op=Tp的状态,直到所有样本添加完。

在最差的情况下,则是一个斜率为常数的直线。

(有的文章CAP算的是占所有样本的比例,而本文算的是个数,都是一样的道理)

然而实际情况不是理想情况,通常预测的正样本中往往掺杂着负样本,如下图所示:

AR值为上图绿色的部分除以橙色绿色的面积和,AR越接近1,说明模型的效果越好。

CAP曲线和AR值的python代码

def AR_cal(y,y_hat):
    # 计算AR值

    if type(y)==pd.core.frame.DataFrame:
        y = y.iloc[:,0]

    total_count = len(y)
    pos_count = int(np.sum(y))

    a = auc([0, total_count], [0, pos_count])
    ap = auc([0, pos_count, total_count], [0, pos_count, pos_count]) - a

    model_y = [y_ for _, y_ in sorted(zip(y_hat, y), reverse=True)]
    y_values = np.append([0], np.cumsum(model_y))
    x_values = np.arange(0, total_count + 1)

    ar = auc(x_values, y_values) - a

    AR = ar/float(ap)
    return AR


def plot_CAP(y,y_hat):
    # 画CAP曲线,与计算AR值

    if type(y)==pd.core.frame.DataFrame:
        y = y.iloc[:,0]

    total_count = len(y)
    pos_count = int(np.sum(y))

    a = auc([0, total_count], [0, pos_count]) # random model
    ap = auc([0, pos_count, total_count], [0, pos_count, pos_count]) - a

    model_y = [y_ for _, y_ in sorted(zip(y_hat, y), reverse=True)]
    y_values = np.append([0], np.cumsum(model_y))
    x_values = np.arange(0, total_count + 1)

    ar = auc(x_values, y_values) - a

    AR = ar/float(ap)

    lw = 2
    plt.figure(figsize=(8, 8))

    plt.plot([0, pos_count, total_count], [0, pos_count, pos_count], color='darkblue',
             lw=lw, label='Perfect Model')
    plt.plot(x_values, y_values, color='darkgreen',
             lw=lw, label='Actual Model')
    plt.plot([0, total_count], [0, pos_count], color='darkorange',
             lw=lw, label='Random Model',linestyle='--')


    plt.xlim([0.0, total_count])
    plt.ylim([0.0, pos_count+1])
    plt.xlabel('Total Observations')
    plt.ylabel('Positive Observations')
    plt.title('Cumulative Accuracy Profile, AR = %.2f'%AR)
    plt.legend(loc="lower right")
    plt.show()
  • 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
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

图形结果:


6、模型稳定性PSI

PSI原理

由于模型是以特定时期的样本所开发的,此模型是否适用于开发样本之外的族群,必须经过稳定性测试才能得知。稳定度指标(population stability index ,PSI)可衡量测试样本及模型开发样本评分的的分布差异,为最常见的模型稳定度评估指针。其实PSI表示的就是按分数分档后,针对不同样本,或者不同时间的样本,population分布是否有变化,就是看各个分数区间内样本占总样本的占比是否有显著变化。

PSI=\sum ((Actual\%-Except\%)*ln(\frac{Actual\%}{Except\%}))

上面的公式可以保证始终不为负,且PSI越小,模型越稳定。

PSI<0.1 样本分布有微小变化

PSI 0.1~0.2 样本分布有变化

PSI>0.2 样本分布有显著变化

PSI既可以评估模型整体的稳定性,也可以评估特征的稳定性

风控模型不稳定时的排查方向

当通过PSI指标发现模型不稳定时,我们该如何去排查原因?引起模型不稳定的因素是多种多样的,主要包括:

  • 申贷客群变化:获客渠道一般决定了客群质量,我们只是从客群的有限特征维度来大致判断是否变化,但这只是有偏判断,因为无法完全获知用户画像。当然,在获客阶段也会做前置风控,预先筛选流量,以及保证客群的稳定。
  • 数据源不稳定:分析特征的PSI,观察入模特征的分数漂移,对于影响较大和偏移较大的变量予以重点关注。再从数据源上确认采集是否可靠,比如数据服务商是否正常提供、接口是否正常工作、网关数据传输过程是否正常等。
  • 特征逻辑有误:在模型上线时,特征逻辑可能没有确认清楚,导致上线后出现意想不到的问题。因此,需要将入模特征的逻辑再次予以Review。
  • 其他相关原因:模型监控报表是否正确计算?线上依赖于离线T+1产出的数据是否正常调度?特征缺失值处理逻辑?

PSI的python代码

def PSI_cal(score_actual, score_except, bins = 10):

    actual_min = score_actual.min()
    actual_max = score_actual.max()

    binlen = (actual_max - actual_min) / bins
    cuts = [actual_min + i * binlen for i in range(1, bins)]
    cuts.insert(0, -float("inf"))
    cuts.append(float("inf"))

    actual_cuts = np.histogram(score_actual, bins=cuts)
    except_cuts = np.histogram(score_except, bins=cuts)

    actual_df = pd.DataFrame(actual_cuts[0], columns=['actual'])
    predict_df = pd.DataFrame(except_cuts[0], columns=['predict'])
    psi_df = pd.merge(actual_df, predict_df, right_index=True, left_index=True)

    psi_df['actual_rate'] = (psi_df['actual'] + 1) / psi_df['actual'].sum()  # 计算占比,分子加1,防止计算PSI时分子分母为0
    psi_df['predict_rate'] = (psi_df['predict'] + 1) / psi_df['predict'].sum()

    psi_df['psi'] = (psi_df['actual_rate'] - psi_df['predict_rate']) * np.log(
        psi_df['actual_rate'] / psi_df['predict_rate'])

    psi = psi_df['psi'].sum()

    return psi
  • 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

7、Lift和Gain图

Lift原理

Lift图衡量的是,与不利用模型相比,模型的预测能力“变好”了多少,lift(提升指数)越大,模型的运行效果越好。

lift = \frac{\frac{TP}{TP+FP}}{\frac{P}{P+N}}

例如,原来不适用模型,而采用随机的营销方式,只有1%的客户会成功转化;而使用了模型进行精准营销后,会有10%的客户成功转化。那么,lift=10%/1%=10。

Gain原理

Gain图是描述整体精准度的指标,也就是PPV,在模型预测是Positive的样本中,模型预测正确的比例,这个比例也是越大越好,在0~1之间。

Gain = \frac{TP}{TP+FP}

Lift和Gain画图

Gain图、Lift图和KS画图的方式比较类似:

(1)把模型对样本的输出概率(predict_proba)从大到小排序,按顺序选取截断点,并得到对应截断点对应的累计样本比例percentage

(2)计算每个截断点对应的Gain和Lift值,分别画(percentage, Gain)和(percentage, Lift)的曲线

正常来说Gain和Lift图除了坐标轴的尺度不同外,曲线应该一样的,但是我查了好多资料发现Gain图的纵坐标是预测出来的正样本中真实的正样本占所有真实正样本的占比(这两个曲线一般没人用,反正意思理解了就可以了)

Lift和Gain的python代码

def lift_cal(y,y_hat):

    if type(y) == pd.core.frame.DataFrame:
        y = y.iloc[:, 0]

    total_count = len(y)
    pos_count = int(np.sum(y))
    pos_proportion = pos_count/float(total_count)

    PRECISION = precision_score(y, y_hat)
    lift = PRECISION/pos_proportion

    return lift


def plot_gain(y,y_hat_proba,bins = 10):

    if type(y) == pd.core.frame.DataFrame:
        y = y.iloc[:, 0]

    gain_list = []

    total_count = len(y)
    pos_count = int(np.sum(y))

    df = pd.DataFrame({'y':y,'y_hat_proba':y_hat_proba})
    df.sort_values(by='y_hat_proba',inplace = True,ascending=False)
    df.reset_index(drop = True,inplace = True)

    binlen = len(y) / bins
    cuts = [int(i * binlen) for i in range(1, bins)]
    cuts.append(total_count)
    x = [0]+[float(i)/bins for i in range(1,bins+1)]

    for c in cuts:
        cumulative_pos = np.sum(df['y'][:c])
        gain_list.append(cumulative_pos/float(pos_count))
    gain_list=[0]+gain_list

    lw = 2
    plt.figure(figsize=(8, 8))
    plt.plot(x, gain_list, color='darkorange',lw=lw,label='proportion of cumulative events(model)')
    plt.plot([0,1],[0,1],color='darkblue',lw=lw,label='proportion of cumulative events(random)',linestyle='--')

    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('The proportion of datasets')
    plt.title('Gain Chart')
    plt.legend(loc="lower right")
    plt.grid()
    plt.show()



def plot_lift(y, y_hat_proba, bins=10):

    if type(y) == pd.core.frame.DataFrame:
        y = y.iloc[:, 0]

    lift_list = []

    total_count = len(y)
    pos_count = int(np.sum(y))
    pos_proportion = pos_count / float(total_count)

    df = pd.DataFrame({'y':y,'y_hat_proba':y_hat_proba})
    df.sort_values(by='y_hat_proba',inplace = True,ascending=False)
    df.reset_index(drop = True,inplace = True)

    binlen = len(y) / bins
    cuts = [int(i * binlen) for i in range(1, bins)]
    cuts.append(total_count)
    x = [float(i)/bins for i in range(1,bins+1)]

    for c in cuts:
        y_hat = list(np.repeat(1,c))+list(np.repeat(0,total_count-c))
        lift_list.append(precision_score(df['y'], y_hat)/pos_proportion)


    lw = 2
    plt.figure(figsize=(8, 8))
    plt.plot(x, lift_list, color='darkorange',lw=lw,label='model lift')
    plt.plot([x[0],x[-1]],[1,1],color='darkblue',lw=lw,label='random lift',linestyle='--')

    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, lift_list[0]+1])
    plt.xlabel('The proportion of datasets')
    plt.title('Lift Chart')
    plt.legend(loc="lower right")
    plt.grid()
    plt.show()
  • 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
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

画图结果

​ Gain曲线图,当橙色的线快速上升至1时,表示模型较好。

​ Lift曲线图,当橙色的线在很高的提升值上保持一段后,迅速下降至1时,表示模型较好。


8、总结

一般我在项目中,通常以召回率、精确率、F1-Score为主,ROC、AUC为辅,其它的一般很少看。当然这个也和项目的最终目标与个人的习惯有很大的关系。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/270566
推荐阅读
相关标签
  

闽ICP备14008679号