天天看點

投票排名算法

原文出處:http://www.cnblogs.com/worldisimple/articles/2455781.html

基于使用者投票的排名算法(一):Delicious和Hacker News

  網際網路的出現,意味着"資訊大爆炸"。

  使用者擔心的,不再是資訊太少,而是資訊太多。如何從大量資訊之中,快速有效地找出最重要的内容,成了網際網路的一大核心問題。

投票排名算法

  各種各樣的排名算法,是目前過濾資訊的主要手段之一。對資訊進行排名,意味着将資訊按照重要性依次排列,并且及時進行更新。排列的依據,可以基于資訊本身的特征,也可以基于使用者的投票,即讓使用者決定,什麼樣的資訊可以排在第一位。

  下面,我将整理和分析一些基于使用者投票的排名算法,打算分成四個部分連載,今天是第一篇。

  一、Delicious

  最直覺、最簡單的算法,莫過于按照機關時間内使用者的投票數進行排名。得票最多的項目,自然就排在第一位。

  舊版的 Delicious,有一個"熱門書簽排行榜",就是這樣統計出來的。

投票排名算法

  它按照"過去 60 分鐘内被收藏的次數"進行排名。每過 60 分鐘,就統計一次。

  這個算法的優點是比較簡單、容易部署、内容更新相當快;缺點是排名變化不夠平滑,前一個小時還排在前列的内容,往往第二個小時就一落千丈。

  二、Hacker News

  Hacker News 是一個網絡社群,可以張貼連結,或者讨論某個主題。

投票排名算法

  每個文章前面有一個向上的三角形,如果你覺得這個内容很好,就點選一下,投上一票。根據得票數,系統自動統計出熱門文章排行榜。但是,并非得票最多的文章排在第一位,還要考慮時間因素,新文章應該比舊文章更容易得到好的排名。

  Hacker News 使用 Paul Graham 開發的 Arc 語言編寫,源碼可以從 arclanguage.org 下載下傳。它的排名算法是這樣實作的:

投票排名算法

  将上面的代碼還原為數學公式:

投票排名算法

  其中,

  P 表示文章的得票數,減去 1 是為了忽略發帖人的投票。

  T 表示距離發帖的時間(機關為小時),加上 2 是為了防止最新的文章導緻分母過小(之是以選擇2,可能是因為從原始文章出現在其他網站,到轉貼至 Hacker News,平均需要兩個小時)。

  G 表示"重力因子"(gravityth power),即将文章排名往下拉的力量,預設值為1.8,後文會詳細讨論這個值。

  從這個公式來看,決定文章排名有三個因素:

  第一個因素是得票數P。

  在其他條件不變的情況下,得票越多,排名越高。

投票排名算法

  從上圖可以看到,有三個同時發表的文章,得票分别為 200 票、60票和 30 票(減 1 後為 199、59和 29),分别以黃色、紫色和藍色表示。在任一個時間點上,都是黃色曲線在最上方,藍色曲線在最下方。

  如果你不想讓"高票文章"與"低票文章"的差距過大,可以在得票數上加一個小于 1 的指數,比如(P-1)^0.8。

  第二個因素是距離發帖的時間T。

  在其他條件不變的情況下,越是新發表的文章,排名越高。或者說,一個文章的排名,會随着時間不斷下降。

  從前一張圖可以看到,經過 24 小時之後,所有文章的得分基本上都小于1,這意味着它們都将跌到排行榜的末尾,保證了排名前列的都将是較新的内容。

  第三個因素是重力因子G。

  它的數值大小決定了排名随時間下降的速度。

