具体研究多标签和极限多标签 (XML) 的时候, 合理使用评价指标是关键.
最近在研究极限多标签算法的时候发现了它和传统多标签算法的评价指标是有异的, 而且我曾经积累的传统多标签评价指标也没有一个系统的体系 (很混乱). 于是写下本文用于自我总结.
查询目录<想看什么直接通过这来查>
- 1.常规多标签的评价指标
- 1.1 逐项匹配的占比
- 1.1.1 Accuracy
- 1.1.2 Hamming Loss
- 1.2 混沌矩阵有关参数引导的评价指标 (非排序版)
- 1.2.1 Precision与Recall
- 1.2.2 非排序版 F 1 -Score F_1\text{-Score} F1-Score
- 1.2.3 非排序版AUC
- 1.3 基于排序的一些评价指标
- 1.3.1 One-Error
- 1.3.2 Coverage
- 1.3.3 Ranking Loss
- 1.3.4 NDCG
- 1.4 混沌矩阵有关参数引导的评价指标 (排序方法)
- 1.4.1 Average Precision (AP)
- 1.4.2 基于排序的AUC
- 1.4.3 Peak- F 1 \text{Peak-}F_1 Peak-F1
- 2.极限多标签的评价指标
- 2.1 Precision@k
- 2.2 NDCG@k
- 2.3 PSP@k
- 2.4 PSnDCG@k
- 3 私货环节 (随时更新)
- 3.1 Python
- 3.2 Matlab
- 4 后记
1.常规多标签的评价指标
常规的多标签学习是训练一个
N
×
L
N \times L
N×L的标签预测矩阵
Y
^
\hat{\mathbf{Y}}
Y^, 然后基于
Y
^
\hat{\mathbf{Y}}
Y^全体数据 (不忽略任何值)来讨论与同形的目标标签矩阵
Y
\mathbf{Y}
Y的关系.
N
N
N是标签向量的个数,
L
L
L是单个标签向量的维度, 有任意
∣
y
i
∣
=
L
|\mathbf{y}_i|=L
∣yi∣=L.
注意, 这里
y
i
j
∈
{
0
,
1
}
L
y_{ij}\in\{0,1\}^L
yij∈{0,1}L,
y
^
i
j
∈
R
L
\hat{y}_{ij}\in\mathbb{R}^{L}
y^ij∈RL. 前者是二元的, 后者是实型的, 其范围由不同算法的预测方式不同而不同, 往往是介于区间
[
0
,
1
)
[0, 1)
[0,1)的小数.
细节上, 评价指标主要分为 基于样本的评价指标 和 基于标签的评价指标
- 基于样本的评价指标 : 对测试集中所有样本预测结果与真实情况之间进行一定的评价对比.
- 基于标签的评价指标 : 通过对每个单独标签的预测结果进行度量, 最后再对所有标签结果取平均.
往往前者的方法我们喜欢称之为微平均, 使用前者的喜欢加上前缀Micro-; 后者的方法称之为宏平均, 使用前者的喜欢加上前缀Macro-. 基本上下述所有的算法都可以从这两个角度去考虑.
1.1 逐项匹配的占比
逐项匹配的占比的评价指标喜欢先对
Y
^
\hat{\mathbf{Y}}
Y^实行一次二值化预测后再来对比. 细节上,
Y
^
\hat{\mathbf{Y}}
Y^将大于阈值
θ
\theta
θ的实数预测为+1, 其余预测为-1. 如此后,
y
^
i
j
\hat{y}_{ij}
y^ij 也成为了二值化数值.
然后就有了下面1.1.1与1.1.2中的两个评价指标.
一般来说, 这种通过逐项匹配来计算占比的算法并没有明显的基于样本或标签的特点, 或者说, 通过这两种方案算出来的结果没有不同.
1.1.1 Accuracy
对于准确度Accuracy来说, 判断 Y \mathbf{Y} Y与二值化后 Y ^ \hat{\mathbf{Y}} Y^的关系, 若有 y ^ i j ∈ Y ^ \hat{y}_{i j}\in \hat{\mathbf{Y}} y^ij∈Y^, 同样的 i i i与 j j j, 也有 y i j ∈ Y y_{i j}\in\mathbf{Y} yij∈Y, 这时若有 y i j = y ^ i j y_{i j} = \hat{y}_{ij} yij=y^ij, 则视为预测正确, 否则视为预测失败. 故Accuracy可以认为是正确预测的占比, 它自然越大是越好的:
Acc = ∑ i , j y ^ i j ⊙ y i j ∣ Y ∣ \textrm{Acc}=\sum_{i,j}\frac{\hat{y}_{ij}\odot y_{ij}}{|\mathbf{Y}|} Acc=i,j∑∣Y∣y^ij⊙yij这里 ⊙ \odot ⊙表示"同或", y ^ i j \hat{y}_{ij} y^ij与 y i j y_{ij} yij不同即0, 否则返回1.
Accuracy有个老生常谈的问题, 当标签稍微稀疏点, 零元素一多, 预测失误会被过多的零元素掩盖.
1.1.2 Hamming Loss
Hamming Loss与Accuracy是类似的, Hamming Loss通过计算多标签分类器预测的标签结果 y ^ i j \hat{y}_{ij} y^ij (二值化后)与实际标签 y i j y_{ij} yij的距离差来进行度量. 本质上也是一个逐个权衡然后再除全体个数. 它的公式如下:
Hamming-Loss = ∑ i , j y ^ i j ⊕ y i j ∣ Y ∣ \textrm{Hamming-Loss}=\sum_{i,j}\frac{\hat{y}_{ij}\oplus y_{ij}}{|\mathbf{Y}|} Hamming-Loss=i,j∑∣Y∣y^ij⊕yij
这里 ⊕ \oplus ⊕是异或, y ^ i j \hat{y}_{ij} y^ij与 y i j y_{ij} yij保持不同就是1, 其余都是0, 因此Hamming Loss越小越好. 我的理解是Hamming Loss是Accuracy的对偶问题, 自然它们也共享相同的缺点
1.2 混沌矩阵有关参数引导的评价指标 (非排序版)
其余还有些零零散散的基于混沌矩阵的参数实现的全局的评价指标
1.2.1 Precision与Recall
其中主要有查准率和查全率的方案. 查准率 (Precision)用来判断在所有我的预测为正的样本中, 我预测对了的占比:
P = ∑ i , j ∈ Y ^ + y ^ i j × y i j ∣ Y ^ + ∣ \textrm{P} = \sum_{i,j \in \hat{\mathbf{Y}}^{+}}\frac{\hat{y}_{ij}\times y_{ij}}{|\hat{\mathbf{Y}}^{+}|} P=i,j∈Y^+∑∣Y^+∣y^ij×yij
这里的
Y
^
+
\hat{\mathbf{Y}}^{+}
Y^+就简单表示那些预测为正的标签组成的集合,
∣
Y
^
+
∣
|\hat{\mathbf{Y}}^{+}|
∣Y^+∣则是它们的数量.
查全率 (Recall)用来判断在所有正样本中, 我们预测对了的占比:
R = ∑ i , j ∈ Y + y ^ i j × y i j ∣ Y + ∣ \textrm{R} = \sum_{i,j \in \mathbf{Y}^{+}}\frac{\hat{y}_{ij}\times y_{ij}}{|\mathbf{Y}^{+}|} R=i,j∈Y+∑∣Y+∣y^ij×yij
1.2.2 非排序版 F 1 -Score F_1\text{-Score} F1-Score
基于这两个准则而建立起来的全局的的 F 1 F_1 F1- Measure \text{Measure} Measure (又称之为 F 1 F_1 F1- Score \text{Score} Score):
F 1 -Score = 2 P R P + R F_1\text{-Score} = \frac{2PR}{P+R} F1-Score=P+R2PR
最好的情况下
P
=
R
=
1.0
P=R=1.0
P=R=1.0, 这个时候
F
1
F_1
F1-
Score
\text{Score}
Score的值为1.
注意, 这种先计算全局的Precision和Recall后再直接算
F
1
F_1
F1-
Score
\text{Score}
Score的叫做
Micro-
F
1
\text{Micro-}F_1
Micro-F1, 即微平均
F
1
F_1
F1
而另外存在一个宏平均的
F
1
F_1
F1, 即
Macro-
F
1
\text{Macro-}F_1
Macro-F1, 这种方法会先计算每个标签的Precision和Recall, 从而计算出每个标签的
F
1
-Score
F_1\text{-Score}
F1-Score, 最后求每个标签的
F
1
-Score
F_1\text{-Score}
F1-Score的平均.
当然, 无论是
Macro-
F
1
\text{Macro-}F_1
Macro-F1还是
Micro-
F
1
\text{Micro-}F_1
Micro-F1, 他们都是一种静态的, 基于一个不加以改动的
Y
^
\hat{\mathbf{Y}}
Y^和
Y
\mathbf{Y}
Y的比较来获得.
可见:
微平均
F
1
F_1
F1:
Micro-
F
1
\text{Micro-}F_1
Micro-F1是基于样本的评价指标
宏平均
F
1
F_1
F1:
Macro-
F
1
\text{Macro-}F_1
Macro-F1是基于标签的评价指标
稍微扩展下, F β -Score F_\beta\text{-Score} Fβ-Score其实是 F 1 -Score F_1\text{-Score} F1-Score的一般形式, F 1 -Score F_1\text{-Score} F1-Score是 F β -Score F_\beta\text{-Score} Fβ-Score中 β = 1 \beta=1 β=1的特殊情况. 这个公式如下:
F β -Score = ( 1 + β 2 ) P R ( 1 + β 2 ) ( P + R ) F_\beta\text{-Score} = \frac{(1+\beta^2)PR}{(1+\beta^2)(P+R)} Fβ-Score=(1+β2)(P+R)(1+β2)PR
其中, 当我们认为Recall更重要时, 往往会将 β = 2 \beta=2 β=2, 另外一些情况, 我们认为Precision更重要, 我们会取 β ∈ ( 0 , 1 ) \beta\in(0,1) β∈(0,1), 一般情况我们会折中取1, 这就是 F 1 -Score F_1\text{-Score} F1-Score被我们官方使用的原因. 当然, 如果你的数据有显然的偏向性, 也可以另辟蹊径试试新的 F F F值.
1.2.3 非排序版AUC
此外还有基于ROC曲线诞生的另一个评价指标AUC (AUC是ROC的曲线面积).
AUC的计算方案没有一个完全统一的标准, 但是就其本质方法来说, 是基于最佳的排序绘制出ROC曲线, 然后计算这个曲线的面积, 但是这个是排序评价指标, 稍后会提到, 故这里不过多赘述.
此外AUC还有其他的一些计算准则, 例如目标空间中有确定的
M
M
M个正样本和
N
N
N个负样本, 这样我们就能找到
M
×
N
M \times N
M×N个正>负的标签对
(
y
i
,
y
j
)
(
i
∈
Y
+
,
j
∈
Y
−
)
(y_i, y_j)(i\in \mathcal{Y}^+, j\in \mathcal{Y}^- )
(yi,yj)(i∈Y+,j∈Y−). 然后将目标空间中这些标签映射到预测空间, 如果发现预测空间也满足
y
^
i
>
y
^
j
\hat{y}_i > \hat{y}_j
y^i>y^j, 那么定义
I
(
y
i
,
y
j
)
=
1
I(y_i, y_j) = 1
I(yi,yj)=1, 其余定义见下式:
AUC = ∑ i , j I ( y i , y j ) M × N , I ( y i , y j ) = { 1 , y ^ i > y ^ j 0.5 , y ^ i = y ^ j 0 , y ^ i < y ^ j \textrm{AUC} = \frac{\sum_{i,j} I(y_i, y_j)}{M\times N}, I(y_{i}, y_{j})=\left\{\begin{array}{l} 1, \hat{y}_{i}>\hat{y}_{j} \\ 0.5, \hat{y}_{i}=\hat{y}_{j} \\ 0, \hat{y}_{i}<\hat{y}_{j} \end{array}\right. AUC=M×N∑i,jI(yi,yj),I(yi,yj)=⎩⎨⎧1,y^i>y^j0.5,y^i=y^j0,y^i<y^j
倘若我们的目标空间与预测空间都是全体的
Y
\mathbf{Y}
Y和
Y
^
\hat{\mathbf{Y}}
Y^, 那么这就是:
微平均
AUC
\text{AUC}
AUC:
Micro-AUC
\text{Micro-AUC}
Micro-AUC, 是基于样本的评价指标
倘若我们的目标空间与预测空间都是每个标签
y
k
\mathbf{y}_k
yk和
y
k
^
\hat{\mathbf{y}_k}
yk^ (
k
k
k表示某个任意标签), 那么我们会计算出每个标签的
AUC
k
\text{AUC}_k
AUCk, 最终进行求和算平均, 这就是:
宏平均
AUC
\text{AUC}
AUC:
Macro-AUC
\text{Macro-AUC}
Macro-AUC, 是基于标签的评价指标
这个方法可以认为是我们后续讲述的Ranking Loss评价指标的一种对偶.
1.3 基于排序的一些评价指标
我将这里的基于排序的方法与混沌矩阵有关参数的方法区分来看.
1.3.1 One-Error
One-Error 衡量了测试集的样例中排名第一的标签不是相关标签的比例, 这个值我们希望越小越好.
One-Error = 1 m ∑ i = 1 m ( ( arg max 0 ≤ j < L y ^ i ) ∉ Y i + ) \textrm{One-Error} = \frac{1}{m}\sum^{m}_{i=1}\left((\argmax_{0\le j <L}\mathbf{\hat{y}}_i)\notin \mathbf{Y}^+_i\right) One-Error=m1i=1∑m((0≤j<Largmaxy^i)∈/Yi+)
这里的
y
^
i
\mathbf{\hat{y}}_i
y^i表示第
i
i
i个标签,
arg min
0
≤
j
<
L
y
^
i
\argmin_{0\le j <L}\mathbf{\hat{y}}_i
0≤j<Largminy^i取出了
i
i
i-th标签向量中预测实数值最高的那个值的下标
j
j
j (L是下标可取的上界),
Y
i
+
\mathcal{Y}^+_i
Yi+是
Y
\mathbf{Y}
Y中的第
i
i
i个标签中正标签的下标集合. 因此, 如果我们对于第
i
i
i个标签中最高预测值预测错了, 那么求和式就会+1, One-Error就会增加.
1.3.2 Coverage
Coverage 衡量了在预测的排序标签列表上需要从头走多少步, 才能覆盖真实标签中所有的正例:
One-Error = 1 m ∑ i = 1 m ( max j ∈ Y i + rank ( y ^ i ) − 1 ) \textrm{One-Error} = \frac{1}{m}\sum^{m}_{i=1}\left(\max_{j \in \mathcal{Y}^+_i} \textrm{rank}(\mathbf{\hat{y}}_i) -1\right) One-Error=m1i=1∑m(j∈Yi+maxrank(y^i)−1)
这里
Y
i
+
\mathcal{Y}^+_i
Yi+是
Y
\mathbf{Y}
Y中的第
i
i
i个标签中正标签的下标集合.
max
j
∈
Y
i
+
rank
(
y
^
i
)
\max_{j \in \mathcal{Y}^+_i} \textrm{rank}(\mathbf{\hat{y}}_i)
maxj∈Yi+rank(y^i) 选择了
y
^
i
\mathbf{\hat{y}}_i
y^i预测标签中排名尽可能靠后的一个在真实标签中映射为+1的标签的下标. 至于式中 - 1是依情况而定, 因为我们这里假定
Rank()
\textrm{Rank()}
Rank()返回排名最小是1, 而最好的情况下一步不走就能覆盖所有 (图中
y
^
3
\hat{y}_3
y^3→
y
3
y_3
y3)
图例解释(
y
^
2
\hat{y}_2
y^2→
y
2
y_2
y2):
y
^
2
=
[
0.11
,
0.47
,
0.55
,
0.29
]
\hat{y}_2 = [0.11,0.47,0.55,0.29]
y^2=[0.11,0.47,0.55,0.29], 排序后的
y
^
2
=
[
0.55
,
0.47
,
0.29
,
0.11
]
\hat{y}_2 = [0.55,0.47,0.29,0.11]
y^2=[0.55,0.47,0.29,0.11], 按照这种排序映射到
y
2
y_2
y2, 为
[
1
,
0
,
1
,
0
]
[1,0,1,0]
[1,0,1,0], 可以发现走了两步走到了最后一个1, 即
[
1
→
0
→
1
,
0
]
[1 \rightarrow 0\rightarrow1,0]
[1→0→1,0].
Coverage 是越小越好.
1.3.3 Ranking Loss
Ranking Loss 反映了所有样本的预测标签排序中, 不相关标签排在相关标签前面的概率.
Ranking-Loss = 1 m ∑ i = 1 m 1 ∣ Y i − ∣ ∣ Y i + ∣ ∣ { ( y i j , y i k ) ∣ y ^ i j ≥ y ^ i k , ( y i j , y i k ) ∈ Y i − × Y i + } ∣ \textrm{Ranking-Loss} = \frac{1}{m}\sum^{m}_{i=1}\frac{1}{|\mathcal{Y}^-_i||\mathcal{Y}^+_i|}| \{ (y_{ij},y_{ik})| \hat{y}_{ij}\ge \hat{y}_{ik}, (y_{ij},y_{ik})\in \mathcal{Y}^-_i\times \mathcal{Y}^+_i\}| Ranking-Loss=m1i=1∑m∣Yi−∣∣Yi+∣1∣{(yij,yik)∣y^ij≥y^ik,(yij,yik)∈Yi−×Yi+}∣
这里
Y
i
−
\mathcal{Y}^-_i
Yi−是标签
y
i
\mathbf{y}_i
yi中的负标签构成的集合, 显然, 其补集就是
Y
i
−
\mathcal{Y}^-_i
Yi−. 下面是以第二个标签为例的图例:
可见, Ranking Loss也是越小越好, 一旦有错误的预测排到非常靠前的话, 这个错误值超过了多少正确值, 那么就会等线性地惩罚它. 他的思想类似与
NDCG
\text{NDCG}
NDCG, 只不过后者是对数级别的.
这个算法可以认为是
AUC
\text{AUC}
AUC一种解法的对偶, 而且它虽然名称中有Rank, 但是它可以不通过排序实现.
1.3.4 NDCG
NDCG 这个评价指标来自于推荐系统, 他是DCG的归一化表示, DCG是评价值与排名的对数损失的比例
(NDCG有的地方也喜欢写作nDCG, 这个好像没有完全统一的标准)
(这里的对数是以2为底的)
DCG ( r , y ^ i ) = ∑ l = 1 k y ^ i l r log ( 1 + l ) \text{DCG}(\mathbf{r}, \mathbf{\hat{y}_i}) = \sum_{l=1}^{k}\frac{\hat{y}^\mathbf{r}_{il}}{\log(1+l)} DCG(r,y^i)=l=1∑klog(1+l)y^ilr
这里的
r
\mathbf{r}
r是一种排序准则, 我们假设它是根据预测的标签
y
^
i
\hat{\mathbf{y}}_{i}
y^i从大到小进行了一次排序得到了
y
^
i
r
\hat{\mathbf{y}}^{\mathbf{r}}_{i}
y^ir, 而排序后的顺序索引由
l
l
l表示, 即可认为
l
=
1
l=1
l=1时的
y
^
i
1
r
\hat{y}^\mathbf{r}_{i1}
y^i1r就是这个标签中预测值最高的标签特征.
k
k
k是这个标签的维度上限, 即标签长度.
我们可以假设一个最佳DCG (又名IDCG)场景, 即我们的标签
y
^
i
\hat{\mathbf{y}}_{i}
y^i完全与
y
i
\mathbf{y}_{i}
yi一致. 若
y
i
\mathbf{y}_{i}
yi是由
m
m
m个
+
1
+1
+1和
n
n
n个
0
0
0构成, 那么可以肯定的是
y
i
r
\mathbf{y}^{\mathbf{r}}_{i}
yir是前部为
m
m
m个
+
1
+1
+1构成, 尾部由
n
n
n个
0
0
0构成的向量
[
+
1
,
+
1
,
+
1
,
…
,
0
,
0
,
0
]
[+1,+1,+1,\dots,0,0,0]
[+1,+1,+1,…,0,0,0]. 其公式可直接表示为:
IDCG ( r , y ^ i ) = ∑ l = 1 m 1 log ( 1 + l ) , ( m = ∣ ∣ y i ∣ ∣ 0 ) \text{IDCG}(\mathbf{r}, \mathbf{\hat{y}_i}) = \sum_{l=1}^{m}\frac{1}{\log(1+l)},\text{ }(m=||\mathbf{y}_i||_0) IDCG(r,y^i)=l=1∑mlog(1+l)1, (m=∣∣yi∣∣0)
可得:
NDCG = DCG IDCG \textrm{NDCG} = \frac{\textrm{DCG}}{\textrm{IDCG}} NDCG=IDCGDCG
1.4 混沌矩阵有关参数引导的评价指标 (排序方法)
这里本质也都是混沌矩阵的一些参数引导的方法, 这里我们会见到刚刚介绍的AUC和
F
1
-Score
F_1\text{-Score}
F1-Score这些老朋友.
只不过这些方法吸取了基于排序的算法的一些经验, 提出了更为"动态化"的方案.
1.4.1 Average Precision (AP)
AP可以认为是P-R曲线围成的面积, 这个曲线是通过Precision作为纵轴, Recall作为横轴.
从数值角度来看, AP可以认为是不同的Recall采样点下的Precision的值的一个平均的结果.
具体来说, 我们在每个Recall的采样点进行一个标记, 判断这些判断点的Precision值, 将他们求和取平均.
我们假设个情况, 现在有五个取样点, 每次取样的时候我们都断言为+1, 一共的正样例有3个, 初始不取样的P与R都为0. 现在我们假设下述取样: (下述内容需要读者提前预备关于Precision与Recall的有关储备知识)
真实情况 | Precision | Recall |
---|---|---|
0 | 0 | 0 2 \frac{0}{2} 20 |
1 | 1 1 \frac{1}{1} 11 | 1 3 \frac{1}{3} 31 |
1 | 2 2 \frac{2}{2} 22 | 2 3 \frac{2}{3} 32 |
0 | 2 3 \frac{2}{3} 32 | 2 3 \frac{2}{3} 32 |
1 | 3 4 \frac{3}{4} 43 | 3 3 \frac{3}{3} 33 |
作图:
其结果是上述四个有效取样点的Precision求平均:
AP
=
1
5
(
0
+
1
+
1
+
2
3
+
3
4
)
\text{AP} = \frac{1}{5}(0+1+1+\frac{2}{3}+\frac{3}{4})
AP=51(0+1+1+32+43)
我们希望PR曲线是越往外"凸"效果越好, 即实际体现出来的就是Precision维持为1的情况尽可能多持续一段时间. 要实现这样的效果就需要将我们的预测标签
Y
^
\mathbf{\hat{Y}}
Y^进行排序 (可以是整体压缩为一维的排序 - 基于样本的评价指标; 也可以是每个标签向量
y
^
i
\mathbf{\hat{y}}_i
y^i排序后单独算AP, 最后取AP的均值: mAP - 基于标签的评价指标) , 排序后, 向量前端的标签预测为
+
1
+1
+1后有更大概率预测对, 从这里开始逐个采样, 能保证在前期更多地采到正确点.
1.4.2 基于排序的AUC
这里再提出AUC是因为AUC也有一种基于排序实现的计算方案.
若说AP是PR曲线的面积, AUC就是ROC曲线的面积, ROC是FPR (假正率, 特异度)与TPR (其实就是Precision)构成.
类似与PR曲线, 我们还是希望整个过程中TPR (Precision)尽可能保持接近1的姿态, 因此ROC曲线越向左上角"凸"那么效果是最好的.
这种情况下, 他的面积 (AUC)也会接近1.0.
具体的实现排序的可行算法 (我偷个懒)可以参考这位博主的文章, 里面的例子还不错: https://blog.csdn.net/pearl8899/article/details/126129148
1.4.3 Peak- F 1 \text{Peak-}F_1 Peak-F1
有的地方可能会将其称之为
Best-
F
1
\text{Best-}F_1
Best-F1, 我在我的MASP算法介绍的博客中详细介绍了这个评价指标的来由和应用(请查看这篇文章的4.3.3节 )
他的思想是将预测标签结果
Y
^
\mathbf{\hat{Y}}
Y^依照从大到小顺序重排, 这个重排的基本单位可以是矩阵全体 (基于样本的评价指标)也可以是单个标签 (基于标签的评价指标)计算后取的平均.
重排后我们按照大到小依序采样, 每个采样点都预测为正标签, 其背后一一对应的真实标签作为参考从而像计算AP那样逐次更新Recall和Precision, 并且按照这个Recall和Precision来计算
F
1
-Score
F_1\text{-Score}
F1-Score.
下面举个例子, 这里是一个五维的标签
真实标签 | 预测标签 |
---|---|
0 | 0.4 |
1 | 0.5 |
1 | 0.2 |
0 | 0.3 |
1 | 0.8 |
现在将其按照预测标签来进行联合的排序, 然后依次采样, 分别预测Precision和Recall, 计算 F 1 -Score F_1\text{-Score} F1-Score
真实标签 (跟随排序后) | 预测标签 (排序) | Precision | Recall | F 1 -Score F_1\text{-Score} F1-Score |
---|---|---|---|---|
1 | 0.8 | 1 1 \frac{1}{1} 11 | 1 3 \frac{1}{3} 31 | 0.496 |
1 | 0.5 | 2 2 \frac{2}{2} 22 | 2 3 \frac{2}{3} 32 | 0.795 |
0 | 0.4 | 2 3 \frac{2}{3} 32 | 2 3 \frac{2}{3} 32 | 0.666 |
0 | 0.3 | 2 4 \frac{2}{4} 42 | 2 3 \frac{2}{3} 32 | 0.569 |
1 | 0.2 | 3 5 \frac{3}{5} 53 | 3 3 \frac{3}{3} 33 | 0.750 |
这时可以看到有段
F
1
-Score
F_1\text{-Score}
F1-Score处于最高值, 我们的
Peak-
F
1
\text{Peak-}F_1
Peak-F1就取这个值.
Peak-F
1
\text{Peak-F}_1
Peak-F1反映出一种阈值效应: 在我们按照预测标签的预测值进行排序后, 在中间某点找到了
Peak-
F
1
\text{Peak-}F_1
Peak-F1, 这时标签的预测值为
p
p
p. 于是设置预测阈值为
θ
=
p
\theta = p
θ=p, 即预测标签中所有预测实值大于
p
p
p的都预测为
+
1
+1
+1, 其余为
0
0
0. 这样的话可以保证全局
F
1
-Score
F_1\text{-Score}
F1-Score能保持在最大的状态, 节约后续预测成本 (后续不再预测为
+
1
+1
+1).
可见上述例子, 不难得出
θ
=
0.5
\theta=0.5
θ=0.5, 标签
[
0.4
,
0.5
,
0.2
,
0.3
,
0.8
]
[0.4,0.5,0.2,0.3,0.8]
[0.4,0.5,0.2,0.3,0.8]预测为
[
0
,
1
,
0
,
0
,
1
]
[0,1,0,0,1]
[0,1,0,0,1], 这样我们哪怕不排序而直接计算
F
1
-Score
F_1\text{-Score}
F1-Score都可以稳定在最高值.
这也是我最近投稿的论文采用的评价指标, 它用来评价还算是不错的. 但是也需要认清一个现实, 往往来说
F
1
-Score
F_1\text{-Score}
F1-Score的数值是非常低的, 所以现在非常多的算法在力图做高
F
1
-Score
F_1\text{-Score}
F1-Score, 这也是目前机器学习中多分类多标签等算法在努力的方向.
2.极限多标签的评价指标
起初我一直以为极限多标签共享一般多标签的评价指标, 但是就几篇论文来看似乎不是这样.
若极限多标签也采用这样的评价方案效率会很低, 而且数值本身也会很差.
下面是我在Bhatia等一行人总结的极限多标签研究的开源网站 (http://manikvarma.org/downloads/XC/XMLRepository.html)中学习得到的.
同时, 参考了部分极限的多标签的算法的源码 (FastXML)
极限多标签的特征数目和标签维度巨大且稀疏的, 相关知识可见我的这篇文章第一节
一些预备知识:
l ∈ rank k ( y ^ ) l \in \operatorname{rank}_{k}(\hat{\mathbf{y}}) l∈rankk(y^)
y
^
\hat{\mathbf{y}}
y^是一个预测得到的标签, 满足
y
^
∈
R
L
\hat{\mathbf{y}}\in \mathbb{R}^{L}
y^∈RL.
rank
(
y
^
)
\operatorname{rank}(\hat{\mathbf{y}})
rank(y^)是将预测标签
y
^
\mathbf{\hat{y}}
y^向量的标签值更改为一个新值, 这个新值是此向量内部按照从大到小排序后每个标签值的新位置的下标.
例如有向量
y
^
=
[
0.5
,
0.8
,
0.2
,
0.3
]
\hat{\mathbf{y}}=[0.5, 0.8, 0.2, 0.3]
y^=[0.5,0.8,0.2,0.3], 有
rank
(
y
^
)
=
[
1
,
0
,
3
,
2
]
\operatorname{rank}(\hat{\mathbf{y}})=[1,0,3,2]
rank(y^)=[1,0,3,2], 而
rank
k
(
y
^
)
\operatorname{rank}_k(\hat{\mathbf{y}})
rankk(y^)就是拎出
y
^
\hat{\mathbf{y}}
y^中前
k
k
k大的下标, 例如
rank
3
(
y
^
)
=
{
0
,
1
,
3
}
\operatorname{rank}_3(\hat{\mathbf{y}}) = \{0,1,3\}
rank3(y^)={0,1,3},
rank
2
(
y
^
)
=
{
0
,
1
}
\operatorname{rank}_2(\hat{\mathbf{y}}) = \{0,1\}
rank2(y^)={0,1},
rank
2
(
y
^
)
=
{
1
}
\operatorname{rank}_2(\hat{\mathbf{y}}) = \{1\}
rank2(y^)={1}.
如果你想不带脑子理解的话, 你可以认为,
rank
k
(
y
^
)
\operatorname{rank}_k(\hat{\mathbf{y}})
rankk(y^)返回了
y
^
\hat{\mathbf{y}}
y^向量中前
k
k
k个最大的标签值的下标.
2.1 Precision@k
这个算法既不像Accuracy, 也不像Precision. 公式如下:
P @ k : = 1 k ∑ l ∈ rank k ( y ^ ) y l \mathrm{P} @ k:=\frac{1}{k} \sum_{l \in \operatorname{rank}_{k}(\hat{\mathbf{y}})} \mathbf{y}_{l} P@k:=k1l∈rankk(y^)∑yl
其中
k
<
∣
∣
y
∣
∣
0
≪
∣
y
∣
k<||\mathbf{y}||_0\ll|\mathbf{y}|
k<∣∣y∣∣0≪∣y∣.
极限多标签算法预测得到了一个预测矩阵
Y
^
\mathbf{\hat{Y}}
Y^, 这关公式关注其中某个标签向量为
y
^
\mathbf{\hat{y}}
y^ (显然, 这个评价指标是基于标签的评价指标)
得到了
y
^
\hat{\mathbf{y}}
y^向量中前
k
k
k个最大的标签值的下标集
l
l
l, 然后通过这些下标来映射到真实标签向量
y
\mathbf{y}
y中, 从而得到
y
l
\mathbf{y}_{l}
yl. 预测正确, 则求和
+
1
+1
+1, 否则求和
+
0
+0
+0, 最后, 取平均.
总结来看,
Precision@
k
\text{Precision@}k
Precision@k 是 对于标签向量最有把握的前
k
k
k个标签值的预测正确率. 当某个标签的
Precision@
5
\text{Precision@}5
Precision@5 = 1.0 说明这个标签向量预测中标签预测值最高的那五个标签都预测对了, 确实在目标标签中它们为
+
1
+1
+1; 但是如果
Precision@
5
\text{Precision@}5
Precision@5 = 0.8, 那么说明那五个标签至少有一个预测错了.
一般的极限多标签算法喜欢将
k
k
k取为1, 3, 5, 往往来说
k
k
k越大, 我们失误的几率越大,
Precision@
k
\text{Precision@}k
Precision@k也会越低; 而
k
=
1
k=1
k=1时, 评价指标退化为One-Error的对偶评价指标; 当
k
=
∣
y
∣
k=|\mathbf{y}|
k=∣y∣, 这个评价指标又变为了一般的Accuracy评价指标.
2.2 NDCG@k
这个算法是基于常规的基于排序的NDCG算法改进得到. 极限多标签算法中有例如FastXML这种专门通过优化这个评价指标而实现的算法.
DCG@ k : = ∑ l ∈ rank k ( y ^ ) y l log ( l + 1 ) \text { DCG@ } k:=\sum_{l \in \operatorname{rank}_{k}(\hat{\mathbf{y}})} \frac{\mathbf{y}_{l}}{\log (l+1)} DCG@ k:=l∈rankk(y^)∑log(l+1)yl
N D C G @ k : = D C G @ k ∑ l = 1 min ( k , ∥ y ∥ 0 ) 1 log ( l + 1 ) \mathrm{NDCG@} k:=\frac{\mathrm{DCG} @ k}{\sum_{l=1}^{\min \left(k,\|\mathbf{y}\|_{0}\right)} \frac{1}{\log (l+1)}} NDCG@k:=∑l=1min(k,∥y∥0)log(l+1)1DCG@k
可见, 相比NDCG, 这里的算法也是多了一个
k
k
k, 它们的判断思路是一致.
并且选择了其中的前
k
k
k的下标集, 将其命名为
l
l
l.
DCG@
k
\text {DCG@} k
DCG@k与
Precision@
k
\text{Precision@}k
Precision@k基本上是一致的, 只不过
DCG@
k
\text { DCG@ } k
DCG@ k在加上每次的结果时都除以一个对数损失, 因此, 随着排名的靠后, 它为
DCG@
k
\text {DCG@} k
DCG@k的贡献会逐步减小, 同时又因为我们加以的是对数损失, 因此在
l
l
l不是特别大时, 这种损失往往又不会导致特别严重的下降.
另外要注意, 这个公式并不是死的, 倘若你的基础编程语言的下标是从0开始的, 那么这里的对数损失应该改为
log
(
l
+
2
)
\log(l+2)
log(l+2), 避免除0错误.
NDCG@
k
\text {NDCG@} k
NDCG@k是归一化的
DCG@
k
\text {DCG@} k
DCG@k, 即对
DCG@
k
\text {DCG@} k
DCG@k除以一个
IDCG@
k
\text {IDCG@} k
IDCG@k.
通过1.4.3可得,
IDCG
\text {IDCG}
IDCG的计算主要取决于标签
y
\mathbf{y}
y中有多少非零元, 而引入
k
k
k后, 因为存在
k
<
∣
∣
y
∣
∣
0
k<||\mathbf{y}||_0
k<∣∣y∣∣0, 因此要遵守木桶原则, 选择最小的那个作为求和上限.
- 了解其原理后可以试着思考: 为什么
NDCG
\text {NDCG}
NDCG可以采用
@k
\text{@k}
@k来限制呢?
因为 NDCG \text {NDCG} NDCG非常依赖于排名靠前的预测, 往后的正确预测固然能带来数值的正反馈, 但是因为对数损失的存在, 它们的贡献始终是少的. 例如 l = 1 , 10 , 20 , 30 , 40 l=1,10,20,30,40 l=1,10,20,30,40的变化: 1 log 2 ( 1 + 1 ) = 1 \frac{1}{\log_2(1+1)}= 1 log2(1+1)1=1, 1 log 2 ( 10 + 1 ) ≈ 0.29 \frac{1}{\log_2(10+1)}\approx 0.29 log2(10+1)1≈0.29, 1 log 2 ( 20 + 1 ) ≈ 0.23 \frac{1}{\log_2(20+1)}\approx 0.23 log2(20+1)1≈0.23, 1 log 2 ( 30 + 1 ) ≈ 0.20 \frac{1}{\log_2(30+1)}\approx 0.20 log2(30+1)1≈0.20, 1 log 2 ( 40 + 1 ) ≈ 0.19 \frac{1}{\log_2(40+1)}\approx 0.19 log2(40+1)1≈0.19 ——可见数值波动逐渐不显著.
可以认为, 因为在极限多标签中, 对标签矩阵进行整体评价的开销非常大, 所以极限多标签的评价指标更侧重于"管中窥豹", 即从局部来最大化推测全局.
而在许多评价指标中, 基于排序的评价指标能很好承担这个职责 (因为排序后的关键信息集中在了前端, 且因为标签稀疏性, 前端的内容也并不会很多); 而在众多基于排序的评价指标中, NDCG@k \text {NDCG@k} NDCG@k又是其中最好的选择之一 (因为它对排名的"中", "末"端的依赖性低, 能最大化缩小复杂度, 同时也保证了通过引入 k k k之后的 NDCG@k \text {NDCG@k} NDCG@k与原 NDCG \text {NDCG} NDCG的差异是可以接受的).
2.3 PSP@k
正在学习…待补
2.4 PSnDCG@k
正在学习…待补
3 私货环节 (随时更新)
私货环节主要是用来存些代码用的.
这些代码有些是我看论文收集的, 有些是我自己写的, 有些是仅仅调库使用过的.
排名不分向后.
3.1 Python
1.3.4 节的 NDCG (自己写的)
注意输入的两个向量都必须是numpy类型得到的
1
×
L
1\times L
1×L 的一维向量, 它是针对标签进行的逐个分析.
如果你想基于样本进行分类, 那么就试着将整个预测和目标矩阵压缩为一维的再测试
def computeNDCG(predictY, targetY):
'''
Compute NDCG
:param predictY: a vector in one of the predicted labels (numpy)
:param targetY: a vector in one of the target labels (numpy)
:return: the value of NDCG
'''
# 按照概率序列排序原1/0串
temp = np.argsort(-predictY)
allLabelSort = targetY[temp]
# 获得最佳序列: 1111...10000...0
sortedTargetVector = np.sort(targetY)[::-1]
# compute DCG(使用预测的顺序, rel是真实顺序, 实际是111110111101110000001000100
DCG = 0
for i in range(temp.size):
rel = allLabelSort[i]
denominator = np.log2(i + 2)
DCG += (rel / denominator)
# compute iDCG(使用最佳顺序: 11111111110000000000)
iDCG = 0
for i in range(temp.size):
rel = sortedTargetVector[i]
denominator = np.log2(i + 2)
iDCG += (rel / denominator)
return DCG / iDCG
1.5.3 节的
Peak-
F
1
\text{Peak-}F_1
Peak-F1 (自己写的, 但参考了师兄的代码)
注意输入的两个向量都必须是numpy类型得到的
1
×
L
1\times L
1×L 的一维向量, 它是针对标签进行的逐个分析.
如果你想基于样本进行分类, 那么就试着将整个预测和目标矩阵压缩为一维的再测试
def computepeakF1Score(predictY, targetY):
'''
Compute the Peak F1-score
:param predictY: a vector in one of the predicted labels (numpy)
:param targetY: a vector in one of the target labels (numpy)
:return: the value of Peak F1-score
'''
temp = np.argsort(-predictY)
allLabelSort = targetY[temp]
tempYF1 = np.zeros(temp.size)
allTP = np.sum(targetY == 1)
for i in range(temp.size):
TP = np.sum(allLabelSort[0:i + 1] == 1)
P = TP / (i + 1)
R = TP / allTP
if (P + R) == 0:
tempYF1[i] = 0
else:
tempYF1[i] = 2.0 * P * R / (P + R)
return np.max(tempYF1)
1.2.3和1.4.2 节的AUC (快乐地调库)
注意输入的两个向量都必须是numpy类型的
会使用的包
from sklearn import metrics
代码
两个向量都必须是numpy类型得到的
1
×
L
1\times L
1×L 的一维向量, 它是针对标签进行的逐个分析.
如果你想基于样本进行分类, 那么就试着将整个预测和目标矩阵压缩为一维的再测试
metrics.roc_auc_score(targetY, predictY)
3.2 Matlab
1.3.1 节的One-Error (来自公开相关代码)
创建一个One_error.m文件, 然后复制下面的代码, 然后就可以调用了
这个代码中的Outputs是
Y
^
⊤
\mathbf{\hat{Y}^{\top}}
Y^⊤, test_target是
Y
⊤
\mathbf{Y^{\top}}
Y⊤, 他们都是形如
L
×
N
L \times N
L×N的矩阵, 其中每列都是一个
L
×
1
L\times 1
L×1的标签向量.
显然, 这里是一个基于标签的评价指标
function OneError=One_error(Outputs,test_target)
%Computing the one error
%Outputs: the predicted outputs of the classifier, the output of the ith instance for the jth class is stored in Outputs(j,i)
%test_target: the actual labels of the test instances, if the ith instance belong to the jth class, test_target(j,i)=1, otherwise test_target(j,i)=-1
[num_class,num_instance]=size(Outputs);
temp_Outputs=[];
temp_test_target=[];
for i=1:num_instance
temp=test_target(:,i);
if((sum(temp)~=num_class)&(sum(temp)~=-num_class))
temp_Outputs=[temp_Outputs,Outputs(:,i)];
temp_test_target=[temp_test_target,temp];
end
end
Outputs=temp_Outputs;
test_target=temp_test_target;
[num_class,num_instance]=size(Outputs);
Label=cell(num_instance,1);
not_Label=cell(num_instance,1);
Label_size=zeros(1,num_instance);
for i=1:num_instance
temp=test_target(:,i);
Label_size(1,i)=sum(temp==ones(num_class,1));
for j=1:num_class
if(temp(j)==1)
Label{i,1}=[Label{i,1},j];
else
not_Label{i,1}=[not_Label{i,1},j];
end
end
end
oneerr=0;
for i=1:num_instance
indicator=0;
temp=Outputs(:,i);
[maximum,index]=max(temp);
for j=1:num_class
if(temp(j)==maximum)
if(ismember(j,Label{i,1}))
indicator=1;
break;
end
end
end
if(indicator==0)
oneerr=oneerr+1;
end
end
OneError=oneerr/num_instance;
1.3.2 节的 Coverage (来自公开相关代码)
创建一个coverage.m文件, 然后复制下面的代码, 然后就可以调用了
这个代码中的Outputs是
Y
^
⊤
\mathbf{\hat{Y}^{\top}}
Y^⊤, test_target是
Y
⊤
\mathbf{Y^{\top}}
Y⊤, 他们都是形如
L
×
N
L \times N
L×N的矩阵, 其中每列都是一个
L
×
1
L\times 1
L×1的标签向量.
显然, 这里是一个基于标签的评价指标版本
function Coverage=coverage(Outputs,test_target)
%Computing the coverage
%Outputs: the predicted outputs of the classifier, the output of the ith instance for the jth class is stored in Outputs(j,i)
%test_target: the actual labels of the test instances, if the ith instance belong to the jth class, test_target(j,i)=1, otherwise test_target(j,i)=-1
[num_class,num_instance]=size(Outputs);
Label=cell(num_instance,1);
not_Label=cell(num_instance,1);
Label_size=zeros(1,num_instance);
for i=1:num_instance
temp=test_target(:,i);
Label_size(1,i)=sum(temp==ones(num_class,1));
for j=1:num_class
if(temp(j)==1)
Label{i,1}=[Label{i,1},j];
else
not_Label{i,1}=[not_Label{i,1},j];
end
end
end
cover=0;
for i=1:num_instance
temp=Outputs(:,i);
[tempvalue,index]=sort(temp);
temp_min=num_class+1;
for m=1:Label_size(i)
[tempvalue,loc]=ismember(Label{i,1}(m),index);
if(loc<temp_min)
temp_min=loc;
end
end
cover=cover+(num_class-temp_min+1);
end
Coverage=(cover/num_instance)-1;
1.3.3 节的 Ranking Loss (来自公开相关代码)
创建一个Ranking_loss.m文件, 然后复制下面的代码, 然后就可以调用了
这个代码中的Outputs是
Y
^
⊤
\mathbf{\hat{Y}^{\top}}
Y^⊤, test_target是
Y
⊤
\mathbf{Y^{\top}}
Y⊤, 他们都是形如
L
×
N
L \times N
L×N的矩阵, 其中每列都是一个
L
×
1
L\times 1
L×1的标签向量.
显然, 这里是一个基于标签的评价指标版本
function RankingLoss=Ranking_loss(Outputs,test_target)
%Computing the hamming loss
%Outputs: the predicted outputs of the classifier, the output of the ith instance for the jth class is stored in Outputs(j,i)
%test_target: the actual labels of the test instances, if the ith instance belong to the jth class, test_target(j,i)=1, otherwise test_target(j,i)=-1
[num_class,num_instance]=size(Outputs);
temp_Outputs=[];
temp_test_target=[];
for i=1:num_instance
temp=test_target(:,i);
if((sum(temp)~=num_class)&(sum(temp)~=-num_class))
temp_Outputs=[temp_Outputs,Outputs(:,i)];
temp_test_target=[temp_test_target,temp];
end
end
Outputs=temp_Outputs;
test_target=temp_test_target;
[num_class,num_instance]=size(Outputs);
Label=cell(num_instance,1);
not_Label=cell(num_instance,1);
Label_size=zeros(1,num_instance);
for i=1:num_instance
temp=test_target(:,i);
Label_size(1,i)=sum(temp==ones(num_class,1));
for j=1:num_class
if(temp(j)==1)
Label{i,1}=[Label{i,1},j];
else
not_Label{i,1}=[not_Label{i,1},j];
end
end
end
rankloss=0;
for i=1:num_instance
temp=0;
for m=1:Label_size(i)
for n=1:(num_class-Label_size(i))
if(Outputs(Label{i,1}(m),i)<=Outputs(not_Label{i,1}(n),i))
temp=temp+1;
end
end
end
rl_binary(i)=temp/(m*n);
rankloss=rankloss+temp/(m*n);
end
RankingLoss=rankloss/num_instance;
1.1.2 节的 Hamming Loss (来自公开相关代码)
创建一个Hamming_loss.m文件, 然后复制下面的代码, 然后就可以调用了
这个代码中的Outputs是
Y
^
⊤
\mathbf{\hat{Y}^{\top}}
Y^⊤, test_target是
Y
⊤
\mathbf{Y^{\top}}
Y⊤, 他们都是形如
L
×
N
L \times N
L×N的矩阵, 其中每列都是一个
L
×
1
L\times 1
L×1的标签向量.
function HammingLoss=Hamming_loss(Pre_Labels,test_target)
%Computing the hamming loss
%Pre_Labels: the predicted labels of the classifier, if the ith instance belong to the jth class, Pre_Labels(j,i)=1, otherwise Pre_Labels(j,i)=-1
%test_target: the actual labels of the test instances, if the ith instance belong to the jth class, test_target(j,i)=1, otherwise test_target(j,i)=-1
[num_class,num_instance]=size(Pre_Labels);
miss_pairs=sum(sum(Pre_Labels~=test_target));
HammingLoss=miss_pairs/(num_class*num_instance);
1.4.1 节的 Average Precision (AP) (来自公开相关代码)
创建一个Average_precision.m文件, 然后复制下面的代码, 然后就可以调用了
这个代码中的Outputs是
Y
^
⊤
\mathbf{\hat{Y}^{\top}}
Y^⊤, test_target是
Y
⊤
\mathbf{Y^{\top}}
Y⊤, 他们都是形如
L
×
N
L \times N
L×N的矩阵, 其中每列都是一个
L
×
1
L\times 1
L×1的标签向量.
显然, 这里是一个基于标签的评价指标版本
function Average_Precision=Average_precision(Outputs,test_target)
%Computing the average precision
%Outputs: the predicted outputs of the classifier, the output of the ith instance for the jth class is stored in Outputs(j,i)
%test_target: the actual labels of the test instances, if the ith instance belong to the jth class, test_target(j,i)=1, otherwise test_target(j,i)=-1
[num_class,num_instance]=size(Outputs);
temp_Outputs=[];
temp_test_target=[];
for i=1:num_instance
temp=test_target(:,i);
if((sum(temp)~=num_class)&(sum(temp)~=-num_class))
temp_Outputs=[temp_Outputs,Outputs(:,i)];
temp_test_target=[temp_test_target,temp];
end
end
Outputs=temp_Outputs;
test_target=temp_test_target;
[num_class,num_instance]=size(Outputs);
Label=cell(num_instance,1);
not_Label=cell(num_instance,1);
Label_size=zeros(1,num_instance);
for i=1:num_instance
temp=test_target(:,i);
Label_size(1,i)=sum(temp==ones(num_class,1));
for j=1:num_class
if(temp(j)==1)
Label{i,1}=[Label{i,1},j];
else
not_Label{i,1}=[not_Label{i,1},j];
end
end
end
aveprec=0;
for i=1:num_instance
temp=Outputs(:,i);
[tempvalue,index]=sort(temp);
indicator=zeros(1,num_class);
for m=1:Label_size(i)
[tempvalue,loc]=ismember(Label{i,1}(m),index);
indicator(1,loc)=1;
end
summary=0;
for m=1:Label_size(i)
[tempvalue,loc]=ismember(Label{i,1}(m),index);
summary=summary+sum(indicator(loc:num_class))/(num_class-loc+1);
end
ap_binary(i)=summary/Label_size(i);
aveprec=aveprec+summary/Label_size(i);
end
Average_Precision=aveprec/num_instance;
1.3.4 节的 NDCG (自己写的)
创建一个computeNDCG.m文件, 然后复制下面的代码, 然后就可以调用了
这个代码中的label_prob是
y
^
\mathbf{\hat{y}}
y^, test_target是
y
\mathbf{y}
y, 他们都是形如
1
×
L
1 \times L
1×L的向量.
这里是一个基于标签的评价指标版本, 当然如果你把整个标签矩阵都压缩为一维, 这个也可以视作一个基于样本的评价指标
(注意, matlab的下标是从1开始的, 因此这里是可以写作
log
2
(
i
+
1
)
\log_2(i+1)
log2(i+1), py是不行的)
function NDCG = computeNDCG(label_prob, label_target)
[sortArray,temp] = sort(-label_prob);
allLabelSort = label_target(temp);
sortedTargetVector = sort(label_target);
sortedTargetVector = fliplr(sortedTargetVector);
dcg = 0;
for i = 1: numel(temp)
rel = allLabelSort(i);
denominator = log2(i + 1);
dcg = dcg + (rel / denominator);
end
idcg = 0;
for i = 1: numel(temp)
rel = sortedTargetVector(i);
denominator = log2(i + 1);
idcg = idcg + (rel / denominator);
end
NDCG = max(dcg / idcg);
end
1.4.3 节的
Peak-
F
1
\text{Peak-}F_1
Peak-F1 (自己写的)
创建一个computePeakF1.m文件, 然后复制下面的代码, 然后就可以调用了
这个代码中的label_prob是
y
^
\mathbf{\hat{y}}
y^, test_target是
y
\mathbf{y}
y, 他们都是形如
1
×
L
1 \times L
1×L的向量.
这里是一个基于标签的评价指标版本, 当然如果你把整个标签矩阵都压缩为一维, 这个也可以视作一个基于样本的评价指标
function peakF1 = computePeakF1(label_prob, label_target)
[sortArray,temp] = sort(-label_prob);
allLabelSort = label_target(temp);
tempF1 = zeros(1, numel(temp));
allTP = sum(label_target == 1);
for i = 1: numel(temp)
sliceArray = allLabelSort(1:i);
TP = sum(sliceArray == 1);
P = TP / (i);
R = TP / allTP;
if(P + R == 0)
tempF1(i) = 0;
else
tempF1(i) = (2.0 * P * R) / (P + R);
end
end
peakF1 = max(tempF1);
end
1.4.2 节的基于排序的AUC (参考至网络)
创建一个computeAUC.m文件, 然后复制下面的代码, 然后就可以调用了
这个代码中的output是
Y
^
⊤
\mathbf{\hat{Y}^{\top}}
Y^⊤, test_targets是
Y
⊤
\mathbf{Y^{\top}}
Y⊤, 他们都是形如
L
×
N
L \times N
L×N的矩阵, 其中每列都是一个
L
×
1
L\times 1
L×1的标签向量.
显然, 这里是一个基于标签的评价指标版本
这个代码中的output是
y
^
\mathbf{\hat{y}}
y^, test_targets是
y
\mathbf{y}
y, 他们都是形如
1
×
L
1 \times L
1×L的向量.
这里是一个基于标签的评价指标版本, 当然如果你把整个标签矩阵都压缩为一维, 这个也可以视作一个基于样本的评价指标
function auc = computeAUC(output, test_targets)
[A,I]=sort(output);
M=0;N=0;
for i=1:length(output)
if(test_targets(i)==1)
M=M+1;
else
N=N+1;
end
end
sigma=0;
for i=M+N:-1:1
if(test_targets(I(i))==1)
sigma=sigma+i;
end
end
auc = (sigma-(M+1)*M/2)/(M*N);
end
2.1 节的Precision@k (参考至相关算法源代码)
创建一个precision_k.m文件, 然后复制下面的代码, 但是请注意! 这个无法直接使用, 因为这里的sort_sparse_mat方法是源码中通过C++提前编译好的方法. 若在无环境的情况下是无法运行的.
这个代码中的score_mat是
Y
^
⊤
\mathbf{\hat{Y}^{\top}}
Y^⊤, true_mat是
Y
⊤
\mathbf{Y^{\top}}
Y⊤, 他们都是形如
L
×
N
L \times N
L×N的矩阵, 其中每列都是一个
L
×
1
L\times 1
L×1的标签向量. 请注意, 这里使用的所有矩阵都是matlab自带的稀疏矩阵方法.
这里是一个基于标签的评价指标版本.
相关函数说明:
- 这里的sort_sparse_mat方法相当于公式中的 rank ( ) \operatorname{rank}() rank(), 它将标签向量中的数值换成了其值在排序的排名值, 若读者有需要可以自行实现.
- spones( )方法是将矩阵中所有非零元素都更改为 1 1 1
function P = precision_k(score_mat,true_mat,K)
P = helper(score_mat,true_mat,K);
end
function P = helper(score_mat,true_mat,K)
num_inst = size(score_mat,2);
num_lbl = size(score_mat,1);
P = zeros(K,1);
rank_mat = sort_sparse_mat(score_mat); % 具体的值更改为这个值在这一列的"排序值"
for k=1:K
mat = rank_mat;
mat(rank_mat>k) = 0; % 把低于阈值排名的排名设置为0
mat = spones(mat); % 将幸存的数值都改为1
mat = mat.*true_mat; % 将预测错的变为0
num = sum(mat,1); % 按照列求和
P(k) = mean(num/k);
end
end
2.2 节的NDCG@k (参考至相关算法源代码)
创建一个nDCG_k.m文件, 然后复制下面的代码, 但是请注意! 这个无法直接使用, 因为这里的sort_sparse_mat方法是源码中通过C++提前编译好的方法. 若在无环境的情况下是无法运行的.
这个代码中的score_mat是
Y
^
⊤
\mathbf{\hat{Y}^{\top}}
Y^⊤, true_mat是
Y
⊤
\mathbf{Y^{\top}}
Y⊤, 他们都是形如
L
×
N
L \times N
L×N的矩阵, 其中每列都是一个
L
×
1
L\times 1
L×1的标签向量. 请注意, 这里使用的所有矩阵都是matlab自带的稀疏矩阵方法.
这里是一个基于标签的评价指标版本.
相关函数说明:
- 这里的sort_sparse_mat方法相当于公式中的 rank ( ) \operatorname{rank}() rank(), 它将标签向量中的数值换成了其值在排序的排名值, 若读者有需要可以自行实现.
- spones( )方法是将矩阵中所有非零元素都更改为 1 1 1
- sparse( )是构建稀疏矩阵的方法, X是非零点的纵坐标数组, Y是非零点的横坐标数组, V是非零值, 它们是一一对应的. 后续num_lbl与num_inst是稀疏矩阵的最大行数和列数. ( L × N = num_lbl × num_inst L \times N = \text{num\_lbl} \times \text{num\_inst} L×N=num_lbl×num_inst)
- cum_wts()用于累加求和, cum_wts([1,1,1]) -> [1,2,3], cum_wts([2,7,8]) -> [2,9,17], 代码中他被用来算IDCG
function N = nDCG_k(score_mat,true_mat,K)
N = helper(score_mat,true_mat,K);
end
function P = helper(score_mat,true_mat,K)
num_inst = size(score_mat,2);
num_lbl = size(score_mat,1);
P = zeros(K,1);
wts = 1./log2((1:num_lbl)+1)';
cum_wts = cumsum(wts); % 计算每个IDCG
rank_mat = sort_sparse_mat(score_mat); % 然后返回每个排序值的位置
[X,Y,V] = find(rank_mat); % 找出rank_mat中非零元素所在的行和列, 并且分别存储在X和Y中, 并将具体非零的值放在V里面
V = 1./log2(V+1); % sort_sparse_mat操作后, 当前列的某个V值体现是原值此向量的中的排名, 符合公式中l的定义
coeff_mat = sparse(X,Y,V,num_lbl,num_inst); % 将原预测稀疏矩阵转变为单DCG的稀疏矩阵
for k=1:K
mat = coeff_mat;
mat(rank_mat>k) = 0; % 把排序没靠在前k的数据改为0, 其余靠在k前的数据的具体的值保持原来的值(1./log2(V+1)的结果)
mat = mat.*true_mat; % 把那些预测错的归零, 这里是haty->y的映射
num = sum(mat,1); % 得到每个列的DCG@k值 (求和过程中若预测错了, + 0)
% 下面是处理IDCG
count = sum(true_mat,1); % 求原Y的每列的||y||_0
count = min(count,k); % min(k, ||y||_0)
count(count==0) = 1; % 为了避免除零, 对于空标签向量, 设置基础值l=1, 保守认为有一个标签
den = cum_wts(count)'; % 得到每一列的IDCG
P(k) = mean(num./den); % 每一列求NDCG
end
end
4 后记
这个文章本质是个记录用, 当然也包含我的一些理解, 有问题欢迎指正!