目录
实验数据
lab3实验数据:lab3实验数据
实验要求
实验内容
实现以下指标评价,并对Experiment2的检索结果进行评价
• Mean Average Precision (MAP)
• Mean Reciprocal Rank (MRR)
• Normalized Discounted Cumulative Gain (NDCG)
实验目的
理解MAP(平均精度均值)、MRR(平均倒数排名)、NDCG(归一化折损累积增益)算法,并且在已有数据上编程实现
实验环境
Windows10、python3.8、Spyder(Anaconda3)
实验基本原理相关算法详解
MAP:平均精度值均值
什么是AP(平均精度值)呢?
那么回顾Precision和Recall。
Precision: 即精确率,检索最相关的顶级文档的能力.
Recall: 即召回率, 检索到语料库中所有相关项的能力。
精确率=检索到的相关文档数量/检索的文档总量, 召回率=检索到的相关文档数量/总的相关文档数量
为什么不用准确率呢?
准确率,也就是accuracy,也就是系统究竟做对了多少。如果是对于平衡样本来说,这
个没问题。但是对于样本不平衡的情况,这就不行。例如信息检索,有99.999%的信息
都对用户没用,而且大部分系统肯定都能判别出来这些没用,所以对于绝大部分系统,
准确率都是99.999%,这个就不能很好的衡量系统性能了。
F-measure
但是我们还是希望有一个数能够衡量系统的性能,否则系统A的precision比系统B高,但
是recall却比系统B低,那么我们就不太好选了。所以综合Precision和Recall,我们得到
一个F-Score.这个F Score是P和R的调和平均数(harmonic mean),β 的作用是表明P与R
谁比较重要。harmonic mean有个特点,就是假如其中一个P或者R比较小,那么整体就
会很小,也就是对两个数中如果有特别小的数的惩罚(penalty)比较大。这是代数平均
数所没有的特性。一般来说,会把β设置成1,这个时候就成为F1-Score.
MAP 计算例子
前面说了一堆概念,不如直接上例子,如下图,两次查询的精确率和召回率解释。
计算方法:
就拿rank1来讲,第一个返回的是正确结果,而且只有一个,所以
precision=1,recall=1/5,
因为有5个相关文档,此时已经检索到了1个,第二个红色,表示它其实不是相关文档,那么正确的还是只有一个,所以
recall=1/5,而precision=0.5,
因为此刻返回来2个相关项,一个正确,一个错误,再看第三个,返回的是相关项,所以
recall=2/5,precision=2/3.
后面的按照此原理可以推导出来。接下来计算MAP
average precision query1=(1.0+0.67+0.5+0.44+0.5)/5=0.62;
average precision query2=(0.5+0.4+0.43)/3=0.44;
mean average precision=(ap1+ap2)/2=0.53;
MAP即是算出所有查询相关项的平均的精确度,再取所有查询的精确度的平均值。
Codes
def MAP_eval(qrels_dict, test_dict, k = 100):
AP_result = [] #返回每一个类对应的AP
for query in qrels_dict:
test_result = test_dict[query]
true_list = set(qrels_dict[query].keys()) #真阳序列表
true_length = len(test_result)
if true_length <= 0:
print('query:', query, ' not found test list')
return []
P_result = [] #每一个词的准确率
i = 0
rank_yes = 0 #序列表中正确的term
for doc_id in test_result[0: true_length]:
i += 1
if doc_id in true_list:
rank_yes += 1
P_result.append(rank_yes / i)
if P_result:
AP = np.sum(P_result) / len(true_list)
AP_result.append(AP)
else:
print('query:', query, ' not found a true value')
AP_result.append(0)
return np.mean(AP_result) #对所有类求平均
Mean Reciprocal Rank (MRR):平均倒数排名
MRR概念
MRR得核心思想很简单:返回的结果集的优劣,跟第一个正确答案的位置有关,第一个正确答案越靠前,结果越好。具体来说:对于一个query,若第一个正确答案排在第n位,则MRR得分就是 1/n 。(如果没有正确答案,则得分为0)
Q为样本query集合,|Q|表示Q中query个数, R a n k i Rank_i Ranki 表示在第i个query中,第一个正确答案的排名
MRR计算例子
假设现在有5个query语句,q1、q2、q3、q4、q5
q1的正确结果在第4位,q2和q3没有正确结果(即未检索到文档),q4正确结果在第5位,q5正确结果在第10位。
那么系统的得分就是1/4+0+0+1/5+1/10=0.55
最后MRR就是求平均,即该系统MRR=0.55/5=0.11
Code
#平均倒数排名,对比MAP,当返回的相关结果较少时,使用它更加合适
def MRR_eval(qrels_dict, test_dict, k = 100):#根据置信度排序,分高的在前面
#对于一个query,若第一个正确答案排在第n位,那么MRR得分就是1/n
# 公式1/QΣ(1/rank)
R_Score = []
for query in qrels_dict:
test_result = test_dict[query]
true_list = set(qrels_dict[query].keys())
true_length = len(test_result)
if true_length <= 0:
print('query ', query, ' not found test list')
return []
P_result = []#正确结果
k = 0
for doc_id in test_result[0: true_length]:
k += 1
if doc_id in true_list:
P_result.append(1 / k) #得分1/k,并求和
break
if P_result:
RR = np.sum(P_result)/1.0
R_Score.append(RR)
else:
R_Score.append(0)
return np.mean(R_Score)
NDCG(归一化折损累积增益)
NDCG,Normalized Discounted cumulative gain 归一化折损累计增益,这个指标通常是用来衡量和评价搜索结果算法
DCG的两个思想:
1、高关联度的结果比一般关联度的结果更影响最终的指标得分;
2、有高关联度的结果出现在更靠前的位置的时候,指标会越高;
累计增益(CG)
r e l i rel_i reli 代表i这个位置上的相关度
折损累计增益(DCG
归一化折损累计增益
先计算理想状态下的的DCG
然后做归一化
NDCG计算例子
1.计算CG
2.计算DCG
4.计算IDCG
5.计算NDCG
Codes
def NDCG_eval(qrels_dict, test_dict, k = 100):
NDCG_result = []
for query in qrels_dict:
test_result = test_dict[query]
true_list = list(qrels_dict[query].values())
true_list = sorted(true_list, reverse=True)
i = 1
DCG = 0.0 #在每一个CG的结果上除以一个折损值
IDCG = 0.0
true_length =len(true_list)
if true_length <= 0:
print('query ', query, ' not found test list')
return []
for doc_id in test_result[1: true_length]:
i += 1
rel = qrels_dict[query].get(doc_id, 0)
DCG += rel / math.log(i, 2)
IDCG += true_list[i-2] / math.log(i, 2)
rel1=qrels_dict[query].get(test_result[0], 0)
DCG += rel1
NDCG = DCG / IDCG #归一化处理
NDCG_result.append(NDCG)
return np.mean(NDCG_result)
实验结果图
代码
(evaluation.py)
import math
import numpy as np
def generate_tweetid_gain(file_name):
qrels_dict = {}
with open(file_name, 'r', errors='ignore') as f:
for line in f:
ele = line.strip().split(' ')
if ele[0] not in qrels_dict:
qrels_dict[ele[0]] = {}
# here we want the gain of doc_id in qrels_dict > 0,
# so it's sorted values can be IDCG groundtruth
if int(ele[3]) > 0:
qrels_dict[ele[0]][ele[2]] = int(ele[3])
return qrels_dict
def read_tweetid_test(file_name):
# input file format
# query_id doc_id
# query_id doc_id
# query_id doc_id
# ...
test_dict = {}
with open(file_name, 'r', errors='ignore') as f:
for line in f:
ele = line.strip().split(' ')
if ele[0] not in test_dict:
test_dict[ele[0]] = []
test_dict[ele[0]].append(ele[1])
return test_dict
#平均精度均值
def MAP_eval(qrels_dict, test_dict, k = 100):
AP_result = [] #返回每一个类对应的AP
for query in qrels_dict:
test_result = test_dict[query]
true_list = set(qrels_dict[query].keys()) #真阳序列表
true_length = len(test_result)
if true_length <= 0:
print('query:', query, ' not found test list')
return []
P_result = [] #每一个词的准确率
i = 0
rank_yes = 0 #序列表中正确的term
for doc_id in test_result[0: true_length]:
i += 1
if doc_id in true_list:
rank_yes += 1
P_result.append(rank_yes / i)
if P_result:
AP = np.sum(P_result) / len(true_list)
AP_result.append(AP)
else:
print('query:', query, ' not found a true value')
AP_result.append(0)
return np.mean(AP_result) #对所有类求平均
#平均倒数排名,对比MAP,当返回的相关结果较少时,使用它更加合适
def MRR_eval(qrels_dict, test_dict, k = 100):#根据置信度排序,分高的在前面
#对于一个query,若第一个正确答案排在第n位,那么MRR得分就是1/n
# 公式1/QΣ(1/rank)
R_Score = []
for query in qrels_dict:
test_result = test_dict[query]
true_list = set(qrels_dict[query].keys())
true_length = len(test_result)
if true_length <= 0:
print('query ', query, ' not found test list')
return []
P_result = []#正确结果
k = 0
for doc_id in test_result[0: true_length]:
k += 1
if doc_id in true_list:
P_result.append(1 / k) #得分1/k,并求和
break
if P_result:
RR = np.sum(P_result)/1.0
R_Score.append(RR)
else:
R_Score.append(0)
return np.mean(R_Score)
#允许以实数形式进行相关性打分,归一化折损累积增益
def NDCG_eval(qrels_dict, test_dict, k = 100):
NDCG_result = []
for query in qrels_dict:
test_result = test_dict[query]
true_list = list(qrels_dict[query].values())
true_list = sorted(true_list, reverse=True)
i = 1
DCG = 0.0 #在每一个CG的结果上除以一个折损值
IDCG = 0.0
true_length =len(true_list)
if true_length <= 0:
print('query ', query, ' not found test list')
return []
for doc_id in test_result[1: true_length]:
i += 1
rel = qrels_dict[query].get(doc_id, 0)
DCG += rel / math.log(i, 2)
IDCG += true_list[i-2] / math.log(i, 2)
rel1=qrels_dict[query].get(test_result[0], 0)
DCG += rel1
NDCG = DCG / IDCG #归一化处理
NDCG_result.append(NDCG)
return np.mean(NDCG_result)
def evaluation():
k = 100
# query relevance file
file_qrels_path = 'qrels.txt'
# qrels_dict = {query_id:{doc_id:gain, doc_id:gain, ...}, ...}
qrels_dict = generate_tweetid_gain(file_qrels_path)
# ur result, format is in function read_tweetid_test, or u can write by ur own
file_test_path = 'result.txt'
# test_dict = {query_id:[doc_id, doc_id, ...], ...}
test_dict = read_tweetid_test(file_test_path)
MAP = MAP_eval(qrels_dict, test_dict, k)
print('MAP', ' = ', round(MAP,3), sep='')
MRR = MRR_eval(qrels_dict, test_dict, k)
print('MRR', ' = ', round(MRR,3), sep='')
NDCG = NDCG_eval(qrels_dict, test_dict, k)
print('NDCG', ' = ', round(NDCG,3), sep='')
if __name__ == '__main__':
evaluation()