推荐系统01:协同过滤(collabrative filtering))

it2026-03-10  3

1. 协同过滤算法

基本思想: 根据用户以往的喜好及其他兴趣相近的用户的选择给用户推荐物品。

仅依赖于用户行为数据,而不依赖于其他附加信息(比如物品自身特征)或者用户的附加信息(年龄,性别等)。

目前应用较广泛的协同过滤算法是基于领域的方法,共分为两种:

基于用户的协同过滤算法(UserCF): 给用户推荐和他兴趣相似的其他用户喜欢的产品

基于物品的协同过滤算法(ItemCF): 给用户推荐和他之前喜欢的物品相似的物品

无论上述哪种算法,计算用户之间的相似度或物品之间的相似度都非常重要,所以首先看看如何去衡量相似度。

2.相似性度量方法

杰卡德(Jaccard)相似系数余弦相似度皮尔逊相关系数

2.1 杰卡德相似系数

主要用来衡量两个集合的相似度。 我查了另外一个博客[2]里的说法,其实杰卡德相似系数描述的就是集合A和集合B的交集元素在其并集元素中所占的比例: J ( A , B ) = A ∩ B A ∪ B J(A,B) = \frac{A\cap B}{A \cup B} JAB=ABAB

将杰卡德系数放在推荐系统中就表示:

两个用户 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)|}{\sqrt{|N(u)| \cup|N(v)|}} simuv=N(u)N(v) N(u)N(v) 由于杰卡德相似系数一般无法反映具体用户的评分喜好信息, 所以常用来评估用户是否会对某商品进行打分, 而不是预估用户会对某商品打多少分。

所谓交互商品可能就是用户的”选择“,比如说购物车里购买的物品,或者是音乐平台听到的歌曲等等。

2.2 余弦相似度

直观一点来讲,向量空间中的两个向量夹角的余弦值可以作为衡量两个个体间差异的大小。

如果余弦值接近1,就说明两个向量的方向越一致[2](当余弦值为1时,两个向量同向)。

如果上面这两个夹角很大,就说明两个向量的相似性很低——基本不相似。 余弦相似度常常被用在判断词向量的相似程度中。 在推荐系统中:

余弦相似度衡量了两个向量的夹角,夹角越小越相似。首先从集合的角度描述余弦相似度,相比于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) 从向量的角度进行描述,令矩阵 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 uv指的是向量点积): 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)=uvuv 上述用户-商品交互矩阵在现实情况下是非常的稀疏了,为了避免存储这么大的稀疏矩阵,在计算用户相似度的时候一般会采用集合的方式进行计算。理论上向量之间的相似度计算公式都可以用来计算用户之间的相似度,但是会根据实际的情况选择不同的用户相似度度量方法。

在具体实现时,可以用到sk-learn中的一个模块来快速计算:

from sklearn.metrics.pairwise import cosine_similarity i = [1, 0, 0, 0] j = [1, 0.5, 0.5, 0] cosine_similarity([a, b])

其实这里会报错,因为cosine_similarity这个函数要求传入的是array数组,而非list。而且要注意,传入的应该是二维数组,否则会提醒reshape。 遂改成:

import numpy as np from sklearn.metrics.pairwise import cosine_similarity i = np.array([1, 0, 0, 0]).reshape(-1,1) j = np.array([1, 0.5, 0.5, 0]).reshape(-1,1) cosine_similarity(i,j)

输出结果:

2.3 皮尔逊相关系数

皮尔逊相关系数就更熟悉了,统计学中经常用到。

皮尔逊相关系数的公式与余弦相似度的计算公式非常的类似,首先对于上述的余弦相似度的计算公式写成求和的形式,其中 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 iruirvi 如下是皮尔逊相关系数计算公式,其中 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)=iI(ruirˉu)2 iI(rvirˉv)2 iI(ruirˉu)(rvirˉv) 所以相比余弦相似度,皮尔逊相关系数通过使用用户的平均分对各独立评分进行修正,减小了用户评分偏置的影响。具体实现, 我们也是可以调包, 这个计算方式很多, 下面是其中的一种:

from scipy.stats import pearsonr i = [1, 0, 0, 0] j = [1, 0.5, 0.5, 0] pearsonr(i, j) #输出结果: #(0.816496580927726, 0.18350341907227397)

3.基于用户的协同过滤(UserCF)

俗话说,人以类聚,物以平分。现在小米菲有个好朋友叫梅儿,她和小米菲一样都喜欢口红,但是小米菲以前只用过Mac口红和植村秀口红,现在小米菲想换个牌子用用,梅儿就把自己爱用的阿玛尼口红介绍给了小米菲。

这就是基于用户的协同过滤。

基于用户的协同过滤(以下用UserCF表示),思想其实比较简单,当一个用户A需要个性化推荐的时候, 我们可以先找到和他有相似兴趣的其他用户, 然后把那些用户喜欢的, 而用户A没有听说过的物品推荐给A。

这一算法主要包括两个步骤:

1.找到和目标用户兴趣相似的集合(口红) 2.找到这个集合中的用户喜欢的(口红牌子), 且目标用户没有听说过的物品推荐给目标用户。

还记得2.中提到的三种相似性度量方法吗?这种方法就可以用来找出与目标用户兴趣相似的用户。 现在有个问题,梅儿喜欢植村秀、娇兰、阿玛尼…这几种口红。爱丽丝涉猎范围很广喜欢植村秀、完美日记。而鲍伯斯是个国货爱好者,喜欢完美日记、橘朵…还有玛丽、芭芭拉等人,大家喜欢的口红都不太一样。

我们需要先和小米菲最接近的几个好朋友。

也就是,如何基于相似用户喜欢的物品对目标用户进行推荐? 上表代表了Alice(小米菲)和她的几个朋友对几种物品(口红)的喜爱程度,每一行可以看作是一个用户向量。 首先我们要通过向量计算其他人和Alice的相似程度。 然后猜测一下,小米菲对物品5(也就是她没用过的阿玛尼口红)的喜爱程度。 如果预测出来的结果,小米菲觉得阿玛尼很好(评分很高),就推荐给她——因为她很大几率是会去购买的。 如果评分并不高,那就不给她推荐咯。

最终结果的预测 相似性度量可以去参考2. 那么怎么去猜小米菲对未知口红阿玛尼(物品5)的打分值呢?

常用方法1:利用用户相似度和相似用户的评价加权平均获得用户的评价预测

