对于rating prediction任务,一般都是根据原有的评分数据,利用矩阵分解等方法去拟合原评分,使得优化后的模型可以去预测新的评分,这里就要衡量你预测的评分和实际评分的差异了,指标也很简单,分为RMSE和MAE。
RMSE = 1 ∣ T ∣ ∑ ( u , i ) ∈ T ( r ^ u i − r u i ) 2 \operatorname{RMSE}=\sqrt{\frac{1}{|\mathcal{T}|} \sum_{(u, i) \in \mathcal{T}}\left(\hat{r}_{u i}-r_{u i}\right)^{2}} RMSE=∣T∣1(u,i)∈T∑(r^ui−rui)2
其中 T \mathcal{T} T是测试集, r ^ u i \hat{r}_{u i} r^ui是模型预测出来的评分, r u i {r}_{u i} rui是测试集的实际评分。
RMSE = 1 ∣ T ∣ ∑ ( u , i ) ∈ T ( r ^ u i − r u i ) 2 \operatorname{RMSE}=\sqrt{\frac{1}{|\mathcal{T}|} \sum_{(u, i) \in \mathcal{T}}\left(\hat{r}_{u i}-r_{u i}\right)^{2}} RMSE=∣T∣1(u,i)∈T∑(r^ui−rui)2
其中 T \mathcal{T} T是测试集, r ^ u i \hat{r}_{u i} r^ui是模型预测出来的评分, r u i {r}_{u i} rui是测试集的实际评分。
RMSE和MAE实际上差别不大的指标,在Rating任务中比较常用
对于Ranking prediction任务,一般是将其化为二分类任务:有评分为1,无评分为0(这是比较粗糙的做法),然后对每个用户取其偏好最高的K个商品(TOP-K)推荐。指标看起来也“复杂”很多,分别有Rrecision@K,Recall@K,MAP,MRR,NDCG
p r e c i s i o n @ K = ∣ R e l u ∩ R e c u ∣ ∣ R e c u ∣ precision@K = \frac{\left|R e l_{u} \cap R e c_{u}\right|}{\left|R e c_{u}\right|} precision@K=∣Recu∣∣Relu∩Recu∣
其中 R e l u R e l_{u} Relu表示与用户 u u u 相关的商品集(测试集), R e c u R e c_{u} Recu表示推荐给用户的前K个列表,二者的交集除以 R e c u R e c_{u} Recu的集合元素个数(其实就是K),得到Precision@K。一般是算出每个用户的Precision@K,然后取平均值。
#计算每个用户的Precision@K值,最后还要取平均值
def cal_precision_at_k(k, rankedlist, test_matrix):
test_set = set(test_matrix)
rank_set = set(rankedlist)
hit = len(test_set & rank_set)
return float(hit / k)
R e c a l @ K = ∣ R e l u ∩ R e c u ∣ ∣ R e l u ∣ Recal@K = \frac{\left|R e l_{u} \cap R e c_{u}\right|}{\left|R e l_{u}\right|} Recal@K=∣Relu∣∣Relu∩Recu∣
其中 R e l u R e l_{u} Relu表示与用户u相关的商品集(测试集), R e c u R e c_{u} Recu表示推荐给用户的前K个列表,二者的交集除以 R e l u R e l_{u} Relu中元素的个数(也就是测试集中用户u评过分的商品数),得到Recall@K。一般是算出每个用户的Recall@K,然后取平均值。
#计算每个用户的Recall@K值,最后还要取平均值
def cal_Recall_at_k_for_each_user(k, rankedlist, testlist):
test_set = set(test_matrix)
rank_set = set(rankedlist)
hit = len(test_set & rank_set)
return float(hit / len(test_set))
M A P = ∑ u ∈ U ∗ e A P u ∣ U t e ∣ M A P=\frac{\sum_{u \in U^{* e}} A P_{u}}{\left|\mathcal{U}^{t e}\right|} MAP=∣Ute∣∑u∈U∗eAPu
首先需要计算每个用户AP(Average Precision)
A P u = 1 ∣ I u t e ∣ ∑ i ∈ I u t e ∑ j ∈ I u t u δ ( r a n k u j < r a n k u i ) + 1 r a n k u i A P_{u}=\frac{1}{\left|\mathcal{I}_{u}^{t e}\right|} \sum_{i \in \mathcal{I}_{u}^{t_{e}}} \frac{\sum_{j \in \mathcal{I}_{u}^{t_{u}}} \delta\left(rank_{u j}<rank_{u i}\right)+1}{rank_{u i}} APu=∣Iute∣1i∈Iute∑rankui∑j∈Iutuδ(rankuj<rankui)+1
公式看起来有点吓人,其中 r a n k u i rank_{u i} rankui表示推荐列表中物品 i 的排序位置, r a n k u j < r a n k u i rank_{u j}<rank_{u i} rankuj<rankui 表示在对用户 u u u 的排序列表中物品 j j j 的排序位置在物品 i i i 的前面。假设你推荐的TOP-K商品中,有N个命中了(与测试集的交集为N),其实就是 A P u = ∑ i ∈ I ∗ u i 在 推 荐 列 表 中 的 排 名 i 在 测 试 集 中 的 排 名 A P_{u} = \frac{\sum_{i \in I^{* u}} i在推荐列表中的排名}{\mathcal i在测试集中的排名} APu=i在测试集中的排名∑i∈I∗ui在推荐列表中的排名其中 I ∗ u I^{* u} I∗u是用户u的推荐列表中命中的元素集合。MAP就是所有用户AP的平均值。 M A P = 1 ∣ U ∣ ∑ u = 1 ∣ U ∣ A P u \mathrm{MAP}=\frac{1}{|U|} \sum_{u=1}^{|U|} {AP_u} MAP=∣U∣1u=1∑∣U∣APu还是不理解?上代码:
#这个只是计算每个用户MAP的代码,最后还要对整个取平均值
def cal_map_for_each_user(rankedlist, testlist):
ap = 0
s = set(testlist)
#命中的元素在testlist中的排名
hits = [ idx for idx, val in enumerate(rankedlist) if val in s ]
count = len(hits)
for i in range(count):
ap += (i+1) / (hits[i] + 1)
if count != 0:
map = ap / count
else:
map = 0
return map
M R R = 1 ∣ Q ∣ ∑ i = 1 ∣ U ∣ 1 rank i \mathrm{MRR}=\frac{1}{|Q|} \sum_{i=1}^{|U|} \frac{1}{\operatorname{rank}_{i}} MRR=∣Q∣1i=1∑∣U∣ranki1
其中 ∣ U ∣ |U| ∣U∣是用户的个数, r a n k i rank_i ranki是对于第 i i i个用户,推荐列表中第一个在测试集结果中的商品所在的排列位置,计算起来也十分简单:
#这个也是只是计算每个用户的MRR,最后还要取均值
if count != 0:
mrr = 1 / (hits[0] + 1)
else:
mrr = 0
NDCG应该是Ranking指标里面最复杂的了,讲NDCG应该先从CG,DCG讲起
C G k = ∑ i = 1 k r e l i C G_{k}=\sum_{i=1}^{k} r e l_{i} CGk=i=1∑kreli
其中, r e l i rel_i reli 表示处于位置 i i i 的推荐结果的相关性,在推荐系统中是命中 r e l i rel_i reli 为1,不命中 r e l i rel_i reli 为0。 k k k是TOP-K中的K。
D C G k = ∑ i = 1 k 2 r e l i − 1 log 2 ( i + 1 ) D C G_{k}=\sum_{i=1}^{k} \frac{2^{r e l_{i}}-1}{\log _{2}(i+1)} DCGk=i=1∑klog2(i+1)2reli−1,推荐系统中,命中则 2 r e l i − 1 2^{r e l_{i}}-1 2reli−1为1,不命中则 2 r e l i − 1 2^{r e l_{i}}-1 2reli−1为0。 D C G DCG DCG引入了位置因素,比 C G CG CG更有价值。
I D C G k = ∑ i = 1 k 1 log 2 ( i + 1 ) I D C G_{k}=\sum_{i=1}^{k} \frac{1}{\log _{2}(i+1)} IDCGk=i=1∑klog2(i+1)1
k k k是TOP-K中的K。 I D C G IDCG IDCG是理想化的 D C G DCG DCG,因此 D C G DCG DCG的值介于[0, I D C G IDCG IDCG]之间。
for i in range(k):
idcg_k += 1 / math.log(i + 2, 2)
N D C G u @ k = D C G u @ k I D C G u N D C G_{u} @ k=\frac{D C G_{u} @ k}{I D C G_{u}} NDCGu@k=IDCGuDCGu@k
终于轮到 N D C G @ K NDCG@K NDCG@K了,它的值就是 D C G DCG DCG与 I D C G IDCG IDCG之间的比值,介于[0,1]之间。
因此所有用户的平均 N D C G @ K NDCG@K NDCG@K为: N D C G @ k = ∑ u ∈ U t e N D C G u @ k ∣ U t e ∣ N D C G @ k=\frac{\sum_{u \in \mathcal{U}^{te} N D C G_{u} @ k}}{\left|\mathcal{U}^{te}\right|} NDCG@k=∣Ute∣∑u∈UteNDCGu@k
其中, U t e U^{te} Ute为测试集中的所有用户。
#计算每个用户的NDCG@K值,最后还要取平均值
def cal_ndcg_at_k_for_each_user(k, rankedlist, testlist):
idcg_k = 0
dcg_k = 0
if len(testlist) < k: k = len(testlist)
for i in range(k):
idcg_k += 1 / math.log(i + 2, 2)
s = set(testlist)
hits = [ idx for idx, val in enumerate(rankedlist) if val in s]
count = len(hits)
for i in range(count):
dcg_k += 1 / math.log(hits[i] + 2, 2)
return float(dcg_k / idcg_k)
既然已经介绍了 N D C G @ K NDCG@K NDCG@K,为啥还要单独介绍一个 N D C G NDCG NDCG呢,二者除了一个 @ K @K @K有啥区别?
主要是看有的论文是用 N D C G @ K NDCG@K NDCG@K,有的是用 N D C G NDCG NDCG,因此两个都记下来好了,二者的区别主要是计算 I D C G IDCG IDCG的不同( D C G DCG DCG计算是一样的)
I D C G n = ∑ i = 1 n 1 log 2 ( i + 1 ) I D C G_{n}=\sum_{i=1}^{n} \frac{1}{\log _{2}(i+1)} IDCGn=i=1∑nlog2(i+1)1
其中n是用户 u u u的测试集长度,这样一算 I D C G IDCG IDCG变大了。然后 N D C G NDCG NDCG计算是公式一样的:
N D C G = ∑ u ∈ U t e N D C G u @ k ∣ U t e ∣ N D C G =\frac{\sum_{u \in \mathcal{U}^{te} N D C G_{u} @ k}}{\left|\mathcal{U}^{te}\right|} NDCG=∣Ute∣∑u∈UteNDCGu@k
其中, U t e U^{te} Ute为测试集中的所有用户。
好了总结完毕~
最后不得不感谢Mathpix Snipping Tool这个工具太好用了,全文的LaTeX公式都是用它做的,想了解的自己去百度搜索,你会发现新天地的。