1. 程式人生 > >推薦系統實踐----基於使用者的協同過濾演算法(python程式碼實現書中案例)

推薦系統實踐----基於使用者的協同過濾演算法(python程式碼實現書中案例)

本文參考項亮的《推薦系統實踐》中基於使用者的協同過濾演算法內容。因其中程式碼實現部分只有片段,又因本人初學,對python還不是很精通,難免頭大。故自己實現了其中的程式碼,將整個過程走了一遍。

1. 過程簡述

a. 首先我們因該先找到和目標使用者興趣相似的使用者集合。簡單來說,如果A是目標使用者(待推薦使用者),那麼我們要先找到和A興趣差不多的一群人(例如B,C,D)。我認為B,C,D喜歡的商品,A也很有可能喜歡。(臭味相投嘛)

b. 找到集合中使用者喜歡的,且目標使用者沒有聽說過的物品推薦給他。就是說,根據B,C,D的物品列表,我們形成了一個商品集合。我們在這個集合列表中找到那些使用者A還沒有把玩過的東西,推薦給他。

2. 具體過程

一個關鍵問題就是我們如何計算使用者的興趣相似度?方法有很多,根據書中的描述呢,有jaccard公式,餘弦相似度,另外還有歐式距離,曼哈頓距離等等。最終選擇什麼方法,取決於實際的應用環境。書中實現的是餘弦相似度。(N(u)表示使用者u曾有過正反饋的物品集合,也就是使用者u評論過的那些商品。)


同樣用書中的例子說明該演算法。A買過{a,b,d},B買過{a,c},那麼利用餘弦相似度計算的A和B的興趣相似度為(分子是他們共同買過的東西,分母是分別買的東西的數量):


所有使用者的具體購物情況如下表:


以此類推,A和B,C,D的相似度計算書中有,不贅述。

實際情況是很多使用者沒有買過相同的商品,那麼存在時間複雜度過大的問題。為了解決這個問題,引入倒排表的概念。同樣使用上面的例子。我們先要建立一個物品-使用者的倒排表,接著建立一個4×4的使用者相似度矩陣W。矩陣預設是0陣,那麼如果兩個使用者在同一個商品下,那麼矩陣中他們對應的值就加一。最終我們會得到一個餘弦相似度中分子部分的矩陣。最後我們再除以對應的分母,就可以得到終端使用者的興趣相似度了。如下圖,具體過程請看書。

具體實現如下:(整體結構和書中差不多,某些細節上我按自己的理解寫的。不當之處,還請指正。)

# -*- coding=utf-8 -*-
import math
#例子中的資料相當於是一個使用者字典{A:(a,b,d),B:(a,c),C:(b,e),D:(c,d,e)}
#我們這樣儲存原始輸入資料

dic={'A':('a','b','d'),'B':('a','c'),'C':('b','e'),'D':('c','d','e')}#簡單粗暴,記得加''

#計算使用者興趣相似度
def Usersim(dicc):
	#把使用者-商品字典轉成商品-使用者字典(如圖中箭頭指示那樣)
	item_user=dict()
	for u,items in dicc.items():
		for i in items:#文中的例子是不帶評分的,所以用的是元組而不是巢狀字典。
			if i not in item_user.keys():
				item_user[i]=set()#i鍵所對應的值是一個集合(不重複)。
			item_user[i].add(u)#向集合中新增使用者。

	C=dict()#感覺用陣列更好一些,真實資料集是數字編號,但這裡是字元,這邊還用字典。
	N=dict()
	for item,users in item_user.items():
		for u in users:
			if u not in N.keys():
				N[u]=0   #書中沒有這一步,但是字典沒有初始值不可以直接相加吧
			N[u]+=1 #每個商品下使用者出現一次就加一次,就是計算每個使用者一共購買的商品個數。
			#但是這個值也可以從最開始的使用者表中獲得。
			#比如: for u in dic.keys():
			#             N[u]=len(dic[u])
			for v in users:
				if u==v:
					continue
				if (u,v) not in C.keys():#同上,沒有初始值不能+=
					C[u,v]=0
				C[u,v]+=1  #這裡我不清楚書中是不是用的巢狀字典,感覺有點迷糊。所以我這樣用的字典。
#到這裡倒排陣就建立好了,下面是計算相似度。
	W=dict()
	for co_user,cuv in C.items():
		W[co_user]=cuv / math.sqrt(N[co_user[0]]*N[co_user[1]])
	return W

把W輸出看看:


計算完興趣相似度,我們就要用UserCF演算法做推薦了。我們給目標使用者u推薦K個他可能喜歡的商品。那麼目標使用者u對物品i的興趣程度:

(我直接把書中這部分解釋複製過來了,也比較詳細)


