轉自:http://blog.csdn.net/ls317842927/article/details/79072662
一、基礎算法
基於物品的協同過濾算法(簡稱ItemCF)給用戶推薦那些和他們之前喜歡的物品相似的物品。不過ItemCF不是利用物品的內容計算物品之間相似度,而是利用用戶的行為記錄。
該算法認為,物品A和物品B具有很大的相似度是因為喜歡物品A的用戶大都也喜歡物品B。這里蘊含一個假設,就是每個用戶的興趣都局限在某幾個方面,因此如果兩個物品屬於同一個用戶的興趣列表,那么這兩個物品可能就屬於有限的幾個領域。而如果兩個物品同時出現在很多用戶的興趣列表,那么它們可能就屬於同一領域,因而具有很大的相似度。
從上述概念出發,定義物品i和j的相似度為
其中,|N(i)||N(i)|是喜歡物品i的用戶數,|N(i)⋂N(j)||N(i)⋂N(j)|是同時喜歡物品i和物品j的用戶數。分母是懲罰物品i和j的權重,因此懲罰了熱門物品和很多物品相似的可能性。
在得到物品相似度之后,ItemCF通過以下公式計算用戶u對未產生行為的物品j的感興趣程度。
這里的N(u)N(u)是用戶喜歡的物品集合,S(j,K)S(j,K)是和物品j最相似的K個物品的集合,wijwij是物品j和i的相似度,ruirui是用戶u對物品j的興趣評分(簡單的,如果用戶u對物品i有過行為,即可令ruirui=1)
下面舉一個例子說明(只考慮用戶有歷史購買行為的物品)。
用戶A購買物品a b d,用戶B購買物品b c e,用戶C購買物品c d,用戶D購買物品b c d,用戶E購買物品a d。
user | item |
---|---|
A | a b d |
B | b c e |
C | c d |
D | b c d |
E | a d |
數據集格式為(用戶,ruirui=1,物品),每行記錄都是唯一的,興趣評分由ruirui決定。
uid_score_bid = ['A,1,a','A,1,b','A,1,d','B,1,b','B,1,c','B,1,e','C,1,c','C,1,d','D,1,b','D,1,c','D,1,d','E,1,a','E,1,d']
- 1
import math class ItemBasedCF: def __init__(self,train_file): self.train_file = train_file self.readData() def readData(self): #讀取文件,並生成數據集(用戶,興趣程度,物品) self.train = dict() for line in self.train_file: user,score,item = line.strip().split(",") self.train.setdefault(user,{}) self.train[user][item] = int(float(score)) print (self.train) #輸出數據集 def ItemSimilarity(self): C = dict() #物品-物品的共現矩陣 N = dict() #物品被多少個不同用戶購買 for user,items in self.train.items(): for i in items.keys(): N.setdefault(i,0) N[i] += 1 #物品i出現一次就計數加一 C.setdefault(i,{}) for j in items.keys(): if i == j : continue C[i].setdefault(j,0) C[i][j] += 1 #物品i和j共現一次就計數加一 print ('N:',N) print ('C:',C) #計算相似度矩陣 self.W = dict() for i,related_items in C.items(): self.W.setdefault(i,{}) for j,cij in related_items.items(): self.W[i][j] = cij / (math.sqrt(N[i] * N[j])) #按上述物品相似度公式計算相似度 for k,v in self.W.items(): print (k+':'+str(v)) return self.W #給用戶user推薦前N個最感興趣的物品 def Recommend(self,user,K=3,N=10): rank = dict() #記錄user的推薦物品(沒有歷史行為的物品)和興趣程度 action_item = self.train[user] #用戶user購買的物品和興趣評分r_ui for item,score in action_item.items(): for j,wj in sorted(self.W[item].items(),key=lambda x:x[1],reverse=True)[0:K]: #使用與物品item最相似的K個物品進行計算 if j in action_item.keys(): #如果物品j已經購買過,則不進行推薦 continue rank.setdefault(j,0) rank[j] += score * wj #如果物品j沒有購買過,則累計物品j與item的相似度*興趣評分,作為user對物品j的興趣程度 return dict(sorted(rank.items(),key=lambda x:x[1],reverse=True)[0:N]) #聲明一個ItemBased推薦的對象 Item = ItemBasedCF(uid_score_bid) Item.ItemSimilarity() recommedDic = Item.Recommend("A") #計算給用戶A的推薦列表 for k,v in recommedDic.items(): print (k,"\t",v )
輸出結果:
數據集self.train
{'A': {'a': 1, 'd': 1, 'b': 1}, 'E': {'a': 1, 'd': 1}, 'D': {'d': 1, 'b': 1, 'c': 1}, 'B': {'e': 1, 'b': 1, 'c': 1}, 'C': {'d': 1, 'c': 1}}
物品被多少個不同用戶購買
N: {'e': 1, 'a': 2, 'd': 4, 'b': 3, 'c': 3}
物品-物品的共現矩陣
C: {'e': {'b': 1, 'c': 1}, 'a': {'d': 2, 'b': 1}, 'd': {'a': 2, 'b': 2, 'c': 2}, 'b': {'a': 1, 'd': 2, 'e': 1, 'c': 2}, 'c': {'e': 1, 'd': 2, 'b': 2}}
物品相似矩陣
a:{'d': 0.7071067811865475, 'b': 0.4082482904638631} d:{'a': 0.7071067811865475, 'b': 0.5773502691896258, 'c': 0.5773502691896258} e:{'b': 0.5773502691896258, 'c': 0.5773502691896258} c:{'d': 0.5773502691896258, 'e': 0.5773502691896258, 'b': 0.6666666666666666} b:{'a': 0.4082482904638631, 'd': 0.5773502691896258, 'e': 0.5773502691896258, 'c': 0.6666666666666666}
用戶A的推薦列表
e 0.5773502691896258
c 1.2440169358562925
二、用戶活躍度對物品相似度的影響
在ItemCF中,兩個物品產生相似度是因為它們共同出現在很多用戶的興趣列表中。假設有這么一個用戶,他是開書店的,並且買了當當網上 80% 的書准備用來自己賣。那么,
他的購物車里包含當當網 80% 的書。所以這個用戶對於他所購買書的兩兩相似度的貢獻應該遠遠小於一個只買了十幾本自己喜歡的書的文學青年。
提出一個稱為 IUF ( Inverse User Frequence ),即用戶活躍度對數的倒數的參數,來修正物品相似度的計算公式。認為活躍用戶對物品相似度的貢獻應該小於不活躍的用戶。
三、物品相似度的歸一化
對於已經得到的物品相似度矩陣w,按照以下公式對w進行按列歸一化,不僅可以增加推薦的准確度,它還可以提高推薦的覆蓋率和多樣性。
假設物品分為兩類—— A 和 B , A 類物品之間的相似度為 0.5 , B 類物品之間的相似度為 0.6 ,而 A 類物品和 B 類物品之間的相似度是 0.2 。在這種情況下,如果一個用戶喜歡了 5個 A 類物品和 5 個 B 類物品,用 ItemCF 給他進行推薦,推薦的就都是 B 類物品,因為 B 類物品之間的相似度大。但如果歸一化之后, A 類物品之間的相似度變成了 1 , B 類物品之間的相似度也是 1 ,那么這種情況下,用戶如果喜歡 5 個 A 類物品和 5 個 B類物品,那么他的推薦列表中 A 類物品和 B 類物品的數目也應該是大致相等的。從這個例子可以看出,相似度的歸一化可以提高推薦的多樣性。
一般來說,熱門的類其類內物品相似度一般比較大。如果不進行歸一化,就會推薦比較熱門的類里面的物品,而這些物品也是比較熱門的。因此,推薦的覆蓋率就比較低。相反,如果進行相似度的歸一化,則可以提高推薦系統的覆蓋率。
結合二,三改進算法
def ItemSimilarity(self): C = dict() N = dict() for user,items in self.train.items(): for i in items.keys(): N.setdefault(i,0) N[i] += 1 C.setdefault(i,{}) for j in items.keys(): if i == j : continue C[i].setdefault(j,0) #C[i][j] += 1 #基礎算法 C[i][j] += 1/math.log(1+len(items)*1.0) #改進第一點 print ('N:',N) print ('C:',C) #計算相似度矩陣 self.W = dict() self.W_max = dict() #記錄每一列的最大值 for i,related_items in C.items(): self.W.setdefault(i,{}) for j,cij in related_items.items(): self.W_max.setdefault(j,0.0)# self.W[i][j] = cij / (math.sqrt(N[i] * N[j])) if self.W[i][j]>self.W_max[j]:# self.W_max[j]=self.W[i][j] #記錄第j列的最大值,按列歸一化 print('W:',self.W) for i,related_items in C.items(): # for j,cij in related_items.items(): # self.W[i][j]=self.W[i][j] / self.W_max[j] # print ('W_max:',self.W_max) for k,v in self.W.items(): print (k+':'+str(v)) return self.W
輸出結果:
物品相似度矩陣W(歸一化之前)
W: {'a': {'b': 0.2944888920518062, 'd': 0.576853026474115}, 'c': {'b': 0.4808983469629878, 'd': 0.470998523813926, 'e': 0.4164701851078906}, 'b': {'a': 0.2944888920518062, 'c': 0.4808983469629878, 'd': 0.4164701851078906, 'e': 0.4164701851078906}, 'd': {'a': 0.576853026474115, 'c': 0.470998523813926, 'b': 0.4164701851078906}, 'e': {'c': 0.4164701851078906, 'b': 0.4164701851078906}}
矩陣W的每列最大值
W_max: {'a': 0.576853026474115, 'c': 0.4808983469629878, 'b': 0.4808983469629878, 'd': 0.576853026474115, 'e': 0.4164701851078906}
物品相似度矩陣W(歸一化之后)
a:{'b': 0.6123724356957947, 'd': 1.0} c:{'b': 1.0, 'd': 0.8164965809277261, 'e': 1.0} b:{'a': 0.5105093993383438, 'c': 1.0, 'd': 0.721969316263228, 'e': 1.0} d:{'a': 1.0, 'c': 0.9794138964885573, 'b': 0.8660254037844387} e:{'c': 0.8660254037844387, 'b': 0.8660254037844387}
用戶A的推薦列表
c 1.9794138964885573
e 1.0
四、評估指標
TopN推薦:R(u)R(u)是根據用戶在訓練集上的行為給用戶做出的推薦列表,T(u)T(u)是用戶在測試集上的行為列表。
推薦結果的召回率:
推薦結果的准確率:
首先,將用戶行為數據集按照均勻分布隨機分成 M份(本文取 M =5 ),挑選一份作為測試集,將剩下的 M -1 份作為訓練集。然后在訓練集上建立用戶興趣模型,並在測試集上對用戶行為進行預測,統計出相應的評測指標。為了保證評測指標並不是過擬合的結果,需要進行 M 次實驗,並且每次都使用不同的測試集。然后將 M 次實驗測出的評測指標的平均值作為最終的評測指標。
每次實驗選取不同的 k ( 0 ≤ k ≤ M - 1 )和相同的隨機數種子 seed ,進行 M 次實驗就可以得到 M 個不同的訓練集和測試集。
如果數據集夠大,模型夠簡單,為了快速通過離線實驗初步地選擇算法,也可以只進行一次實驗。
#-*-coding:utf-8 -*- import math import numpy as np import random #進行5折交叉驗證,計算平均准確率和召回率 class ItemBasedCF: def __init__(self,data_file): self.data_file = data_file def splitData(self,k,M=5,seed=1): self.train_file = [] self.test_file = [] random.seed(seed) for line in open(self.data_file): if random.randint(0,M)==k: self.test_file.append(line) else: self.train_file.append(line) def readData(self,file): #讀取文件,並生成用戶-物品的評分表 self.data_dict = dict() #用戶-物品的評分表 for line in file: tmp = line.strip().split(" ") if len(tmp)<3: continue user,score,item = tmp[:3] self.data_dict.setdefault(user,{}) self.data_dict[user][item] = int(float(score)) return self.data_dict def ItemSimilarity(self): #同上 #給用戶user推薦 def Recommend(self,user,K=10,N=100): #同上 #聲明一個ItemBased推薦的對象 uid_score_bid='/home/lady/tmp/liushuang/1.9Item-basedCF/data/buy_user_spu.1210_0109' Item = ItemBasedCF(uid_score_bid)#讀取數據集 M=5 pre_lst=[] rec_lst=[] for k in range(M): #進行5次交叉驗證 Item.splitData(k,M,seed=1) Item.train=Item.readData(Item.train_file) Item.test=Item.readData(Item.test_file) Item.ItemSimilarity() #計算物品相似度矩陣 recommedDic = dict() hit = 0 n_pre = 0 n_rec = 0 print '訓練集數量',len(Item.train) print '測試集數量',len(Item.test) for user in Item.train.keys(): recommedDic[user] = Item.Recommend(user) #對於訓練user生成推薦列表 n_pre+=len(recommedDic[user]) rec_item=set() for item in recommedDic[user]: rec_item.add(item[0]) #測試user真實購買的商品 buy_item=set() if user not in Item.test: continue for item in Item.test[user].keys(): buy_item.add(item) hit += len(rec_item & buy_item) for user in Item.test: n_rec += len(Item.test[user]) pre = hit/(1.0*n_pre) rec = hit/(1.0*n_rec) pre_lst.append(pre) rec_lst.append(rec) print k,' hit:',hit,'n_pre:',n_pre,'n_rec;',n_rec print pre_lst,'平均:',np.mean(pre_lst) print rec_lst,'平均:',np.mean(rec_lst)