投票排名算法

  從上圖可以看到,三根曲線的其他參數都一樣,G的值分别為1.5、1.8和2.0。G值越大,曲線越陡峭,排名下降得越快,意味着排行榜的更新速度越快。

  知道了算法的構成,就可以調整參數的值,以适用你自己的應用程式。

  [參考文獻]

  * How Hacker News ranking algorithm works

  * How to Build a Popularity Algorithm You can be Proud of

  基于使用者投票的排名算法(二):Reddit

  Hacker News 排名算法的特點是使用者隻能投贊成票,但是很多網站還允許使用者投反對票。就是說,除了好評以外,你還可以給某篇文章差評。

投票排名算法

  Reddit 是美國最大的網上社群,它的每個文章前面都有向上和向下的箭頭,分别表示"贊成"和"反對"。使用者點選進行投票,Reddit 根據投票結果,計算出最新的"熱點文章排行榜"。

  怎樣才能将贊成票和反對票結合起來,計算出一段時間内最受歡迎的文章呢?如果文章A有 100 張贊成票、5張反對票,文章B有 1000 張贊成票、950張反對票,誰應該排在前面呢?

  Reddit 的程式是開源的,使用 Python 語言編寫。排名算法的代碼大緻如下:

投票排名算法

  這段代碼考慮了這樣幾個因素:

  (1)文章的新舊程度t

t = 發貼時間 - 2005 年 12 月 8 日7:46:43

  t 的機關為秒,用 unix 時間戳計算。不難看出,一旦文章發表,t就是固定值,不會随時間改變,而且文章越新,t值越大。至于 2005 年 12 月 8 日,應該是 Reddit 成立的時間。

  (2)贊成票與反對票的差x

x = 贊成票 - 反對票

  (3)投票方向y

投票排名算法

  y 是一個符号變量,表示對文章的總體看法。如果贊成票居多,y就是 +1;如果反對票居多,y就是-1;如果贊成票和反對票相等,y就是0。

  (4)文章的受肯定程度z

投票排名算法

  z 表示贊成票超過反對票的數量。如果贊成票少于或等于反對票,那麼z就等于1。

  結合以上幾個變量,Reddit 的最終得分計算公式如下:

投票排名算法

  這個公式可以分成兩個部分來讨論:

  (一)

投票排名算法

  這個部分表示,贊成票超過反對票的數量越多,得分越高。

  需要注意的是,這裡用的是以 10 為底的對數,意味着z=10可以得到 1 分,z=100可以得到 2 分。也就是說,前 10 個投票人與後 90 個投票人(乃至再後面 900 個投票人)的權重是一樣的,即如果一個文章特别受到歡迎,那麼越到後面投贊成票,對得分越不會産生影響。

  當反對票超過或等于贊成票,z=1,是以這個部分等于0,也就是不産生得分。

  (二)

投票排名算法

  這個部分表示,t越大,得分越高,即新文章的得分會高于老文章。它起到自動将老文章的排名往下拉的作用。

  分母的 45000 秒,等于 12.5 個小時,也就是說,後一天的文章會比前一天的文章多得 2 分。結合前一部分,可以得到結論,如果前一天的文章在第二天還想保持原先的排名,在這一天裡面,它得到的淨贊成票必須增加 100 倍。

  y 的作用是用來産生正分和負分。當贊成票超過反對票時,得分為正;當贊成票少于反對票時,得分為負;當兩者相等,得分為0。這就保證了得到大量淨贊成票的文章,會排在前列;得到大量淨反對票的文章,會排在最後。

  (三)

  這種算法的一個問題是,對于那些有争議的文章(贊成票和反對票非常接近),它們不可能排到前列。假定同一時間有兩個文章發表,文章A有 1 張贊成票(發帖人投的)、0張反對票,文章B有 1000 張贊成票、1000張反對票,那麼A的排名會高于B,這顯然不合理。

  結論就是,Reddit 的排名,基本上由發帖時間決定,超級受歡迎的文章會排在最前面,一般性受歡迎的文章、有争議的文章都不會很靠前。這決定了 Reddit 是一個符合大衆口味的社群,不是一個很激進、可以展示少數派想法的地方。

  [參考資料]

  * How Reddit ranking algorithms work

  基于使用者投票的排名算法(三):Stack Overflow

  Reddit 排名算法的特點是,使用者可以投贊成票,也可以投反對票。也就是說,除了時間因素以外,隻要考慮兩個變量就夠了。

  但是,還有一些特定用途的網站,必須考慮更多的因素。世界排名第一的程式員問答社群 Stack Overflow,就是這樣一個網站。

