所谓协同过滤, 基本思想是根据用户之前的喜好以及其他兴趣相近的用户的选择来给用户推荐物品(基于对用户历史行为数据的挖掘发现用户的喜好偏向, 并预测用户可能喜好的产品进行推荐),一般是仅仅基于用户的行为数据(评价、购买、下载等), 而不依赖于项的任何附加信息(物品自身特征)或者用户的任何附加信息(年龄, 性别等),是最经典、最常见到的推荐算法。
本文介绍的是基于用户的协同过滤算法及其Python实现,作为推荐算法"鼻祖",关于协同过滤算法的原理,网上一搜一把一把的,这里就不做赘述了,重点以一个网上的公开数据集,介绍一下UserCF的实现思路。
水平有限,不妥之处还希望大佬多多指正。
数据集与完整代码:https://github.com/ziyuan0014/rec_userCF
本案例所用数据为用户对物品的评分数据(貌似为movieLen中的数据,但是前期学习找了太多资料,具体数据源我确实无法确定= =!),数据样式如下表所示:
user_id | content_id | score | ts | |
---|---|---|---|---|
0 | 1 | 1 | 5 | 874965758 |
1 | 1 | 2 | 3 | 876893171 |
2 | 1 | 3 | 4 | 878542960 |
3 | 1 | 4 | 3 | 876893119 |
4 | 1 | 5 | 3 | 889751712 |
先放代码
def cirRatMatrix(pd_data,userId,contentId,score):
"""
计算评分矩阵
- param:
pd_data: 原始数据
userId: 用户列名
contentId: 内容列名
score: 评分列名
- return:
pd_data: 新增了两个编码列的数据表
rating: 评分矩阵
"""
pd_data['user_factorize_id'],_ = pd.factorize(pd_data[userId])
pd_data['content_factorize_id'],_ = pd.factorize(pd_data[contentId])
userNo = pd_data['user_factorize_id'].max() + 1
contentNo = pd_data['content_factorize_id'].max() + 1
rating = np.zeros((contentNo,userNo))
#查看矩阵ratings_df的第一维度是多少
for _,row in pd_data.iterrows():
#interrows(),对原始数据进行遍历
rating[int(row['content_factorize_id']),int(row['user_factorize_id'])] = row[score]
return pd_data,rating
});
需要特别说明的是,在这一步中,计算评分矩阵时用的是经过pandas编码函数pd.factorize()后的数据列,这是因为在实践中,我们所能得到的用户评分数据往往是非连续的,即很多用户注册了账号但是并没有进行相关 *** 作,这就会导致这个评分表中的user列数值上并不连续,进而使得用 np.zeros(max_content_id,max_user_id)生成的评分矩阵有大量0向量出现。
出于节省空间的考虑,这里先进行编码,然后再进行遍历赋值,从而避免上述问题。关于pandas的factorize()方法的说明,这里推荐一位博主的文章:https://blog.csdn.net/ssswill/article/details/86555935
由于进行了编码,这里还需要再定义两个取值函数:
def reGetUserId(pd_factorize_data,user_factorize_id):
''' 根据 user_factorize_id 返回对应的 user_id '''
user_id = pd_factorize_data[pd_factorize_data['user_factorize_id'] == user_factorize_id]["user_id"].values[0]
return user_id
def reGetContentId(pd_factorize_data,content_factorize_id):
''' 根据 content_factorize_id 返回对应的 content_id '''
content_id = pd_factorize_data[pd_factorize_data['content_factorize_id'] == content_factorize_id]['content_id'].values[0]
return content_id
基于用户的协同过滤
有了评分矩阵,下一步就可以根据UserCF的基本思想来实现了。在工程上,整个实现总共有三个步骤:
步骤一:计算用户之间的相似度先定义相似度计算函数(这里用的是余弦相似度):
def cos_dist(vec1,vec2):
"""
- param:
vec1: 向量1
vec2: 向量2
- return:
dist: 两个向量的余弦相似度
"""
if vec1.sum()==0 or vec2.sum()==0:
dist = 0
else:
dist = float(np.dot(vec1,vec2)/(np.linalg.norm(vec1)*np.linalg.norm(vec2)))
return dist
然后计算用户相似度矩阵
userNum = pd_factorize_data['user_factorize_id'].max() + 1
user_sim_matrix = np.zeros((userNum,userNum))
rating_T = rating.T
idx = 0
while idx < len(rating_T):
idy = idx + 1
while idy < len(rating_T):
user_sim_matrix[idx,idy] = cos_dist(rating_T[idx],rating_T[idy])
idy = idy +1
idx = idx + 1
特别注意一下,上一步计算的评分矩阵,行代表内容,列代表相应的用户,因此这一步需要先进行转置,然后再依次进行计算。
步骤二:返回相似用户选出sim_user_max_len个和用户user最相似的用户,程序如下:
sim_user_dict = {}
sim_user_max_len = 5 # 定义给每个人最大的相似用户数量
for i in range(userNum):
temp_series = pd.DataFrame(user_sim_matrix[i],columns=['user_factorize_'+str(i)])
sim_user_index = temp_series.sort_values(by = 'user_factorize_'+str(i),ascending = False)[0:sim_user_max_len].index.tolist()
for user_index in sim_user_index:
if temp_series.iloc[user_index].values == 0:
sim_user_index.remove(user_index)
sim_user_dict[i] = sim_user_index
整体思路就是先排序取最大的几个index,然后再把其中相似度为0的去掉,最后每个用户的相似用户使用字典进行存储。
步骤三:根据相似用户的浏览情况进行推荐还是直接放代码
user_rec_dict = {}
user_rec_score_dict = {}
for user,sim_user_list in sim_user_dict.items():
#print('now deal with the user:'+str(user))
user_data = pd_factorize_data[pd_factorize_data['user_factorize_id'] == user]
user_content_list = user_data[['content_id']].values # 获取用户之前浏览过的内容
rec_num = 0
rec_list = []
rec_score_list = []
for sim_user in sim_user_list:
sim_user_data = pd_factorize_data[pd_factorize_data['user_factorize_id'] == sim_user]
sim_user_data['rec_socre'] = user_sim_matrix[user][sim_user] * sim_user_data['score'] # 推荐得分 = 用户相似度 * 用户对content_id的评分
sim_user_data = sim_user_data.sort_values(by = 'rec_socre',ascending=False)
# 去重
for indexs in sim_user_data.index:
if sim_user_data.loc[indexs]['rec_socre']>0 and sim_user_data.loc[indexs]['content_id'] not in user_content_list:
if sim_user_data.loc[indexs]['content_id'] not in rec_list: # 不同的相似用户可能都会推荐同一个content,这里默认只取第一个相似用户的推荐
rec_list.append(sim_user_data.loc[indexs]['content_id'])
rec_score_list.append(sim_user_data.loc[indexs]['rec_socre'])
rec_num = rec_num + 1
if len(rec_list) > 0:
user_rec_dict[reGetUserId(pd_factorize_data,user)] = rec_list
user_rec_score_dict[reGetUserId(pd_factorize_data,user)] = rec_score_list
这里定义了两个dict,一个用来存储为每个用户进行推荐的内容id,另一个则用来存储相应内容id的推荐分数,最终返回的两个dict,其中的user和content都是编码前的id,个人感觉必要的内容都在注释里,这里不再赘述了。
需要特别说明的是保留推荐分数是由于对于一个即将上线的推荐系统而言,往往是多个推荐策略同时使用,这时候就需要一个分数从而方便后期进行选择。
这一步比较耗时,在这个数据集中,943个用户大概跑了八分钟才完成。
至此整个算法实现完毕,后续根据项目需求灵活取用即可。
尾言协同过滤(Collaborative Filtering)推荐算法是推荐系统中最经典推荐算法,这种基于相似用户的喜好来对目标用户的喜好进行预测的理念同样具有非常广泛的推广价值,其中非常重要的步骤就是计算用户和用户或者物品和物品之间的相似度。在本文中,相似度的计算使用的是余弦相似度,除此以外还有杰卡德(Jaccard)相似系数、欧式距离等。指的一提的是,还有一位博主,针对两个用户之间一方数据大、一方数据小的问题,引入了Pearson相关系数来衡量两个变量之间的线性相关性,进而衡量用户的相似度,该方法同样值得学习,链接:https://blog.csdn.net/qq_25948717/article/details/81839463。
然而,尽管协同过滤的方法很普遍,实际的工程项目(尤其是新项目)中它却并不是一个最常用的方法,这是由于一方面,这种方法高度依赖用户的行为,当一个用户在系统中所记录到的 *** 作行为太少,最终反映到数据中就是大面积0的出现(数据稀疏),这会严重影响相似度的计算;另一方面,随着用户和内容的数量的增长,计算量也会随之呈指数级增长,算法的可拓展性很差,很难适应实际的需求。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)