机器学习评价指标

目录

基础-TP FN FP TN

组合-TPR FPR TNR accuracy precision recall F-1 F-measure

BOSS-ROC曲线与AUC面积

BOSS-KS及计算过程

 

综合了网上看的一些知识,加深下理解,有不对请指出

基础-TP FN FP TN

TP,FN,FP,TN,含义如下表,True/False代表该预测是对的还是错的,Positive/Negative代表预测的值是正还是负。以TP为例,指预测为正,预测对了,也就是实际为正。

 预测1预测0
实际1True Positive(TP)False Negative(FN)
实际0False Positive(FP)True Negative(TN)

举个例子:一个筐里放了25个芒果🥭,15个榴莲(一个有味道的例子)。我对着筐里的水果做预测,打算把所有榴莲找出来,打标签为1。挑出来20个,其中13个榴莲,7个芒果。那么对应的

组合-TPR FPR TNR accuracy precision recall F-1 F-measure

TPR、FPR用在二分类关注正样本的情景中,比如找出欺诈交易

  • 真正类率(true positive rate,TPR):如果一个样本是正样本,被挑出来的概率是多少

TPR=TP/(TP+FN)

  • 负正类率(false positive rate,FPR):如果一个样本不是正样本,误判的概率是多少

FPR = FP/(FP+TN)

  • 真负类率(True Negative Rate,TNR ):TNR = TN /(FP+TN)=1-FPR
  • 准确率accuracy:预测对的个数/总个数

accuracy=\frac{(TP+TN)}{(TP+FP+TN+FN)}

  • 精确率precision:预测对的正例/预测的正例,也可以作为预测的置信度

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

  • 召回率recall:预测对的正例/总共的正例

recall=\frac{TP}{TP+FN}

F-measure主要是为了解决precision和recall有些矛盾这个问题,当追求precision高时,自然想少预测些,会预测的准;当想recall高时,自然多预测些,会预测的全。以上面芒果榴莲的例子,追求高precision,我可以只预测得分最高的一颗为榴莲,大概率precision就是100%了;追求高recall,我把一筐全部预测为榴莲,这样recall肯定是100%,只是准确率下降了。为了解决这个问题,就有了F-measure来综合衡量precision和recall。当\alpha的值取1时,即为常见的F1.

  • F1值 F1-score

F1-score=\frac{2*precision*recall}{precision+recall}

  • F-measure(P指precision,R指recall)

F=\frac{(\alpha^ {2}+1)*P*R}{\alpha ^{2}*(P+R)}

BOSS-ROC曲线与AUC面积

看似上面这些指标很完美了,那么为何需要ROC(Receiver Operating Characteristic)呢?接下来的描述高仿https://www.cnblogs.com/maybe2030/p/5375175.html,但是用了榴莲芒果的例子

还是上面那个例子,假设我们的目标是尽量把榴莲找出来。那么TPR越高越好,FPR越低越好。那如果我们把榴莲的标准要求得很严格,那么TPR和FPR都会比较低;如果把榴莲的标准放得宽,那么TPR和FPR都会升高,也就是找出来的榴莲多了,错找出的芒果也多了。

然后,以FPR为横轴,TPR为纵轴,得到ROC空间。如下图

左上角的点pefect classification代表分的全对,找出了全部的榴莲和0个芒果。这个应该好理解,就是TPR为1,FPR为0.

红色对角虚线上的点B,代表蒙的,这个地方我纠结了好一会儿==。我纠结的点在于,假设说正负样本比例是1:100,那怎样算蒙呢,50%和50%的概率来分正负样本?100:1的概率来分正负样本?还是50.5:50.5的概率来分正负样本?我自己举了两个例子,发现了规律。只要是在真正的正样本,你打的正负标签的比和在真正的负样本中,你打的正负标签的比相等,那么就是随机的。同理,在你打的正样本中,真正的正样本和假正样本的比例和在你打的负样本中,真正的正样本和假正样本的比例相等,也是随机的。也就是TP/FN=FP/TN或者TP/FP=FN/TN,也就是不管实际是正还是负样本,你打的正负标签的比例都是一样的,说明是蒙的,完全没有考虑到样本特性的。不管你是按1:100的正负比来打标签还是按100:1的正负比来打标签,只要你对真实的正负样本一视同仁,那么你的结果就会落在红色对角虚线上。假设在全部样本都按100:0的正负比来打标签,那么点就是右上角(1,1)。

那要是落在右下角区域,说明你预测的反的,也就是预测命中正样本的概率比把假样本当正样本的概率还低,哈哈哈,模型出现这种情况时考虑是否标签取反了。

