看完这篇AUC文章,搞定任何有关AUC的面试不成问题~
随机挑选一个正样本和负样本,分类器将正样本排在负样本前面的概率。
使用AUC或者logloss可以避免把预测概率转换成类别。
与基尼系数关系:gini+1 = 2*AUC
可以通过约登指数(TPR+1-FPR)取得最大时的阈值来确定一个分类器合适的阈值;
样本概率从大到小排,然后遍历每个样本进行设置阈值,分别计算横纵坐标,当我们将threshold设置为1和0时,分别可以得到ROC曲线上的(0,0)和(1,1)两个点。将这些(FPR,TPR)对连接起来,就得到了ROC曲线。当threshold取值越多,ROC曲线越平滑。(记好召回率大的时候就是阈值小的时候,把所有物品都判为正。)
其实分子就是想计算出所有正比负大的组合数目,那么我们先按照score排序后,把所有正的score加起来,每个正的排名(99),就代表着这个正样本要比99个样本score大,但是里面包含了一部分正正情况(第一名正,包含了M-1种情况,第二名正包含了M-2种,最后一名正包含0种正正),所以将所有正的排名加起来再减去各自正正的个数,就是所有样本中正比负score大的个数。
手动版:
上式中,统计一下所有的 M×N(M为正类样本的数目,N为负类样本的数目)个正负样本对中,有多少个组中的正样本的score大于负样本的score。当二元组中正负样本的 score相等的时候,按照0.5计算。然后除以MN。
实现1:
实现2:
def calAUC(prob,labels):
"""
计算AUC主题逻辑
:param prob:
:param labels:
:return:
"""
# 组装预测值、标签,返回 List[(0.011547, 1), (0.00102014, 0), (0.000152839, 1)] 形式
f = list(zip(prob, labels))
# 以预测值为key进行排序,得到rank,记录了按照预测值排序后的label
rank = [values2 for values1, values2 in sorted(f, key=lambda x:x[0])]
# rankList记录了rank中正样本的位置(从1开始)
rankList = [i+1 for i in range(len(rank)) if rank[i]==1]
posNum = 0
negNum = 0
# 遍历label列表,查找正负样本数
for i in range(len(labels)):
if labels[i] == 1:
posNum += 1
else:
negNum += 1
auc = 0
# 计算AUC, 计算公式 AUC = {正样本位置的和 - [正样本数*(正样本数+1)]/2} / 正样本数*负样本数
auc = (sum(rankList) - (posNum*(posNum+1))/2)/(posNum*negNum)
return auc
y = []
pred = []
with open("result_v1", 'r') as infile:
for line in infile:
pred.append(float(line.strip()))
with open("data/test_shuffle", 'r') as infile:
for line in infile:
y.append(int(line.strip().split(' ')[0]))
print(calAUC(pred,y))
实现3:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# ******************************************************************************
# 程序名称: roc_auc_score.py
# 功能描述: 计算roc曲线下方的面积
# 创建人名: aylanyang
# 创建日期: 2019-09-03
# 版本说明: v1.0
# ******************************************************************************
## import
import numpy as np
from sklearn.metrics import roc_auc_score, roc_curve
import matplotlib.pyplot as plt
def roc_auc_score_v1(prob, label):
'''
:param prob: 预测值
:param label: 真实值
:return: auc
'''
listTuple = list(zip(label, prob))
rank = [values2 for values1, values2 in sorted(listTuple, key=lambda x: x[0])]
rankList = [i+1 for i in range(len(rank)) if rank[i]==1]
posNum = 0
negNum = 0
for i in range(len(label)):
if(label[i] == 1):
posNum += 1
else:
negNum += 1
auc = (sum(rankList) - (posNum*(posNum+1))/2)/(posNum*negNum)
return auc
def plot_roc(label, prob):
fpr, tpr, _ = roc_curve(label, prob, pos_label=1)
plt.figure()
plt.plot(fpr, tpr, color='darkorange', lw=2, label='ROC Curve (Area = {:.2f}'.format(roc_auc_score(label, prob)))
plt.plot([0, 1], [0, 1], color='navy', linestyle='--')
plt.xlim([0, 1])
plt.ylim([0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Postive Rate')
plt.title('Receiver Operating Characteristic Curve')
plt.legend(loc='lower right')
plt.savefig('roc.png')
plt.show()
if __name__ == "__main__":
prob = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
label = np.array([0, 0, 0, 1, 0, 1, 1, 0, 1])
auc = roc_auc_score_v1(label, prob)
print('roc auc score: {:.4f}'.format(auc))
auc = roc_auc_score(label, prob)
print('roc auc score: {:.4f}'.format(auc))
plot_roc(label, prob)
实现4:
import numpy as np
import matplotlib.pyplot as plt
def get_roc(y_label, y_score):
"""
:param y_label:
:param y_score:
:return:
"""
assert len(y_label) == len(y_score)
# invert sort y_pred
score_indices = np.argsort(y_score, kind="mergesort")[::-1]
y_prob = np.array(y_score)[score_indices]
y_true = np.array(y_label)[score_indices]
# ------------------get tps and fps at distinct value -------------------------
# extract the indices associated with the distinct values
distinct_value_indices = np.where(np.diff(y_prob))[0]
threshold_idxs = np.r_[distinct_value_indices, y_true.size - 1]
# accumulate the true positives with decreasing threshold
tps = np.cumsum(y_true)[threshold_idxs]
# computer false positive
fps = threshold_idxs + 1 - tps
# ------------------------------ computer tpr and fpr---------------------------
# Add an extra threshold position
# to make sure that the curve starts at (0, 0)
tps = np.r_[0, tps]
fps = np.r_[0, fps]
if fps[-1] <= 0:
fpr = np.repeat(np.nan, fps.shape)
else:
fpr = fps / fps[-1]
if tps[-1] <= 0:
tpr = np.repeat(np.nan, tps.shape)
else:
tpr = tps / tps[-1]
# -------------------------------computer auc------------------------------------
height = np.diff(fpr)
bottom = np.convolve(tpr, v=[1, 1], mode='valid')
auc = np.sum(height * bottom / 2)
return tpr, fpr, auc
def roc_plot(tpr, fpr, auc):
"""
:param tpr:
:param fpr:
:param auc:
:return:
"""
plt.figure(figsize=(12, 8))
plt.plot(fpr, tpr, color='darkorange',
lw=2, label='ROC curve (area = {:.4f})'.format(auc))
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.fill_between(fpr, tpr, color='C0', alpha=0.4, interpolate=True)
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="upper left")
plt.show()
def main():
y_label = [1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0]
y_score = [-0.20079125, 0.30423529, 0.2010557, 0.27523383, 0.42592946, -0.15043958,
-0.08794977, -0.12733765, 0.22931154, -0.23913774, -0.0638661, -0.14958713,
-0.04915145, 0.09898199, 0.05155884, -0.1142967, 0.16105883, 0.04871601,
-0.08258422, -0.26105925]
tpr, fpr, auc = get_roc(y_label, y_score)
roc_plot(tpr, fpr, auc=auc)
print('Done')
if __name__ == "__main__":
main()
实现5 sql:
整体计算:
select
(ry - 0.5*n1*(n1+1))/n0/n1 as auc
from(
select
sum(if(y=0, 1, 0)) as n0, --50
sum(if(y=1, 1, 0)) as n1,--100
sum(if(y=1, r, 0)) as ry --100
from(
select y, row_number() over(order by score asc) as r
from(
select label as y, score
from table.name
)A
)B
)C
分场景计算:
select
scene,(ry - 0.5*n1*(n1+1))/n0/n1 as auc
from(
select scene,
sum(if(y=0, 1, 0)) as n0, --50
sum(if(y=1, 1, 0)) as n1,--100
sum(if(y=1, r, 0)) as ry --100
from(
select scene,y, row_number() over(partition by scene order by score asc) as r
from(
select scene,label as y, score
from table.name
)A
)B group by scene
)C
用SQL计算AUC的三种方法
参考:LTR那点事—AUC及其与线上点击率的关联详解
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import numpy as np
from sklearn.metrics import roc_auc_score
from collections import defaultdict
def gauc(label, pred, user_id):
'''
:param label: ground truth
:param prob: predicted prob
:param user_id: user index
:return: gauc
'''
if(len(label) != len(user_id)):
raise ValueError("impression id num should equal to the sample num,"\
"impression id num is {}".format(len(user_id)))
group_truth = defaultdict(lambda: [])
group_score = defaultdict(lambda: [])
for idx, truth in enumerate(label):
uid = user_id[idx]
group_truth[uid].append(label[idx])
group_score[uid].append(pred[idx])
group_flag = defaultdict(lambda: False)
for uid in set(user_id):
truths = group_truth[uid]
for i in range(len(truths)-1):
if(truths[i] != truths[i+1]):
flag = True
break
group_flag[uid] = flag
total_auc = 0
total_impression = 0
for uid in group_flag:
if group_flag[uid]:
total_auc += len(group_truth[uid]) * roc_auc_score(np.asarray(group_truth[uid]), np.asarray(group_score[uid]))
total_impression += len(group_truth[uid])
group_auc = float(total_auc) / total_impression
group_auc = round(group_auc, 4)
return group_auc
if __name__ == '__main__':
user_id = ['a', 'a', 'a', 'b', 'b', 'b', 'a']
label = [1, 0, 1, 0, 1, 1, 0]
pred = [0.4, 0.5, 0.7, 0.2, 0.6, 0.7, 0.4]
group_auc = gauc(label, pred, user_id)
print('group_auc: {:.4f}'.format(group_auc))
auc = roc_auc_score(label, pred)
print("auc: {:.4f}".format(auc))
参考:推荐算法评价指标
https://zhuanlan.zhihu.com/p/552278753
但是在广告排序场景下,线上排序通常考虑收益最大化,通过CTR * Bid进行排序,而非仅仅通过CTR进行排序。如果线下仅仅通过AUC来评价离线模型的效果,你往往会发现,线下的AUC涨了,但是线上的收入eCPM(千次广告展示收入)却降了。这是因为线下AUC的评估仅考虑点击率CTR,而线上展示不仅考虑了CTR,同时考虑了广告主的出价BID,二者之间存在一定的gap。
csAUC中,样本的排序是多层次的,负例是的level是最低的(lowest),而正例会按照其对应的bid进行排序,正例的bid越高,其level也是越高的。
参考:RS Meet DL(75)-考虑CPM的评估方法csAUC
P-R曲线及与ROC曲线区别
PR曲线会随着正负样本比例的变化而变化;但是ROC曲线不会。
从公式(2)和表中可以看出,TPR考虑的是第一行,实际都是正例,FPR考虑的是第二行,实际都是负例。因此,在正负样本数量不均衡的时候,比如负样本的数量增加到原来的10倍,那TPR不受影响,FPR的各项也是成比例的增加,并不会有太大的变化。因此,在样本不均衡的情况下,同样ROC曲线仍然能较好地评价分类器的性能,这是ROC的一个优良特性,也是为什么一般ROC曲线使用更多的原因。
即假设采样是随机的,采样完成后,给定一条正样本,模型预测为score1,由于采样随机,则大于score1的负样本和小于score1的负样本的比例不会发生变化。
但如果采样不是均匀的,比如采用word2vec的negative sample,其负样本更偏向于从热门样本中采样,则会发现auc值发生剧烈变化。