協同過濾推薦(Collaborative Filtering Recommendation)主要包括基於用戶的協同過濾算法與基於物品的協同過濾算法。
下面,以movielens數據集為例,分別實踐這兩種算法。
movielens數據集包含四列,【用戶ID|電影ID|打分|時間戳】,根據用戶的歷史評分向用戶召回電影候選集。
UserCF
基於用戶的協同過濾算法主要包括兩個步驟。
(1) 找到和目標用戶興趣相似的用戶集合。
(2) 找到這個集合中的用戶喜歡的,且目標用戶沒有聽說過的物品推薦給目標用戶
步驟(1)的關鍵就是計算兩個用戶的興趣相似度。可以通過Jaccard(傑卡德)公式或者通過余弦相似度計算。代碼中主要使用了余弦相似度:
主函數為recommend(self,userID,K,N,useIIF):
def recommend(self,userID,K,N,useIIF):
W, user_item = self._UserSimilarity(self.X, self.y, useIIF)
rank = {}
interacted_items = user_item[userID]
for v, wuv in sorted(W[userID].items(), reverse=True)[:K]:
for i in user_item[v]:
if i not in interacted_items:
rank.setdefault(i, 0)
rank[i] += wuv
return sorted(rank.items(), key=lambda d: d[1], reverse=True)[:N]
其中,userID是將要為其推薦的用戶ID,\(K\)代表要考慮多少個相似用戶,\(N\)代表輸出多少個推薦item。
函數_UserSimilarity用於計算用戶之間的相似度,通過用戶物品表與物品用戶表計算出兩個用戶觀看的相同的電影數量,當設定useIIF=True時,相同的電影數量變為加\(1 / math.log(1 + len(users))\),原因是懲罰用戶\(u\)和用戶\(v\)共同興趣列表中的熱門物品。
然后,會挑選出\(K\)名最相似的用戶,選出這些用戶下的\(N\)部電影作為推薦目標。這里涉及到兩個排序,一個是用戶的相似度排序,一個是item與用戶的權重排序。注意,這里的電影的權重是由用戶相似度累加決定的。
全部代碼如下所示:
import math
import pandas as pd
class UserCF:
def __init__(self,X,y):
self.X,self.y = X,y
def recommend(self,userID,K,N,useIIF):
"""
Args:
userID:user id
k: K users closest to the user's interest
N:the number of recommendable item
userIIF:whether or not use userIIF
Returns:
top N recommendation
rank:[(item_id1,interest1),(item_id2,interest2)...]
"""
W, user_item = self._UserSimilarity(self.X, self.y, useIIF)
rank = {}
interacted_items = user_item[userID]
for v, wuv in sorted(W[userID].items(), reverse=True)[:K]:
for i in user_item[v]:
if i not in interacted_items:
rank.setdefault(i, 0)
rank[i] += wuv
return sorted(rank.items(), key=lambda d: d[1], reverse=True)[:N]
def _UserSimilarity(self,X,Y,useIIF=False):
"""
Args:
X: user id list
Y: item id list
userIIF: whether or not use userIIF
Returns:
W : user's interest correlation
user_item: a dict:{user_id1:[item1,item2,...],..user_idn:[]}
"""
# 建立倒排表
item_user=dict()
for i in range(X.count()):
user=X.iloc[i]
item=Y.iloc[i]
if item not in item_user:
item_user[item]=set()
item_user[item].add(user)
user_item=dict()
for i in range(Y.count()):
user=X.iloc[i]
item=Y.iloc[i]
if user not in user_item:
user_item[user]=set()
user_item[user].add(item)
C={}
N={}
# C:輸出用戶u與v共同的物品數目矩陣
for i,users in item_user.items():
for u in users:
N.setdefault(u,0)
N[u]+=1
C.setdefault(u,{})
for v in users:
if u==v:
continue
C[u].setdefault(v,0)
if not useIIF:
C[u][v]+=1
else:
C[u][v]+=1 / math.log(1 + len(users))# 懲罰用戶u和用戶v共同興趣列表中熱門物品
W=C.copy()
for u,related_users in C.items():
for v,cuv in related_users.items():
W[u][v]=cuv/math.sqrt(N[u]*N[v])
return W,user_item
if __name__ == '__main__':
moviesPath = '../data/ml-1m/movies.dat'
ratingsPath = '../data/ml-1m/ratings.dat'
usersPath = '../data/ml-1m/users.dat'
ratingsDF = pd.read_csv(ratingsPath, index_col=None, sep='::', header=None,names=['user_id', 'movie_id', 'rating', 'timestamp'])
X=ratingsDF['user_id'][:100000]
Y=ratingsDF['movie_id'][:100000]
rank = UserCF(X,Y).recommend(1,K=10,N=10,useIIF=True)# 輸出對用戶1推薦的 top10 item
print('UserCF result',rank)
ItemCF
基於物品的協同過濾(item-based collaborative filtering)算法是目前業界應用最多的算法。基於物品的協同過濾算法主要分為兩步。
(1) 計算物品之間的相似度。
(2) 根據物品的相似度和用戶的歷史行為給用戶生成推薦列表
與UserCF類似,下面也使用了余弦相似度作用item相似度的衡量。另外,也對活躍用戶做了一種軟性的懲罰。
全部代碼如下所示:
#-*-coding:utf-8-*-
"""
author:jamest
date:20190306
ItemCF function
"""
import math
import pandas as pd
class ItemCF:
def __init__(self,X,y):
self.X,self.y = X,y
def recommend(self,userID,K,N,useIUF):
"""
Args:
userID:user id
k: K items closest to the user's items
N:the number of recommendable item
useIUF:whether or not use useIUF
Returns:
top N recommendation
rank:[(item_id1,interest1),(item_id2,interest2)...]
"""
W, user_item = self._ItemSimilarity(self.X, self.y, useIUF)
rank = {}
interacted_items = user_item[userID]
for i in interacted_items:
for j, wij in sorted(W[i].items(), reverse=True)[0:K]:
if j not in interacted_items:
rank.setdefault(j, 0)
rank[j] += wij
return sorted(rank.items(), key=lambda d: d[1], reverse=True)[:N]
def _ItemSimilarity(self,X,Y,useIUF=False):
"""
Args:
X: user id list
Y: item id list
useIUF: whether or not use useIUF
Returns:
W : item's correlation
user_item: a dict:{user_id1:[item1,item2,...],..user_idn:[]}
"""
# 建立倒排表
user_item = dict()
for i in range(Y.count()):
user = X.iloc[i]
item = Y.iloc[i]
if user not in user_item:
user_item[user] = set()
user_item[user].add(item)
C = {}
N = {}
for u, items in user_item.items():
for i in items:
N.setdefault(i, 0)
N[i] += 1
C.setdefault(i, {})
for j in items:
if i == j:
continue
C[i].setdefault(j, 0)
if not useIUF:
C[i][j] += 1
else:
C[i][j] += 1 / math.log(1 + len(items)) # 對活躍用戶做了一種軟性的懲罰
W = C.copy()
for i, related_items in C.items():
for j, cij in related_items.items():
W[i][j] = cij / math.sqrt(N[i] * N[j])
return W, user_item
if __name__ == '__main__':
moviesPath = '../data/ml-1m/movies.dat'
ratingsPath = '../data/ml-1m/ratings.dat'
usersPath = '../data/ml-1m/users.dat'
# usersDF = pd.read_csv(usersPath,index_col=None,sep='::',header=None,names=['user_id', 'gender', 'age', 'occupation', 'zip'])
# moviesDF = pd.read_csv(moviesPath,index_col=None,sep='::',header=None,names=['movie_id', 'title', 'genres'])
ratingsDF = pd.read_csv(ratingsPath, index_col=None, sep='::', header=None,names=['user_id', 'movie_id', 'rating', 'timestamp'])
X=ratingsDF['user_id'][:10000]
Y=ratingsDF['movie_id'][:10000]
rank = ItemCF(X,Y).recommend(1,K=10,N=10,useIUF=True)#輸出對用戶1推薦的 top10 item
print('ItemCF result',rank)