tie-aware的检索指标

2023-10-31

检索常用指标:P(precision)、R(recall)、F1、AP(average precision)、RR(reciprocal rank)、NDCG(normalized discounted cumulative gain)ACG(average cumulative gain)WAP(Weighted mean Average Precision)
检索时采用的某种 metric(如 hashing 的 hamming 距离),对某个 query 产生的检索序列,可能会有同距离的点(如同一 hamming radius),同距离的点的集合称为一个 tie。tie 内各元素对于该 metric 等价,但它们不同的排序会影响最终指标的结果,例子见 [4, 7]。
对于这种情况,有两种评估策略:

  • 随机选一个合法的排序情况来评估。简单,但不合理,非确定性,相当于引入了未控制的排序变量,见 [4]。
  • 枚举所有合法的排序情况,分别评估,再取平均(macro-average)。合理,但暴力枚举复杂度是阶乘级。

为综合两者优势,可以设法直接导出第二种方法的平均,避免暴力枚举,见 [1]。这样导出的指标称为原指标的 tie-aware 版,加下标 T _T T 表示。

Notations

  • q。query 样本;
  • d ( ⋅ ) d(\cdot) d()。所选 metric,值越小表明样本与 q 越相似;
  • V = < v 1 , … , v n > V=<v_1,\dots,v_n> V=<v1,,vn>。用 d 对 q 的检索序列,有: d ( v i ) ≤ d ( v j ) , ∀ i < j d(v_i)\leq d(v_j), \forall i<j d(vi)d(vj),i<j。n 个检索样本根据 d 值相等割为 m 个 tie,第 i 个记为 V i V_i Vi
  • T = < t 0 , t 1 , … , t m > T=<t_0,t_1,\dots,t_m> T=<t0,t1,,tm>。一个下标序列, t i t_i ti 表示 V i V_i Vi最后一个元素的位置( t 0 t_0 t0 纯粹是为了表述方便而加的),于是 V i = { v t i − 1 + 1 , … , v t i } V_i=\{v_{t_{i-1}+1},\dots,v_{t_i}\} Vi={vti1+1,,vti},或用区间表示成 ( t i − 1 , t i ] (t_{i-1},t_i] (ti1,ti]
  • rel ( v i ) \text{rel}(v_i) rel(vi) v i v_i vi 与 q 的(ground-truth 的)相似性。默认的情况是相似为 1,不相似为 0,如 P、R、F1、AP 中都是这种定义;
  • r i , n i r_i,n_i ri,ni。分别表示 V i V_i Vi 中相似元素个数、总元素个数, r i = ∑ j = t i − 1 + 1 t i r e l ( v j ) r_i=\sum_{j=t_{i-1}+1}^{t_i}rel(v_j) ri=j=ti1+1tirel(vj) n i = ∣ V i ∣ n_i=|V_i| ni=Vi
  • R i R_i Ri r i r_i ri 的前缀和, R i = ∑ j = 1 i r i R_i=\sum_{j=1}^{i}r_i Ri=j=1iri。为了方便,定义 R 0 = 0 R_0=0 R0=0

P

考虑 P@k,可以兼容 P(即 P = P@n),后同。原始 P@k: P @ k ( V ) = 1 k ∑ i = 1 k r e l ( v i ) P@k(V)=\frac{1}{k}\sum_{i=1}^krel(v_i) P@k(V)=k1i=1krel(vi) 忽略常系数之后就是求和。当 k 卡在第 c 个 tie 中, k ∈ ( t c − 1 , t c ] k\in(t_{c-1},t_c] k(tc1,tc] V c V_c Vc 之前的相似元素个数是 R c − 1 R_{c-1} Rc1,与顺序无关;之后的对 P@k 无贡献; V c V_c Vc 中平均每个位置有 r c n c \frac{r_c}{n_c} ncrc 个相关的样本(从「是否相关」的角度共有 C n c r c C_{n_c}^{r_c} Cncrc 种组合,其中第 j 个位置放相关元素的有 C 1 1 ⋅ C n c − 1 r c − 1 C_1^1\cdot C_{n_c-1}^{r_c-1} C11Cnc1rc1 种),而处于前 k 的位置有 k − t c − 1 k-t_{c-1} ktc1 个,所以 tie-aware 版 P@k(V) 是: P T @ k ( V ) = 1 k [ R c − 1 + ( k − t c − 1 ) r c n c ] P_T@k(V)=\frac{1}{k}[R_{c-1}+(k-t_{c-1})\frac{r_c}{n_c}] PT@k(V)=k1[Rc1+(ktc1)ncrc]

R

recall 同 precision 几乎一样,只是系数不同: R T @ k ( V ) = 1 R m [ R c − 1 + ( k − t c − 1 ) r c n c ] R_T@k(V)=\frac{1}{R_m}[R_{c-1}+(k-t_{c-1})\frac{r_c}{n_c}] RT@k(V)=Rm1[Rc1+(ktc1)ncrc] 其中 R m R_m Rm 就是 m 个 tie 中所有相关样本的总个数。

F1

