本篇为Datawhale组队学习笔记,datawhale推荐系统基础
计算相似度是其中非常重要的步骤。
这个是衡量两个集合的相似度一种指标。 两个用户 u u u和 v v v交互商品交集的数量占这两个用户交互商品并集的数量的比例,称为两个集合的杰卡德相似系数,用符号 s i m u v sim_{uv} simuv表示,其中 N ( u ) , N ( v ) N(u),N(v) N(u),N(v)分别表示用户 u u u和用户 v v v交互商品的集合。 s i m u v = ∣ N ( u ) ∩ N ( v ) ∣ ∣ N ( u ) ∪ N ( v ) ∣ sim_{uv}=\frac{|N(u) \cap N(v)|}{|N(u) \cup N(v)|} simuv=∣N(u)∪N(v)∣∣N(u)∩N(v)∣ 由于杰卡德相似系数一般无法反映具体用户的评分喜好信息, 所以常用来评估用户是否会对某商品进行打分, 而不是预估用户会对某商品打多少分。
余弦相似度衡量了两个向量的夹角,夹角越小越相似。
相比于Jaccard公式来说就是分母有差异,不是两个用户交互商品的并集的数量,而是两个用户分别交互的商品数量的乘积开根号,公式如下: 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)∣
集合可以当作一个[0,1,0,1]的向量,存在元素为1,不存在为0
从向量的角度进行描述,令矩阵 A A A为用户-商品交互矩阵(因为是TopN推荐并不需要用户对物品的评分,只需要知道用户对商品是否有交互就行),即矩阵的每一行表示一个用户对所有商品的交互情况,有交互的商品值为1没有交互的商品值为0,矩阵的列表示所有商品。
用户和商品数量分别为 m , n m,n m,n的话,交互矩阵 A A A就是一个 m m m行 n n n列的矩阵。此时用户的相似度可以表示为(其中 u ⋅ v u\cdot v u⋅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 上述用户-商品交互矩阵在现实情况下是非常的稀疏了,为了避免存储这么大的稀疏矩阵,在计算用户相似度的时候一般会采用集合的方式进行计算。理论上向量之间的相似度计算公式都可以用来计算用户之间的相似度,但是会根据实际的情况选择不同的用户相似度度量方法。
from sklearn.metrics.pairwise import cosine_similarity i = [1, 0, 0, 0] j = [1, 0.5, 0.5, 0] consine_similarity([a, b])首先对于上述的余弦相似度的计算公式写成求和的形式,其中 r u i , r v i r_{ui},r_{vi} rui,rvi分别表示用户 u u u和用户 v v v对商品 i i i是否有交互(或者具体的评分值): 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 u u和用户 v v v对商品 i i i是否有交互(或者具体的评分值), r ˉ u , r ˉ v \bar r_u, \bar r_v rˉu,rˉv分别表示用户 u u u和用户 v v v具体评分的平均值。
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)当一个用户A需要个性化推荐的时候, 我们可以先找到和他有相似兴趣的其他用户, 然后把那些用户喜欢的, 而用户A没有听说过的物品推荐给A。
两个步骤: 1.找到和目标用户兴趣相似的用户集合 2.找到这个集合中的用户喜欢的, 且目标用户没有听说过的物品推荐给目标用户。
第2个步骤中,如何选择物品?依赖于目标用户对相似用户喜欢的物品的一个喜好程度,那么如何衡量这个程度大小呢?
给用户推荐物品的过程可以形象化为一个猜测用户对商品进行打分的任务,上面表格里面是5个用户对于5件物品的一个打分情况,就可以理解为用户对物品的喜欢程度
应用UserCF算法的两个步骤:
首先根据前面的这些打分情况(或者说已有的用户向量)计算一下Alice和用户1, 2, 3, 4的相似程度, 找出与Alice最相似的n个用户根据这n个用户对物品5的评分情况和与Alice的相似程度会猜测出Alice对物品5的评分, 如果评分比较高的话, 就把物品5推荐给用户Alice, 否则不推荐。2中常用方式之一是利用用户相似度和相似用户的评价加权平均获得用户的评价预测。
R u , p = ∑ s ∈ S ( w u , s ⋅ R s , p ) ∑ s ∈ S w u , s R_{\mathrm{u}, \mathrm{p}}=\frac{\sum_{\mathrm{s} \in S}\left(w_{\mathrm{u}, \mathrm{s}} \cdot R_{\mathrm{s}, \mathrm{p}}\right)}{\sum_{\mathrm{s} \in S} w_{\mathrm{u}, \mathrm{s}}} Ru,p=∑s∈Swu,s∑s∈S(wu,s⋅Rs,p) 这个式子里面, 权重 w u , s w_{u,s} wu,s是用户 u u u和用户 s s s的相似度, R s , p R_{s,p} Rs,p是用户 s s s对物品 p p p的评分。
还有一种方式如下, 这种方式考虑的更加前面, 依然是用户相似度作为权值, 但后面不单纯的是其他用户对物品的评分, 而是该物品的评分与此用户的所有评分平均值的差值进行加权平均, 这时候考虑到了有的用户内心的评分标准不一的情况, 即有的用户喜欢打高分, 有的用户喜欢打低分的情况。
P i , j = R ˉ i + ∑ k = 1 n ( S i , k ( R k , j − R ˉ k ) ) ∑ k = 1 n S j , k P_{i, j}=\bar{R}{i}+\frac{\sum_{k=1}^{n}\left(S_{i, k}\left(R_{k, j}-\bar{R}_{k}\right)\right)}{\sum_{k=1}^{n} S_{j, k}} Pi,j=Rˉi+∑k=1nSj,k∑k=1n(Si,k(Rk,j−Rˉk))
在获得用户 u u u对不同物品的评价预测后, 最终的推荐列表根据预测评分进行排序得到。 至此,基于用户的协同过滤算法的推荐过程完成。
梳理一下上面的过程其实就是三步: 计算用户相似性矩阵、得到前n个相似用户、计算最终得分。
所以我们下面的程序也是分为这三步:
首先, 先把数据表给建立起来 # 定义数据集, 也就是那个表格, 注意这里我们采用字典存放数据, 因为实际情况中数据是非常稀疏的, 很少有情况是现在这样 import pandas as pd 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 计算用户相似性矩阵 """计算用户相似性矩阵""" 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]) # 这里可以获得相似性矩阵(共现矩阵),corrcoef皮尔逊相关系数 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]结果:
计算前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_dfUser-based算法存在两个重大问题:
数据稀疏性。 一个大型的电子商务推荐系统一般有非常多的物品,用户可能买的其中不到1%的物品,不同用户之间买的物品重叠性较低,导致算法无法找到一个用户的邻居,即偏好相似的用户。这导致UserCF不适用于那些正反馈获取较困难的应用场景(如酒店预订, 大件商品购买等低频应用)
算法扩展性。 基于用户的协同过滤需要维护用户相似度矩阵以便快速的找出Topn相似用户, 该矩阵的存储开销非常大,存储空间随着用户数量的增加而增加,不适合用户数据量大的情况使用。
由于UserCF技术上的两点缺陷, 导致很多电商平台并没有采用这种算法, 而是采用了ItemCF算法实现最初的推荐系统。
ItemCF算法并不利用物品的内容属性计算物品之间的相似度, 主要通过分析用户的行为记录计算物品之间的相似度, 该算法认为, 物品a和物品c具有很大的相似度是因为喜欢物品a的用户大都喜欢物品c。
基于物品的协同过滤算法主要分为两步:
计算物品之间的相似度根据物品的相似度和用户的历史行为给用户生成推荐列表(购买了该商品的用户也经常购买的其他商品) 如果想知道Alice对物品5打多少分, 基于物品的协同过滤算法会这么做: 首先计算一下物品5和物品1, 2, 3, 4之间的相似性(它们也是向量的形式, 每一列的值就是它们的向量表示, 因为ItemCF认为物品a和物品c具有很大的相似度是因为喜欢物品a的用户大都喜欢物品c, 所以就可以基于每个用户对该物品的打分或者说喜欢程度来向量化物品)找出与物品5最相近的n个物品根据Alice对最相近的n个物品的打分去计算对物品5的打分情况首先是步骤1: P A l i c e , 物 品 5 = R ˉ 物 品 5 + ∑ k = 1 n ( S 物 品 5 , 物 品 k ( R A l i c e , 物 品 k − R ˉ 物 品 k ) ) ∑ k = 1 n S 物 品 k , 物 品 5 = 13 4 + 0.97 ∗ ( 5 − 3.2 ) + 0.58 ∗ ( 4 − 3.4 ) 0.97 + 0.58 = 4.6 P_{Alice, 物品5}=\bar{R}_{物品5}+\frac{\sum_{k=1}^{n}\left(S_{物品5,物品 k}\left(R_{Alice, 物品k}-\bar{R}_{物品k}\right)\right)}{\sum_{k=1}^{n} S_{物品k, 物品5}}=\frac{13}{4}+\frac{0.97*(5-3.2)+0.58*(4-3.4)}{0.97+0.58}=4.6 PAlice,物品5=Rˉ物品5+∑k=1nS物品k,物品5∑k=1n(S物品5,物品k(RAlice,物品k−Rˉ物品k))=413+0.97+0.580.97∗(5−3.2)+0.58∗(4−3.4)=4.6
召回率
对用户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%。
新颖度
用推荐列表中物品的平均流行度度量推荐结果的新颖度。 如果推荐出的物品都很热门, 说明推荐的新颖度较低。 由于物品的流行度分布呈长尾分布, 所以为了流行度的平均值更加稳定, 在计算平均流行度时对每个物品的流行度取对数。
协同过滤算法存在的问题之一就是泛化能力弱, 即协同过滤无法将两个物品相似的信息推广到其他物品的相似性上。 导致的问题是热门物品具有很强的头部效应, 容易跟大量物品产生相似, 而尾部物品由于特征向量稀疏, 导致很少被推荐。 比如下面这个例子: 为了解决这个问题, 同时增加模型的泛化能力,2006年,矩阵分解技术(Matrix Factorization,MF)被提出, 该方法在协同过滤共现矩阵的基础上, 使用更稠密的隐向量表示用户和物品, 挖掘用户和物品的隐含兴趣和隐含特征, 在一定程度上弥补协同过滤模型处理稀疏矩阵能力不足的问题。 具体细节等后面整理, 这里先铺垫一下。