搞懂推荐系统中的评价指标NDCG(CG、DCG、IDCG)
这些指标都是衡量搜索引擎算法的指标。搜索引擎一般采用PI(peritem)的方式进行评测,简单地说就是逐条对搜索结果进行分等级的打分。假设我们现在在Google上搜索一个词,然后得到5个结果。我们对这些结果进行3个等级的区分:Good(好)、Fair(一般)、Bad(差),然后赋予他们分值分别为3、2、1,下面我们举例子按照此评级来。
CG(Cumulative Gain)累计收益
CG就是DCG的前身,它不考虑在搜索结果的位置信息,只考虑搜索结果列表中所有结果的等级对应得分的总和。如过搜索结果列表有P个结果,CG形式化定义为:
\(rel_i\)是第\(i\)位结果的得分。
举个例子来说,假设我搜索得到了5个结果,按照前面提到的评级标准,结果可以表示为:3、2 、1 、3、 2。那么,CG就等于这些结果评级值的累和。即,CG=3+2+1+3+2=11。
在假设,另外一个模型,也得到了了五个结果,表示为:3、2 、3、2 、1。则CG也为11(CG=3+3+2+2+1)。
所以,通过上述的例子,可以看到CG的统计并不能影响到搜索结果的排序,CG得分高只能说明这个结果页面总体的质量比较高,而并不能说明这个算法做的排序好或差。
根据我们多年使用搜索引擎的经验来讲,什么是好的排序?我们希望好的结果排在前面,而不好的结果排在后面。也就是上面举例中的第二个模型得到的结果(3、3 、2、2 、1)应该要比第一种好。此时,我们需要一个指标来区分这两者的区别。DCG说“我可以”。
细心的小伙伴可能想问,我把上面的例子中第一个结果排个顺序不就好了么?啊,其实不是这样的。我们模型得到的结果可能已经按照模型算出的分值排好序了。可以对上面的五个结果进行标号,比如模型选出来的是:A、D、E、G、F这五个结果,而且算出的分值分别是:5/4/3/2/1(这里纯粹为了排序,跟上面的评级数字无关)。此时结果已经定了。最后看看真实情况,发现A是好的(3分)、D是次好(2分)等等以此类推,就得到了上面的结果3、2 、1 、3、 2。
DCG(Discounted cumulative gain)折扣累计收益
DCG的思想是等级比较高的结果却排到了比较后面,那么在统计分数时,就应该对这个结果的得分有所打折。一个有P个结果的搜索结果页面的DCG定义为:
符号的含义跟前面的公式一样。也就是说,结果的分值是一方面,但是要按照位置给一个折扣,此处是乘以一个\(1/log_2i\),来计算折扣。
这样一来,结果为3、2 、1 、3、 2的DCG计算为:3+2+0.63+1.5+0.86=7.99。代码如下:
r=np.array([3, 2, 1, 3, 2])
r[0] + np.sum(r[1:] / np.log2(np.arange(2, r.size + 1)))
还有第二种计算方法,据说公式只适合打分分两档的评测。
按照这种计算方式,结果为3、2 、1 、3、 2的DCG计算为:3+1.26+0.5+1.29+0.77=6.82
r=np.array([3, 2, 1, 3, 2])
np.sum(r / np.log2(np.arange(2, r.size + 2)))
DCG解决了结果排序优劣的问题,但时呢?不能比较不同的查询结果。比如说,模型一查询出来5个结果,但是另个模型查询出来10个结果,比较DCG也无法对比结果差异了。
这时候,NDCG默默的提出了。
NDCG(Normalize DCG)归一化折扣累计收益
因为不同查询结果有多有少,所以不同查询的DCG值就没有办法来做对比。所以需要对每个结果进行归一化,具体公式如下:
这里的IDCG是idea DCG,就是理想的DCG。计算IDCG,首先要拿到搜索的结果,人工对这些结果进行排序,排到最好的状态后,算出这个排列下本次结果的DCG,就是IDCG。
比如说,结果3、2 、1 、3、 2的理想结果应该是3、3、2、2、1,此理想结果的DCG=3+3+1.26+1+0.43=8.69(按照DCG的第一种方式来算)。
那么,3、2 、1 、3、 2的nDCG=7.99/8.69=0.91。
其他情况
有时候在推荐中,我们给用户推荐了5个物品,例如A,B,C,D,E。但是真实情况下,用户只购买了A、D、G、F、K。。。可以看到,只有A、D是正确的,但是也没有评级啊。
此时推荐的5个物品分数只能用命中与否来衡量:[1,0,0,1,0]。但是这个结果的理想状态应该是[1,1,0,0,0],即起码A、D应该排在前面,所以此时的nDCG=0.75。
计算代码如下:
r=np.array([1,0,0,1,0])
dcg = r[0] + np.sum(r[1:] / np.log2(np.arange(2, r.size + 1)))
r_s = sorted(r,reverse=True)
r_s = np.array(r_s)
dcg_max = r_s[0] + np.sum(r_s[1:] / np.log2(np.arange(2, r_s.size + 1)))
print(dcg,dcg_max)
dcg/dcg_max