F1 是 P 和 R 的调和平均: F 1 @ k ( V ) = 1 1 P @ k ( V ) + 1 R @ k ( V ) = 2 k + R m ∑ i = 1 k r e l ( v i ) F1@k(V)=\frac{1}{\frac{1}{P@k(V)}+\frac{1}{R@k(V)}}=\frac{2}{k+R_m}\sum_{i=1}^krel(v_i) F1@k(V)=P@k(V)1+R@k(V)11=k+Rm2i=1krel(vi) 分析、改法类似前面,变成: F 1 T @ k ( V ) = 2 k + R m [ R c − 1 + ( k − t c − 1 ) r c n c ] F1_T@k(V)=\frac{2}{k+R_m}[R_{c-1}+(k-t_{c-1})\frac{r_c}{n_c}] F1T@k(V)=k+Rm2[Rc1+(ktc1)ncrc]

AP

  • (2021.11.10)有个问题:当 @k 时,分母在 k 所在 tie 的计算是不是也应该按概率处理一下?这个问题目前未解决,所以目前有用 @ALL 是保证没问题的。
  • (2020.11.13)由于采用 [1] 公式在 @k 时会与普通 mAP 差别很大,所以改成与 [9] 一致,分母用「检索序列前 k 个结果中的相关样本数」,见代码注释。
  • 计算 mAP@k 时关于分母应该是「整个检索序列的相关样本数」,还是「检索序列前 k 个结果中的相关样本数」可见 [9] 中笔记。本文暂时以 [1] 的公式为准,即采用前者,而与 [9] 中同。 不过,当计算 m A P T mAP_T mAPT@ALL 的时候,这种分歧不造成影响。

原始 AP@k 公式是: A P @ k ( V ) = ∑ i = 1 k P @ i ( V ) ⋅ r e l ( v i ) R m AP@k(V)=\frac{\sum_{i=1}^kP@i(V)\cdot rel(v_i)}{R_m} AP@k(V)=Rmi=1kP@i(V)rel(vi) 对于每个位置 i,假设其落在第 f ( i ) f(i) f(i) 个 tie: i ∈ ( t f ( i ) − 1 , t f ( i ) ] i\in(t_{f(i)-1},t_{f(i)}] i(tf(i)1,tf(i)] P T @ i ( V ) P_T@i(V) PT@i(V) 的分母部分是 i; r e l ( v i ) rel(v_i) rel(vi) 有两种情况:

  • r e l ( v i ) = 0 rel(v_i)=0 rel(vi)=0,贡献为 0,加了等于没加;
  • r e l ( v i ) = 1 rel(v_i)=1 rel(vi)=1,频率是 r f ( i ) n f ( i ) \frac{r_{f(i)}}{n_{f(i)}} nf(i)rf(i)。此时 P T @ i ( V ) P_T@i(V) PT@i(V) 的分子部分是 R f ( i ) − 1 + ( i − t f ( i ) − 1 − 1 ) r f ( i ) − 1 n f ( i ) − 1 + 1 R_{f(i)-1}+(i-t_{f(i)-1}-1)\frac{r_{f(i)}-1}{n_{f(i)}-1}+1 Rf(i)1+(itf(i)11)nf(i)1rf(i)1+1,中间那项的分析类似前面,其中 -1 是除了第 i 个位置,而第 i 个位置的贡献就是第三项 +1

所以综合两种情况: A P T @ k ( V ) = 1 R m ∑ i = 1 k 1 i [ R f ( i ) − 1 + ( i − t f ( i ) − 1 − 1 ) r f ( i ) − 1 n f ( i ) − 1 + 1 ] ⋅ r f ( i ) n f ( i ) AP_T@k(V)=\frac{1}{R_m}\sum_{i=1}^k\frac{1}{i}[R_{f(i)-1}+(i-t_{f(i)-1}-1)\frac{r_{f(i)}-1}{n_{f(i)}-1}+1]\cdot\frac{r_{f(i)}}{n_{f(i)}} APT@k(V)=Rm1i=1ki1[Rf(i)1+(itf(i)11)nf(i)1rf(i)1+1]nf(i)rf(i) 需要注意 n f ( i ) = 1 n_{f(i)}=1 nf(i)=1 的情况,即整个 tie 只有一个元素,此时 r f ( i ) − 1 n f ( i ) − 1 \frac{r_{f(i)}-1}{n_{f(i)}-1} nf(i)1rf(i)1 无意义,需要将 ( i − t f ( i ) − 1 − 1 ) r f ( i ) − 1 n f ( i ) − 1 (i-t_{f(i)-1}-1)\frac{r_{f(i)}-1}{n_{f(i)}-1} (itf(i)11)nf(i)1rf(i)1 整项置零,而此元素是否有贡献由后面的 r f ( i ) r_{f(i)} rf(i) 决定。

NDCG