(解釋一下就是:在K個興趣相近的使用者中找對物品i感興趣的幾個人,然後用每個人的使用者相似度乘以對物品的興趣度就得到目標使用者可能對物品的興趣度,最後把他們幾個人加起來。)

具體的程式碼實現如下:

def Recommend(user,dicc,W2,K):
	rvi=1 #這裡都是1,實際中可能每個使用者就不一樣了。就像每個人都喜歡beautiful girl,但有的喜歡可愛的多一些,有的喜歡御姐多一些。
	rank=dict()
	related_user=[]
	interacted_items=dicc[user]
	for co_user,item in W2.items():
		if co_user[0]==user:
			related_user.append((co_user[1],item))#先建立一個和待推薦使用者興趣相關的所有的使用者列表。
	for v,wuv in sorted(related_user,key=itemgetter(1),reverse=True)[0:K]:
	#找到K個相關使用者以及對應興趣相似度,按興趣相似度從大到小排列。itemgetter要導包。
		for i in dicc[v]:
			if i in interacted_items:
				continue #書中少了continue這一步吧?
			if i not in rank.keys():#如果不寫要報錯,是不是有更好的方法?
				rank[i]=0
			rank[i]+=wuv*rvi
	return rank

結果如下:



這樣我們已經得到了一個待推薦字典了。{商品:待推薦使用者對商品的興趣,......}

我用書中的例子資料將整個過程跑了一遍,能夠更好的理解整個過程。如果有朋友要跑真實的MovieLens資料集,我推薦看這個博文(親測有效):

http://blog.csdn.net/ygrx/article/details/15501679

接下來貼上完整程式碼:

# -*- coding=utf-8 -*-
import math
from operator import *
#例子中的資料相當於是一個使用者字典{A:(a,b,d),B:(a,c),C:(b,e),D:(c,d,e)}
#我們這樣儲存原始輸入資料

dic={'A':('a','b','d'),'B':('a','c'),'C':('b','e'),'D':('c','d','e')}#簡單粗暴,記得加''

#計算使用者興趣相似度
def Usersim(dicc):
	#把使用者-商品字典轉成商品-使用者字典(如圖中箭頭指示那樣)
	item_user=dict()
	for u,items in dicc.items():
		for i in items:#文中的例子是不帶評分的,所以用的是元組而不是巢狀字典。
			if i not in item_user.keys():
				item_user[i]=set()#i鍵所對應的值是一個集合(不重複)。
			item_user[i].add(u)#向集合中新增使用者。

	C=dict()#感覺用陣列更好一些,真實資料集是數字編號,但這裡是字元,這邊還用字典。
	N=dict()
	for item,users in item_user.items():
		for u in users:
			if u not in N.keys():
				N[u]=0   #書中沒有這一步,但是字典沒有初始值不可以直接相加吧
			N[u]+=1 #每個商品下使用者出現一次就加一次,就是計算每個使用者一共購買的商品個數。
			#但是這個值也可以從最開始的使用者表中獲得。
			#比如: for u in dic.keys():
			#             N[u]=len(dic[u])
			for v in users:
				if u==v:
					continue
				if (u,v) not in C.keys():#同上,沒有初始值不能+=
					C[u,v]=0
				C[u,v]+=1  #這裡我不清楚書中是不是用的巢狀字典,感覺有點迷糊。所以我這樣用的字典。
#到這裡倒排陣就建立好了,下面是計算相似度。
	W=dict()
	for co_user,cuv in C.items():
		W[co_user]=cuv / math.sqrt(N[co_user[0]]*N[co_user[1]])#因為我不是用的巢狀字典,所以這裡有細微差別。
	return W

def Recommend(user,dicc,W2,K):
	rvi=1 #這裡都是1,實際中可能每個使用者就不一樣了。就像每個人都喜歡beautiful girl,但有的喜歡可愛的多一些,有的喜歡御姐多一些。
	rank=dict()
	related_user=[]
	interacted_items=dicc[user]
	for co_user,item in W2.items():
		if co_user[0]==user:
			related_user.append((co_user[1],item))#先建立一個和待推薦使用者興趣相關的所有的使用者列表。
	for v,wuv in sorted(related_user,key=itemgetter(1),reverse=True)[0:K]:
	#找到K個相關使用者以及對應興趣相似度,按興趣相似度從大到小排列。itemgetter要導包。
		for i in dicc[v]:
			if i in interacted_items:
				continue #書中少了continue這一步吧?
			if i not in rank.keys():#如果不寫要報錯,是不是有更好的方法?
				rank[i]=0
			rank[i]+=wuv*rvi
	return rank


if __name__=='__main__':
	W3=Usersim(dic)
	Last_Rank=Recommend('A',dic,W3,2)
	print Last_Rank

因本人程式碼能力有限,實為初學,為加深理解故走了一遍流程。不當之處,還望大家指正。