推薦系統之基於圖的推薦:基於隨機遊走的PersonalRank演算法
一 基本概念
基於圖的模型是推薦系統中相當重要的一種方法,以下內容的基本思想是將使用者行為資料表示為一系列的二元組,每一個二元組(u,i)代表使用者u對物品i產生過行為,這樣便可以將這個資料集表示為一個二分圖。
假設我們有以下的資料集,只考慮使用者喜不喜歡該物品而不考慮使用者對物品的喜歡程度,
其中使用者user=[A,B,C],物品item=[a,b,c],使用者和物品有以下的關係:
上述便是一個典型的二分圖,我們用G(V,E)來表示,其中V為使用者user和物品item組成的頂點集即[A,B,C,a,b,c],而E則代表每一個二元組(u,i)之間對應的邊e(u,i),我們這裡不考慮使用者對物品的喜愛程度,即預設喜愛則e=1,不喜愛則e=0。
那麼我們如何使用上述的二分圖模型進行物品的推薦呢?根據使用者與物品的相關性,對於相關性高的頂點有如下的定義:
(1)兩個頂點之間有很多路徑相連
(2)連線兩個頂點之間的路徑長度都比較短
(3)連線兩個頂點之間的路徑不會經過度比較大的頂點
上面有一個概念需要理解,度,頂點的度是指和該頂點相關聯的邊數。
基於上述的定義,我們這裡使用基於隨機遊走的PersonalRank演算法來計算,那麼這個演算法是什麼意思呢?
在解釋之前,我們先理解一下另一個演算法,pageRank演算法,這個一個用來衡量搜尋引擎中特定網頁相對於其他網頁重要性的演算法,使用這個演算法作為搜尋結果網頁排名相當重要的一部分。
它的基本思想是,假設網頁之前通過超連結相互連線,網際網路上的所有網頁便構成了一張圖。使用者隨機的開啟一個網頁,並通過超連結跳轉到另一個網頁。每當使用者到達一個網頁,他都有兩種選擇,停留在當前網頁或者通過繼續訪問其他網頁。如果使用者繼續訪問網頁的概率為d,那麼使用者停留在當前網頁的概率便是1-d。如果使用者繼續訪問其他網頁,則會以均勻分佈的方式隨機訪問當前網頁指向的另一網頁,這是一個隨機遊走的過程。當用戶多次訪問網頁後,每一個網頁被訪問到的概率便會收斂到某個值,而計算出來的結果便可以用於網頁排名,我們用以下的公式來表示:
其中PR(i)是網頁i被訪問到的概率,d代表使用者繼續訪問網頁的概率,N為所有網頁的數量,in(i)代表所有指向網頁i的網頁集合,out(j)代表網頁j指向的其他網頁集合。
接下來我們分析一下這個公式,網頁i被訪問到的概率由兩部分組成:
第一部分是網頁i作為起點,第一個被使用者點選後停留在當前頁面的概率,即:
第二部分是使用者點選其他網頁後(無論網頁i是不是起點),再次跳轉回到網頁i的概率:
這兩部分的和便是網頁i被點選到的概率。
介紹完pageRank演算法後,我們再來看看PersonalRank演算法,這個演算法是基於pageRank演算法進行了一些變化,在pageRank演算法中,計算出來的是每一個頂點相對其他頂點的相關性,代入到我們的使用者物品二分圖中,這顯然不是我們想要的,我們需要的是所有物品相對於特定某個使用者的相關性,有公式如下:
對比pageRank,不同點只在於r的值不同,u代表根節點,即我們的目標使用者節點,意思便是我們每次都是從目標使用者節點出發,進行隨機遊走,而不同於pageRank的起點是隨機從所有網頁中進行選擇,personalRank演算法得出的結果便是所有頂點相對於目標使用者結點的相關性。
二 實戰
資料集:採用movielens的1M資料集
1 獲取ratings資料集的資料
def getResource(csvpath):
'''
獲取原始資料
:param csvpath: csv路徑
:return: frame
'''
frame = pd.read_csv(csvpath)
return frame
2 整理使用者及物品二分圖,不考慮權重,只要使用者對電影評過分便認為喜歡,e=1
(1)使用者頂點二元組
def getUserGraph(frame, userID=1):
'''
獲取目標使用者二分圖, 不計權重
:param frame: ratings資料
:param userID: 目標ID
:return: 二分圖字典
'''
print(userID)
itemList = list(set(frame[frame['UserID']==userID]['MovieID']))
graphDict = {'i'+str(item): 1 for item in itemList}
return graphDict
(2)物品頂點二元組def getItemGraph(frame, itemID=1):
'''
獲取目標物品二分圖, 不計權重
:param frame: ratings資料
:param userID: 目標ID
:return: 二分圖字典
'''
print(itemID)
userList = list(set(frame[frame['MovieID']==itemID]['UserID']))
graphDict = {'u'+str(user): 1 for user in userList}
return graphDict
(3)整理成規範二分圖Gdef initGraph(frame):
'''
初始化二分圖
:param frame: ratings資料集
:return: 二分圖
'''
userList = list(set(frame['UserID']))
itemList = list(set(frame['MovieID']))
G = {'u'+str(user): getUserGraph(frame, user) for user in userList}
for item in itemList: G['i'+str(item)] = getItemGraph(frame, item)
return G
3 利用PersonalRank演算法進行計算def personalRank(G, alpha, userID, iterCount=20):
'''
隨機遊走迭代
:param G: 二分圖
:param alpha: 隨機遊走的概率
:param userID: 目標使用者
:param iterCount: 迭代次數
:return: series
'''
rank = {g: 0 for g in G.keys()}
rank['u'+str(userID)] = 1 #根節點為起點選擇概率為1,其他頂點為0
for k in range(iterCount):
tmp = {g: 0 for g in G.keys()}
for i, ri in G.items(): #遍歷每一個頂點
for j, wij in ri.items(): #遍歷每個頂點連線的頂點
tmp[j] += alpha * rank[i] / len(ri)
tmp['u' + str(userID)] += 1 - alpha #根頂點r=1,加上1-alpha
rank = tmp
series = pd.Series(list(rank.values()), index=list(rank.keys()))
series = series.sort_values(ascending=False)
return series #返回排序後的series
4 推薦和目標使用者沒有直接相連而且是物品的頂點def recommend(frame, series, userID, TopN=10):
'''
推薦TopN個使用者沒有評分的物品
:param frame: ratings資料
:param series: series
:param userID: 目標使用者
:param TopN: TopN
:return: 推薦物品
'''
itemList = ['i'+str(i) for i in list(set(frame[frame['UserID']==userID]['MovieID']))]
recommendList = [{u: series[u]} for u in list(series.index) if u not in itemList and 'u' not in u]
return recommendList[:TopN]
5 迭代結果如下,可以看出經過15次左右的迭代,結果就基本趨於穩定了
[{'i3571': 0.0}, {'i830': 0.0}, {'i2942': 0.0}, {'i1725': 0.0}, {'i2445': 0.0}, {'i3822': 0.0}, {'i102': 0.0}, {'i2180': 0.0}, {'i264': 0.0}, {'i2615': 0.0}]
[{'i3867': 0.0}, {'i3922': 0.0}, {'i2061': 0.0}, {'i1020': 0.0}, {'i1395': 0.0}, {'i1877': 0.0}, {'i135': 0.0}, {'i2341': 0.0}, {'i1115': 0.0}, {'i3308': 0.0}]
[{'i2858': 0.00083046633303730691}, {'i1196': 0.00071671776671615873}, {'i1210': 0.00067806429079155233}, {'i593': 0.0006350175259251657}, {'i2396': 0.00061076227633024617}, {'i1198': 0.00060722720491067177}, {'i480': 0.00059923101084963497}, {'i2571': 0.00058276347994485863}, {'i318': 0.00058003203495347985}, {'i589': 0.00057600264742804167}]
[{'i2858': 0.0003321865332149249}, {'i1196': 0.00028668710668646325}, {'i1210': 0.00027122571631662092}, {'i593': 0.00025400701037006602}, {'i2396': 0.00024430491053209827}, {'i1198': 0.00024289088196426857}, {'i480': 0.00023969240433985376}, {'i2571': 0.00023310539197794378}, {'i318': 0.00023201281398139187}, {'i589': 0.00023040105897121686}]
[{'i2858': 0.00060349653055173386}, {'i1196': 0.00052196086934052385}, {'i1210': 0.00049776616762635677}, {'i593': 0.00045784751864878575}, {'i480': 0.00044765392929357667}, {'i1198': 0.0004410926232734298}, {'i589': 0.00043557149444675361}, {'i2571': 0.00043437223855112258}, {'i2396': 0.00043412826788239101}, {'i110': 0.00041807554733736419}]
[{'i2858': 0.00044071053214964735}, {'i1196': 0.00038079661174808798}, {'i1210': 0.00036184189684051532}, {'i593': 0.00033554321368155346}, {'i480': 0.00032287701432134349}, {'i1198': 0.00032217157848793285}, {'i2396': 0.00032023425347221535}, {'i2571': 0.00031361213060721635}, {'i589': 0.00031246923316143255}, {'i110': 0.00030344760820981306}]
[{'i2858': 0.00053694131852169088}, {'i1196': 0.00046458802316738787}, {'i1210': 0.00044259993946193073}, {'i593': 0.00040788262090828896}, {'i480': 0.00039758669454385479}, {'i1198': 0.00039265396543004474}, {'i2396': 0.00038687577725018962}, {'i589': 0.00038649101791954219}, {'i2571': 0.00038600611873183192}, {'i110': 0.00037182824192947688}]
[{'i2858': 0.0004792028466984644}, {'i1196': 0.0004143131763158079}, {'i1210': 0.00039414511388908164}, {'i593': 0.00036447897657224798}, {'i480': 0.00035276088641034826}, {'i1198': 0.00035036453326477753}, {'i2396': 0.00034689086298340466}, {'i2571': 0.0003425697258570615}, {'i589': 0.00034207794706467542}, {'i110': 0.00033079986169767844}]
[{'i2858': 0.00051376165005418437}, {'i1196': 0.00044444351663661672}, {'i1210': 0.00042319333008984872}, {'i593': 0.0003904669163388696}, {'i480': 0.00037967291853824856}, {'i1198': 0.00037570074620764637}, {'i2396': 0.00037078219926530098}, {'i589': 0.00036875659476582576}, {'i2571': 0.00036865439148347004}, {'i110': 0.00035541212342766543}]
[{'i2858': 0.00049302636804075199}, {'i1196': 0.00042636531244413177}, {'i1210': 0.00040576440036938898}, {'i593': 0.00037487415247889689}, {'i480': 0.00036352569926150825}, {'i1198': 0.00036049901844192547}, {'i2396': 0.00035644739749616432}, {'i2571': 0.00035300359210762509}, {'i589': 0.00035274940614513588}, {'i110': 0.00034064476638967305}]
[{'i2858': 0.00050546190424658681}, {'i1196': 0.00043721087645157386}, {'i1210': 0.0004162214547918957}, {'i593': 0.00038422614151956412}, {'i480': 0.00037321662425625601}, {'i1198': 0.0003696182084851502}, {'i2396': 0.00036504184399821651}, {'i2571': 0.00036239701620892931}, {'i589': 0.00036235686867035807}, {'i110': 0.0003495057342957748}]
[{'i2858': 0.00049800058252308604}, {'i1196': 0.00043070353804710909}, {'i1210': 0.00040994722213839196}, {'i593': 0.00037861494809516286}, {'i480': 0.0003674020692594065}, {'i1198': 0.00036414669445921434}, {'i2396': 0.00035988517609698454}, {'i2571': 0.00035676096174814604}, {'i589': 0.00035659239115522413}, {'i110': 0.00034418915355211341}]
[{'i2858': 0.00050247696507266541}, {'i1196': 0.00043460788156115355}, {'i1210': 0.00041371180788641855}, {'i593': 0.00038198137743991227}, {'i480': 0.0003708910680577479}, {'i1198': 0.00036742949422326811}, {'i2396': 0.00036297872662805993}, {'i2571': 0.00036014288057225089}, {'i589': 0.0003600513587190007}, {'i110': 0.00034737918324631687}]
[{'i2858': 0.00049979113554291703}, {'i1196': 0.00043226527545272642}, {'i1210': 0.00041145305643760225}, {'i593': 0.00037996151983306353}, {'i480': 0.00036879766877874302}, {'i1198': 0.00036545981436483658}, {'i2396': 0.00036112259630941473}, {'i2571': 0.00035811372927778781}, {'i589': 0.0003579759781807344}, {'i110': 0.00034546516542979523}]
[{'i2858': 0.00050140260178169259}, {'i1196': 0.0004336708357653612}, {'i1210': 0.00041280831402142163}, {'i593': 0.00038117341069881573}, {'i480': 0.00037005373269728003}, {'i1198': 0.00036664161465205691}, {'i2396': 0.00036223624105388892}, {'i2571': 0.00035933124573009519}, {'i589': 0.00035922123055598534}, {'i110': 0.00034661358443908758}]
[{'i2858': 0.00050043572203842799}, {'i1196': 0.00043282749957778078}, {'i1210': 0.00041199515947113016}, {'i593': 0.00038044627617936426}, {'i480': 0.0003693000943461583}, {'i1198': 0.0003659325344797252}, {'i2396': 0.00036156805420720528}, {'i2571': 0.0003586007358587113}, {'i589': 0.00035847407913083547}, {'i110': 0.00034592453303351333}]
[{'i2858': 0.00050101584739736013}, {'i1196': 0.0004333335010326456}, {'i1210': 0.00041248305287340628}, {'i593': 0.00038088255489434063}, {'i480': 0.00036975227950034447}, {'i1198': 0.00036635798197326311}, {'i2396': 0.00036196896372303849}, {'i2571': 0.00035903904402319342}, {'i589': 0.00035892237202062873}, {'i110': 0.00034633796465562979}]
[{'i2858': 0.00050066777218199944}, {'i1196': 0.00043302990015972582}, {'i1210': 0.0004121903168320396}, {'i593': 0.00038062078766535462}, {'i480': 0.00036948096840783211}, {'i1198': 0.00036610271347714009}, {'i2396': 0.00036172841801353889}, {'i2571': 0.00035877605912450363}, {'i589': 0.00035865339628675218}, {'i110': 0.00034608990568235995}]
[{'i2858': 0.00050087661711107777}, {'i1196': 0.00043321206065948117}, {'i1210': 0.00041236595851715116}, {'i593': 0.00038077784783332579}, {'i480': 0.00036964375524926104}, {'i1198': 0.00036625587452236827}, {'i2396': 0.00036187274523237836}, {'i2571': 0.00035893385025763939}, {'i589': 0.00035881478189912932}, {'i110': 0.00034623874113694431}]
[{'i2858': 0.00050075131015363142}, {'i1196': 0.00043310276435962864}, {'i1210': 0.00041226057350608519}, {'i593': 0.00038068361173254341}, {'i480': 0.00036954608314440407}, {'i1198': 0.00036616397789523172}, {'i2396': 0.00036178614890107399}, {'i2571': 0.00035883917557775791}, {'i589': 0.00035871795053170325}, {'i110': 0.0003461494398641943}]
以上就是基於隨機遊走的PersonalRank演算法的介紹了,詳細程式碼可以在我的github上找到: https://github.com/lpty