推荐算法(8)评测指标
1.用户满意度(在线)
用户满意度没有办法离线计算,只能通过用户调查或者在线实验获得。
用户调查获得用户满意度主要是通过调查问卷的形式。
2.预测准确度(离线)
这个指标是最重要的推荐系统离线评测指标。
2.1评分预测
评分预测的预测准确度一般通过均方根误差(RMSE)和平均绝对误差(MAE)计算。
对于测试集中的一个用户u和物品i,令rui是用户u对物品i的实际评分,而rui是推荐算法给出的预测评分
RMSE的定义:
MAE采用绝对值计算预测误差,它的定义为:
假设我们用一个列表records存放用户评分数据,令records[i] = [u, i, rui, pui],其中rui是用户u对物品i的实际评分,pui是算法预测出来的用户u对物品i的评分,那么下马的代码分别实现了RMSE和MAE的计算过程。
def RMSE(records): return math.sqrt(sum([(rui-pui)*(rui-pui) for u,i,rui,pui in records])/float(len(records))) def MAE(records): return sum([abs(rui-pui) for u,i,rui,pui in records])/float(len(records))
1
2
3
4
5
1
2
3
4
5
2.2 TopN推荐
站在提供推荐服务时,一般是给童虎一个个性化的推荐列表,这种推荐叫TopN推荐。
令R(u)是根据用户在训练集上的行为给用户做出的推荐列表,而T(u)是用户在测试集上的行为列表。
2.2.1.1准确率:
准确率表示预测正确的样本数占总样本书的比例。
2.2.1.2 Mean Average Precision(MAP) 平均准确率
平均准确率AP,假使当我们使用google搜索某个关键词,返回了10个结果。当然最好的情况是这10个结果都是我们想要的相关信息。但是假如只有部分是相关的,比如5个,那么这5个结果如果被显示的比较靠前也是一个相对不错的结果。但是如果这个5个相关信息从第6个返回结果才开始出现,那么这种情况便是比较差的。这便是AP所反映的指标,与recall的概念有些类似,不过是“顺序敏感的recall。
对于用户u uu,给他推荐一些物品,那么u uu的平均准确率为:
其中,Ω u表示ground-truth的结果,Pui表示ii物品在推荐列表中的位置,Pui>Puj 表示j物品在推荐列表中排在i物品之前。
举个例子:
def AP(ranked_list, ground_truth): '''Compute the average precision (AP) of a list of ranked items ''' hits = 0 sum_precs = 0 for n in range(len(ranked_list)): if ranked_list[n] in ground_truth: hits += 1 sum_precs += hits / (n + 1.0) if hits > 0: return sum_precs / len(ground_truth) else: return 012345678910111213141234567891011121314
MAP表示所有用户u uu的AP再取均值,计算公式如下:
2.2.2.1召回率
# 定义精确率指标计算方式 def precision(self): all, hit = 0, 0 for user in self.test: test_items = set([x[0] for x in self.test[user]]) rank = self.recs[user] for item, score in rank: if item in test_items: hit += 1 all += len(rank) return round(hit / all * 100, 2) if all > 0 else 0.0 # 定义召回率指标计算方式 def recall(self): all, hit = 0, 0 for user in self.test: test_items = set([x[0] for x in self.test[user]]) rank = self.recs[user] for item, score in rank: if item in test_items: hit += 1 all += len(test_items) return round(hit / all * 100, 2) if all > 0 else 0.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2.2.2.2 Hit Ratio(HR) 和召回率等价的
在top-K推荐中,HR是一种常用的衡量召回率的指标,计算公式为:
分母是所有的测试集合,分子表示每个用户top-K列表中属于测试集合的个数的总和。
举个简单的例子,三个用户在测试集中的商品个数分别是10,12,8,模型得到的top-10推荐列表中,分别有6个,5个,4个在测试集中,那么此时HR的值是
(6+5+4)/(10+12+8) = 0.5。
def hit(gt_items, pred_items): count = 0 for item in pred_items: if item in gt_items: count += 1 return count123456123456
例子:
比如有一个训练集为(苹果,香蕉,橘子,草莓,哈密瓜,西红柿,黄瓜),用户选中其中几样,以此训练,
测试集为(梨子,菠萝,龙眼,黑莓,白菜,冬瓜)
根据用户在训练集上的行为:
给用户做出的推荐列表为R(u) =(梨子,菠萝,龙眼),用户在测试集上的实际行为列表T(u) =(梨子,黑莓,白菜,冬瓜)
那么R(u)和T(u)的交集为1,R(u)=3,T(u)=4,故准确率为1/3,召回率为1/4
3.覆盖率
覆盖率(Coverage)描述一个推荐系统对物品长尾的发掘能力。最简单的定义为推荐系统能够推荐出来的物品占总物品集合的比例。
假设系统的用户集合为U,总物品集合为I ,推荐系统给每个用户推荐一个长度为N的物品列表R(u):
可以通过研究物品在推荐列表中出现的次数的分布描述推荐系统挖掘长尾的能力。如果这个分布比较平,那么说明推荐系统的覆盖率比较高,而如果这个分布比较陡峭,说明这个推荐系统的覆盖率比较低。在信息论和经济学中有两个著名的指标可以用来定义覆盖率。
def Coverage(train,test,N): recommend_items=set() all_items=set() for user in train.keys(): for item in train[user].keys(): all_items.add(item) rank=GetRecommendation(user,N) for item,Pui in rank: recommend_items.add(item) return len(recommend_items)/(len(all_items)*1.0)
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
3.1 第一个是信息熵:
这里p(i)是物品i的流行度除以所有物品流行度之和。
3.2 基尼系数(Gini Index):
这里,ij是按照物品流行度p从小到大排序的物品列表中的第j个物品。
评测推荐系统是否具有马太效应的简单办法就是使用基尼系数。
马太效应:强者更强,弱者更弱
def GiniIndex(p): j = 1 n = len(p) G=0 for item,weight in sorted(p.items(),key=itemgetter(1)): G += (2*j-n-1)*weight return G/float(n-1)1234567812345678
4.多样性
4.1用户的兴趣是广泛的,一个经常看动漫的用户也可以喜欢其他类型的视频。为了满足用户广泛的兴趣,推荐列表需要能够覆盖用户不同的兴趣领域,即推荐结果需要具有多样性。
多样性描述了推荐列表中物品两两之间的不相似性。假设
v定义了物品i和物品j之间的相似度,那么用户u的推荐列表R(u)的多样性定义为:
而推荐系统的整体多样性可以定义为所有用户推荐列表多样性的平均值:
4.2 ILS
ILS 衡量推荐列表多样性的指标,计算公式:
如果,S(bi,bj)计算的是i ii和j jj两个物品的相似性,如果推荐列表中的物品越不相似,ILS越小,那么推荐结果的多样性越好。
5.新颖性
新颖的推荐是指给用户推荐那些他们以前没有听说过的物品。
评测新颖度最简单的方法是利用推荐结果的平均流行度,因为越不热门的物品越可能让用户觉得新颖。
# 定义新颖度指标计算方式 def popularity(self): # 计算物品的流行度 item_pop = {} for user in self.train: for item in self.train[user]: if item not in item_pop: item_pop[item] = 0 item_pop[item] += 1 num, pop = 0, 0 for user in self.test: rank = self.recs[user] for item, score in rank: # 取对数,防止因长尾问题带来的被流行物品所主导 pop += math.log(1 + item_pop[item]) num += 1 return round(pop / num, 6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
6.惊喜度
惊喜度是指出乎用户意料的,推荐用户认知范围外的物品给用户且令用户满意。
目前没有什么公认的惊喜度的指标定义方式。
7.信任度
两个不同的信任系统,尽管推荐结果可能相同,但是用户可能会选择更让他信任的那个。比如京东和淘宝推荐同样的物品给用户,假如用户更多在淘宝上消费的话,用户可能会更倾向于使用淘宝推荐的物品。
8.实时性
在新闻类网站上,例如今日头条和微博,物品具有很强的实时性。比如一条新闻,系统需要第一时间推送给用户。推荐昨天的新闻显然不如推荐今天的新闻。
推荐系统的实时性包括两个方面。首先,推荐系统需要实时的更新推荐列表来满足用户新的行为变化。第二个方面是推荐系统需要能够将新加入系统的物品推荐给用户。
9. 健壮性
推荐系统会面对各种各样的作弊行为,针对推荐系统的规则来设计一些作弊行为,以此来攻击推荐系统。而健壮性是指衡量一个推荐系统抗击作弊的能力。
算法健壮性的评测主要利用模拟攻击。
在实际的系统中,提高系统的健壮性,除了选择健壮性高的算法,还有以下方法。
设计推荐系统时尽量使用代价比较高的用户行为。如在用户的购买行为和浏览行为中,显然购买行为的代价更高。
在使用数据时,进行攻击检测,从而对数据进行清理。
10 Normalized Discounted Cummulative Gain(NDCG) 衡量排序质量的指标
累积增益CG,推荐系统中CG表示将每个推荐结果相关性的分值累加后作为整个推荐列表的得分:
其中,reli 表示位置i的推荐结果的相关性,k表示推荐列表的大小。
CG没有考虑每个推荐结果处于不同位置对整个推荐结果的影响,例如,我们总是希望相关性大大的结果排在前面,相关性低的排在前面会影响用户体验。
DCG在CG的基础上引入了位置影响因素,计算公式如下:
从上面的式子可以得出:1)推荐结果的相关性越大,DCG越大。2)相关性好的排在推荐列表前面的话,推荐效果越好,DCG越大。
DCG针对不同的推荐列表之间很难进行横向评估,而我们评估一个推荐系统不可能仅使用一个用户的推荐列表及相应结果进行评估,而是对整个测试集中的用户及其推荐列表结果进行评估。那么,不同用户的推荐列表的评估分数就需要进行归一化,也就是NDCG。
IDCG表示推荐系统某一用户返回的最好推荐结果列表, 即假设返回结果按照相关性排序, 最相关的结果放在最前面, 此序列的DCG为IDCG。因此DCG的值介于 (0,IDCG] ,故NDCG的值介于(0,1],那么用户u的NDCG@K定义为:
平均NDCG的值为:
例子:
import numpy as npdef ndcg(pred_rel): dcg = 0 for (index,rel) in enumerate(pred_rel): dcg += (rel * np.reciprocal(np.log2(index+2))) print('dcg ' + str(dcg)) idcg = 0 for(index,rel) in enumerate(sorted(pred_rel,reverse=True)): idcg += (rel * np.reciprocal(np.log2(index+2))) print('idcg ' + str(idcg)) return dcg/idcgpred_rel = [3,1,2,3,2]print('ndcg ' + str(ndcg(pred_rel)))123456789101112131415161718123456789101112131415161718
11.Mean Reciprocal Rank(MRR) 平均倒数排名
正确检索结果值在检索结果中的排名来评估检索系统的性能。
其中,∣Q∣是用户的个数,rank是对于第i ii个用户,推荐列表中第一个在ground-truth结果中的item所在的排列位置。
举个例子:假如检索三次的结果如下,需要的结果(cat,torus,virus)分别排在3,2,1的话,此系统地MRR为(1/3 + 1/2 + 1)/3 = 11/18
如果检索出来的正确结果多于一个时,可以使用MAP(Mean Average Precision)算法。
import numpy as npdef mrr(gt_items, pred_items): for index,item in enumerate(pred_items): if item in gt_items: return 1/index
1
2
3
4
5
6
7
1
2
3
4
5
6
7