1. 协同过滤算法介绍
协同过滤(Collaborative Filtering)* 推荐算法是最经典、最常用的推荐算法。简称CF|
所谓协同过滤, 基本思想是根据用户之前的喜好以及其他兴趣相近的用户的选择来给用户推荐物品 (基于对用户历史行为数据的挖掘发现用户的喜好偏向, 并预测用户可能喜好的产品进行推荐),一般是仅仅基于用户的行为数据(评价、购买、下载等), 而不依赖于项的任何附加信息(物品自身特征)或者用户的任何附加信息(年龄, 性别等)。目前应用比较广泛的协同过滤算法是基于邻域的方法去分类,有下面两种算法:
- 基于用户的协同过滤算法(UserCF): 给用户推荐和他兴趣相似的其他用户喜欢的产品
- 基于物品的协同过滤算法(ItemCF): 给用户推荐和他之前喜欢的物品相似的物品
不管是UserCF还是ItemCF算法, 非常重要的步骤之一就是计算用户和用户或者物品和物品之间的相似度, 所以下面先整理常用的相似性度量方法, 然后再对每个算法的具体细节进行展开
基于模型的协同过滤
- 奇异值分解(SVD)
- 潜在语义分析(LSA)
- 支持向量机(SVM)
2. 相似性度量方法
- 余弦相似度:
余弦相似度衡量了两个向量的夹角,夹角越小越相似。公式如下:s i m u v = ∣ N ( u ) ∣ ∩ ∣ N ( v ) ∣ ∣ N ( u ) ∣ ⋅ ∣ N ( v ) ∣ sim_{uv}=\frac{|N(u)| \cap |N(v)|}{\sqrt{|N(u)|\cdot|N(v)|}}simuv=∣N(u)∣⋅∣N(v)∣∣N(u)∣∩∣N(v)∣
从向量的角度进行描述,令矩阵A AA为用户-商品交互矩阵(因为是TopN推荐并不需要用户对物品的评分,只需要知道用户对商品是否有交互就行),即矩阵的每一行表示一个用户对所有商品的交互情况,有交互的商品值为1没有交互的商品值为0,矩阵的列表示所有商品。若用户和商品数量分别为m , n m,nm,n的话,交互矩阵A AA就是一个m mm行n nn列的矩阵。此时用户的相似度可以表示为(其中u ⋅ v u\cdot vu⋅v指的是向量点积):s i m u v = c o s ( u , v ) = u ⋅ v ∣ u ∣ ⋅ ∣ v ∣ sim_{uv} = cos(u,v) =\frac{u\cdot v}{|u|\cdot |v|}simuv=cos(u,v)=∣u∣⋅∣v∣u⋅v
其取值范围是[-1,1],夹角越小,余弦值越接近于1,它们的方向越吻合,则越相似
- 皮尔逊相关系数:
皮尔逊相关系数的公式与余弦相似度的计算公式非常的类似,其中r u i , r v i r_{ui},r_{vi}rui,rvi分别表示用户u uu和用户v vv对商品i ii是否有交互(或者具体的评分值):s i m u v = ∑ i r u i ∗ r v i ∑ i r u i 2 ∑ i r v i 2 sim_{uv} = \frac{\sum_i r_{ui}*r_{vi}}{\sqrt{\sum_i r_{ui}^2}\sqrt{\sum_i r_{vi}^2}}simuv=∑irui2∑irvi2∑irui∗rvi如下是皮尔逊相关系数计算公式,其中r u i , r v i r_{ui},r_{vi}rui,rvi分别表示用户u uu和用户v vv对商品i ii是否有交互(或者具体的评分值),r ˉ u , r ˉ v \bar r_u, \bar r_vrˉu,rˉv分别表示用户u uu和用户v vv交互的所有商品交互数量或者具体评分的平均值。s i m ( u , v ) = ∑ i ∈ I ( r u i − r ˉ u ) ( r v i − r ˉ v ) ∑ i ∈ I ( r u i − r ˉ u ) 2 ∑ i ∈ I ( r v i − r ˉ v ) 2 sim(u,v)=\frac{\sum_{i\in I}(r_{ui}-\bar r_u)(r_{vi}-\bar r_v)}{\sqrt{\sum_{i\in I }(r_{ui}-\bar r_u)^2}\sqrt{\sum_{i\in I }(r_{vi}-\bar r_v)^2}}sim(u,v)=∑i∈I(rui−rˉu)2∑i∈I(rvi−rˉv)2∑i∈I(rui−rˉu)(rvi−rˉv)所以相比余弦相似度,皮尔逊相关系数通过使用用户的平均分对各独立评分进行修正,减小了用户评分偏置的影响。具体实现, 我们也是可以调包。
#这就是一种方法 from scipy.stats import pearsonr i = [1, 0, 0, 0] j = [1, 0.5, 0.5, 0] pearsonr(i, j) #返回值的第一项是皮尔森相关系数,第二项是p_value值。 # 一般来说皮尔森相关系数越大,p_value越小,线性相关性就越大。
(0.8164965809277258, 0.1835034190722742)
- 杰卡德(Jaccard)相似系数:
一般计算交集与并集的,计算评分0,1布尔值相似。两个用户u uu和v vv交互商品交集的数量占这两个用户交互商品并集的数量的比例,称为两个集合的杰卡德相似系数,用符号s i m u v sim_{uv}simuv表示,其中N ( u ) , N ( v ) N(u),N(v)N(u),N(v)分别表示用户u uu和用户v vv交互商品的集合。s i m u v = ∣ N ( u ) ∩ N ( v ) ∣ ∣ N ( u ) ∣ ∪ ∣ N ( v ) ∣ sim_{uv}=\frac{|N(u) \cap N(v)|}{\sqrt{|N(u)| \cup|N(v)|}}simuv=∣N(u)∣∪∣N(v)∣∣N(u)∩N(v)∣
3. 基于用户的协同过滤
图像中的ALice和cary两者的信息比较相似,所以我们可以基于用户,根据alice的信息去推算出Cary的泰坦尼克号评分信息
- UserCF算法主要包括两个步骤:
1、找到和目标用户兴趣相似的集合
2、找到这个集合中的用户喜欢的, 且目标用户没有听说过的物品推荐给目标用户。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xsDgP4fl-1603368735944)(attachment:image.png)]
4. UserCF编程实现
首先, 先把数据表给建立起来
# 定义数据集, 也就是那个表格, 注意这里我们采用字典存放数据, 因为实际情况中数据是非常稀疏的, 很少有情况是现在这样 def loadData(): items={'A': {1: 5, 2: 3, 3: 4, 4: 3, 5: 1}, 'B': {1: 3, 2: 1, 3: 3, 4: 3, 5: 5}, 'C': {1: 4, 2: 2, 3: 4, 4: 1, 5: 5}, 'D': {1: 4, 2: 3, 3: 3, 4: 5, 5: 2}, 'E': {2: 3, 3: 5, 4: 4, 5: 1} } users={1: {'A': 5, 'B': 3, 'C': 4, 'D': 4}, 2: {'A': 3, 'B': 1, 'C': 2, 'D': 3, 'E': 3}, 3: {'A': 4, 'B': 3, 'C': 4, 'D': 3, 'E': 5}, 4: {'A': 3, 'B': 3, 'C': 1, 'D': 5, 'E': 4}, 5: {'A': 1, 'B': 5, 'C': 5, 'D': 2, 'E': 1} } return items,users items, users = loadData() item_df = pd.DataFrame(items).T user_df = pd.DataFrame(users).T
print(item_df) print(user_df)
1 2 3 4 5 A 5.0 3.0 4.0 3.0 1.0 B 3.0 1.0 3.0 3.0 5.0 C 4.0 2.0 4.0 1.0 5.0 D 4.0 3.0 3.0 5.0 2.0 E NaN 3.0 5.0 4.0 1.0 A B C D E 1 5.0 3.0 4.0 4.0 NaN 2 3.0 1.0 2.0 3.0 3.0 3 4.0 3.0 4.0 3.0 5.0 4 3.0 3.0 1.0 5.0 4.0 5 1.0 5.0 5.0 2.0 1.0
- 计算用户相似性矩阵:
这个是一个共现矩阵, 5*5,行代表每个用户, 列代表每个用户, 值代表用户和用户的相关性,这里的思路是这样, 因为要求用户和用户两两的相关性, 所以需要用双层循环遍历用户-物品评分数据, 当不是同一个用户的时候, 我们要去遍历物品-用户评分数据, 在里面去找这两个用户同时对该物品评过分的数据放入到这两个用户向量中。
import numpy as np import pandas as pd """计算用户相似性矩阵""" similarity_matrix = pd.DataFrame(np.zeros((len(users), len(users))), index=[1, 2, 3, 4, 5], columns=[1, 2, 3, 4, 5]) # 遍历每条用户-物品评分数据 for userID in users: for otheruserId in users: vec_user = [] vec_otheruser = [] if userID != otheruserId: for itemId in items: # 遍历物品-用户评分数据 itemRatings = items[itemId] # 这也是个字典 每条数据为所有用户对当前物品的评分 if userID in itemRatings and otheruserId in itemRatings: # 说明两个用户都对该物品评过分 vec_user.append(itemRatings[userID]) vec_otheruser.append(itemRatings[otheruserId]) # 这里可以获得相似性矩阵(共现矩阵) similarity_matrix[userID][otheruserId] = np.corrcoef(np.array(vec_user), np.array(vec_otheruser))[0][1] #similarity_matrix[userID][otheruserId] = cosine_similarity(np.array(vec_user), np.array(vec_otheruser))[0][1]
similarity_matrix
1 | 2 | 3 | 4 | 5 | |
1 | 0.000000 | 0.852803 | 0.707107 | 0.000000 | -0.792118 |
2 | 0.852803 | 0.000000 | 0.467707 | 0.489956 | -0.900149 |
3 | 0.707107 | 0.467707 | 0.000000 | -0.161165 | -0.466569 |
4 | 0.000000 | 0.489956 | -0.161165 | 0.000000 | -0.641503 |
5 | -0.792118 | -0.900149 | -0.466569 | -0.641503 | 0.000000 |
计算前n个相似的用户
"""计算前n个相似的用户""" n = 2 similarity_users = similarity_matrix[1].sort_values(ascending=False)[:n].index.tolist() # [2, 3] 也就是用户1和用户2
计算最终得分
"""计算最终得分""" base_score = np.mean(np.array([value for value in users[1].values()])) weighted_scores = 0. corr_values_sum = 0. for user in similarity_users: # [2, 3] corr_value = similarity_matrix[1][user] # 两个用户之间的相似性 mean_user_score = np.mean(np.array([value for value in users[user].values()])) # 每个用户的打分平均值 weighted_scores += corr_value * (users[user]['E']-mean_user_score) # 加权分数 corr_values_sum += corr_value final_scores = base_score + weighted_scores / corr_values_sum print('用户Alice对物品5的打分: ', final_scores) user_df.loc[1]['E'] = final_scores user_df
用户Alice对物品5的打分: 4.871979899370592
A | B | C | D | E | |
1 | 5.0 | 3.0 | 4.0 | 4.0 | 4.87198 |
2 | 3.0 | 1.0 | 2.0 | 3.0 | 3.00000 |
3 | 4.0 | 3.0 | 4.0 | 3.0 | 5.00000 |
4 | 3.0 | 3.0 | 1.0 | 5.0 | 4.00000 |
5 | 1.0 | 5.0 | 5.0 | 2.0 | 1.00000 |
5 、基于物品的协同过滤
基于物品的协同过滤,图片中泰坦尼克号和阿甘正传,阿甘正传和机器人总动员相似。我们目测这两行比较相似:因此喜欢泰坦尼克号的也有可能喜欢阿甘正传
- 基于物品的协同过滤算法主要分为两步:
1)、计算物品之间的相似度
2)、根据物品的相似度和用户的历史行为给用户生成推荐列表(购买了该商品的用户也经常购买的其他商品)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1wLnI7O-1603368735966)(attachment:image.png)]
6.ItemCF编程实现
"""计算物品的相似矩阵""" similarity_matrix = pd.DataFrame(np.ones((len(items), len(items))), index=['A', 'B', 'C', 'D', 'E'], columns=['A', 'B', 'C', 'D', 'E']) # 遍历每条物品-用户评分数据 for itemId in items: for otheritemId in items: vec_item = [] # 定义列表, 保存当前两个物品的向量值 vec_otheritem = [] #userRagingPairCount = 0 # 两件物品均评过分的用户数 if itemId != otheritemId: # 物品不同 for userId in users: # 遍历用户-物品评分数据 userRatings = users[userId] # 每条数据为该用户对所有物品的评分, 这也是个字典 if itemId in userRatings and otheritemId in userRatings: # 用户对这两个物品都评过分 #userRagingPairCount += 1 vec_item.append(userRatings[itemId]) vec_otheritem.append(userRatings[otheritemId]) # 这里可以获得相似性矩阵(共现矩阵) similarity_matrix[itemId][otheritemId] = np.corrcoef(np.array(vec_item), np.array(vec_otheritem))[0][1] #similarity_matrix[itemId][otheritemId] = cosine_similarity(np.array(vec_item), np.array(vec_otheritem))[0][1]
similarity_matrix
A | B | C | D | E | |
A | 1.000000 | -0.476731 | -0.123091 | 0.532181 | 0.969458 |
B | -0.476731 | 1.000000 | 0.645497 | -0.310087 | -0.478091 |
C | -0.123091 | 0.645497 | 1.000000 | -0.720577 | -0.427618 |
D | 0.532181 | -0.310087 | -0.720577 | 1.000000 | 0.581675 |
E | 0.969458 | -0.478091 | -0.427618 | 0.581675 | 1.000000 |
然后也是得到与物品5相似的前n个物品, 计算出最终得分来
"""得到与物品5相似的前n个物品""" n = 2 similarity_items = similarity_matrix['E'].sort_values(ascending=False)[1:n+1].index.tolist() # ['A', 'D'] """计算最终得分""" base_score = np.mean(np.array([value for value in items['E'].values()])) weighted_scores = 0. corr_values_sum = 0. for item in similarity_items: # ['A', 'D'] corr_value = similarity_matrix['E'][item] # 两个物品之间的相似性 mean_item_score = np.mean(np.array([value for value in items[item].values()])) # 每个物品的打分平均值 weighted_scores += corr_value * (users[1][item]-mean_item_score) # 加权分数 corr_values_sum += corr_value final_scores = base_score + weighted_scores / corr_values_sum print('用户Alice对物品5的打分: ', final_scores) user_df.loc[1]['E'] = final_scores user_df
用户Alice对物品5的打分: 4.6
A | B | C | D | E | |
1 | 5.0 | 3.0 | 4.0 | 4.0 | 4.6 |
2 | 3.0 | 1.0 | 2.0 | 3.0 | 3.0 |
3 | 4.0 | 3.0 | 4.0 | 3.0 | 5.0 |
4 | 3.0 | 3.0 | 1.0 | 5.0 | 4.0 |
5 | 1.0 | 5.0 | 5.0 | 2.0 | 1.0 |
7. 算法评估
由于UserCF和ItemCF结果评估部分是共性知识点, 所以在这里统一标识。 这里介绍评测指标:
- 召回率
对用户u推荐N个物品记为R ( u ) R(u)R(u), 令用户u在测试集上喜欢的物品集合为T ( u ) T(u)T(u), 那么召回率定义为:Recall = ∑ u ∣ R ( u ) ∩ T ( u ) ∣ ∑ u ∣ T ( u ) ∣ \operatorname{Recall}=\frac{\sum_{u}|R(u) \cap T(u)|}{\sum_{u}|T(u)|}Recall=∑u∣T(u)∣∑u∣R(u)∩T(u)∣这个意思就是说, 在用户真实购买或者看过的影片里面, 我模型真正预测出了多少, 这个考察的是模型推荐的一个全面性。 - 准确率:
准确率定义为:Precision = ∑ u ∣ R ( u ) ∩ T ( u ) ∣ ∑ u ∣ R ( u ) ∣ \operatorname{Precision}=\frac{\sum_{u} \mid R(u) \cap T(u)|}{\sum_{u}|R(u)|}Precision=∑u∣R(u)∣∑u∣R(u)∩T(u)∣这个意思再说, 在我推荐的所有物品中, 用户真正看的有多少, 这个考察的是我模型推荐的一个准确性。 为了提高准确率, 模型需要把非常有把握的才对用户进行推荐, 所以这时候就减少了推荐的数量, 而这往往就损失了全面性, 真正预测出来的会非常少,所以实际应用中应该综合考虑两者的平衡。 - 覆盖率:
覆盖率反映了推荐算法发掘长尾的能力, 覆盖率越高, 说明推荐算法越能将长尾中的物品推荐给用户。 Coverage = ∣ ⋃ u ∈ U R ( u ) ∣ ∣ I ∣ \text { Coverage }=\frac{\left|\bigcup_{u \in U} R(u)\right|}{|I|} Coverage =∣I∣∣∣⋃u∈UR(u)∣∣该覆盖率表示最终的推荐列表中包含多大比例的物品。如果所有物品都被给推荐给至少一个用户, 那么覆盖率是100%。 - 新颖度:
用推荐列表中物品的平均流行度度量推荐结果的新颖度。 如果推荐出的物品都很热门, 说明推荐的新颖度较低。 由于物品的流行度分布呈长尾分布, 所以为了流行度的平均值更加稳定, 在计算平均流行度时对每个物品的流行度取对数
8、协同过滤算法的问题分析
协同过滤算法存在的问题之一就是泛化能力弱, 即协同过滤无法将两个物品相似的信息推广到其他物品的相似性上。 导致的问题是热门物品具有很强的头部效应, 容易跟大量物品产生相似, 而尾部物品由于特征向量稀疏, 导致很少被推荐。
为了解决这个问题, 同时增加模型的泛化能力,2006年,矩阵分解技术(Matrix Factorization,MF)
9、UserCF和ItemCF区别
UserCF很强的社交特性, 这样的特点非常适于用户少, 物品多, 时效性较强的场合, 比如新闻推荐场景, 因为新闻本身兴趣点分散, 相比用户对不同新闻的兴趣偏好, 新闻的及时性,热点性往往更加重要, 所以正好适用于发现热点,跟踪热点的趋势。 另外还具有推荐新信息的能力, 更有可能发现惊喜, 因为看的是人与人的相似性, 推出来的结果可能更有惊喜,可以发现用户潜在但自己尚未察觉的兴趣爱好。
对于用户较少, 要求时效性较强的场合, 就可以考虑UserCF。
ItemCF 这个更适用于兴趣变化较为稳定的应用, 更接近于个性化的推荐, 适合物品少,用户多,用户兴趣固定持久, 物品更新速度不是太快的场合, 比如推荐艺术品, 音乐, 电影。 下面是UserCF和ItemCF的优缺点对比: (来自项亮推荐系统实践)
10、协同过滤优缺点
协同过滤作为一种经典的推荐算法种类,在工业界应用广泛,它的优点很多,模型通用性强,不需要太多对应数据领域的专业知识,工程实现简单,效果也不错。这些都是它流行的原因。
当然,协同过滤也有些难以避免的难题,比如令人头疼的“冷启动”问题,我们没有新用户任何数据的时候,无法较好的为新用户推荐物品。同时也没有考虑情景的差异,比如根据用户所在的场景和用户当前的情绪。当然,也无法得到一些小众的独特喜好,这块是基于内容的推荐比较擅长的。
最后。如果文章中有不足之处,请务必指出,一定迅速改正。谢谢