投票排名算法

  你在上面提出各種關于程式設計的問題,等待别人回答。通路者可以對你的問題進行投票(贊成票或反對票),表示這個問題是不是有價值。

投票排名算法

  一旦有人回答了你的問題,其他人也可以對這個回答投票(贊成票或反對票)。根據投票結果,系統自動找出最佳回答。

投票排名算法

  排名算法的作用是,找出某段時間内的熱點問題,即哪些問題最被關注、得到了最多的讨論。

  在 Stack Overflow 的頁面上,每個問題前面有三個數字,分别表示問題的得分、回答的數目和該問題的浏覽次數。以這些變量為基礎,就可以設計算法了。

投票排名算法

  創始人之一的 Jeff Atwood,曾經在幾年前,公布過排名得分的計算公式。

投票排名算法

  寫成 php 代碼,就是下面這樣:

投票排名算法

  各個算法變量的含義如下:

  (1)Qviews(問題的浏覽次數)

投票排名算法

  某個問題的浏覽次數越多,就代表越受關注,得分也就越高。這裡使用了以 10 為底的對數,用意是當通路量越來越大,它對得分的影響将不斷變小。

  (2)Qscore(問題得分)和 Qanswers(回答的數量)

投票排名算法

  首先,Qscore(問題得分)= 贊成票-反對票。如果某個問題越受到好評,排名自然應該越靠前。

  Qanswers 表示回答的數量,代表有多少人參與這個問題。這個值越大,得分将成倍放大。這裡需要注意的是,如果無人回答,Qanswers 就等于0,這時 Qscore 再高也沒用,意味着再好的問題,也必須有人回答,否則進不了熱點問題排行榜。

  (3)Ascores(回答得分)

投票排名算法

  一般來說,"回答"比"問題"更有意義。這一項的得分越高,就代表回答的品質越高。

  但是我感覺,簡單加總的設計還不夠全面。這裡有兩個問題。首先,一個正确的回答勝過一百個無用的回答,但是,簡單加總會導緻,1個得分為 100 的回答與 100 個得分為 1 的回答,總得分相同。其次,由于得分會出現負值,是以那些特别差的回答,會拉低正确回答的得分。

  (4)Qage(距離問題發表的時間)和 Qupdated(距離最後一個回答的時間)

投票排名算法

  改寫一下,可以看得更清楚:

投票排名算法

  Qage 和 Qupdated 的機關都是秒。如果一個問題的存在時間越久,或者距離上一次回答的時間越久,Qage 和 Qupdated 的值就相應增大。

  也就是說,随着時間流逝,這兩個值都會越變越大,導緻分母增大,是以總得分會越來越小。

  (5)總結

  Stack Overflow 熱點問題的排名,與參與度(Qviews 和 Qanswers)和品質(Qscore 和 Ascores)成正比,與時間(Qage 和 Qupdated)成反比。

  基于使用者投票的排名算法(四):牛頓冷卻定律 

  這個系列的前三篇,介紹了 Hacker News,Reddit 和 Stack Overflow 的排名算法。

  今天,讨論一個更一般的數學模型。

  這個系列的每篇文章,都是可以分開讀的。但是,為了保證所有人都在同一頁上,我再說一下,到目前為止,我們用不同方法,企圖解決的都是同一個問題:根據使用者的投票,決定最近一段時間内的"熱文排名"。

  你可能會覺得,這是一個全新的課題,伴随着網際網路而産生,需要全新的方法來解決。但是,實際上不是。我們可以把"熱文排名"想象成一個"自然冷卻"的過程:

