PageRank算法原理剖析及Spark實現

1. 什麼是PageRank

PageRank對網頁排名的算法,曾是Google發家致富的法寶。PageRank算法計算每一個網頁的PageRank值,然後根據這個值的大小對網頁的重要性進行排序。


2. 簡單PageRank算法

首先,將Web做如下抽象:

  • 將每個網頁抽象成一個節點
  • 如果一個頁面A 有鏈接直接鏈向B ,則存在一條有向邊從AB (多個相同鏈接不重複計算邊)。

因此,整個Web被抽象爲一張有向圖。現在假設世界上只有四張網頁:ABCD ,其抽象結構如下圖:

簡單PageRank計算

顯然這個圖是強連通的(從任一節點出發都可以到達另外任何一個節點)。然後需要用一種合適的數據結構表示頁面間的連接關係。

PageRank算法基本思想描述:被用戶訪問越多的網頁更可能質量越高,而用戶在瀏覽網頁時主要通過超鏈接進行頁面跳轉,因此需要通過分析超鏈接組成的拓撲結構來推算每個網頁被訪問頻率的高低。最簡單的,我們可以假設當一個用戶停留在某頁面時,跳轉到頁面上每個被鏈頁面的概率相同

例如,上圖中A 頁面鏈向BCD ,所以一個用戶從A 跳轉到BCD 的概率各爲1/3 。設一共有N個網頁,則可以組織這樣一個N維矩陣,其中ij 列的值表示用戶從頁面j 轉到頁面i 的概率。這樣一個矩陣叫做轉移矩陣(Transition Matrix)。下面是上圖的轉移矩陣M

M=01/31/31/31/201/2000011/21/200(1)

設初始時每個頁面的rank 值爲1/N ,這裏就是1/4 。按AD 順序得到向量v

v=1/41/41/41/4(2)

注意:M 第一行分別是ABCD 轉移到頁面A 的概率,而v 的第一列分別是ABCD 當前的rank ,因此用M 的第一行乘以v 的第一列,所得結果就是頁面A最新rank 的合理估計,同理,Mv 的結果就分別代表ABCDrank 值。

Mv=1/45/245/251/4(3)

然後用M 再乘以這個新的rank 向量,又會產生一個rank 向量。迭代這個過程,可以證明v 最終會收斂,即vMv ,此時計算停止。最終的v 就是各個頁面的pagerank 值。上面的向量經過幾步迭代後,大約收斂在1/4,1/4,1/5,1/4 ,這就是ABCD 最後的pagerank


3. 終止點問題

上面過程要滿足收斂性,需要具備一個條件:圖是強連通的,即從任意網頁可以到達其他任意網頁。

互聯網中存在網頁不滿足強連通的特性,因爲有一些網頁不指向任何網頁,按照上面公式迭代計算下去,導致前面累計得到的轉移概率被清零,最終得到的概率分佈向量所有元素幾乎都爲0

假設把上面圖中CD 的鏈接丟掉,C 變成了一個終止點,得到下面這個圖:

終止點問題

轉移矩陣M 爲:

M=01/31/31/31/201/2000001/21/200(4)

不斷迭代,最終得到所有元素都爲0。

v=1/41/41/41/41/45/245/241/127/481/89/481/125/4813/1441/97/1445/7213/2881/97/144...0000(5)

4. 陷阱問題

陷阱問題:是指有些網頁不存在指向其他網頁的鏈接,但存在指向自己的鏈接。比如下面這個圖:

陷阱問題

這種情況下,PageRank算法不斷迭代會導致概率分佈值全部轉移到c 網頁上,這使得其他網頁的概率分佈值爲0,從而整個網頁排名就失去了意義。如果按照上面圖則對應的轉移矩陣M 爲: 

M=01/31/31/31/201/2000101/21/200(6)

不斷迭代,最終得到如下結果:

v=1/41/41/41/41/45/2411/241/120010(7)

5. 完整PageRank算法

爲了解決終止點問題和陷阱問題,下面需要對算法進行改進。假設選取下一個跳轉頁面時,既不選當前頁面,也不選當前網頁上的其他鏈接,而是以一定概率跳轉到其他不相關網頁,那麼上面兩個問題就能得到很好的解決,這就是完整PageRank算法思想

假設跳轉到當前頁面(包括當前頁面上的鏈接)的概率爲a (也稱爲基尼係數),那麼跳轉到其他頁面概率爲(1a) ,進一步假設每個頁面被訪問的概率相同都是1/n ,於是原來的迭代公式轉化爲:

v=αMv+(1α)e(8)

假設α 的值爲0.85,e 是網頁數目的倒數,共4個網頁,所以e 等於1/4 。現在計算有陷阱的網頁的概率分佈:

v===αMv+(1α)e0.8501/31/31/31/201/2000101/21/2001/41/41/41/4+0.151/41/41/41/40.250.220.430.11(9)

利用上面公式繼續迭代下去,直到收斂,得到最終rank 值。

6. Spark實現RageRank

這裏簡化初始值爲1.0,α/N 設置爲0.15,迭代次數參考《數學之美》中提到:“一般來講,只要10次左右的迭代基本上就收斂了”,這裏設置爲10次。

    // 生成網頁邊的關係
    val links = sc.parallelize(Array(('A',Array('D')),('B',Array('A')),
       ('C',Array('A','B')),('D',Array('A','C'))),2).map(x => (x._1, x._2)).cache()

    // 初始化rank值,2表示分兩個partition
    var ranks = sc.parallelize(Array(('A',1.0),('B',1.0),('C',1.0),('D',1.0)), 2)

    // 迭代10次
    for ( i <- 1 to 10){
       val contribs = links.join(ranks, 2)
       val flatMapRDD = contribs.flatMap {
           case (url,(links,rank)) => links.map(dest => (dest, rank/links.size))
        }
       val reduceByKeyRDD = flatMapRDD.reduceByKey(_ + _, 2)
       ranks = reduceByKeyRDD.mapValues(0.15 + 0.85 * _)

    }

7. 參考


【完】

發佈了84 篇原創文章 · 獲贊 324 · 訪問量 66萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章