借用上述网址中的图,如下。横轴为阈值。

第一部分,蓝色的表示负例,红色部分表示正例。图中给出了A、B、C三条线做比较,以C线为例,虽然没有正例被错分为负例,但是太多负例被错分为正例。A存在类似的问题,太多正例被错分。因此B线是比较合理的分割线,B线对应的阈值也就是比较合理的阈值,我们可以认为大于该阈值的是榴莲,其它是芒果。

遍历所有阈值,就能得到图中下半部分的曲线。曲线离左上角越近,说明在相同FPR时,TPR更高,因此分类效果越好。

AUC(area under curve),曲线下面积的大小,就很好地表示了该曲线离左上角的远近程度。离得越近,AUC越大。AUC=1时是完美分类器,不管设定什么阈值都能完美预测,一般不存在。0.5<AUC<1时,优于随机预测。若妥善设定阈值,能有预测价值。AUC=0.5时,没有预测价值,ACU<0.5时,可反着用。

auc的计算:

目前用的python,里面sklearn库metrics。假设真正的标签是y,其中1表示榴莲,0表示芒果,预测的值是scores,代码如下:

from sklearn import metrics

fpr,tpr,thresholds=metrics.roc_curve(y,scores,pos_label=1) #pos_label默认是1,但比如你的正样例用2表示,那么就pos_label=2来指定

metrics.auc(fpr,tpr)

原理https://www.cnblogs.com/maybe2030/p/5375175.html

第一种方法:AUC为ROC曲线下的面积,那我们直接计算面积可得。面积为一个个小的梯形面积之和。计算的精度与阈值的精度有关。

第二种方法:根据AUC的物理意义,我们计算正样本score大于负样本的score的概率。取N*M(N为正样本数,M为负样本数)个二元组,比较score,最后得到AUC。时间复杂度为O(N*M)。

第三种方法:与第二种方法相似,直接计算正样本score大于负样本的概率。我们首先把所有样本按照score排序,依次用rank表示他们,如最大score的样本,rank=n(n=N+M),其次为n-1。那么对于正样本中rank最大的样本,rank_max,有M-1个其他正样本比他score小,那么就有(rank_max-1)-(M-1)个负样本比他score小。其次为(rank_second-1)-(M-2)。最后我们得到正样本大于负样本的概率为

  时间复杂度为O(N+M)。

BOSS-KS及计算过程

参考https://blog.csdn.net/Orange_Spotty_Cat/article/details/82016928

KS曲线是两条线,横轴是阈值,纵轴是TPR与FPR.两条曲线之间相隔最远的地方对应的阈值就是最能划分模型的阈值,KS值是MAX(TPR_FPR),即两曲线相距最远的距离。TPR指如果一个样本是正样本,被挑出来的概率是多少,FPR指如果一个样本不是正样本,误判的概率是多少。我们希望,能尽量多的挑出正样本,同时尽量少地误判负样本。因此选TPR-FPR最大的。

ks值<0.2,一般认为模型没有区分能力;ks值[0.2,0.3],模型具有一定区分能力,勉强可以接受;ks值[0.3,0.5],模型具有较强的区分能力;ks值大于0.75,往往表示模型有异常。

ks曲线用python计算绘制请见https://www.cnblogs.com/bigdatafengkong/p/9079093.html

下面是我基于自己的理解对代码写的注释

####################### PlotKS ##########################
#为了便于理解,假设该模型是用来预测一笔交易是否是欺诈交易,0表示正常交易;1表示欺诈交易
def PlotKS(preds, labels, n, asc):
    # 根据后面的代码部分推测,当预测值越高,代表欺诈交易的可能性越大时,asc=0;
    # 当打分值越低,代表欺诈交易的可能性越大时,asc=1。
    # preds is score: asc=1
    # preds is prob: asc=0
    # n样本个数

    pred = preds  # 预测值
    bad = labels  # 取1为bad, 0为good,这里要注意,1是bad。第一遍看的时候没注意,默认1是good,导致后面有些疑惑。
    ksds = DataFrame({'bad': bad, 'pred': pred})    #ksds有两列,分别是真实标签和预测值
    ksds['good'] = 1 - ksds.bad
    #ksds的good这一列:如果bad取1,表示该客户是欺诈客户,该列对应值就是0,表示不是正常客户.反过来如果bad取0,表名不是欺诈客户,该列就取1,表示是正常客户。
    
    #我们希望,能尽量多的挑出正样本,同时尽量少地误判负样本。因此选TPR-FPR最大的。把最有可能为1的排在前面依次来计算。从最有可能的到最不可能来排序
    if asc == 1:#当分数越低,欺诈概率越大(标签为1的概率越大)时:
        ksds1 = ksds.sort_values(by=['pred', 'bad'], ascending=[True, True])
    elif asc == 0:#当分数越高,欺诈概率越大(标签为1的概率越大)时:
        ksds1 = ksds.sort_values(by=['pred', 'bad'], ascending=[False, True])
    ksds1.index = range(len(ksds1.pred))#打乱的index按0-len(ksds1.pred)顺序排
    ksds1['cumsum_good1'] = 1.0*ksds1.good.cumsum()/sum(ksds1.good)#截止该行为止所有1的个数/一共1的个数,也就是截止该行,所有正常客户的个数/一共的正常客户数,FPR
    ksds1['cumsum_bad1'] = 1.0*ksds1.bad.cumsum()/sum(ksds1.bad)#截止该行,所有欺诈客户的个数/一共的欺诈客户数。TPR