下面这个公式很简单,谁和小米菲更像,那小米菲的打分值应该跟她更相近,这个人的权重也越高。实际上,这个人的权重就是相似度:

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=sSwu,ssS(wu,sRs,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的评分。

第二种方法比第一种方法考虑的更加全面。

常用方法2: 权重:相似度(这和前面是一样的) 分值:是其他用户(比如梅儿)对该物品的评分与其他用户(梅儿)对该物品的评分差值的加权平均。

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,kk=1n(Si,k(Rk,jRˉk)) 所以这一种计算方式更为推荐。下面的计算将使用这个方式。

在获得用户 u u u对不同物品的评价预测后, 最终的推荐列表根据预测评分进行排序得到。 至此,基于用户的协同过滤算法的推荐过程完成。

根据上面的问题, 下面手算一下:

目标: 猜测Alice对物品5的得分: 2.根据相似度用户计算Alice对物品5的最终得分 用户1对物品5的评分是3, 用户2对物品5的打分是5, 那么根据上面的计算公式, 可以计算出Alice对物品5的最终得分是 P A l i c e , 物 品 5 = R ˉ A l i c e + ∑ k = 1 2 ( S A l i c e , u s e r k ( R u s e r k , 物 品 5 − R ˉ u s e r k ) ) ∑ k = 1 2 S A l i c e , u s e r k = 4 + 0.85 ∗ ( 3 − 2.4 ) + 0.7 ∗ ( 5 − 3.8 ) 0.85 + 0.7 = 4.87 P_{Alice, 物品5}=\bar{R}{Alice}+\frac{\sum{k=1}^{2}\left(S_{Alice,user k}\left(R_{userk, 物品5}-\bar{R}{userk}\right)\right)}{\sum{k=1}^{2} S_{Alice, userk}}=4+\frac{0.85*(3-2.4)+0.7*(5-3.8)}{0.85+0.7}=4.87 PAlice,5=RˉAlice+k=12SAlice,userkk=12(SAlice,userk(Ruserk,5Rˉuserk))=4+0.85+0.70.85(32.4)+0.7(53.8)=4.87

3.根据用户评分对用户进行推荐 这时候, 我们就得到了Alice对物品5的得分是4.87, 根据Alice的打分对物品排个序从大到小: 物 品 1 > 物 品 5 > 物 品 3 = 物 品 4 > 物 品 2 物品1>物品5>物品3=物品4>物品2 1>5>3=4>2 这时候,如果要向Alice推荐2款产品的话, 我们就可以推荐物品1和物品5给Alice

至此, 基于用户的协同过滤算法原理介绍完毕。

4.编程实现

这里简单的通过编程实现上面的案例,为后面的大作业做一个热身, 梳理一下上面的过程其实就是三步: 计算用户相似性矩阵、得到前n个相似用户、计算最终得分。

所以我们下面的程序也是分为这三步:

首先, 先把数据表给建立起来 这里我采用了字典的方式, 之所以没有用pandas, 是因为上面举得这个例子其实是个个例, 在真实情况中, 我们知道, 用户对物品的打分情况并不会这么完整, 会存在大量的空值, 所以矩阵会很稀疏, 这时候用DataFrame, 会有大量的NaN。故这里用字典的形式存储。 用两个字典, 第一个字典是物品-用户的评分映射, 键是物品1-5, 用A-E来表示, 每一个值又是一个字典, 表示的是每个用户对该物品的打分。 第二个字典是用户-物品的评分映射, 键是上面的五个用户, 用1-5表示, 值是该用户对每个物品的打分。

# 定义数据集, 也就是那个表格, 注意这里我们采用字典存放数据, 因为实际情况中数据是非常稀疏的, 很少有情况是现在这样 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

2.计算用户相似性矩阵 这个是一个共现矩阵, 5*5,行代表每个用户, 列代表每个用户, 值代表用户和用户的相关性,这里的思路是这样, 因为要求用户和用户两两的相关性, 所以需要用双层循环遍历用户-物品评分数据, 当不是同一个用户的时候, 我们要去遍历物品-用户评分数据, 在里面去找这两个用户同时对该物品评过分的数据放入到这两个用户向量中。 因为正常情况下会存在很多的NAN, 即可能用户并没有对某个物品进行评分过, 这样的不能当做用户向量的一部分, 没法计算相似性。 还是看代码吧, 感觉不太好描述:

"""计算用户相似性矩阵""" 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)如下: 3.计算前n个相似的用户

"""计算前n个相似的用户""" n = 2 similarity_users = similarity_matrix[1].sort_values(ascending=False)[:n].index.tolist() # [2, 3] 也就是用户1和用户2

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

得分结果如下:

![在这里插入代码片](https://img-blog.csdnimg.cn/20201022213933755.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pZmZ5X2xvdmVfenl4,size_16,color_FFFFFF,t_70#pic_center)

思考

1.什么时候使用用户协同过滤?

UserCF 由于是基于用户相似度进行推荐, 所以具备更强的社交特性, 这样的特点非常适于用户少, 物品多, 时效性较强的场合, 比如新闻推荐场景, 因为新闻本身兴趣点分散, 相比用户对不同新闻的兴趣偏好, 新闻的及时性,热点性往往更加重要, 所以正好适用于发现热点,跟踪热点的趋势。 另外还具有推荐新信息的能力, 更有可能发现惊喜, 因为看的是人与人的相似性, 推出来的结果可能更有惊喜,可以发现用户潜在但自己尚未察觉的兴趣爱好。

References:

[1] https://github.com/datawhalechina/team-learning-rs/blob/master/RecommendationSystemFundamentals/02%20%E5%8D%8F%E5%90%8C%E8%BF%87%E6%BB%A4.md

[2] 杰卡德相似系数 http://blog.sina.com.cn/s/blog_407e5c1c0102vxyy.html

[3] 余弦相似度 https://blog.csdn.net/u012160689/article/details/15341303

最新回复(0)