介绍见 [5],此时 rel ( ⋅ ) \text{rel}(\cdot) rel() 可以是多值的,表示相似的程度。原始公式: D C G @ k ( V ) = ∑ i = 1 k G [ r e l ( v i ) ] ⋅ D ( i ) DCG@k(V)=\sum_{i=1}^kG[rel(v_i)]\cdot D(i) DCG@k(V)=i=1kG[rel(vi)]D(i) 其中 G ( ⋅ ) G(\cdot) G() 是 gain 函数,一般取 G ( x ) = 2 x − 1 G(x)=2^x-1 G(x)=2x1 D ( ⋅ ) D(\cdot) D() 是 discount 函数,取 D ( x ) = 1 log ⁡ ( 1 + x ) D(x)=\frac{1}{\log(1+x)} D(x)=log(1+x)1,此时可重写成: D C G @ k ( V ) = ∑ i = 1 k 2 r e l ( v i ) − 1 log ⁡ ( 1 + i ) DCG@k(V)=\sum_{i=1}^k\frac{2^{rel(v_i)}-1}{\log(1+i)} DCG@k(V)=i=1klog(1+i)2rel(vi)1 分子这个形式可以增大不同 r e l ( v i ) rel(v_i) rel(vi) 取值之间的差异,而当 r e l ( ⋅ ) ∈ { 0 , 1 } rel(\cdot)\in \{0,1\} rel(){0,1} 时与直接取 G ( x ) = x G(x)=x G(x)=x 等价。总体就是与相似程度正相关,与位置负相关。假设对于 q,最优的检索序列是 I I I,则: N D C G @ k ( V ) = D C G @ k ( V ) D C G @ k ( I ) NDCG@k(V)=\frac{DCG@k(V)}{DCG@k(I)} NDCG@k(V)=DCG@k(I)DCG@k(V) 给定 q 时 I I I D C G @ k ( I ) DCG@k(I) DCG@k(I) 都是常量,可以另外算。考虑 DCG@k(V) 的计算。每一个 tie V c V_c Vc 分别算,有两种情况:

  • V c V_c Vc 包含在 [1,k] 之内: 0 ≤ t c − 1 < t c ≤ k 0\leq t_{c-1}<t_c\leq k 0tc1<tck。对于 V c V_c Vc 中的任一样本 v,gain 是 G [ r e l ( v ) ] G[rel(v)] G[rel(v)],可以遍历所有 V c V_c Vc 的位置,且处于每个位置的频率都是 1 n c \frac{1}{n_c} nc1,所以 v 对 DCG@k 的贡献是: G [ r e l ( v ) ] n c ∑ i = t c − 1 + 1 t c D ( i ) \frac{G[rel(v)]}{n_c}\sum_{i=t_{c-1}+1}^{t_c}D(i) ncG[rel(v)]i=tc1+1tcD(i) 此处 G [ r e l ( v ) ] n c \frac{G[rel(v)]}{n_c} ncG[rel(v)] 可以看作 v 的 gain 的均值,故 V c V_c Vc 中所有元素对 DCG@k 的总贡献是: ∑ v ∈ V c G [ r e l ( v ) ] n c ∑ i = t c − 1 + 1 t c D ( i ) \sum_{v\in V_c}\frac{G[rel(v)]}{n_c}\sum_{i=t_{c-1}+1}^{t_c}D(i) vVcncG[rel(v)]i=tc1+1tcD(i)
  • k 在 V c V_c Vc 内: k ∈ ( t c − 1 , t c ] k\in(t_{c-1},t_c] k(tc1,tc]。此时 v 的 gain 均值不变(还是考虑 V c V_c Vc 所有可能的排列),只是位置截到前 k 就停,所以贡献是: ∑ v ∈ V c G [ r e l ( v ) ] n c ∑ i = t c − 1 + 1 k D ( i ) \sum_{v\in V_c}\frac{G[rel(v)]}{n_c}\sum_{i=t_{c-1}+1}^{k}D(i) vVcncG[rel(v)]i=tc1+1kD(i)

