输入:预测值prob和标签label
输出:auc
xgboost基于“从集合中任意选择一个正样本和负样本,正样本预测值大于负样本预测值的概率”实现了带weight的auc。
将待计算的items按prob由高至低排列,根据排列后的items来统计共有多少个正pair(即满足正样本prob值大于负样本prob值的pair)。用sumpos和sumneg分别记录当前正负例个数,若第i个item的label为负,则增加了sumpos个正pair,依次类推。
def eval_auc(items):
items.sort(key=lambda e: e.prob, reverse=True)
auc, sum_pos, sum_neg = 0.0, 0.0
for item in items:
if item.label > 0:
sum_pos += 1
else:
auc += sum_pos
sum_neg += 1
auc = auc / (sum_pos + sum_neg)
return auc
这样的实现缺少考虑存在若干个item具有相同prob不同label的情况,需要设计一个buf来处理,bufpos和bufneg用于记录当前具有相同prob的正负例个数,此时增加了0.5 · bufneg · bufpos个正pair,修正后的python代码如下:
def eval_auc(items):
items.sort(key=lambda e: e.prob, reverse=True)
auc, sum_pos, sum_neg, buf_pos, buf_neg = 0.0, 0.0, 0.0, 0.0, 0.0
for i, item in enumerate(items):
if (i != 0 and item.prob != items[i - 1].prob):
auc += buf_neg * (sum_pos + buf_pos * 0.5)
sum_pos += buf_pos
sum_neg += buf_neg
buf_pos = buf_neg = 0.0
if item.label > 0:
buf_pos +=1
else:
buf_neg +=1
auc += buf_neg * (sum_pos + buf_pos * 0.5)
sum_pos += buf_pos
sum_neg += buf_neg
auc = auc / (sum_pos * sum_neg)
sklearn通过计算roc曲线下的面积得到auc的值。由于测试样本有限,auc曲线呈现阶梯形状,当多个测试样本的prob相等且label不完全相同时,auc在该区域表现为梯形。首先将prob由高至低排序,根据item的label计算得到TPR和FPR值,并用distinct_value_indices进行过滤,最后计算以TPR为y轴,FPR为x轴的roc曲线下方的梯形面积。
def eval_auc(items):
items.sort(key=lambda e: e.prob, reverse=True)
probs = np.ravel(list(map(lambda e: e.prob, items)))
labels = np.ravel(list(map(lambda e: e.label, items)))
distinct_value_indices = np.where(np.diff(probs))[0]
threshold_idxs = np.r_[distinct_value_indices, len(labels) - 1]
sum_pos = np.sum(labels)
sum_neg = np.sum(1 - labels)
true_positive_count = np.cumsum(labels, dtype=np.float64)[
threshold_idxs]
false_positive_count = np.cumsum(
1 - labels, dtype=np.float64)[threshold_idxs]
true_positive_rate = np.r_[0, true_positive_count] / sum_pos # TPR
false_positive_rate = np.r_[0, false_positive_count] / sum_neg # FPR
return np.trapz(true_positive_rate, false_positive_rate)
可以看到sklearn和xgboost的实现实际上是等价的。