协同过滤算法
1.协同过滤算法概述
协同过滤(Collaborative Filtering)算法是一种经典的 “推荐算法”, 基本思想是根据用户之前的喜好以及其他兴趣相近的用户的选择来给用户推荐物品(基于对用户历史行为数据的挖掘发现用户的喜好偏向, 并预测用户可能喜好的产品进行推荐), 一般是仅仅基于用户的行为数据(评价、购买、下载等), 而不依赖于项的任何附加信息(物品自身特征)或者用户的任何附加信息(年龄, 性别等)。目前应用比较广泛的协同过滤算法是基于邻域的方法, 而这种方法主要有下面两种算法:
- 基于用户的协同过滤算法(UserCF): 给用户推荐和他兴趣相似的其他用户喜欢的产品。
- 基于物品的协同过滤算法(ItemCF): 给用户推荐和他之前喜欢的物品相似的物品。
2.基于用户的协同过滤(UserCF)
2.1 概述
基于用户的协同过滤(UserCF)可以追溯到1993年, 可以说是非常早的一种算法了, 这种算法的思想其实比较简单, 当一个用户A需要个性化推荐的时候, 我们可以先找到和她有相似兴趣的其他用户, 然后把那些用户喜欢的, 而用户A没有听说过的物品推荐给A。
基于用户的协同过滤算法主要包括两个步骤:
- 找到和目标用户兴趣相似的集合, 也就是计算每个用户之间的相似度。
- 找到这个集合中的用户喜欢的, 且目标用户没有听说过的物品推荐给用户。
举个栗子:
物品1 | 物品2 | 物品3 | 物品4 | 物品5 | |
---|---|---|---|---|---|
Alice | 5 | 3 | 4 | 4 | ? |
用户1 | 3 | 1 | 2 | 3 | 3 |
用户2 | 4 | 3 | 4 | 3 | 5 |
用户3 | 3 | 3 | 1 | 5 | 4 |
用户4 | 1 | 5 | 5 | 2 | 1 |
其实, 给用户推荐物品的过程可以形象化为一个猜测用户对商品进行打分的任务,上面表格里面是5个用户对于5件物品的一个打分情况,就可以理解为用户对物品的喜欢程度。
任务:判断到底该不该把物品5推荐给用户Alice呢?
基于以上步骤,则有:
- 首先根据前面的这些打分情况(或者说已有的用户向量)计算一下Alice和用户1, 2, 3, 4的相似程度, 找出与Alice最相似的n个用户
- 根据这n个用户对物品5的评分情况和与Alice的相似程度会猜测出Alice对物品5的评分, 如果评分比较高的话, 就把物品5推荐给用户Alice, 否则不推荐。
2.2 两个向量之间的相似度
计算相似度需要根据特点的不同选择不同的相似度计算方法, 比较常用的有下面:杰卡德相似系数、余弦相似度、皮尔逊相关系数。
杰卡德(Jaccard)相似系数
衡量两个集合的相似度一种指标。 两个集合A和B的交集元素在A,B的并集中所占的比例,称为两个集合的杰卡德相似系数,用符号J(A,B)J(A,B)J(A,B)表示。
J(A,B)=∣A⋂B∣∣A⋃B∣J(A,B)=\\frac{|A\\bigcap B|}{|A\\bigcup B|} J(A,B)=∣A⋃B∣∣A⋂B∣
余弦相似度
衡量了用户向量 AAA 和 BBB之间的向量夹角的大小, 夹角越小, 说明相似度越大, 两个用户越相似。 公式如下:
sim(A,B)==A⋅B∣A∣∣B∣=∑i=1nAiBi∑i=1nAi2∑i=1nBi2\\begin{align} sim(A,B)=&=\\frac{A ·B}{|A||B|}\\\\[3ex] &=\\frac{\\sum\\limits^n\\limits_{i=1}A_iB_i}{\\sqrt{\\sum\\limits^n\\limits_{i=1}A^2_i}\\sqrt{\\sum\\limits^n\\limits_{i=1}B^2_i}} \\end{align} sim(A,B)==∣A∣∣B∣A⋅B=i=1∑nAi2i=1∑nBi2i=1∑nAiBi
from sklearn.metrics.pairwise import cosine_similarity
i = [1, 0, 0, 0]
j = [1, 0.5, 0.5, 0]
consine_similarity([i, j])
Q:余弦相似度存在什么问题么?
余弦相似度比较常用的, 一般效果也不会太差, 但是对于评分数据不规范的时候, 也就是说, 存在有的用户喜欢打高分, 有的用户喜欢打低分情况的时候,有的用户喜欢乱打分的情况, 这时候余弦相似度算出来的结果可能就不是那么准确了, 比如下面这种情况:
X | Y | Z | |
---|---|---|---|
d | 4 | 4 | 5 |
e | 1 | 1 | 2 |
f | 4 | 1 | 5 |
如果用余弦相似度进行计算, 会发现用户d和用户f比较相似, 而实际上, 如果看这个商品喜好的一个趋势的话, 其实d和e比较相近, 只不过e比较喜欢打低分, d比较喜欢打高分。 所以对于这种用户评分偏置的情况, 余弦相似度就不是那么有效, 可以考虑使用下面的皮尔逊相关系数。
皮尔逊相关系数
相比余弦相似度, 皮尔逊相关系数通过使用用户平均分对个独立评分进行修正, 减少了用户评分偏置的影响。 简单的说, 其实pearson做的就是把两个向量都减去他们的均值, 然后再计算consine值。 用pearson来计算用户相似进行推荐的话, 效果还是好于consine的。公式如下:
P(A,B)=∑i=1n(Ai−A‾)(Bi−B‾)∑i=1n(Ai−A‾)2∑i=1n(Bi−B‾)2P(A,B)=\\frac{\\sum\\limits^n\\limits_{i=1}(A_i-\\overline A)(B_i-\\overline B)}{\\sqrt{\\sum\\limits^n\\limits_{i=1}(A_i-\\overline A)^2}\\sqrt{\\sum\\limits^n\\limits_{i=1}(B_i-\\overline B)^2}} P(A,B)=i=1∑n(Ai−A)2i=1∑n(Bi−B)2i=1∑n(Ai−A)(Bi−B)
from scipy.stats import pearsonr
i = [1, 0, 0, 0]
j = [1, 0.5, 0.5, 0]
pearsonr(i, j)
Q: 为什么在一些场景中要使用余弦相似度而不是欧式距离呢?
欧式距离体现数值上的绝对差异,而余弦距离体现方向上的相对差异。具体使用中,看需求决定。
如果要统计两部剧的用户观看行为,用户A的观看向量(0,1), 用户B为(1,0), 此时二者的余弦距离很大,而欧式距离很小。我们分析两个用户对于不同视频的偏好,更关注相对差异,显然应当用余弦距离。
而当我们分析用户活跃度,以登录次数和平均观看时长作为特征时, 余弦距离会认为(1,10)和(10,100)两个用户距离很近,但显然这两个用户活跃度是有着极大差异的。此时我们关注的是数值绝对差异,应当使用欧式距离。
2.3 结果预测
可以通过上述相似度的计算方法计算出 Alice和其他用户的相近程度 ,此时可以选出与Alice最相近的前n个用户, 基于他们对物品5的评价猜测出Alice的打分值, 那么是怎么计算的呢?
方式1:利用用户相似度和相似用户的评价加权平均获得用户的评价预测, 公式如下:
Ru^,i=∑u∈U(wu^,u⋅Ru,i)∑i∈Iwu^,uR_{\\hat u,i}=\\frac{\\sum\\limits_{u\\in U}( w_{\\hat u,u}·R_{u,i}) }{\\sum\\limits_{i\\in I}w_{\\hat u,u}} Ru^,i=i∈I∑wu^,uu∈U∑(wu^,u⋅Ru,i)
权重wu^,uw_{\\hat u,u}wu^,u是用户 u^\\hat uu^ 和用户 uuu 的相似度,Ru,iR_{u,i}Ru,i 是用户uuu 对物品 iii 的评分。
方式2: 用户相似度作为权值, 但后面不单纯的是其他用户对物品的评分, 而是该物品的评分与此用户的所有评分的差值进行加权平均, 这时候考虑到了有的用户内心的评分标准不一的情况, 即有的用户喜欢打高分, 有的用户喜欢打低分的情况。
Pi,j=Rˉi+∑k=1n(Si,k(Rk,j−Rˉk))∑k=1nSi,kP_{i,j}=\\bar R_i+\\frac{\\sum\\limits^n\\limits_{k=1}(S_{i,k}(R_{k,j}-\\bar R_k))}{\\sum\\limits^n\\limits_{k=1}S_{i,k}} Pi,j=Rˉi+k=1∑nSi,kk=1∑n(Si,k(Rk,j−Rˉk))
- Pi,jP_{i,j}Pi,j 表示用户 iii 对物品 jjj 的评分
- Rˉi\\bar R_iRˉi 表示用户 iii 的所有评分的平均值
- nnn 表示的是与用户 iii 相似度的 nnn 个用户
- Sj,kS_{j,k}Sj,k 是用户 iii和用户 kkk 的相似度
- Rk,jR_{k,j}Rk,j 表示用户 kkk 对物品用户 jjj 的评分
- Rˉk\\bar R_kRˉk 表示用户 kkk 的所有评分的平均值
2.4 举个栗子
对于上述栗子:
物品1 | 物品2 | 物品3 | 物品4 | 物品5 | |
---|---|---|---|---|---|
Alice | 5 | 3 | 4 | 4 | ? |
用户1 | 3 | 1 | 2 | 3 | 3 |
用户2 | 4 | 3 | 4 | 3 | 5 |
用户3 | 3 | 3 | 1 | 5 | 4 |
用户4 | 1 | 5 | 5 | 2 | 1 |
猜测Alice对物品5的得分: 基于用户的协同过滤算法流程:
- 1.计算Alice与其他用户的相似度(这里使用皮尔逊相关系数)
用户向量:
Alice=[5,3,4,4]user1=[3,1,2,3]user2=[4,3,4,3]user3=[3,3,1,5]user4=[1,5,5,2]Alice=[5,3,4,4]\\quad user_1=[3,1,2,3]\\quad user_2=[4,3,4,3]\\quad user_3=[3,3,1,5]\\quad user_4=[1,5,5,2]Alice=[5,3,4,4]user1=[3,1,2,3]user2=[4,3,4,3]user3=[3,3,1,5]user4=[1,5,5,2]
计算Alice与user1user_1user1的余弦相似度:
sim(Alice,user1)=cosine(Alice,user1)=5×3+3×1+4×2+4×325+9+16+169+1+4+9=0.975sim(Alice,user_1)=cosine(Alice,user_1)=\\frac{5×3+3×1+4×2+4×3}{\\sqrt{25+9+16+16}\\sqrt{9+1+4+9}}=0.975 sim(Alice,user1)=cosine(Alice,user1)=25+9+16+169+1+4+95×3+3×1+4×2+4×3=0.975
计算Alice与user1user_1user1的皮尔逊相关系数:
Alice_ave=4user1_ave=2.25Alice\\_ave=4\\ user_1\\_ave=2.25Alice_ave=4 user1_ave=2.25
向量减去均值得到: Alice=[1,−1,0,0]user1=[0.75,−1.25,−0.25,0.75]Alice=[1,-1,0,0]\\quad user_1=[0.75,-1.25,-0.25,0.75]Alice=[1,−1,0,0]user1=[0.75,−1.25,−0.25,0.75]
计算两向量的余弦相似度,结果为0.85230.85230.8523
sim(Alice,user1)=1×0.75+−1×−1.25+0+01+1+0+00.752+−1.252+0.252+0.752=0.853sim(Alice,user_1)=\\frac{1×0.75+-1×-1.25+0+0}{\\sqrt{1+1+0+0}\\sqrt{0.75^2+-1.25^2+0.25^2+0.75^2}}=0.853 sim(Alice,user1)=1+1+0+00.752+−1.252+0.252+0.7521×0.75+−1×−1.25+0+0=0.853
使用皮尔逊相关系数, 也就是Alice与用户1的相似度是 0.850.850.85。同样的方式, 我们就可以计算与其他用户的相似度, 这里可以使用numpy的相似度函数得到用户的相似性矩阵:
from sklearn.metrics.pairwise import cosine_similarity
import numpy as npusers = np.array([[5, 3, 4, 4], [3, 1, 2, 3], [4, 3, 4, 3], [3, 3, 1, 5], [1, 5, 5, 2]])
print("==================余弦相似度===============================")
print(cosine_similarity(users))
print("==================皮尔逊相关系数============================")
print(np.corrcoef(users))
从上述结果看出, Alice用户和用户2, 用户3, 用户4的相似度是0.7,0,−0.790.7,\\ 0,\\ -0.790.7, 0, −0.79。 所以如果 n=2n=2n=2, 找到与Alice最相近的两个用户是用户1(和Alice的相似度是 0.850.850.85 )以及 用户2(和Alice相似度是 0.70.70.7)。
-
2.根据相似度用户计算Alice对物品5的最终得分
Pi,j=Rˉi+∑k=1n(Si,k(Rk,j−Rˉk))∑k=1nSi,kP_{i,j}=\\bar R_i+\\frac{\\sum\\limits^n\\limits_{k=1}(S_{i,k}(R_{k,j}-\\bar R_k))}{\\sum\\limits^n\\limits_{k=1}S_{i,k}} Pi,j=Rˉi+k=1∑nSi,kk=1∑n(Si,k(Rk,j−Rˉk))
用户1对物品5的评分是3, 用户2对物品5的打分是5, 那么根据上面的计算公式, 可以计算出Alice对物品5的最终得分是:
PAlice,物品5=RˉAlice+∑i=12(SAlice,userk(Ruserk,物品5−Rˉuserk))∑i=12SAlice,userk=4+(0.85×(3−2.4))+(0.7×(5−3.8))0.85+0.7=4.87\\begin{align} P_{Alice,物品5}&=\\bar R_{Alice}+\\frac{\\sum\\limits^2\\limits_{i=1}(S_{Alice,user_k}(R_{user_k,物品5}-\\bar R_{user_k}))}{\\sum\\limits^2\\limits_{i=1}S_{Alice,user_k}}\\\\[2ex] &=4+\\frac{(0.85×(3-2.4))+(0.7×(5-3.8))}{0.85+0.7}\\\\[2ex] &=4.87 \\end{align} PAlice,物品5=RˉAlice+i=1∑2SAlice,userki=1∑2(SAlice,userk(Ruserk,物品5−Rˉuserk))=4+0.85+0.7(0.85×(3−2.4))+(0.7×(5−3.8))=4.87 -
3.根据用户评分对用户进行推荐
Alice对物品5的得分是4.874.874.87, 根据Alice的打分对物品排个序从大到小:
物品1>物品5>物品3=物品4>物品2物品1\\gt物品5\\gt物品3=物品4\\gt物品2 物品1>物品5>物品3=物品4>物品2
如果要向Alice推荐2款产品的话, 我们就可以推荐物品1和物品5给Alice。
2.5 编程实现
上述过程主要分为三部分: 计算用户相似性矩阵、得到前n个相似用户、计算最终得分。
2.5.1 准备数据
# 定义数据集, 也就是那个表格, 注意这里我们采用字典存放数据, 因为实际情况中数据是非常稀疏的, 很少有情况是现在这样
def loadData():# key:item value:{item:ratings} 物品-用户评分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}}# key:user value:{item:ratings} 用户-物品评分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,usersitems, users = loadData()
item_df = pd.DataFrame(items).T
user_df = pd.DataFrame(users).T
2.5.2 计算相似矩阵
"""计算用户相似性矩阵"""
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就是我们的用户相似性矩阵, 结果如下:
得到相似性矩阵, 我们就可以得到与Alice最相关的前n个用户。
2.5.3 计算前n个相似的用户
"""计算前n个相似的用户"""
n = 2
similarity_users = similarity_matrix[1].sort_values(ascending=False)[:n].index.tolist() # [2, 3] 即为用户1和用户2
2.5.4 计算最终得分
"""计算最终得分"""
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
有了这个评分, 其实就可以对该用户做推荐了。 这其实就是微型版的UserCF的工作过程了。
3.基于物品的协同过滤算法(ItemCF)
3.1 概述
基于物品的协同过滤(ItemCF)的基本思想是预先根据所有用户的历史偏好数据计算物品之间的相似性,然后把与用户喜欢的物品相类似的物品推荐给用户。比如物品a和c非常相似,因为喜欢a的用户同时也喜欢c,而用户A喜欢a,所以把c推荐给用户A。ItemCF算法并不利用物品的内容属性计算物品之间的相似度, 主要通过分析用户的行为记录计算物品之间的相似度, 该算法认为, 物品a和物品c具有很大的相似度是因为喜欢物品a的用户大都喜欢物品c。
基于物品的协同过滤算法主要分为两步:
- 计算物品之间的相似度。
- 根据物品的相似度和用户的历史行为给用户生成推荐列表(购买了该商品的用户也经常购买的其他商品)。
3.2 举个栗子
举个栗子:
物品1 | 物品2 | 物品3 | 物品4 | 物品5 | |
---|---|---|---|---|---|
Alice | 5 | 3 | 4 | 4 | ? |
用户1 | 3 | 1 | 2 | 3 | 3 |
用户2 | 4 | 3 | 4 | 3 | 5 |
用户3 | 3 | 3 | 1 | 5 | 4 |
用户4 | 1 | 5 | 5 | 2 | 1 |
计算Alice对物品5打多少分, 基于物品的协同过滤算法的流程:
-
1.计算物品5和物品1, 2, 3, 4之间的相似性
物品向量:
item1=[3,4,3,1]item2=[1,3,3,5]item3=[2,4,1,5]item4=[3,3,5,2]item5=[3,5,4,1]item_1=[3,4,3,1]\\quad item_2=[1,3,3,5]\\quad item_3=[2,4,1,5]\\quad item_4=[3,3,5,2]\\quad item_5=[3,5,4,1]item1=[3,4,3,1]item2=[1,3,3,5]item3=[2,4,1,5]item4=[3,3,5,2]item5=[3,5,4,1]
计算物品5与物品1之间的余弦相似性:
sim(item5,item1)=cosine(item5,item1)=3×3+5×4+4×3+1×19+25+16+19+16+9+1=0.994sim(item_5,item_1)=cosine(item_5,item_1)=\\frac{3×3+5×4+4×3+1×1}{\\sqrt{9+25+16+1}\\sqrt{9+16+9+1}}=0.994 sim(item5,item1)=cosine(item5,item1)=9+25+16+19+16+9+13×3+5×4+4×3+1×1=0.994
计算物品5与物品1的皮尔逊相关系数:item5_ave=3.25item1_ave=2.75item_5\\_ave=3.25\\ item_1\\_ave=2.75item5_ave=3.25 item1_ave=2.75
向量减去均值得到: item5=[−0.25,1.75,0.75,−2.25]item1=[0.25,1.25,0.25,−1.75]item_5=[-0.25,1.75,0.75,-2.25]\\quad item_1=[0.25,1.25,0.25,-1.75]item5=[−0.25,1.75,0.75,−2.25]item1=[0.25,1.25,0.25,−1.75]
计算两向量的余弦相似度,结果为 0.9690.9690.969
sim(item5,item1)=−0.25×0.25+1.75×1.25+0.75×0.25×+−2.25×−1.75(−0.25)2+1.752+0.752+(−2.25)20.252+−1.252+0.252+(−1.75)2=0.969\\begin{align} &sim(item_5,item_1)\\\\[2ex] &=\\frac{-0.25×0.25+1.75×1.25+0.75×0.25×+-2.25×-1.75}{\\sqrt{(-0.25)^2+1.75^2+0.75^2+(-2.25)^2}\\sqrt{0.25^2+-1.25^2+0.25^2+(-1.75)^2}}\\\\[2ex] &=0.969 \\end{align} sim(item5,item1)=(−0.25)2+1.752+0.752+(−2.25)20.252+−1.252+0.252+(−1.75)2−0.25×0.25+1.75×1.25+0.75×0.25×+−2.25×−1.75=0.969
使用皮尔逊相关系数, 也就是Alice与用户1的相似度是0.85。同样的方式, 我们就可以计算与其他用户的相似度, 这里可以使用numpy的相似度函数得到用户的相似性矩阵:from sklearn.metrics.pairwise import cosine_similarity import numpy as np import pandas as pditems = np.array([[3, 4, 3, 1], [1, 3, 3, 5], [2, 4, 1, 5], [3, 3, 5, 2], [3, 5, 4, 1]]) cols = ['item' + str(i) for i in range(1, 6)] print("==================余弦相似度===============================") cosine_similarity_item = pd.DataFrame(cosine_similarity(items), columns=cols, index=cols) print(cosine_similarity_item) print("==================皮尔逊相关系数============================") pearson_similarity_item = pd.DataFrame(np.corrcoef(items), columns=cols, index=cols) print(pearson_similarity_item)
-
2.找出与物品5最相近的n个物品(n=2)
根据皮尔逊相关系数, 可以找到与物品5最相似的2个物品是 item1item_1item1 和 item4item_4item4(n=2), 下面基于上面的公式计算最终得分:
Pi,j=Rˉi+∑k=1n(Si,k(Rk,j−Rˉk))∑k=1nSi,kP_{i,j}=\\bar R_i+\\frac{\\sum\\limits^n\\limits_{k=1}(S_{i,k}(R_{k,j}-\\bar R_k))}{\\sum\\limits^n\\limits_{k=1}S_{i,k}} Pi,j=Rˉi+k=1∑nSi,kk=1∑n(Si,k(Rk,j−Rˉk))
计算Alice对 item5item_5item5 的打分:
PAlice,item5=Rˉitem5+∑i=12(Sitem5,itemk(RAlice,itemk−Rˉitemk))∑i=12Sitem5,itemk=134+(0.97×(5−3.2))+(0.58×(4−3.4))0.97+0.58=4.6\\begin{align} P_{Alice,item_5}&=\\bar R_{item_5}+\\frac{\\sum\\limits^2\\limits_{i=1}(S_{item_5,item_k}(R_{Alice,item_k}-\\bar R_{item_k}))}{\\sum\\limits^2\\limits_{i=1}S_{item_5,item_k}}\\\\[2ex] &=\\frac{13}{4}+\\frac{(0.97×(5-3.2))+(0.58×(4-3.4))}{0.97+0.58}\\\\[2ex] &=4.6 \\end{align} PAlice,item5=Rˉitem5+i=1∑2Sitem5,itemki=1∑2(Sitem5,itemk(RAlice,itemk−Rˉitemk))=413+0.97+0.58(0.97×(5−3.2))+(0.58×(4−3.4))=4.6 -
3.根据Alice对最相近的n个物品的打分去计算对物品5的打分情况
- Alice对物品5的得分是 4.64.64.6, 根据Alice的打分对物品排序从大到小:
物品1>物品5>物品3=物品4>物品2物品1\\gt物品5\\gt物品3=物品4\\gt物品2 物品1>物品5>物品3=物品4>物品2
如果要向Alice推荐2款产品的话, 我们就可以推荐物品1和物品5给Alice。
3.3 编程实现
上述过程主要分为三部分: 计算用户相似性矩阵、得到前n个相似用户、计算最终得分。
3.3.1 准备数据
# 定义数据集, 也就是那个表格, 注意这里我们采用字典存放数据, 因为实际情况中数据是非常稀疏的, 很少有情况是现在这样
def loadData():# key:item value:{item:ratings} 物品-用户评分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}}# key:user value:{item:ratings} 用户-物品评分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,usersitems, users = loadData()
item_df = pd.DataFrame(items).T
user_df = pd.DataFrame(users).T
3.3.2 计算相似矩阵
"""计算物品的相似矩阵"""
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 += 1vec_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就是我们的用户相似性矩阵, 结果如下:
3.3.3 计算前n个相似的用户
"""得到与物品5相似的前n个物品"""
n = 2
similarity_matrix.replace(1,np.nan,inplace=True)
similarity_items = similarity_matrix['E'].sort_values(ascending=False)[:n].index.tolist() # ['A', 'D']
3.3.4 计算最终得分
"""计算最终得分"""
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
有了这个评分, 其实就可以对该用户做推荐了。 这其实就是微型版的ItemCF的工作过程了。
4.协同过滤算法对比
4.1 应用场景
- UserCF
- 基于用户相似度进行推荐, 所以具备更强的社交特性, 这样的特点非常适于用户少, 物品多, 时效性较强的场合, 比如新闻推荐场景, 因为新闻本身兴趣点分散, 相比用户对不同新闻的兴趣偏好, 新闻的及时性,热点性往往更加重要, 所以正好适用于发现热点,跟踪热点的趋势。
- 推荐新信息的能力, 更有可能发现惊喜, 因为看的是人与人的相似性, 推出来的结果可能更有惊喜,可以发现用户潜在但自己尚未察觉的兴趣爱好。
- ItemCF
- 兴趣变化较为稳定的应用, 更接近于个性化的推荐, 适合物品少,用户多,用户兴趣固定持久, 物品更新速度不是太快的场合, 比如推荐艺术品, 音乐, 电影。
4.2 优缺点对比
UserCF | ItemCF | |
---|---|---|
性能 | 适用于用户较少的场合,如果用户很多,计算用户相似度矩阵代价很大 | 适用于物品数明显小于用户数的场合,如果物品很多,物品相似度矩阵会非常巨大 |
适用领域 | 适用于时效性较强,用户个性化兴趣不太明显的领域 | 适用于长尾物品丰富,用户个性化比较强烈的领域 |
实时性 | 用户有新行为,不一定会造成推荐结果的变化 | 用户有新行为,一定会导致推荐结果的变化 |
冷启动 | 在新用户对很少的物品产生行为后,不能立即对他进行个性化推荐,因为用户相似度表是每隔一段时间离线计算的新用户只要对一个物品产生行为,就可以给他推荐和该物品相关的其他物品 | 新物品上线后一段时间,一旦有用户对物品产生行为,就可以将新物品推荐给和对它产生行为的用户兴趣相似的其他用户但没有办法在不离线更新物品相似度表的情况下将新物品推荐给用户 |
推荐理由 | 很难提供令用户信服的推荐解释 | 利用用户的历史行为给用户做推荐解释,可以令用户比较信服 |
4.3 协同过滤算法存在的问题
协同过滤的特点就是完全没有利用到物品本身或者是用户自身的属性, 仅仅利用了用户与物品的交互信息就可以实现推荐,是一个可解释性很强, 非常直观的模型, 但是也存在一些问题。
1.泛化能力弱
协同过滤无法将两个物品相似的信息推广到其他物品的相似性上。 导致的问题是热门物品具有很强的头部效应, 容易跟大量物品产生相似, 而尾部物品由于特征向量稀疏, 导致很少被推荐。
A, B, C, D是物品, 看右边的物品共现矩阵, 可以发现物品D与A、B、C的相似度比较大, 所以很有可能将D推荐给用过A、B、C的用户。 但是物品D与其他物品相似的原因是因为D是一件热门商品, 系统无法找出A、B、C之间相似性的原因是其特征太稀疏, 缺乏相似性计算的直接数据。
2.无法利用更多的信息
协同过滤的特点就是完全没有利用到物品本身或者是用户自身的属性, 仅仅利用了用户与物品的交互信息就可以实现推荐,比较简单高效, 但这也是它的一个短板所在, 由于无法有效的引入用户年龄, 性别,商品描述,商品分类,当前时间,地点等一系列用户特征、物品特征和上下文特征, 这就造成了有效信息的遗漏,不能充分利用其它特征数据。
5.工程上的处理方式
工程上的问题以及常用的处理方法:
- 常用的处理方法:离线计算好的结果存储redis,以供使用
- 假设评分矩阵很大,可考虑稀疏存储问题(eg.存储1的坐标值)
- 用户的评分矩阵往往难以收集,可考虑用户其他维度的属性从而缓解此问题或者先进行一些聚类运算(停留时长、点击、收藏、评论、分享)
- 可能有很多活跃用户可能与其他用户都相似(马太效应),去掉这些用户
- 一般协同不会使用全量用户的协同评分,一般取前n天的较活跃用户来生成评分矩阵
- 协同的结果往往不会直接使用,一般还会根据业务规则进行二次过滤
6.总结
- UserCF: 给用户推荐和他兴趣相似的其他用户喜欢的产品。
- ItemCF: 给用户推荐和他之前喜欢的物品相似的物品。
- 常见相似度的计算方法:cosine相似度、皮尔逊相关系数
- UserCF:适于用户少, 物品多, 时效性较强的场合, 比如新闻推荐场景
- ItemCF: 适合物品少,用户多,用户兴趣固定持久, 物品更新速度不是太快的场合
- 协同过滤算法存在的问题:泛化能力差、无法引入更多的信息
本文仅仅作为个人学习记录,不作为商业用途,谢谢理解。
参考:
1.https://zhongqiang.blog.csdn.net/article/details/107891787
2.https://cloud.tencent.com/developer/article/1940858