#这部分的计算过程和上面ksds1非常相似,唯一的区别是排序的时候,对'bad'这一列,从增序变为降序。也就是当打分(或概率)相同时,ksds1是把标签0的放在前面的,而如下ksds2是把标签1的放在前面的。防止打分相同时,标签的不同排序带来的偏差。
    if asc == 1:
        ksds2 = ksds.sort_values(by=['pred', 'bad'], ascending=[True, False])
    elif asc == 0:
        ksds2 = ksds.sort_values(by=['pred', 'bad'], ascending=[False, False])
    ksds2.index = range(len(ksds2.pred)) #index重新排
    ksds2['cumsum_good2'] = 1.0*ksds2.good.cumsum()/sum(ksds2.good) #FPR
    ksds2['cumsum_bad2'] = 1.0*ksds2.bad.cumsum()/sum(ksds2.bad)#TPR

#对上述两种排序方式的TPR、FPR取平均
    ksds = ksds1[['cumsum_good1', 'cumsum_bad1']]
    ksds['cumsum_good2'] = ksds2['cumsum_good2']
    ksds['cumsum_bad2'] = ksds2['cumsum_bad2']
    ksds['cumsum_good'] = (ksds['cumsum_good1'] + ksds['cumsum_good2'])/2
    ksds['cumsum_bad'] = (ksds['cumsum_bad1'] + ksds['cumsum_bad2'])/2
    
    # ks
    ksds['ks'] = ksds['cumsum_bad'] - ksds['cumsum_good']#TPR-FPR的值存在ksds['ks'],找出ksds['ks']的最大值即ks值。


    ksds['tile0'] = range(1, len(ksds.ks) + 1)
    ksds['tile'] = 1.0*ksds['tile0']/len(ksds['tile0'])
    
    qe = list(np.arange(0, 1, 1.0/n))
    qe.append(1)
    qe = qe[1:]
    
    ks_index = Series(ksds.index)
    ks_index = ks_index.quantile(q = qe)
    ks_index = np.ceil(ks_index).astype(int)
    ks_index = list(ks_index)
    
    ksds = ksds.loc[ks_index]
    ksds = ksds[['tile', 'cumsum_good', 'cumsum_bad', 'ks']]
    ksds0 = np.array([[0, 0, 0, 0]])
    ksds = np.concatenate([ksds0, ksds], axis=0)
    ksds = DataFrame(ksds, columns=['tile', 'cumsum_good', 'cumsum_bad', 'ks'])
    
    ks_value = ksds.ks.max()
    ks_pop = ksds.tile[ksds.ks.idxmax()]
    print ('ks_value is ' + str(np.round(ks_value, 4)) + ' at pop = ' + str(np.round(ks_pop, 4)))
    
    # chart
    plt.plot(ksds.tile, ksds.cumsum_good, label='cum_good',
                         color='blue', linestyle='-', linewidth=2)
                         
    plt.plot(ksds.tile, ksds.cumsum_bad, label='cum_bad',
                        color='red', linestyle='-', linewidth=2)
                        
    plt.plot(ksds.tile, ksds.ks, label='ks',
                   color='green', linestyle='-', linewidth=2)
                       
    plt.axvline(ks_pop, color='gray', linestyle='--')
    plt.axhline(ks_value, color='green', linestyle='--')
    plt.axhline(ksds.loc[ksds.ks.idxmax(), 'cumsum_good'], color='blue', linestyle='--')
    plt.axhline(ksds.loc[ksds.ks.idxmax(),'cumsum_bad'], color='red', linestyle='--')
    plt.title('KS=%s ' %np.round(ks_value, 4) +  
                'at Pop=%s' %np.round(ks_pop, 4), fontsize=15)
    

    return ksds
####################### over ##########################

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值