用PersonalRank實現基於圖的推薦算法

今天咱們講一個下怎麼使用隨機遊走算法PersonalRank實現基於圖的推薦。

在推薦系統中,用戶行爲數據能夠表示成圖的形式,具體來講是二部圖。用戶的行爲數據集由一個個(u,i)二元組組成,表示爲用戶u對物品i產生過行爲。本文中咱們認爲用戶對他產生過行爲的物品的興趣度是同樣的,也就是咱們只考慮「感興趣」OR「不感興趣」。假設有下圖所示的行爲數據集。
python


其中users集U={A, B, C},items集I = {a,b,c,d}。則用戶物品的二部圖以下所示:
算法

咱們用G(V, E)來表示這個圖,則頂點集V=U∪I,圖中的邊則是由數據集中的二元組肯定。二元組(u, i)表示u對i有過行爲,則在圖中表現爲有邊相連,即e(u,i)。【注意】,本文中咱們不考慮各邊的權重(即u對i的興趣度),權重都默認爲1。感興趣即有邊相連,不感興趣則沒有邊相連。

那有了二部圖以後咱們要對u進行推薦物品,就轉化爲計算用戶頂點u和與全部物品頂點之間的相關性,而後取與做者沒有直接邊相連的物品,按照相關性的高低生成推薦列表。說白了,這是一個圖上的排名問題,咱們最容易想到的就是Google的pageRank算法。

PageRank是Larry Page 和 Sergey Brin設計的用來衡量特定網頁相對於搜索引擎中其餘網頁的重要性的算法,其計算結果做爲google搜索結果中網頁排名的重要指標。網頁之間經過超連接相互鏈接,互聯網上不可勝數的網頁就構成了一張超大的圖。PageRank假設用戶從全部網頁中隨機選擇一個網頁進行瀏覽,而後經過超連接在網頁直接不斷跳轉。到達每一個網頁後,用戶有兩種選擇:到此結束或者繼續選擇一個連接瀏覽。算法令用戶繼續瀏覽的機率爲d,用戶以相等的機率在當前頁面的全部超連接中隨機選擇一個繼續瀏覽。這是一個隨機遊走的過程。當通過不少次這樣的遊走以後,每一個網頁被訪問用戶訪問到的機率就會收斂到一個穩定值。這個機率就是網頁的重要性指標,被用於網頁排名。算法迭代關係式以下所示:
搜索引擎


上式中PR(i)是網頁i的訪問機率(也就是重要度),d是用戶繼續訪問網頁的機率,N是網頁總數。in(i)表示指向網頁i的網頁集合,out(j)表示網頁j指向的網頁集合。

用user節點和item節點替換上面的網頁節點就能夠計算出每一個user,每一個item在全局的重要性,給出全局的排名,顯然這並非咱們想要的,咱們須要計算的是物品節點相對於某一個用戶節點u的相關性。怎麼作呢?Standford的Haveliwala於2002年在他《Topic-sensitive pagerank》一文中提出了PersonalRank算法,該算法可以爲用戶個性化的對全部物品進行排序。它的迭代公式以下:
google


咱們發現PersonalRank跟PageRank的區別只是用替換了1/N,也就是說從不一樣點開始的機率不一樣。u表示咱們推薦的目標用戶,這樣使用上式計算的就是全部頂點相對於頂點u的相關度。

與PageRank隨機選擇一個點開始遊走(也就是說從每一個點開始的機率都是相同的)不一樣,若是咱們要計算全部節點相對於用戶u的相關度,則PersonalRank從用戶u對應的節點開始遊走,每到一個節點都以1-d的機率中止遊走並從u從新開始,或者以d的機率繼續遊走,從當前節點指向的節點中按照均勻分佈隨機選擇一個節點往下游走。這樣通過不少輪遊走以後,每一個頂點被訪問到的機率也會收斂趨於穩定,這個時候咱們就能夠用機率來進行排名了。

在執行算法以前,咱們須要初始化每一個節點的初始機率值。若是咱們對用戶u進行推薦,則令u對應的節點的初始訪問機率爲1,其餘節點的初始訪問機率爲0,而後再使用迭代公式計算。而對於pageRank來講,因爲每一個節點的初始訪問機率相同,因此全部節點的初始訪問機率都是1/N (N是節點總數)。

spa

我本身用Python實現了一下PersonalRank:(可執行,感興趣的童鞋可經過附件下載源碼文件,如有錯誤懇請指正^_^).net

 

#coding=utf-8
__author__ = 'Harry Huang'


def PersonalRank(G, alpha, root, max_step):
    rank = dict()
    rank = {x:0 for x in G.keys()}
    rank[root] = 1
    #開始迭代
    for k in range(max_step):
        tmp = {x:0 for x in G.keys()}
        #取節點i和它的出邊尾節點集合ri
        for i, ri in G.items():
            #取節點i的出邊的尾節點j以及邊E(i,j)的權重wij, 邊的權重都爲1,在這不起實際做用
            for j, wij in ri.items():
                #i是j的其中一條入邊的首節點,所以須要遍歷圖找到j的入邊的首節點,
                #這個遍歷過程就是此處的2層for循環,一次遍歷就是一次遊走
                tmp[j] += alpha * rank[i] / (1.0 * len(ri))
        #咱們每次遊走都是從root節點出發,所以root節點的權重須要加上(1 - alpha)
        #在《推薦系統實踐》上,做者把這一句放在for j, wij in ri.items()這個循環下,我認爲是有問題。
        tmp[root] += (1 - alpha)
        rank = tmp

        #輸出每次迭代後各個節點的權重
        print 'iter: ' + str(k) + "\t",
        for key, value in rank.items():
            print "%s:%.3f, \t"%(key, value),
        print

    return rank


if __name__ == '__main__' :
    G = {'A' : {'a' : 1, 'c' : 1},
         'B' : {'a' : 1, 'b' : 1, 'c':1, 'd':1},
         'C' : {'c' : 1, 'd' : 1},
         'a' : {'A' : 1, 'B' : 1},
         'b' : {'B' : 1},
         'c' : {'A' : 1, 'B' : 1, 'C':1},
         'd' : {'B' : 1, 'C' : 1}}

    PersonalRank(G, 0.85, 'A', 100)


數據集使用的本文一開始講的那個,最終各個節點的機率結果以下所示:設計

 

 


上面的代碼是對本文一開始描述的數據集中的用戶A進行推薦。上圖給出了不一樣迭代次數後各節點的機率值。發現46次迭代以後,全部節點的機率值全都收斂。在這個例子中,A用戶沒有產生過行爲的物品是b和d,相對於A的訪問機率分別是0.039,0.076,d的訪問機率顯然要大於b,全部給A用戶的推薦列表爲{d,b}。code

 


附件:PersonalRank.py排序

相關文章
相關標籤/搜索