(1)任一時刻,網站中所有的文章,都有一個"目前溫度",溫度最高的文章就排在第一位。

(2)如果一個使用者對某篇文章投了贊成票,該文章的溫度就上升一度。

(3)随着時間流逝,所有文章的溫度都逐漸"冷卻"。

投票排名算法

  這樣假設的意義,在于我們可以照搬實體學的冷卻定律,使用現成的公式,建立"溫度"與"時間"之間的函數關系,輕松建構一個"指數式衰減"(Exponential decay)的過程。

投票排名算法

  偉大的實體學家牛頓,早在 17 世紀就提出了溫度冷卻的數學公式,被後人稱作"牛頓冷卻定律"(Newton's Law of Cooling)。我們就用這個定律建構排名算法。

投票排名算法

  "牛頓冷卻定律"非常簡單,用一句話就可以概況:

物體的冷卻速度,與其目前溫度與室溫之間的溫差成正比。

  寫成數學公式就是:

投票排名算法

  其中,

- T (t)是溫度(T)的時間(t)函數。微積分知識告訴我們,溫度變化(冷卻)的速率就是溫度函數的導數T'(t)。

- H 代表室溫,T(t)-H就是目前溫度與室溫之間的溫差。由于目前溫度高于室溫,是以這是一個正值。

- 常數α(α>0)表示室溫與降溫速率之間的比例關系。前面的負号表示降溫。不同的物質有不同的α值。

  這是一個微分方程,為了計算目前溫度,需要求出T(t)的函數表達式。

  第一步,改寫方程,然後等式兩邊取積分。

投票排名算法
投票排名算法

  第二步,求出這個積分的解(c為常數項)。

投票排名算法
投票排名算法
投票排名算法

  第三步,假定在時刻t0,該物體的溫度是T(t0),簡寫為T0。代入上面的方程,得到

投票排名算法
投票排名算法

  第四步,将上一步的C代入第二步的方程。

投票排名算法

  假定室溫H為 0 度,即所有物體最終都會"冷寂",方程就可以簡化為

投票排名算法

  上面這個方程,就是我們想要的最終結果:

本期溫度 = 上一期溫度 x exp (-(冷卻系數) x 間隔的小時數)

  将這個公式用在"排名算法",就相當于(假定本期沒有增加淨贊成票)

本期得分 = 上一期得分 x exp (-(冷卻系數) x 間隔的小時數)

  其中,"冷卻系數"是一個你自己決定的值。如果假定一篇新文章的初始分數是 100 分,24小時之後"冷卻"為 1 分,那麼可以計算得到"冷卻系數"約等于0.192。如果你想放慢"熱文排名"的更新率,"冷卻系數"就取一個較小的值,否則就取一個較大的值。

投票排名算法

  [參考文獻]

  Rank Hotness With Newton's Law of Cooling

  基于使用者投票的排名算法(五):威爾遜區間

  迄今為止,這個系列都在讨論,如何給出"某個時段"的排名,比如"過去 24 小時最熱門的文章"。

  但是,很多場合需要的是"所有時段"的排名,比如"最受使用者好評的産品"。

  這時,時間因素就不需要考慮了。這個系列的最後兩篇,就研究不考慮時間因素的情況下,如何給出排名。

  一種常見的錯誤算法是:

得分 = 贊成票 - 反對票

  假定有兩個項目,項目A是 60 張贊成票,40張反對票,項目B是 550 張贊成票,450張反對票。請問,誰應該排在前面?按照上面的公式,B會排在前面,因為它的得分(550 - 450 = 100)高于A(60 - 40 = 20)。但是實際上,B的好評率隻有 55%(550 / 1000),而A為 60%(60 / 100),是以正确的結果應該是A排在前面。

  Urban Dictionary 就是這種錯誤算法的執行個體。

投票排名算法

  另一種常見的錯誤算法是

得分 = 贊成票 / 總票數

  如果"總票數"很大,這種算法其實是對的。問題出在如果"總票數"很少,這時就會出錯。假定A有 2 張贊成票、0張反對票,B有 100 張贊成票、1張反對票。這種算法會使得A排在B前面。這顯然錯誤。

  Amazon 就是這種錯誤算法的執行個體。

投票排名算法

  那麼,正确的算法是什麼呢?

  我們先做如下設定:

(1)每個使用者的投票都是獨立事件。

(2)使用者隻有兩個選擇,要麼投贊成票,要麼投反對票。

(3)如果投票總人數為n,其中贊成票為k,那麼贊成票的比例p就等于k/n。

  如果你熟悉統計學,可能已經看出來了,p服從一種統計分布,叫做"兩項分布"(binomial distribution)。這很重要,下面馬上要用到。

  我們的思路是,p越大,就代表這個項目的好評比例越高,越應該排在前面。但是,p的可信性,取決于有多少人投票,如果樣本太小,p就不可信。好在我們已經知道,p服從"兩項分布",是以我們可以計算出p的置信區間。所謂"置信區間",就是說,以某個機率而言,p會落在的那個區間。比如,某個産品的好評率是 80%,但是這個值不一定可信。根據統計學,我們隻能說,有 95% 的把握可以斷定,好評率在 75% 到 85% 之間,即置信區間是[75%, 85%]。

  這樣一來,排名算法就比較清晰了:

第一步,計算每個項目的"好評率"(即贊成票的比例)。

第二步,計算每個"好評率"的置信區間(以 95% 的機率)。

第三步,根據置信區間的下限值,進行排名。這個值越大,排名就越高。

  這樣做的原理是,置信區間的寬窄與樣本的數量有關。比如,A有 8 張贊成票,2張反對票;B有 80 張贊成票,20張反對票。這兩個項目的贊成票比例都是 80%,但是B的置信區間(假定[75%, 85%])會比A(假定[70%, 90%])窄得多,是以B的置信區間的下限值(75%)會比A(70%)大,是以B應該排在A前面。

  置信區間的實質,就是進行可信度的修正,彌補樣本量過小的影響。如果樣本多,就說明比較可信,不需要很大的修正,是以置信區間會比較窄,下限值會比較大;如果樣本少,就說明不一定可信,必須進行較大的修正,是以置信區間會比較寬,下限值會比較小。

  二項分布的置信區間有多種計算公式,最常見的是"正态區間"(Normal approximation interval),教科書裡幾乎都是這種方法。但是,它隻适用于樣本較多的情況(np > 5 且 n (1 − p) > 5),對于小樣本,它的準确性很差。

  1927年,美國數學家 Edwin Bidwell Wilson 提出了一個修正公式,被稱為"威爾遜區間",很好地解決了小樣本的準确性問題。

投票排名算法

  在上面的公式中,

投票排名算法

表示樣本的"贊成票比例",n表示樣本的大小,

投票排名算法

表示對應某個置信水準的z統計量,這是一個常數,可以通過查表或統計軟體包得到。一般情況下,在 95% 的置信水準下,z統計量的值為1.96。

  威爾遜置信區間的均值為

投票排名算法

  它的下限值為

投票排名算法

  可以看到,當n的值足夠大時,這個下限值會趨向

投票排名算法

。如果n非常小(投票人很少),這個下限值會大大小于

投票排名算法

。實際上,起到了降低"贊成票比例"的作用,使得該項目的得分變小、排名下降。

  Reddit 的評論排名,目前就使用這個算法。

投票排名算法

  [參考文獻]

  * How Not To Sort By Average Rating

  基于使用者投票的排名算法(六):貝葉斯平均

  上一篇介紹了"威爾遜區間",它解決了投票人數過少、導緻結果不可信的問題。

  舉例來說,如果隻有 2 個人投票,"威爾遜區間"的下限值會将贊成票的比例大幅拉低。這樣做固然保證了排名的可信性,但也帶來了另一個問題:排行榜前列總是那些票數最多的項目,新項目或者冷門的項目,很難有出頭機會,排名可能會長期靠後。

  以 IMDB 為例,它是世界最大的電影資料庫,觀衆可以對每部電影投票,最低為 1 分,最高為 10 分。

投票排名算法

  系統根據投票結果,計算出每部電影的平均得分。然後,再根據平均得分,排出最受歡迎的前 250 名的電影。

投票排名算法

  這裡就有一個問題:熱門電影與冷門電影的平均得分,是否真的可比?舉例來說,一部好萊塢大片有 10000 個觀衆投票,一部小成本的文藝片隻有 100 個觀衆投票。這兩者的投票結果,怎麼比較?如果使用"威爾遜區間",後者的得分将被大幅拉低,這樣處理是否公平,能不能反映它們真正的品質?

  一個合理的思路是,如果要比較兩部電影的好壞,至少應該請同樣多的觀衆觀看和評分。既然文藝片的觀衆人數偏少,那麼應該設法為它增加一些觀衆。

  在排名頁面的底部,IMDB 給出了它的計算方法。

投票排名算法
  •  WR, 權重得分(weighted rating)。
  •  R,該電影的使用者投票的平均得分(Rating)。
  •  v,該電影的投票人數(votes)。
  •  m,排名前 250 名的電影的最低投票數(現在為 3000)。
  •  C, 所有電影的平均得分(現在為6.9)。

  仔細研究這個公式,你會發現,IMDB 為每部電影增加了 3000 張選票,并且這些選票的評分都為6.9。這樣做的原因是,假設所有電影都至少有 3000 張選票,那麼就都具備了進入前 250 名的評選條件;然後假設這 3000 張選票的評分是所有電影的平均得分(即假設這部電影具有平均水準);最後,用現有的觀衆投票進行修正,長期來看,v/(v+m)這部分的權重将越來越大,得分将慢慢接近真實情況。

  這樣做拉近了不同電影之間投票人數的差異,使得投票人數較少的電影也有可能排名前列。

  把這個公式寫成更一般的形式:

投票排名算法
  •  C,投票人數擴充的規模,是一個自行設定的常數,與整個網站的總體使用者人數有關,可以等于每個項目的平均投票數。
  •  n,該項目的現有投票人數。
  •  x,該項目的每張選票的值。
  • m,總體平均分,即整個網站所有選票的算術平均值。

  這種算法被稱為"貝葉斯平均"(Bayesian average)。因為某種程度上,它借鑒了"貝葉斯推斷"(Bayesian inference)的思想:既然不知道投票結果,那就先估計一個值,然後不斷用新的資訊修正,使得它越來越接近正确的值。

  在這個公式中,m(總體平均分)是"先驗機率",每一次新的投票都是一個調整因子,使總體平均分不斷向該項目的真實投票結果靠近。投票人數越多,該項目的"貝葉斯平均"就越接近算術平均,對排名的影響就越小。

  是以,這種方法可以給一些投票人數較少的項目,以相對公平的排名。

  "貝葉斯平均"也有缺點,主要問題是它假設使用者的投票是正态分布。比如,電影A有 10 個觀衆評分,5個為五星,5個為一星;電影B也有 10 個觀衆評分,都給了三星。這兩部電影的平均得分(無論是算術平均,還是貝葉斯平均)都是三星,但是電影A可能比電影B更值得看。

  解決這個問題的思路是,假定每個使用者的投票都是獨立事件,每次投票隻有n個選項可以選擇,那麼這就服從"多項分布"(Multinomial distribution),就可以結合貝葉斯定理,計算該分布的期望值。由于這涉及複雜的統計學知識,這裡就不深入了,感興趣的朋友可以繼續閱讀 William Morgan 的How to rank products based on user input。

繼續閱讀