這些指標都是衡量搜索引擎算法的指標。搜索引擎一般采用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