推薦系統--基於圖的推薦算法

        基於圖的模型(graph−basedmodel )是推薦系統中的重要內容。在研究基於圖的模型之前,首先需要將用戶行爲數據表示成圖的形式。這裏我們將用戶行爲數據用二分圖表示,例如用戶數據是由一系列的二元組(也可以使用列表)組成,其中每個元組 (u,i) 表示用戶 u 對物品 i 產生過行爲。下圖爲 A , B , C 用戶感興趣的音樂:

         

產生的二分圖模型如下:

     

將用戶行爲表示爲二分圖模型後,下面的任務就是在二分圖上給用戶進行個性化推薦。如果將個性化推薦算法放到二分圖模型上,那麼給用戶 u 推薦物品的任務就可以轉化爲度量用戶頂點 vu 和與 vu 沒有邊直接相連的物品節點在圖上的相關性,相關性越高的物品在推薦列表中的權重就越高。
  度量圖中兩個頂點之間相關性的方法很多,但一般來說圖中頂點的相關性主要取決於下面3個因素:
    1. 兩個頂點之間的路徑數;
    2. 兩個頂點之間路徑的長度;
    3. 兩個頂點之間的路徑經過的頂點。
  而相關性高的一對頂點一般具有如下特徵:
    1. 兩個頂點之間有很多路徑相連;
    2. 連接兩個頂點之間的路徑長度都比較短;
    3. 連接兩個頂點之間的路徑不會經過出度比較大的頂點。

      我們可以舉個例子來說明,如上圖,用戶 A 沒有對《故鄉的原風景》《偷功》有直接表達喜好,但是可以通過 {A,英雄的黎明,B,故鄉的原風景} , {A,最後的莫西幹人,B,故鄉的原風景}  兩條路徑爲 3 的路徑對《故鄉的原風景》產生聯繫,同樣也可以通過 {A,最後的莫西幹人,C,偷功} , {A,最後的莫西幹人,B,偷功}兩條路徑爲 3 的路徑對《偷功》產生聯繫。那麼,用戶 A 與《偷功》之間的相關性要高於用戶 A 與《故鄉的原風景》,因而《偷功》在用戶 A  的推薦列表中應該排在《故鄉的原風景》之前。而 {A,最後的莫西幹人,C,偷功} 經過點的出度爲 {2,3,2,2} ,{A,最後的莫西幹人,B,偷功} 經過點的出度爲 {2,3,4,2},所以 {A,最後的莫西幹人,B,偷功} 對 {A−>偷功} 的貢獻要大於 {A,最後的莫西幹人,C,偷功} 。

  基於上面的例子,產生了基於隨機遊走的 PersonalRank 算法。假設要給用戶 u 進行個性化推薦,可以從用戶 u 對應的節點vu 開始在用戶物品二分圖上進行隨機遊走。遊走到任何一個節點時,首先按照概率 α 決定是繼續遊走,還是停止這次遊走並從 vu 節點開始重新遊走。如果決定繼續遊走,那麼就從當前節點指向的節點中按照均勻分佈隨機選擇一個節點作爲遊走下次經過的節點。這樣,經過很多次隨機遊走後,每個物品節點被訪問到的概率會收斂到一個數。最終的推薦列表中物品的權重就是物品節點的訪問概率

實例分析

    數據準備
         實際使用的數據肯定不像這樣工整,具體數據具體分析 

	def load_data(file_path):
	    records = []
	    f = open(file_path, "r", encoding="utf-8")
	    for line in f:
	        info = line.strip().split("\t")
	        records.append(info)
	    
	    return records

