1. 程式人生 > >PersonalRank-基於圖的推薦演算法

PersonalRank-基於圖的推薦演算法

演算法介紹

在推薦系統中,使用者行為資料可以表示成圖的形式,具體來說是二部圖。使用者的行為資料集由一個個(u,i)二元組組成,表示為使用者u對物品i產生過行為。本文中我們認為使用者對他產生過行為的物品的興趣度是一樣的,也就是我們只考慮“感興趣”OR“不感興趣”。假設有下圖所示的行為資料集。


其中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演算法,該演算法能夠為使用者個性化的對所有物品進行排序。它的迭代公式如下:


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

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

在執行演算法之前,我們需要初始化每個節點的初始概率值。如果我們對使用者u進行推薦,則令u對應的節點的初始訪問概率為1,其他節點的初始訪問概率為0,然後再使用迭代公式計算。而對於pageRank來說,由於每個節點的初始訪問概率相同,所以所有節點的初始訪問概率都是1/N (N是節點總數)。

圖中頂點的相關度主要取決與以下因素: 
1)兩個頂點之間路徑數 
2)兩個頂點之間路徑長度 
3)兩個頂點之間路徑經過的頂點 
而相關性高的頂點一般有如下特性: 
1)兩個頂點有很多路徑相連 
2)連線兩個頂點之間的路徑長度比較短 
3)連線兩個頂點之間的路徑不會經過出度較大的頂點

程式碼示例

#coding=utf-8

def PersonalRank(G,alpha,root,max_depth):
    rank=dict()
    rank={x:0 for x in G.keys()}
    rank[root]=1
    for k in range(max_depth):
        tmp={x:0 for x in G.keys()}
        #取出節點i和他的出邊尾節點集合ri
        for i,ri in G.items():
            #取節點i的出邊的尾節點j以及邊E(i,j)的權重wij,邊的權重都為1,歸一化後就是1/len(ri)
            for j,wij in ri.items():
                tmp[j]+=alpha*rank[i]/(1.0*len(ri))
        tmp[root]+=(1-alpha)
        rank=tmp
    lst=sorted(rank.items(),key=lambda x:x[1],reverse=True)
    for ele in lst:
        print ("%s:%.3f, \t" %(ele[0],ele[1]))
    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}