综上,有: D C G T @ k ( V ) = ∑ c = 1 m ′ ∑ v ∈ V c G [ r e l ( v ) ] n c ∑ i = t c − 1 + 1 min ⁡ { t c , k } D ( i ) DCG_T@k(V)=\sum_{c=1}^{m'}\sum_{v\in V_c}\frac{G[rel(v)]}{n_c}\sum_{i=t_{c-1}+1}^{\min\{t_c,k\}}D(i) DCGT@k(V)=c=1mvVcncG[rel(v)]i=tc1+1min{tc,k}D(i) 其中 m’ 使得 k ∈ ( t m ′ − 1 , t m ′ ] k\in(t_{m'-1},t_{m'}] k(tm1,tm]

Codes

  • (2021.10.24 fix bug)NDCG_tie 更新,之前在处理 k 所在 tie 的 gain 均值时有错,现而改正。不过此 bug 只在 k < A L L k < ALL k<ALL 时显现,影响 N D C G @ A L L NDCG@ALL NDCG@ALL 的结果。
  • 与 [10] 提供的程序对拍过,应该是正确的,样例还是 [9] 那个。注意 [10] 的代码是假设 hash 码是 0/1 的而不是 -1/1 的。因 [10] 的程序是没有 @k 的,所以测试通过是指在 m A P T @ A L L mAP_T@ALL mAPT@ALL N D C G T @ A L L NDCG_T@ALL NDCGT@ALL 下与 [10] 结果相同。
# import numpy as np

def hamming(A, B=None):
    """A, B: [None, bit]
    elements in {-1, 1}
    """
    if B is None: B = A
    bit = A.shape[1]
    return (bit - A.dot(B.T)) // 2


def sim_mat(label, label_2=None, sparse=False):
    if label_2 is None:
        label_2 = label
    if sparse:
        S = label[:, np.newaxis] == label2[np.newaxis, :]
    else:
        S = np.dot(label, label_2.T) > 0
    return S.astype(label.dtype)


def P_R_F1_tie(qH, rH, qL, rL, k=-1, sparse=False):
    """tie-aware precision@k, recall@k, F1@k
    qH, rH: [n, bit] & [m, bit], hash code of query & database samples
    qL, rL: [n, #class] & [m, #class], label of query & database samples
        if sparse, shapes are [n] & [m]
    k: position threshold of `P@k`
    sparse: whether label is sparse class ID or one-hot vector
    """
    m = rH.shape[0]
    if (k < 0) or (k > m):
        k = m
    D = hamming(qH, rH)
    Rnk = np.argsort(D)
    S = sim_mat(qL, rL, sparse)
    # 找 k 所在 tie: t_{c-1} < k <= t_c
    D_sort = np.vstack([d[r] for d, r in zip(D, Rnk)])
    D_at_k = D_sort[:, k-1:k]  # `-1` 因为下标是 0-base
    mask_tie = np.equal(D, D_at_k).astype(np.int)
    # r_c, n_c
    nc = mask_tie.sum(1)
    rc = (mask_tie * S).sum(1)
    # find t_{c-1}
    pos = np.arange(m)[np.newaxis, :]  # [1, m]
    tie_pos = np.where(np.equal(D_sort, D_at_k), pos, np.inf)
    tc_1 = np.min(tie_pos, 1) # - 1 + 1  # `+1` to shift 0-base to 1-base
    # R_{c-1}
    mask_pre = (D < D_at_k).astype(np.int)
    Rc_1 = (mask_pre * S).sum(1)
    # P@k, R@k, F1@k
    _common = Rc_1 + (k - tc_1) * rc / nc  # [n]
    Rm = S.sum(1)
    P_at_k = _common / k
    R_at_k = _common / Rm
    F1_at_k = 2 * _common / (k + Rm)
    return P_at_k.mean(), R_at_k.mean(), F1_at_k.mean()


def mAP_tie(qH, rH, qL, rL, k=-1, sparse=False):
    """tie-aware mAP
    ref:
    - https://blog.csdn.net/HackerTom/article/details/107458334
    - https://github.com/kunhe/TALR/blob/master/%2Beval/tieAP.m
    """
    n, m = qH.shape[0], rH.shape[0]
    if (k < 0) or (k > m):
        k = m
    D = hamming(qH, rH)
    Rnk = np.argsort(D)
    S = sim_mat(qL, rL, sparse)
    AP = 0
    pos = np.arange(m)  # 0-base
    # t_fi_1[k]: t_{f(k) - 1}
    t_fi_1 = np.zeros([m])
    # r_fi[k]: #relevant samples in the tie where k lies in
    r_fi = np.zeros([m])
    # n_fi[k]: #samples in the tie where k lies in
    n_fi = np.zeros([m])
    # R_fi_1[k]: prefix sum of r_fi (exclude r_fi[k])
    R_fi_1 = np.zeros([m])
    for d, s, rnk in zip(D, S, Rnk):
        # Rm = s.sum()  # #rel in all
        s_sort = s[rnk]
        Rm = s_sort[:k].sum()  # #rel in top-k
        if 0 == Rm:
            continue
        d_unique = np.unique(d)  # ascending
        d_sort = d[rnk]
        # s_sort = s[rnk]
        _R_fi_1 = 0  # R_{f(i) - 1}
        for _d in d_unique:
            tie_idx = (d_sort == _d)
            t_fi_1[tie_idx] = pos[tie_idx].min() # - 1 + 1  # `+1` to shift 0-base to 1-base
            _r_fi = s_sort[tie_idx].sum()
            r_fi[tie_idx] = _r_fi
            n_fi[tie_idx] = tie_idx.astype(np.int).sum()
            R_fi_1[tie_idx] = _R_fi_1  # exclude `_r_fi`
            _R_fi_1 += _r_fi

        # deal with invalid terms
        n_fi_1, r_fi_1 = n_fi - 1, r_fi - 1
        idx_invalid = (n_fi_1 == 0)
        n_fi_1[idx_invalid] = 1
        r_fi_1[idx_invalid] = 0
        # in computing (i - t_{f(i)-1} - 1),
        # the lastest `-1` is megered: pos = i - 1
        kernel = (R_fi_1 + (pos - t_fi_1) * r_fi_1 / n_fi_1 + 1) * r_fi / n_fi / (pos + 1)
        AP += kernel[:k].sum() / Rm

    return AP / n


def NDCG_tie(qH, rH, qL, rL, k=-1, sparse=False):
    """tie-aware NDCG
    ref:
    - https://blog.csdn.net/HackerTom/article/details/107458334
    - https://github.com/kunhe/TALR/blob/master/%2Beval/tieNDCG.m
    """
    n, m = Dist.shape
    if (k < 0) or (k > m):
        k = m
    G = 2 ** Rel - 1
    pos = np.arange(m) + 1  # 1-base
    D_inv = 1 / np.log2(1 + pos)
    Rank = np.argsort(Dist)

    _NDCG = 0
    for g, d, rnk in zip(G, Dist, Rank):
        dcg_best = (np.sort(g)[::-1][:k] * D_inv[:k]).sum()
        if 0 == dcg_best:
            continue
        d_unique = np.unique(d)  # ascending
        d_sort = d[rnk]
        g_sort = g[rnk]
        dcg = 0
        for _d in d_unique:
            tie_idx = (d_sort == _d)
            tie_pos = pos[tie_idx]
            tc_1 = tie_pos[0] - 1  # i.e. tie_pos.min() - 1
            if tc_1 >= k:  # k <= t_{c-1} < t_c, out of range
                break  # continue
            n_c = tie_idx.astype(np.int).sum()
            tc = tie_pos[-1]
            sum_d = (1 / np.log2(1 + np.arange(tc_1 + 1, min(k, tc) + 1))).sum()
            tie_avg_gain = g_sort[tie_idx].sum() / n_c
            dcg += tie_avg_gain * sum_d

        _NDCG += dcg / dcg_best

    return _NDCG / n

multiple R R R

  • 升级版,支持单个或多个 position thresholds,传 int 或 int tuple/list。
  • multi_nDCG_tie2021.10.24 debug 之后的单 k 版一致,在 @ A L L @ALL @ALL 时与 [10] 也一致。
def multi_mAP_tie(Dist, Sim, k=-1):
    """支持单 k、多 k
    多个 k 时传 int tuple/list
    """
    if isinstance(k, int):
        k = [k]
    n, m = Dist.shape
    for kid in range(len(k)):
        if (k[kid] < 0) or (k[kid] > m):
            k[kid] = m
    k = sorted(k)  # ascending
    assert k[0] != 0, "`@0` is meaningless and disallowed for efficiency"
    Rnk = np.argsort(Dist, axis=-1)
    # AP = 0
    AP = np.zeros([len(k)], dtype=np.float32)
    pos = np.arange(m)  # 0-base
    # t_fi_1[k]: t_{f(k) - 1}
    t_fi_1 = np.zeros([m])
    # r_fi[k]: #relevant samples in the tie where k lies in
    r_fi = np.zeros([m])
    # n_fi[k]: #samples in the tie where k lies in
    n_fi = np.zeros([m])
    # R_fi_1[k]: prefix sum of r_fi (exclude r_fi[k])
    R_fi_1 = np.zeros([m])
    for d, s, rnk in zip(Dist, Sim, Rnk):
        # Rm = s.sum()  # #rel in all
        s_sort = s[rnk]
        # Rm = s_sort[:k].sum()  # #rel in top-k
        # if 0 == Rm:
        #     continue
        Rm_list = s_sort.cumsum()
        if 0 == Rm_list[-1]:  # = Rm.max() = s_sort.sum()
            continue
        d_unique = np.unique(d)  # ascending
        d_sort = d[rnk]
        # s_sort = s[rnk]
        _R_fi_1 = 0  # R_{f(i) - 1}
        for _d in d_unique:
            tie_idx = (d_sort == _d)
            t_fi_1[tie_idx] = pos[tie_idx].min() # - 1 + 1  # `+1` to shift 0-base to 1-base
            _r_fi = s_sort[tie_idx].sum()
            r_fi[tie_idx] = _r_fi
            n_fi[tie_idx] = tie_idx.astype(np.int).sum()
            R_fi_1[tie_idx] = _R_fi_1  # exclude `_r_fi`
            _R_fi_1 += _r_fi

        # deal with invalid terms
        n_fi_1, r_fi_1 = n_fi - 1, r_fi - 1
        idx_invalid = (n_fi_1 == 0)
        n_fi_1[idx_invalid] = 1
        r_fi_1[idx_invalid] = 0
        # in computing (i - t_{f(i)-1} - 1),
        # the lastest `-1` is megered: pos = i - 1
        kernel = (R_fi_1 + (pos - t_fi_1) * r_fi_1 / n_fi_1 + 1) * r_fi / n_fi / (pos + 1)
        # AP += kernel[:k].sum() / Rm
        kernel_cumsum = np.cumsum(kernel)
        for kid, _k in enumerate(k):
            if Rm_list[_k - 1]:
                AP[kid] += kernel_cumsum[_k - 1] / Rm_list[_k - 1]

    _mAP = AP / n
    if 1 == _mAP.shape[0]:
        _mAP = _mAP[0]
    return _mAP


def multi_nDCG_tie(Dist, Rel, k=-1):
    if isinstance(k, int):
        k = [k]
    n, m = Dist.shape
    for kid in range(len(k)):
        if (k[kid] < 0) or (k[kid] > m):
            k[kid] = m
    k = sorted(k)  # ascending
    assert k[0] != 0, "`@0` is meaningless and disallowed for efficiency"
    G = 2 ** Rel - 1
    pos = np.arange(m) + 1  # 1-base
    D_inv = 1 / np.log2(1 + pos)
    Rank = np.argsort(Dist, axis=-1)

    _nDCG = np.zeros([len(k)], dtype=np.float32)
    for g, dist, rnk in zip(G, Dist, Rank):
        g_desc = np.sort(g)[::-1]
        if 0 == g_desc[0]:  # biggist DCG
            continue
        dcg_best_list = (g_desc * D_inv).cumsum()
        dist_unique = np.unique(dist)  # ascending
        dist_sort = dist[rnk]
        g_sort = g[rnk]

        _start_kid = 0
        dcg_list = np.zeros([len(k)], dtype=np.float32)
        for _d in dist_unique:
            tie_idx = (dist_sort == _d)
            tie_pos = pos[tie_idx]
            tc_1 = tie_pos[0] - 1  # i.e. tie_pos.min() - 1
            tc = tie_pos[-1]
            while _start_kid < len(k):
                if k[_start_kid] > tc_1:
                    break
                else:
                    _start_kid += 1
            if _start_kid >= len(k):
                break
            n_c = tie_idx.sum()
            tie_avg_gain = g_sort[tie_idx].sum() / n_c  # != g_sort[tie_idx].mean()
            tie_sum_d_list = (1 / np.log2(1 + np.arange(tc_1 + 1, tc + 1))).cumsum()
            for kid_offset, _k in enumerate(k[_start_kid:]):
                tie_sum_d = tie_sum_d_list[min(_k, tc) - tc_1 - 1]
                tie_dcg = tie_avg_gain * tie_sum_d
                dcg_list[kid_offset + _start_kid] += tie_dcg

        for kid, _k in enumerate(k):
            _ndcg = dcg_list[kid] / dcg_best_list[_k - 1]  # 0-base
            _nDCG[kid] += _ndcg

    _nDCG /= n
    if 1 == _nDCG.shape[0]:
        _nDCG = _nDCG[0]
    return _nDCG


if __name__ == "__main__":
    print("与原单 k 版对拍。结论:一致")
    N, M = 5, 20
    qB = np.random.randint(0, 2, size=(N, 3)) * 2 - 1
    rB = np.random.randint(0, 2, size=(M, 3)) * 2 - 1
    qL = np.random.randint(0, 2, size=(N, 7))
    rL = np.random.randint(0, 2, size=(M, 7))
    D = hamming(qB, rB)
    Rel = qL.dot(rL.T)
    S = (Rel > 0).astype(np.int32)
    k_list = [1] + list(range(0, M + 1, 5)[1:])
    print("k_list:", k_list)
    print("-> 对拍 mAP_tie")
    map1 = [mAP_tie(D, S, k=_k) for _k in k_list]
    map2 = multi_mAP_tie(D, S, k_list)
    print("mAP 1:", map1)
    print("mAP 2:", map2)
    print("-> 对拍 nDCG_tie")
    ndcg1 = [nDCG_tie(D, Rel, k=_k) for _k in k_list]
    ndcg2 = multi_nDCG_tie(D, Rel, k_list)
    print("nDCG 1:", ndcg1)
    print("nDCG 2:", ndcg2)

References

  1. Computing Information Retrieval Performance Measures Efficiently in the Presence of Tied Scores
  2. C# implementations
  3. Hashing as Tie-Aware Learning to Rank
  4. Tie-Breaking Bias: Effect of an Uncontrolled Parameter on Information Retrieval Evaluation
  5. Discounted cumulative gain
  6. NDCG及实现
  7. numpy和pytorch的argsort结果不同
  8. kunhe/TALR
  9. mAP(@R)计算代码
  10. kunhe/TALR/+eval
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

tie-aware的检索指标 的相关文章

  • 在同步函数中使用 javascript `crypto.subtle`

    在javascript中 是否可以使用浏览器内置的sha256哈希 https developer mozilla org en US docs Web API SubtleCrypto digest Converting a digest
  • 如何使redis中的“HSET”子键“过期”?

    我需要使 Redis 哈希中所有超过 1 个月的密钥过期 这不可能 https github com antirez redis issues 167 issuecomment 2559040 为了保持 Redis 简单 https git
  • 将 Python 中的 SHA 哈希计算转换为 C#

    有人可以帮我将以下两行 python 代码转换为 C 代码吗 hash hmac new secret data digestmod hashlib sha1 key hash hexdigest 8 如果您有兴趣 其余的看起来像这样 us
  • NodeJS 在目录中递归地哈希文件

    我能够实现目录中的递归文件遍历 即探索目录中的所有子目录和文件 为此我使用了answer https stackoverflow com questions 5827612 node js fs readdir recursive dire
  • 防止 .exe 时间戳发生变化

    有谁知道如何防止可执行文件的时间戳更改 我正在尝试为 exe 生成一致的哈希代码 但我认为时间戳可能会阻止这种情况发生 每次我重新编译代码 VS C 时 FastSum 都会生成不同的校验和 Thanks PE 文件格式 如 EXE 中 具
  • 在 MySQL 中插入时检查并防止相似字符串

    简要信息 我有3张桌子 Set id name SetItem set id item id position TempSet id 我有一个函数可以生成新的随机组合Item桌子 基本上 总是在成功生成之后 我在中创建一个新行Set表 获取
  • JSON 中的哈希到底是什么?

    我正在学习 JSON 但我发现你也可以将所谓的 哈希 放入 JSON 中 我在哪里可以找到什么是哈希 或者你能向我解释一下什么是哈希吗 另外 什么是哈希图 我有 C 和 C 经验 正在学习 JS Jquery 和 JSON 哈希是一个稀疏数
  • Rails/Ruby 合并两个具有相同键、不同值的哈希值

    我有两个想要合并的哈希值 它们看起来像这样 Hello gt 3 Hi gt 43 Hola gt 43 第二个哈希看起来像 Hello gt 4 Hi gt 2 Bonjour gt 2 我想合并这两个哈希数组 使结果看起来像 Hello
  • iPhone 和加密库

    我想我必须在我的 iPhone 应用程序中使用加密库 我想问你有关苹果公司实施的加密货币出口政策的影响 我需要做一些额外的事情吗 例如填写表格等 1 如果我使用 MD5 进行哈希处理 2 如果我使用对称加密 Thanks EDIT 2009
  • Jenkins Hash 的 Python 实现?

    是否存在该方法的原生 Python 实现詹金斯哈希 http burtleburtle net bob hash doobs html算法 我需要一个哈希算法 它接受任意字符串并将其转换为 32 位整数 对于给定的字符串 必须保证跨平台返回
  • 根据哈希值确认文件内容

    我需要 检查完整性 content文件数量 文件将写入 CD DVD 可能会被复制多次 这个想法是识别正确复制的副本 在从 Nero 等中删除它们之后 我对此很陌生 但快速搜索表明Arrays hashCode byte http down
  • c# AudioFingerprinting 和局部敏感哈希

    我之前发现过类似的帖子 但没有真正回答这个问题 在我的指纹识别中 我生成了一个包含 5 个整数的记录集 例如 33 42 88 121 194 这些对应于特定音乐样本的最高幅度的频率 例如 对于 30ms 的音频样本 我有以下频率的桶 0
  • MD5 哈希怎么可能无法“解密”呢? [复制]

    这个问题在这里已经有答案了 可能的重复 为什么 MD5 哈希值不可逆 https stackoverflow com questions 330207 how come md5 hash values are not reversible
  • 在 Perl 中将整个文件读入哈希值

    我在 Perl 中将文件读入哈希时遇到一些问题 Chr1 supercontig 000000000 1 500 PILOT21 588 1 3 14602 59349 1 Chr1 supercontig 000000001 5 100
  • 检测重复文件

    我想检测目录树中的重复文件 当发现两个相同的文件时 将仅保留其中一个重复文件 并删除其余的重复文件以节省磁盘空间 重复是指具有相同内容的文件 但文件名和路径可能不同 我正在考虑为此目的使用哈希算法 但不同的文件有可能具有相同的哈希值 因此我
  • 使用 openssl 库获取 x509 证书哈希

    我目前正在开发一个应用程序 它使用 openssl 库 libcrypto 来生成证书 现在我必须获取现有证书的哈希值 当我使用终端时 我可以使用以下命令生成哈希值 openssl x509 hash in cert pem noout 输
  • 如果数据库可访问,加盐和散列有什么意义?

    我刚刚学习了散列的概念 嘿 不要忘记盐 并使用盐来确保密码安全 散列它是一种单向加密 实际上不是加密而是散列 因此无法对其进行逆向工程 加盐是在散列之前在密码上添加随机创建的值的前缀或附加值 因为散列 只是散列 的问题是 一些天才提供了字典
  • 如何高效识别字符串命令?

    给定一系列命令和必须为每个命令运行的非常独特的代码 if cmd cmd setBoosterRocket else if cmd cmd windSales else if cmd cmd selfDustruct else if cmd
  • JavaScript 文件中的快速低冲突非加密哈希

    我正在寻找一种用 JavaScript 实现的低冲突的快速哈希 它不需要是加密哈希 我基本上使用它作为查看给定文件是否已上传 或部分上传 到用户帐户的方式 以节省他们在大型 视频 文件上上传的时间 我正在使用新的 HTML5 文件 API
  • 在表单中编辑序列化哈希?

    我正在序列化存储在settings表中的字段 并且希望能够在表单字段中编辑该哈希 class Template lt ActiveRecord Base serialize settings end 但我就是这么做那么文本区域只显示序列化数

随机推荐

  • 实现 后台需要显示订单信息,但是订单信息里面涉及到查询2张表 。本代码使用了MapListHandler

    Dao层的代码实现 Service层实现 Servlet层实现 Bean 实现效果 以上是图片展示 一下是部分代码展示 DAO部分 通过查出2个表的数据where u id o id的数据MapListHandler 然后再通过遍历MapL
  • CVE-2021-21287:MiniO未授权SSRF漏洞

    一 介绍 MinIO 是一个基于Apache License v2 0开源协议的对象存储服务 它兼容亚马逊S3云存储服务接口 非常适合于存储大容量非结构化的数据 例如图片 视频 日志文件 备份数据和容器 虚拟机镜像等 而一个对象文件可以是任
  • 尼科彻斯定理

    链接 尼科彻斯定理 牛客题霸 牛客网 nowcoder com 描述 验证尼科彻斯定理 即 任何一个整数m的立方都可以写成m个连续奇数之和 例如 1 3 1 2 3 3 5 3 3 7 9 11 4 3 13 15 17 19 输入一个正整
  • [Machine Learning & Algorithm] 随机森林(Random Forest)

    1 什么是随机森林 作为新兴起的 高度灵活的一种机器学习算法 随机森林 Random Forest 简称RF 拥有广泛的应用前景 从市场营销到医疗保健保险 既可以用来做市场营销模拟的建模 统计客户来源 保留和流失 也可用来预测疾病的风险和病
  • 使用MySQL8.0以上版本和MySQL驱动包8.0以上出现的问题

    目录 1 时区问题 2 驱动程序类问题 1 时区问题 问题代码
  • dll,lib,.a,.so的联系与区别。什么是共享库?与dll的区别是什么?

    dll lib a so的联系与区别 什么是共享库 与dll的区别是什么 区别与联系 静态库与动态库 问题 疑问 什么是共享存档 其他内容 map pdb 文件 区别与联系 本文结合所学和理解进行简单了描述dll与lib a so文件的关系
  • IBatis.net介绍

    从上而下的理解IBatis net这个简易的ORM框架 1 DAL层 public class AccountService public int TestInsertOne Accounts account Object obj Mapp
  • 基于QT开发的截图工具

    概述 这是一个使用QT设计的截图工具 目前效果图 历程 意动 现在网上免费的截图工具很多 最近用了一款很不错的 叫Snipaste 这个软件就是基于QT开发的 不过并没有开源 软件设计的很好用 界面也很清新 于是我也想自己尝试这设计一个这样
  • Oracle 性能最大化

    配置和优化有什么不同 获得最大的性能 配置操作系统 配置Oracle Oracle 性能 调整和配置数据库对象 优化Oracle 最大化 如果你问很多Oracle DBA 你工作中最大的一部分是什么 几乎所有的回答都是 数据库的配置和优化
  • google cartographer参数配置和话题转发

    为了对google cartographer进行实验仿真 安装完成后首先用官方rosbag进行实验没问题后再尝试用自己的rosbag文件 重要的参考资料 https google cartographer ros readthedocs i
  • win10安装TeXLive2019

    下载安装包 到TeX Live官网下载iso安装包 Acquiring TeX Live as an ISO image 点击上图中的链接 会根据网络选择合适的镜像 方便我们下载 我的镜像是上海交通大学的 http mirrors sjtu
  • Pytorch 图像增强 实现翻转裁剪色调等 附代码(全)

    目录 前言 1 裁剪 1 1 中心裁剪 1 2 随机裁剪 1 3 随机尺寸裁剪 2 翻转 2 1 水平翻转 2 2 垂直翻转 2 3 随机旋转 3 色调 3 1 灰度变换 3 2 色彩抖动 3 3 随机翻转颜色 3 4 随机调整锐度 3 5
  • 微信 支付和回调

    1 微信支付 兼容小程序 app h5等方式 RequestMapping value recharge getSign public JSONMessage getSign RequestParam int payType Request
  • Ubuntu 20.04虚拟机开机卡在 /dev/sda* clean ,针对AMD核显的解决办法

    问题描述如标题 此类问题存在的普遍解释是 1 存储空间不足 2 显卡驱动问题 因此在解决问题之前需要先判断自己的问题 首先重启 开机时按Esc建 进入Grub界面 选择第一个选项Ubuntu 之后选择Advanced options for
  • Java中的数据库连接--JDBC

    JDBC Java DataBase Connectivity 即Java数据库连接 就是使用Java语言操作数据库 JDBC的本质 官方定义的定义的一套操作所有关系型数据库的规则 即接口 这边使用MySQL数据库进行测试 1 快速入门 首
  • 非常有趣的的免费API接口,基本上很全了

    一 图灵聊天机器人 http doc tuling123 com openapi2 263611 二 百度地图开放平台 http lbsyun baidu com index php title webapi 三 Eolinker API
  • 【吐血整理】mysql密码正确但无法登陆

    一面 1 二叉搜索树和平衡二叉树有什么关系 强平衡二叉树 AVL 树 和弱平衡二叉树 2 B 树和 B 树的区别 为什么 MySQL 要使用 B 树 3 HashMap 如何解决 Hash 冲突 4 epoll 和 poll 的区别 及其应
  • C++ 语言的单元测试与代码覆盖率

    点击蓝字 关注我们 来源于网络 侵删 前言 测试是软件开发过程中一个必须的环节 测试确保软件的质量符合预期 对于工程师自己来说 单元测试也是提升自信心的一种方式 直接交付没有经过测试的代码是不太好的 因为这很可能会浪费整个团队的时间 在一些
  • Mysql 的安装与配置

    一 windows 服务器下的 mysql 1 安装软件安装 按软件提示一路确定下去 2 压缩包安装 1 解压安装包到自定义路径 2 修改 my ini 配置文件 复制解压好的文件路径 记事本打开 my ini 文件 将basedir 与
  • tie-aware的检索指标

    检索常用指标 P precision R recall F1 AP average precision RR reciprocal rank NDCG normalized discounted cumulative gain ACG av