# 	數據集:  [['A', '英雄的黎明'], ['A', '最後的莫西幹人'], ['B', '英雄的黎明'], ['B', '故鄉的原風景'], ['B', '最後的莫西幹人'], ['B', '偷功'], ['C', '最後的莫西幹人'], ['C', '偷功']]

    數據處理
    有了數據集,我們就需要找出頂點及遊走路徑,我們這裏需要獲取用戶頂點,歌曲頂點

	def calc_user_item(records):  # 建立物品-用戶的倒排列表
	    user_item = dict()
	    item_user = dict()
	    
	    for user, item in records:
	        user_item.setdefault(user, dict())
	        user_item[user].setdefault(item, 0)
	        user_item[user][item] = 1   # 用戶頂點
	        
	        item_user.setdefault(item, dict())
	        item_user[item].setdefault(user, 0)
	        item_user[item][user] = 1   # 物品頂點  
	        
	    print("用戶頂點: ", user_item)
   	    print("物品頂點: ", item_user)
   	
	    return user_item, item_user

"""
	用戶頂點:  {'A': {'英雄的黎明': 1, '最後的莫西幹人': 1}, 'B': {'英雄的黎明': 1, '故鄉的原風景': 1, '最後的莫西幹人': 1, '偷功': 1}, 'C': {'最後的莫西幹人': 1, '偷功': 1}}
	物品頂點:  {'英雄的黎明': {'A': 1, 'B': 1}, '最後的莫西幹人': {'A': 1, 'B': 1, 'C': 1}, '故鄉的原風景': {'B': 1}, '偷功': {'B': 1, 'C': 1}}

"""

 

有了頂點,但是我們需要將其整理到一個頂點數據集中:

	def initGraph(user_item, item_user):
	    G= dict()
	    
	    G= dict(user_item, **item_user)
	
		print("G: ", G)
		
	    return G

"""
G:  {'A': {'英雄的黎明': 1, '最後的莫西幹人': 1}, 'B': {'英雄的黎明': 1, '故鄉的原風景': 1, '最後的莫西幹人': 1, '偷功': 1}, 'C': {'最後的莫西幹人': 1, '偷功': 1}, '英雄的黎明': {'A': 1, 'B': 1}, '最後的莫西幹人': {'A': 1, 'B': 1, 'C': 1}, '故鄉的原風景': {'B': 1}, '偷功': {'B': 1, 'C': 1}}
"""

算法實現 

       獲取頂點集合後,我們數據處理算是完成,接下來就需要使用PersonalRank 來計算每個節點的訪問概率,這裏我設置 alpha 爲 0.85 ,初始節點爲 A ,最大步數爲 100 :

推薦

      有了每個頂點的訪問概率,即用戶和歌曲的訪問概率,我們就可以開始給用戶推薦了,我們這裏就給用戶 A 推薦歌曲,因爲頂點有用戶頂點和歌曲頂點,所以我們去掉用戶頂點和推薦用戶本身已經感興趣的頂點:

     

	def Recommend(user, rank, user_item):
	    rec = []
	    for music in rank:
	        data = music[0]
	        rec.append(data)
	        
	    for u, v in user_item.items():			# 移除用戶頂點
	        for i in rec:
	            if i == u:
	                rec.remove(i)
	
	    for u, v in user_item[user].items():	# 移除用戶已經標記過的歌曲
	        for i in rec:
	            if i == u:
	                rec.remove(i)
	                    
	    return rec   

# 	推薦物品:  ['偷功', '故鄉的原風景']

算法改進

       雖然 PersonalRank 算法可以通過隨機遊走進行比較好的理論解釋,但該算法在時間複雜度上有明顯的缺點。因爲在爲每個用戶進行推薦時,都需要在整個用戶物品二分圖上進行迭代,直到整個圖上的每個頂點的 PR 值收斂。這一過程的時間複雜度非常高,不僅無法在線提供實時推薦,甚至離線生成推薦結果也很耗時。
  爲了解決PersonalRank每次都需要在全圖迭代並因此造成時間複雜度很高的問題,這裏給出兩種解決方案。第一種很容易想到,就是減少迭代次數,在收斂之前就停止。這樣會影響最終的精度,但一般來說影響不會特別大。另一種方法就是從矩陣論出發,重新設計算法。對矩陣運算比較熟悉的讀者可以輕鬆將 PersonalRank 轉化爲矩陣的形式。令 M 爲用戶物品二分圖的轉移概率矩陣,即:

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章