衡量两个次序的差异的指标
1. Fitness
\[ F = \frac{1}{Z} \sum_j \frac{w_j}{\alpha (|p_i - q_j|+1) + (1-\alpha) p_i} \]
其中:
- $p_j$: 待排序中第 $j$ 条结果的位置
- $q_j$: 待测排序的第 $j$ 条结果在标准排序中的位置
- $w_j$: 标准排序中位置 $j$ 上的权重
- $Z$: 归一化因子
2. Kendall tau distance (wikipedia)
\[ K = \frac{2}{N(N-1)} \sum_{i=1}^{N-1} \sum_{j<i}^N k(i,j) \]
where
\[ k(i,j) = \begin{cases} 0 & \text{(if $i$, $j$ is in same order)} \\ 1 & \text{(if $i$, $j$ is not in same order)} \end{cases} \]
$K$ 的取值范围是 $[0,1]$,两种顺序完全相同时为 $0$,完全相反时为 $1$.
3. NDCG (wikipedia)
两种定义(第一个有改动):
\[ NDCG = \frac{1}{Z} \sum_{j = 1}^N \frac{r_j}{\log_2 (1+j)} \]
\[ NDCG = \frac{1}{Z} \sum_{j = 1}^N \frac{2^{r_j}-1}{\log_2 (1+j)} \]
- $r_j$: 第 $j$ 条结果的评分等级
- $Z$: 归一化因子,理想最大值
4. AUC(wikipedia)
数据样例:
- $y$: 正负样本标记,预测目标, $ y \in \{0, 1\} $
- $s$: 预测分数 score
- $r$:按 score 排序后的顺序标记 rank
- $m, n$:正负样本个数
score | 0.86 | 0.81 | 0.73 | 0.66 | 0.52 | 0.43 | 0.36 | 0.31 | 0.26 |
rank | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
y | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 |
ROC曲线:
- ROC:receiver operating characteristic curve。
- 横坐标:FP (false positive),纵坐标:TF(True positive)。
- 遍历分类器阈值,可得到一系列不同的 (FP,TP) ,这些点构成的曲线即为ROC。
- 等效于,在排序好的数据列表中,从左向右扫过score。若将当前扫过的位置作为分类线,当前 score 作为分类阈值,则每个位置对应一个 (FP, TP) 值对。FP、TP 分别等于当前位置左侧的 $y=1, y=0$ 的个数。
\[(0,0), (0,1), (0,2), (1,2), (1,3), (1,4), (2, 4), (2,5), (3,5), (4,5)\]
AUC:
AUC 即 ROC 曲线下的面积。三种计算方式:
1,根据定义:遍历排好序的样本,每个样本画出ROC上的一个点。每遇一个正样本,纵轴升一个(TP++);每遇一个负样本,横轴加一个(FP++),同时累积面积增加TP。遍历完后,TP = $m$, FP = $n$,所得面积除以总面积 ($m \cdot n$) 即为AUC。
只需遍历一次,复杂度为 $O(N)$
2,在按 score 排序的样本序列中,每个正样本之后的负样本的个数的和,除以 $m \cdot n$。
复杂度为 $O(N^2)$。
$$ AUC = \frac{\sum_{i_+} | \{ s_{j_-} |s_{j_-} < s_{i_+}\}|} {m \cdot n} $$
3,方法2换个角度看,就是把正负样本之间两两组合,其中正样本 score 大于负样本 score 的组合的个数(score相等的算0.5) 。结果再除以 $m \cdot n$
$$ AUC = \frac{|\{(s_{i_+}, s_{j_-}) | s_{i_+} > s_{j_-}\}|}{m \cdot n} $$
4,计算正样本的 rank 的和,然后按下式计算
\[ AUC = \frac{\sum r_i y_i - m (m + 1) / 2}{m \cdot n} \]
5,区块方法。以上方法都是对单例计算的,无法计算 AUC_UP。将值相近的 score 聚合成区块,用一个得分 $t_i$ 表示,统计每个区块 $i$ 中的正负例个数 $m_i, n_i$,形成压缩的新数据元:$(t_i, m_i, n_i)$。按 $t_i$ 排序后,再按 1 中的方法计算累积面积(用梯形面积近似,所以每次增加面积是 $(TP + m_i/2) \cdot n_i$)。每次纵轴、横轴增加的量分别为 $m_i, n_i$。
计算 AUC_UP 时,只需将序列按新数据元的经验概率 $m_i/(m_i+n_i)$ 排序,再按相同方法计算 AUC。
2->1 方法的等效性:
方法 2 中,每个正例之后的负例的个数,等于在 ROC 曲线中,该正例点向右的长度。即每个正例对 ROC 下的面积的贡献,是其对应的点向右的宽度为1的横带。这是因为正例的出现只使 ROC 曲线在纵向上增长。
2->4 方法的等效性:
对于第一个正例,其后的负样本的个数为 $9-m$。对于第 $i_+$ 个正例,其后共有 $r_{i+} - 1$ 个样本,其中正例有 $m-i$ 个,所以其后的负例个数是 $r_{i_+} - 1 - (m-i_+)$。所以求和过程做以下整理后,即与方法 3 相同。
$$ \sum_{i_+ = 1}^m [ r_{i+} - 1 - (m-i_+) ]
= \sum_{i_+ = 1}^m r_{i+} - m - m^2 + \sum_{i_+ = 1}^m i_+ \\
\qquad = \sum_{i_+ = 1}^m r_{i+} - m (m+1)/2 \\
\qquad = \sum_i r_i y_i - m (m+1)/2 $$
5->1 方法的等效性:
单数据元中,正负例 $y_i=0,1$ 分别对应batch方法中 $(m_i, n_i)$ 等于 $(1, 0), (0,1)$。所以方法 1 是 4 的特例,可从实现代码中看出。
示例代码:
1 #!/usr/bin/env python 2 #!encoding:utf-8 3 4 s = [0.86, 0.81, 0.73, 0.66, 0.52, 0.43, 0.36, 0.31, 0.26] 5 y = [1, 1, 0, 1, 1, 0, 1, 0, 0] 6 7 ll = [[s[i], y[i]] for i in range(len(s)) ] 8 9 def auc_1(ll): 10 m, n, area = 0, 0, 0 11 for s, y in ll: 12 if y == 1: 13 m += 1 14 if y == 0: 15 n += 1 16 area += m 17 return 1. * area / (m * n) 18 19 def auc_2(ll): 20 m, n, cnt = 0, 0, 0. 21 for i, (s, y) in enumerate(ll): 22 if y == 0: 23 n += 1 24 continue 25 if y == 1: 26 m += 1 27 for j in range(i, len(ll)): 28 if ll[j][1] == 0: 29 cnt += 1 30 return cnt / (m * n) 31 32 def auc_3(ll): 33 m, n, cnt = 0, 0, 0 34 for i in range(len(ll)): 35 if ll[i][1] == 1: 36 m += 1 37 elif ll[i][1] == 0: 38 n += 1 39 for j in range(i+1, len(ll)): 40 if ll[i][0] == ll[j][0]: 41 cnt += 0.5 42 elif ll[i][0] > ll[j][0] and ll[i][1] > ll[j][1]: 43 cnt += 1 44 if ll[i][0] < ll[j][0] and ll[i][1] < ll[j][1]: 45 cnt += 1 46 return 1. * cnt / (m * n) 47 48 def auc_4(ll): 49 m, n, q = 0, 0, 0 50 for i, (s, y) in enumerate(ll): 51 if y == 1: 52 m += 1 53 q += len(ll) - i 54 if y == 0: 55 n += 1 56 auc = 1. * ( q - m * (m + 1) / 2) / (m * n) 57 return auc 58 59 def for_auc_5(score_np): 60 m, n, area = 0, 0, 0. 61 for t, [mi, ni] in score_np: 62 area += (m + mi /2.)*ni 63 m += mi 64 n += ni 65 return area / (m * n) 66 67 def auc_5(ll): 68 score_block = {} 69 for s, y in ll: 70 t = round(s, 5) # 粗粒化。省略则与方法1相同。粒度越粗AUC_UP越低 71 if t in score_block: 72 score_block[t][0] += y 73 score_block[t][1] += 1-y 74 else: 75 score_block[t] = [y, 1-y] 76 score_np = sorted(score_block.items(), key = lambda x: -x[0]) 77 auc = for_auc_5(score_np) 78 score_np = sorted(score_block.items(), key = lambda x: -x[1][0]/(x[1][0]+x[1][1])) 79 auc_up = for_auc_5(score_np) 80 return auc, auc_up 81 82 ll.sort(key = lambda x: -x[0]) 83 84 print "auc1:", auc_1(ll) 85 print "auc2:", auc_2(ll) 86 print "auc3:", auc_3(ll) 87 print "auc4:", auc_4(ll) 88 print "auc5:", auc_5(ll)