一、場景
豆瓣圖書、時光電影等索引類站點的不考慮時間因素的產品評分,其核心是通過用戶的評價計算出可量化的分數來衡量產品的受歡迎程度。使用威爾遜區間法進行評分,並使用貝葉斯平均法修正評分。
二、威爾遜區間法
威爾遜區間法是基於二項分布的一種計算方法,其結果與好評率和評價次數相關。其假設只有“喜歡”和“不喜歡”兩個可選項,使其符合二項分布,並根據置信水平得到結果。
計算公式如下:
其中Smax是最大評分,pmin是威爾遜區間下限值,p是好評率(通常為平均值/總分值),n是評價總數,K是統計量常數(表示某個置信水平下z的統計量,在90%的置信水平下,值為1.64,在95%的置信水平下,值為1.96,在99%的置信水平下,值為2.58)。
以豆瓣的評分機制為例,其評價選項為一星至五星,評分十分制。若存在產品ABCD,其評價如下表所示,那么其在95%置信水平下的評分為:
產品 |
一星 |
二星 |
三星 |
四星 |
五星 |
平均值 |
評分 |
A |
32 |
32 |
32 |
32 |
32 |
5.00 |
4.23 |
B |
50 |
25 |
10 |
25 |
50 |
5.00 |
4.23 |
C |
320 |
320 |
320 |
320 |
320 |
5.00 |
4.76 |
D |
500 |
250 |
100 |
250 |
500 |
5.00 |
4.76 |
可以看出,相對於常用的平均值法,威爾遜區間法更准確的反映除了評價差異。
三、貝葉斯平均法
嚴格來說,貝葉斯平均法並不是一個評分模型,而是平衡模型。其核心是為冷門題目提供一個補償值使其不至於因為少量評價而產生不可靠的評分,而在評價數量多后減小補償值的比例,最終使得隨着評價數量的增加而評分逐漸逼近實際值。
計算公式如下:
其中C是補償評價數,M是補償評分,n是評價數量,s是已計算評分。通常C、M應為常量,使最終評分只與評價數量和已計算評分相關。
以豆瓣的評分機制為例,其評價選項為一星至五星,評分十分制。若存在產品EFGH,其評價如下表所示,那么其在95%置信水平,C=64、M=4.0368的補償條件下的修正評分為:
產品 |
一星 |
二星 |
三星 |
四星 |
五星 |
平均值 |
評分 |
修正 |
E |
0 |
0 |
1 |
0 |
0 |
5.00 |
0.55 |
3.98 |
F |
10 |
0 |
5 |
0 |
10 |
5.00 |
3.18 |
3.79 |
G |
10 |
10 |
10 |
10 |
10 |
5.00 |
3.66 |
3.87 |
H |
100 |
50 |
20 |
50 |
100 |
5.00 |
4.46 |
4.39 |
顯然,對評價數量少的產品評分進行了大幅修正。
四、總結
需要明確威爾遜區間法用於對單個產品評分,貝葉斯平均法用於對多個產品平衡評分。對於威爾遜區間法,需要確保好評率的正確性及選擇合適的置信水平。對於貝葉斯平均法,需要使補償評價數和補償評分為常量,以使得單個產品的評分僅與自身的評價相關。
五、更多
更為細致的評分,需要考慮評分的用戶的權重性(資深用戶和普通用戶)、傾向性評分(用戶的評分習慣),二者都可以在上述方法上擴充修改。
參考
阮一峰,《基於用戶投票的排名算法(五):威爾遜區間》
阮一峰,《基於用戶投票的排名算法(六):貝葉斯平均》
實現代碼-Python
import sys def Avg(data,scoreMap): totalScore = float(sum(map(lambda t:t[0]*t[1], map(None,data,scoreMap)))) totalN = sum(data) return totalScore / totalN def Wilson(p, n, maxScore): p = float(p) K = 1.96 # 95% confidence level _K2_div_n = (K ** 2) / n pmin = (p + _K2_div_n / 2.0 - K*((p*(1-p)/n + _K2_div_n/n/4.0)**0.5)) / (1 + _K2_div_n) return pmin * maxScore def WilsonAvgP(n): totalP = 0.0; totalN = 0 p = 0.01 while True: totalP += Wilson(p, n, 1); totalN += 1 p += 0.01 if p >= 1: break return totalP / totalN def Bayesian(C, M, n, s): return (C*M + n*s) / (n + C) DATA = { "A" : (32,32,32,32,32), "B" : (50,25,10,25,50), "C" : (320,320,320,320,320), "D" : (500,250,100,250,500), "E" : (0,0,1,0,0), "F" : (10,0,5,0,10), "G" : (10,10,10,10,10), "H" : (100,50,20,50,100), } SCORE_MAP = (0,2.5,5,7.5,10) MAX_SCORE = 10 result = {} # key : avgScore, wilsonScore, wilsonRank C = 64; M = WilsonAvgP(C) * MAX_SCORE for k,v in DATA.items(): n = sum(v) avgScore = Avg(v,SCORE_MAP) wilsonScore = Wilson(avgScore / MAX_SCORE, n, MAX_SCORE) wilsonRank = Bayesian(C, M, n, wilsonScore) result[k] = (avgScore, wilsonScore, wilsonRank) for k in sorted(result.keys()): v = result[k] print k,":","%.2f" % v[0]," ","%.2f" % v[1]," ","%.2f" % v[2]