本随笔主要记录本人对协同过滤算法的学习理解与Python的实现,主要参考资料为项亮老师的《推荐系统实践》和Prateek Joshi 老师的《Python机器学习经典实例》两本书。
一.基于用户的协同过滤简介
利用用户行为数据构建推荐系统有三类算法:基于邻域的算法、隐语义模型和基于图的模型。
基于邻域的算法主要有基于用户的协同过滤算法和基于物品的协同过滤算法,这里要学习的是基于用户的协同过滤算法。
基于用户的协同过滤算法(UserCF)是推荐系统中最古老的算法。可以不夸张地说,这个算法的诞生标志了推荐系统的诞生。
二.算法思路
当一个用户 A 需要个性化推荐时,可以先找到和他有相似兴趣的其他用户,然后把那些用户喜欢的、而用户 A 没有听说过的物品推荐给 A 。这种方法称为基于用户的协同过滤算法。
从上面的描述中可以看到,基于用户的协同过滤算法主要包括两个步骤。
(1) 找到和目标用户兴趣相似的用户集合。
(2) 找到这个集合中的用户喜欢的,且目标用户没有听说过的物品推荐给目标用户。
几个关键的问题:
用户之间的兴趣相似度如何度量?
答:目前提供四种选择
首先得到下面的评分矩阵数据结构(矩阵或是字典),横轴为用户,纵轴为物品,Fij为用户i对物品j的评分值(可以看作已经选择了该物品),空值为未选择该物品。
1. 欧式距离,有选择物品为1,未选择物品为0,可以计算两个用户选择物品向量的欧式距离。
2.Jaccard 公式,分子为两个用户共同选择的物品数,分母为两个用户选择物品的并集物品数。
3.余弦相似度,分子为两个用户共同选择的物品数,分母为两个用户选择物品数乘积的平方根。
4.对余弦相似度的改进,|N(i)|为选择物品i的用户数(物品i的流行度),物品i是两个用户共同选择的物品。分子为基于这些物品的流行度的算式结果的和,分母为两个用户选择物品数乘积的平方根。
三.Python实现
1.数据
这里使用MovieLens电影评分数据集进行实验,包含6040个用户,3900个电影,1000209个评分。可以自己下载,或者网盘连接提取。
链接:https://pan.baidu.com/s/1ZLnw4-z88GAYTdgf_ZAq9A
提取码:fqx3
数据文件夹如下,
README为数据介绍,
movies为电影信息数据,有电影ID,电影名称,电影风格三个属性。
users为用户信息数据,有用户ID,性别,年龄,职业,zip-code五个属性。
ratings为用户评分数据,有用户ID,电影ID,评分三个属性。
2.目标
使用MovieLens电影评分数据集构建一个推荐系统引擎。推荐引擎可以对数据集中的用户推荐若干电影(不涉及冷启动问题),输入用户ID,返回给该用户的电影推荐候选集。
3.代码结构
代码结构如下,
UserDF_1用于读取原始数据,并将评分数据构建成字典并存储为json文件持久化,即得到评分矩阵(见二部分)。
UserDF_2用于读取评分矩阵,计算用户兴趣相似度矩阵且建成字典存储为json文件持久化,即得到用户兴趣相似度矩阵。
图用户兴趣相似度矩阵。
UserDF_3用于基于评分矩阵和用户兴趣相似度矩阵计算目标用户的TopN个候选推荐电影的集合。
UserDF_1.py
#coding=utf-8 #基于用户的协同过滤算法 #1.读取数据 #2.构建需要的数据结构 #3.计算用户间的兴趣相似度 (余弦相似度),取K个与目标用户兴趣最相似用户 #4.计算目标用户对其未观看电影感兴趣程度 (在K的兴趣最相似用户观看的且目标用户未观看的电影中,计算目标用户对每个电影的感兴趣程度, # 感兴趣程度=K个相似用户对电影有评分记录的用户的相似度之和) import pandas as pd import numpy as np from sklearn.model_selection import KFold import json import pickle #1-n:读取数据,将原始数据转换为方便计算的数据结构 r_nanes = ['userid','movieid','rating','timestamp'] ratings_data = pd.read_table(filepath_or_buffer=u'E:\DataSet\ml-1m-电影推荐\ml-1m\\ratings.dat', sep='::',header=None,names=r_nanes,engine='python') print(ratings_data.columns) print(ratings_data.shape) print(ratings_data.head(5)) u_nanes = ['userid','gender','age','occupation','zip-code'] users_data = pd.read_table(filepath_or_buffer=u'E:\DataSet\ml-1m-电影推荐\ml-1m\\users.dat', sep='::',header=None,names=u_nanes,engine='python') print(users_data.columns) print(users_data.shape) m_nanes = ['movieid','title','genres'] movies_data = pd.read_table(filepath_or_buffer=u'E:\DataSet\ml-1m-电影推荐\ml-1m\\movies.dat', sep='::',header=None,names=m_nanes,engine='python') print(movies_data.columns) print(movies_data.shape) num_user = len(users_data['userid']) num_movie = len(movies_data['movieid']) print(num_user) print(num_movie) userids = users_data['userid'].values movieids = movies_data['movieid'].values ratings_data_narray = ratings_data.values userid_movieid_rating = {} key = 0 for userid in userids: print('userid:',userid) ratings_dict = {} for i in (range(len(ratings_data_narray))[key:]): if ratings_data_narray[i][0]==userid: ratings_dict[str(ratings_data_narray[i][1])]=str(ratings_data_narray[i][2]) print(key) key += 1 else: continue userid_movieid_rating[str(userid)]=ratings_dict print('----------------') print(key) print(len(userid_movieid_rating)) with open(file='userid_movieid_rating.json',mode='w') as f: json.dump(userid_movieid_rating,f)
UserDF_2.py
import numpy as np import json # 2-n读取持久化数据,计算用户相似度,得到用户相似度矩阵 def simlar_score(dataset, user1, user2): if user1 not in dataset: raise TypeError('User ' + user1 + ' not present in the dataset') if user1 not in dataset: raise TypeError('User ' + user2 + ' not present in the dataset') Itemset_user1 = list(dataset[user1].keys()) Itemset_user2 = list(dataset[user2].keys()) #print(type(Itemset_user1)) intersection = np.intersect1d(Itemset_user1, Itemset_user2) # if intersection!=[]: # print(type(Itemset_user1)) # print(Itemset_user2) # print(intersection) numerator = len(intersection) #print(numerator) denominator = np.sqrt(len(Itemset_user1) * len(Itemset_user2)) #print(denominator) simlar_score = numerator / denominator #print(simlar_score) return simlar_score with open(file='userid_movieid_rating.json',mode='r') as f: dataset = json.load(f) users_simlar_score = {} for user1 in dataset: print(user1) user_user_simlar_scores = {} for user2 in dataset: simlar_sc = simlar_score(dataset,user1,user2) user_user_simlar_scores[user2] = str(simlar_sc) users_simlar_score[user1] = user_user_simlar_scores with open(file='users_simlar_score.json',mode='w') as f: json.dump(users_simlar_score,f)
UserDF_3.py
import numpy as np import json #取得一个用户的作品 # def getItemOfUser(dataset_users_rating,userid): # # items = list[dataset_users_rating[userid].keys()] # # return items #获取目标用户的推荐候选集 def getRecommendationItemForUser(dataset_users_rating,dataset_users_simlar,tuserid,K=5,topN=500): #得到目标用户与其他用户的相似度 item_ranks = np.array([[userid,float(users_simalar)] for userid,users_simalar in dataset_users_simlar[tuserid].items()]) #按相似度排序(由大到小),取得前K个其他用户的[用户ID,相似度值] item_ranks_K_ousers = item_ranks[np.argsort(item_ranks[:,1])[::-1]][1:K+1] #print(item_ranks_K_ousers) #得到相似度最大的K的用户的用户ID K_ousers = item_ranks_K_ousers[:,0] print('与目标用户兴趣最相似的',str(K),'个用户') print(K_ousers) print('----------') #得到似度最大的K的用户的选择物品的并集 items_candidate = [] for userid in K_ousers: items_user = list(dataset_users_rating[userid].keys()) #print(len(items_user)) items_candidate = np.union1d(items_candidate,items_user) print('似度最大的K的用户的选择物品的并集,物品数量为:') print(len(items_candidate)) print('----------') #得到在似度最大的K的用户的选择物品的并集中且不在目标用户选择物品集合中的物品作为推荐候选集合 RecommendationItems = np.setdiff1d(items_candidate,list(dataset_users_rating[tuserid].keys())) print('在似度最大的K的用户的选择物品的并集中且不在目标用户选择物品集合中的物品作为推荐候选集合,物品数量为:') print(len(RecommendationItems)) print('----------') #print(RecommendationItems) #计算用户对于所有候选集合物品的偏好度,偏好度=K个用户中对物品有行为的用户与目标用户的相似度的和 user_like_scores = {} for item_r in RecommendationItems: user_like_score = [] for user in K_ousers: if item_r in list(dataset_users_rating[user].keys()): #print(tuserid,user) #print(dataset_users_simlar[tuserid][user]) user_like_score.append(float(dataset_users_simlar[tuserid][user])) user_like_scores[item_r] = str(sum(user_like_score)) #按偏好度排序取topN user_like_scores_list = np.array([[item,like_score] for item,like_score in user_like_scores.items()]) RecommendationItems_topN = user_like_scores_list[np.argsort(user_like_scores_list[:,1])[::-1]][:topN] #print(len(RecommendationItems_topN),'--------------') print('兴趣最高的top',str(topN),'个物品及用户兴趣度:') print(RecommendationItems_topN) print('----------') #返回topN的物品名称 return RecommendationItems_topN[:,0] if __name__ == '__main__': with open(file='userid_movieid_rating.json', mode='r') as f: dataset_users_rating = json.load(f) with open(file='users_simlar_score.json', mode='r') as f: dataset_users_simlar = json.load(f) tuserid = '10' topN = 100 K = 3 RecommendationItems_topN = getRecommendationItemForUser(dataset_users_rating, dataset_users_simlar, tuserid,K=K,topN=topN) print('用户',tuserid,'的top',str(topN),'推荐物品列表:') print(RecommendationItems_topN)