推荐系统知识梳理——协同过滤

注:本次为参加datawhale的打卡活动~详细资料在team-learning-rs

核心系列内容:

  • 协同过滤算法: 包括基于用户的协同过滤(UserCF)和基于商品的协同过滤(ItemCF),这是入门推荐系统的人必看的内容,因为这些算法可以让初学者更加容易的理解推荐算法的思想。
  • 矩阵分解算法: 矩阵分解算法通过引入了隐向量的概念,加强了模型处理稀疏矩阵的能力,也为后续深度学习推荐系统算法中Embedding的使用打下了基础。
  • FM(Factorization Machines): 该算法属于对逻辑回归(LR)算法应用在推荐系统上的一个改进,在LR模型的基础上加上了特征交叉项,该思想不仅在传统的推荐算法中继续使用过,在深度学习推荐算法中也对其进行了改进与应用。
  • GBDT+LR: 该模型仍然是对LR模型的改进,使用树模型做特征交叉,相比于FM的二阶特征交叉,树模型可以对特征进行深度的特征交叉,充分利用了特征之间的相关性。
  • Wide&Deep: 从深度学习推荐系统的演化图中可以看出Wide&Deep模型处在最中间的位置,可以看出该模型在推荐系统发展中的重要地位,此外该算法模型的思想与实现都比较的简单,非常适合初学深度学习推荐系统的学习者们去学习。

协同过滤算法

协同过滤(Collaborative Filtering)推荐算法是最经典、最常用的推荐算法。

所谓协同过滤, 基本思想是根据用户之前的喜好以及其他兴趣相近的用户的选择来给用户推荐物品(基于对用户历史行为数据的挖掘发现用户的喜好偏向, 并预测用户可能喜好的产品进行推荐),一般是仅仅基于用户的行为数据(评价、购买、下载等), 而不依赖于项的任何附加信息(物品自身特征)或者用户的任何附加信息(年龄, 性别等)。目前应用比较广泛的协同过滤算法是基于邻域的方法, 而这种方法主要有下面两种算法:

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

不管是UserCF还是ItemCF算法, 非常重要的步骤之一就是计算用户和用户或者物品和物品之间的相似度, 所以下面先整理常用的相似性度量方法, 然后再对每个算法的具体细节进行展开。

相似性度量方法

  1. 杰卡德(Jaccard)相似系数 这个是衡量两个集合的相似度一种指标。两个用户 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) 由于杰卡德相似系数一般无法反映具体用户的评分喜好信息, 所以常用来评估用户是否会对某商品进行打分, 而不是预估用户会对某商品打多少分。

    • 实例:
      如集合N(u)={1,2,3,4};N(v)={3,4,5,6};
      那么他们的J(X,Y)=1{3,4}/1{1,2,3,4,5,6}=1/3;
#当两个集合元素个数相同
from numpy import *
import scipy.spatial.distance as dist  
matV = mat([[1,1,0,1,0,1,0,0,1],[0,1,1,0,0,0,1,1,1]])
print ("dist.jaccard:", dist.pdist(matV,'jaccard'))
#dist.jaccard: [0.75]

#当集合元素个数不同
def correlation(set_a,set_b):
    unions = len(set_a.union(set_b))
    intersections = len(set_a.intersection(set_b))
    return 1. * intersections / unions

set_a=set([1,1,0,1,0,1,0,0,1])
set_b=set([0,1,1,0,0,0,1,1])
correlation(set_a,set_b)
#1.0
  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 上述用户-商品交互矩阵在现实情况下是非常的稀疏了,为了避免存储这么大的稀疏矩阵,在计算用户相似度的时候一般会采用集合的方式进行计算。理论上向量之间的相似度计算公式都可以用来计算用户之间的相似度,但是会根据实际的情况选择不同的用户相似度度量方法。

这个在具体实现的时候, 可以使用cosine_similarity进行实现:

from sklearn.metrics.pairwise import cosine_similarity
i = [1, 0, 0, 0]
j = [1, 0.5, 0.5, 0]
cosine_similarity([i,j])
'''
array([[1.        , 0.81649658],
       [0.81649658, 1.        ]])
       '''
  1. 皮尔逊相关系数
    皮尔逊相关系数的公式与余弦相似度的计算公式非常的类似,首先对于上述的余弦相似度的计算公式写成求和的形式,其中 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)

基于用户的协同过滤

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

UserCF算法主要包括两个步骤:

  • 找到和目标用户兴趣相似的集合
  • 找到这个集合中的用户喜欢的, 且目标用户没有听说过的物品推荐给目标用户。

上面的两个步骤中, 第一个步骤里面, 我们会基于前面给出的相似性度量的方法找出与目标用户兴趣相似的用户, 而第二个步骤里面, 如何基于相似用户喜欢的物品来对目标用户进行推荐呢? 这个要依赖于目标用户对相似用户喜欢的物品的一个喜好程度, 那么如何衡量这个程度大小呢? 为了更好理解上面的两个步骤, 下面用一个具体的例子把两个步骤具体化。

以下图为例,此例将会用于本文各种算法中

在这里插入图片描述

给用户推荐物品的过程可以形象化为一个猜测用户对商品进行打分的任务,上面表格里面是5个用户对于5件物品的一个打分情况,就可以理解为用户对物品的喜欢程度

应用UserCF算法的两个步骤:

  • 首先根据前面的这些打分情况(或者说已有的用户向量)计算一下Alice和用户1, 2, 3, 4的相似程度, 找出与Alice最相似的n个用户
  • 根据这n个用户对物品5的评分情况和与Alice的相似程度会猜测出Alice对物品5的评分, 如果评分比较高的话, 就把物品5推荐给用户Alice, 否则不推荐。

关于第一个步骤, 上面已经给出了计算两个用户相似性的方法, 这里不再过多赘述, 这里主要解决第二个问题, 如何产生最终结果的预测。

最终结果的预测

根据上面的几种方法, 我们可以计算出向量之间的相似程度, 也就是可以计算出Alice和其他用户的相近程度, 这时候我们就可以选出与Alice最相近的前n个用户, 基于他们对物品5的评价猜测出Alice的打分值, 那么是怎么计算的呢?

这里常用的方式之一是利用用户相似度和相似用户的评价加权平均获得用户的评价预测, 用下面式子表示:

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的评分。

还有一种方式如下, 这种方式考虑的更加全面, 依然是用户相似度作为权值, 但后面不单纯的是其他用户对物品的评分, 而是该物品的评分与此用户的所有评分的差值进行加权平均, 这时候考虑到了有的用户内心的评分标准不一的情况, 即有的用户喜欢打高分, 有的用户喜欢打低分的情况。

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))

P i j P_{i j} Pij 表示的是用户 i i i 对物品 j j j 的评分 , R ˉ i , \quad \bar{R}_{i} ,Rˉi 表示的是用户 i i i 的所有评分的平均值, n n n 表示的是与用户 i i i 相 似的 n n n 个用户 , S i , k , \quad S_{i, k} ,Si,k 表示的是用户 i i i 和用户 k k k 的相似度, R k , j R_{k, j} Rk,j 表示的是用户 k k k 对物品 j j j 的评分 , R ˉ k , \quad \bar{R}_{k} ,Rˉk 表 示的是用户 k k k 的所有评分的平均值。所以这一种计算方式更为推荐。下面的计算将使用这个方式。

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

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

Aim: 猜测Alice对物品5的得分:

  1. 计算Alice与其他用户的相似度(这里使用皮尔逊相关系数)
    用户向量:Alice ( 5 , 3 , 4 , 4 ) , (5,3,4,4), (5,3,4,4), user 1 ( 3 , 1 , 2 , 3 ) , 1(3,1,2,3), 1(3,1,2,3), user 2 ( 4 , 3 , 4 , 3 ) , 2(4,3,4,3), 2(4,3,4,3), user 3 ( 3 , 3 , 1 , 5 ) , 3(3,3,1,5), 3(3,3,1,5), user 4(1,5,5,2)
    这里计算Alice与user1的余弦相似性:
    sim ⁡ (  Alice, user  1 ) = cos ⁡ (  Alice, user  1 ) = 15 + 3 + 8 + 12 sqrt ⁡ ( 25 + 9 + 16 + 16 ) ∗ sqrt ⁡ ( 9 + 1 + 4 + 9 ) = 0.975 \operatorname{sim}(\text { Alice, user } 1)=\cos (\text { Alice, user } 1)=\frac{15+3+8+12}{\operatorname{sqrt}(25+9+16+16){*\operatorname{sqrt}(9+1+4+9)}}=0.975 sim( Alice, user 1)=cos( Alice, user 1)=sqrt(25+9+16+16)sqrt(9+1+4+9)15+3+8+12=0.975
    计算Alice与user1皮尔逊相关系数:
    Alice_ave = 4 =4 \quad =4 user1_ave = 2.25 =2.25 =2.25
    向量减去均值:
     Alice  ( 1 , − 1 , 0 , 0 )  user  1 ( 0.75 , − 1.25 , − 0.25 , 0.75 ) \text { Alice }(1,-1,0,0) \quad \text { user } 1(0.75,-1.25,-0.25,0.75)  Alice (1,1,0,0) user 1(0.75,1.25,0.25,0.75)
a = [5, 3, 4, 4]
b = [3, 1, 2, 3]
pearsonr(a, b)
#(0.8528028654224415, 0.14719713457755845)

计算这lia俩新向量的余弦相似度和上面计算过程一致,结果是0.852

这里我们使用皮尔逊相关系数, 也就是Alice与用户1的相似度是0.85。同样的方式, 我们就可以计算与其他用户的相似度, 这里可以使用numpy的相似度函数得到用户的相似性矩阵:

users = np.array([[5, 3, 4, 4],[3, 1, 2, 3],[4,3,4,3],[3,3,1,5],[1,5,5,2]])
cosine_similarity(users)#余弦相似度
'''
array([[1.        , 0.9753213 , 0.99224264, 0.89072354, 0.79668736],
       [0.9753213 , 1.        , 0.94362852, 0.91160719, 0.67478587],
       [0.99224264, 0.94362852, 1.        , 0.85280287, 0.85811633],
       [0.89072354, 0.91160719, 0.85280287, 1.        , 0.67082039],
       [0.79668736, 0.67478587, 0.85811633, 0.67082039, 1.        ]])
'''

np.corrcoef(users)#皮尔逊相关系数
'''
array([[ 1.        ,  0.85280287,  0.70710678,  0.        , -0.79211803],
       [ 0.85280287,  1.        ,  0.30151134,  0.42640143, -0.88662069],
       [ 0.70710678,  0.30151134,  1.        , -0.70710678, -0.14002801],
       [ 0.        ,  0.42640143, -0.70710678,  1.        , -0.59408853],
       [-0.79211803, -0.88662069, -0.14002801, -0.59408853,  1.        ]])
'''       

从上面看出, Alice用户和用户2,用户3,用户4的相似度是0.7,0, -0.79。 所以如果n=2, 找到与Alice最相近的两个用户是用户1, 和Alice的相似度是0.85, 用户2, 和Alice相似度是0.7

根据相似度用户计算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

其他计算大同小异,之后根据用户评分对用户进行推荐 这时候, 我们就得到了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

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

UserCF编程实现

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

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

  1. 首先, 先把数据表给建立起来 这里我采用了字典的方式, 之所以没有用pandas, 是因为上面举得这个例子其实是个个例, 在真实情况中, 我们知道, 用户对物品的打分情况并不会这么完整, 会存在大量的空值, 所以矩阵会很稀疏, 这时候用DataFrame, 会有大量的NaN。故这里用字典的形式存储。
    用两个字典, 第一个字典是物品-用户的评分映射, 键是物品1-5, 用A-E来表示, 每一个值又是一个字典, 表示的是每个用户对该物品的打分。 第二个字典是用户-物品的评分映射, 键是上面的五个用户, 用1-5表示, 值是该用户对每个物品的打分。
# 定义数据集, 也就是那个表格, 注意这里我们采用字典存放数据, 因为实际情况中数据是非常稀疏的, 很少有情况是现在这样
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

item_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
'''

user_df
'''
	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
'''
  1. 计算用户相似性矩阵 这个是一个共现矩阵, 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#用户相似性矩阵
‘‘’
		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
‘’’

有了相似性矩阵, 我们就可以得到与Alice最相关的前n个用户。

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

similarity_users
#[2, 3]
  1. 计算最终得分 这里就是上面的那个公式了。
"""计算最终得分"""
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
'''

至此, 我们就用代码完成了上面的小例子, 有了这个评分, 我们其实就可以对该用户做推荐了。 这其实就是微型版的UserCF的工作过程了。

注意:基于用户协同过滤的完整代码参考源代码文件中的UserCF.py

UserCF优缺点

User-based算法存在两个重大问题:

  1. 数据稀疏性。 一个大型的电子商务推荐系统一般有非常多的物品,用户可能买的其中不到1%的物品,不同用户之间买的物品重叠性较低,导致算法无法找到一个用户的邻居,即偏好相似的用户。这导致UserCF不适用于那些正反馈获取较困难的应用场景(如酒店预订, 大件商品购买等低频应用)
  2. 算法扩展性。 基于用户的协同过滤需要维护用户相似度矩阵以便快速的找出Topn相似用户, 该矩阵的存储开销非常大,存储空间随着用户数量的增加而增加,不适合用户数据量大的情况使用。

由于UserCF技术上的两点缺陷, 导致很多电商平台并没有采用这种算法, 而是采用了ItemCF算法实现最初的推荐系统。

基于物品的协同过滤

基于物品的协同过滤(ItemCF)的基本思想是预先根据所有用户的历史偏好数据计算物品之间的相似性,然后把与用户喜欢的物品相类似的物品推荐给用户。比如物品a和c非常相似,因为喜欢a的用户同时也喜欢c,而用户A喜欢a,所以把c推荐给用户A。

ItemCF算法并不利用物品的内容属性计算物品之间的相似度, 主要通过分析用户的行为记录计算物品之间的相似度, 该算法认为, 物品a和物品c具有很大的相似度是因为喜欢物品a的用户大都喜欢物品c。
在这里插入图片描述
基于物品的协同过滤算法主要分为两步

  • 计算物品之间的相似度
  • 根据物品的相似度和用户的历史行为给用户生成推荐列表(购买了该商品的用户也经常购买的其他商品)

基于物品的协同过滤算法和基于用户的协同过滤算法很像, 所以我们这里直接还是拿上面Alice的那个例子来看。
在这里插入图片描述

如果想知道Alice对物品5打多少分, 基于物品的协同过滤算法会这么做:

  • 首先计算一下物品5和物品1, 2, 3, 4之间的相似性(它们也是向量的形式, 每一列的值就是它们的向量表示, 因为ItemCF认为物品a和物品c具有很大的相似度是因为喜欢物品a的用户大都喜欢物品c, 所以就可以基于每个用户对该物品的打分或者说喜欢程度来向量化物品)
  • 找出与物品5最相近的n个物品
  • 根据Alice对最相近的n个物品的打分去计算对物品5的打分情况

下面我们就可以具体计算一下,首先是步骤1:

物品向量 :物品1 (3,4,3,1) \quad 物品2 (1,3,3,5) \quad 物品3 (2,4,1,5) \quad 物品4 (3,3,5,2) 物品5 5(3,5,41)

下面计算物品5和物品1之间的余弦相似性:
sim ⁡ (  物品1,   物品5)  = cosine ⁡ (  物品1  ,  物品5  ) = 9 + 20 + 12 + 1 sqrt ⁡ ( 9 + 16 + 9 + 1 ) + sqrt ⁡ ( 9 + 25 + 16 + 1 ) \operatorname{sim}\left(\text { 物品1, } \quad\right. \text { 物品5) }=\operatorname{cosine}(\text { 物品1 }, \quad \text { 物品5 })=\frac{9+20+12+1}{\operatorname{sqrt}(9+16+9+1)+\operatorname{sqrt}(9+25+16+1)} sim( 物品1  物品5) =cosine( 物品, 物品)=sqrt(9+16+9+1)+sqrt(9+25+16+1)9+20+12+1
皮尔逊相关系数依然是向量先各自减去均值,然后求余弦相似性,

python计算:

items = 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)]
pd.DataFrame(np.corrcoef(items),columns=cols, index=cols)#皮尔逊相关系数
'''
			item1		item2		item3		item4		item5
item1	1.000000	-0.648886	-0.435286	0.473684	0.969458
item2	-0.648886	1.000000	0.670820	-0.324443	-0.478091
item3	-0.435286	0.670820	1.000000	-0.870572	-0.427618
item4	0.473684	-0.324443	-0.870572	1.000000	0.581675
item5	0.969458	-0.478091	-0.427618	0.581675	1.000000
'''

pd.DataFrame(cosine_similarity(items),columns=cols, index=cols)#余弦相似度
'''
		item1		item2		item3		item4		item5
item1	1.000000	0.738988	0.747667	0.936916	0.994100
item2	0.738988	1.000000	0.933564	0.813629	0.738851
item3	0.747667	0.933564	1.000000	0.709718	0.722610
item4	0.936916	0.813629	0.709718	1.000000	0.939558
item5	0.994100	0.738851	0.722610	0.939558	1.000000
'''

根据皮尔逊相关系数, 可以找到与物品5最相似的2个物品是item1和item4(n=2), 下面基于上面的公式计算最终得分:

P A l i c e , 物 品 5 = R ˉ 物 品 5 + ∑ k = 1 2 ( S 物 品 5 , 物 品 k ( R A l i c e , 物 品 k − R ˉ 物 品 k ) ) ∑ k = 1 2 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}^{2}\left(S_{物品5,物品 k}\left(R_{Alice, 物品k}-\bar{R}{物品k}\right)\right)}{\sum{k=1}^{2} 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=12Sk,5k=12(S5,k(RAlice,kRˉk))=413+0.97+0.580.97(53.2)+0.58(43.4)=4.6

这时候依然可以向Alice推荐物品5。下面也是python编程实现一下, 和之前例子的差不多:

注:此处的items仍旧为字典形式,并非array

"""计算物品的相似矩阵"""
similarity_matrix = pd.DataFrame(np.ones((len(items), len(items))), index=['A', 'B', 'C', 'D', 'E'], columns=['A', 'B', 'C', 'D', 'E'])
#users = np.array([[5, 3, 4, 4],[3, 1, 2, 3],[4,3,4,3],[3,3,1,5],[1,5,5,2]])

# 遍历每条物品-用户评分数据
for itemId in items:
    for otheritemId in items:
        vec_item = []         # 定义列表, 保存当前两个物品的向量值
        vec_otheritem = []
        #userRagingPairCount = 0     # 两件物品均评过分的用户数
        #print(itemId)
        #print(otheritemId)
        if itemId != otheritemId:    # 物品不同
            for userId in users:    # 遍历用户-物品评分数据
                userRatings = users[userId]    # 每条数据为该用户对所有物品的评分, 这也是个字典
                
                if itemId in userRatings and otheritemId in userRatings:   # 用户对这两个物品都评过分
                    #userRagingPairCount += 1
                    #print(userRatings)
                    #print(itemId)
                    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)[:n].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

完整代码参见:itemcf.py

算法评估

由于UserCF和ItemCF结果评估部分是共性知识点, 所以在这里统一标识。 这里介绍评测指标:

  1. 召回率

对用户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=uT(u)uR(u)T(u) 这个意思就是说, 在用户真实购买或者看过的影片里面, 我模型真正预测出了多少, 这个考察的是模型推荐的一个全面性。

  1. 准确率 准确率定义为: 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=uR(u)uR(u)T(u) 这个意思再说, 在我推荐的所有物品中, 用户真正看的有多少, 这个考察的是我模型推荐的一个准确性。 为了提高准确率, 模型需要把非常有把握的才对用户进行推荐, 所以这时候就减少了推荐的数量, 而这往往就损失了全面性, 真正预测出来的会非常少,所以实际应用中应该综合考虑两者的平衡。

  2. 覆盖率 覆盖率反映了推荐算法发掘长尾的能力, 覆盖率越高, 说明推荐算法越能将长尾中的物品推荐给用户。  Coverage  = ∣ ⋃ u ∈ U R ( u ) ∣ ∣ I ∣ \text { Coverage }=\frac{\left|\bigcup_{u \in U} R(u)\right|}{|I|}  Coverage =IuUR(u)

  3. 该覆盖率表示最终的推荐列表中包含多大比例的物品。如果所有物品都被给推荐给至少一个用户, 那么覆盖率是100%。

  4. 新颖度 用推荐列表中物品的平均流行度度量推荐结果的新颖度。 如果推荐出的物品都很热门, 说明推荐的新颖度较低。 由于物品的流行度分布呈长尾分布, 所以为了流行度的平均值更加稳定, 在计算平均流行度时对每个物品的流行度取对数。

协同过滤算法的权重改进

算法1:
w i j = ∣ N ( i ) ∩ N ( j ) ∣ ∣ N ( i ) ∣ w_{i j}=\frac{|N(i) \cap N(j)|}{|N(i)|} wij=N(i)N(i)N(j)

算法2:
w i j = ∣ N ( i ) ∩ N ( j ) ∣ ∣ N ( i ) ∣ ∣ N ( j ) ∣ w_{i j}=\frac{|N(i) \cap N(j)|}{\sqrt{|N(i)||N(j)|}} wij=N(i)N(j) N(i)N(j)

算法3:
w i j = ∣ N ( i ) ∩ N ( j ) ∣ ∣ N ( i ) ∣ ∣ − α ∣ N ( j ) ∣ α w_{i j}=\frac{|N(i) \cap N(j)|}{|N(i)|^{\mid-\alpha}|N(j)|^{\alpha}} wij=N(i)αN(j)αN(i)N(j)

算法4:
w i j = ∑ ν ∈ N ( i ) ∩ N ( j ) 1 log ⁡ 1 + ∣ N ( u ) ∣ ∣ N ( i ) ∣ 1 − α ∣ N ( j ) ∣ α w_{i j}=\frac{\sum_{\nu \in N(i) \cap N(j)} \frac{1}{\log 1+|N(u)|}}{|N(i)|^{1-\alpha}|N(j)|^{\alpha}} wij=N(i)1αN(j)ανN(i)N(j)log1+N(u)1

  • 基础算法 1为最简单的计算物品相关度的公式, 分子为同时喜好itemi和itemj的用户数
  • 对热门物品的惩罚 图1存在一个问题, 如果 item-j 是很热门的商品,导致很多喜欢 item-i 的用户都喜欢 item-j,这时 w i j w_{ij} wij 就会非常大。同样,几乎所有的物品都和 item-j 的相关度非常高,这显然是不合理的。所以算法2中分母通过引入 N ( j ) N(j) N(j) 来对 item-j 的热度进行惩罚。如果物品很热门, 那么 N ( j ) N(j) N(j)就会越大, 对应的权重就会变小。
  • 对热门物品的进一步惩罚 如果 item-j 极度热门,上面的算法还是不够的。举个例子,《Harry Potter》非常火,买任何一本书的人都会购买它,即使通过算法2的方法对它进行了惩罚,但是《Harry Potter》仍然会获得很高的相似度。这就是推荐系统领域著名的 Harry Potter Problem。
  • 如果需要进一步对热门物品惩罚,可以继续修改公式为如算法3所示,通过调节参数 α α α,$α $越大,惩罚力度越大,热门物品的相似度越低,整体结果的平均热门程度越低。
  • 对活跃用户的惩罚 同样的,Item-based CF 也需要考虑活跃用户(即一个活跃用户(专门做刷单)可能买了非常多的物品)的影响,活跃用户对物品相似度的贡献应该小于不活跃用户。算法4为集合了该权重的算法。

协同过滤算法的问题分析

协同过滤算法存在的问题之一就是泛化能力弱, 即协同过滤无法将两个物品相似的信息推广到其他物品的相似性上。 导致的问题是热门物品具有很强的头部效应, 容易跟大量物品产生相似, 而尾部物品由于特征向量稀疏, 导致很少被推荐。 比如下面这个例子:
在这里插入图片描述

A, B, C, D是物品, 看右边的物品共现矩阵, 可以发现物品D与A、B、C的相似度比较大, 所以很有可能将D推荐给用过A、B、C的用户。 但是物品D与其他物品相似的原因是因为D是一件热门商品, 系统无法找出A、B、C之间相似性的原因是其特征太稀疏, 缺乏相似性计算的直接数据。 所以这就是协同过滤的天然缺陷:推荐系统头部效应明显, 处理稀疏向量的能力弱。

为了解决这个问题, 同时增加模型的泛化能力,2006年,矩阵分解技术(Matrix Factorization,MF)被提出, 该方法在协同过滤共现矩阵的基础上, 使用更稠密的隐向量表示用户和物品, 挖掘用户和物品的隐含兴趣和隐含特征, 在一定程度上弥补协同过滤模型处理稀疏矩阵能力不足的问题。详细参见下一个博客~

Question

1.什么时候使用UserCF,什么时候使用ItemCF?为什么?

答案来自:项亮推荐系统实践

  • UserCF 由于是基于用户相似度进行推荐, 所以具备更强的社交特性, 这样的特点非常适于用户少, 物品多, 时效性较强的场合, 比如新闻推荐场景, 因为新闻本身兴趣点分散, 相比用户对不同新闻的兴趣偏好, 新闻的及时性,热点性往往更加重要, 所以正好适用于发现热点,跟踪热点的趋势。

    另外还具有推荐新信息的能力, 更有可能发现惊喜, 因为看的是人与人的相似性, 推出来的结果可能更有惊喜,可以发现用户潜在但自己尚未察觉的兴趣爱好。

对于用户较少, 要求时效性较强的场合, 就可以考虑UserCF。

  • ItemCF 这个更适用于兴趣变化较为稳定的应用, 更接近于个性化的推荐, 适合物品少,用户多,用户兴趣固定持久, 物品更新速度不是太快的场合, 比如推荐艺术品, 音乐, 电影。

2.协同过滤在计算上有什么缺点?有什么比较好的思路可以解决(缓解)?

较差的稀疏向量处理能力

第一个问题就是泛化能力弱, 即协同过滤无法将两个物品相似的信息推广到其他物品的相似性上。 导致的问题是热门物品具有很强的头部效应, 容易跟大量物品产生相似, 而尾部物品由于特征向量稀疏, 导致很少被推荐。 比如下面这个例子:
图片

A, B, C, D是物品, 看右边的物品共现矩阵, 可以发现物品D与A、B、C的相似度比较大, 所以很有可能将D推荐给用过A、B、C的用户。 但是物品D与其他物品相似的原因是因为D是一件热门商品, 系统无法找出A、B、C之间相似性的原因是其特征太稀疏, 缺乏相似性计算的直接数据。 所以这就是协同过滤的天然缺陷:推荐系统头部效应明显, 处理稀疏向量的能力弱。

为了解决这个问题, 同时增加模型的泛化能力,2006年,矩阵分解技术(Matrix Factorization,MF)被提出, 该方法在协同过滤共现矩阵的基础上, 使用更稠密的隐向量表示用户和物品, 挖掘用户和物品的隐含兴趣和隐含特征, 在一定程度上弥补协同过滤模型处理稀疏矩阵能力不足的问题。

3.上面介绍的相似度计算方法有什么优劣之处?

cosine相似度还是比较常用的, 一般效果也不会太差, 但是对于评分数据不规范的时候, 也就是说, 存在有的用户喜欢打高分, 有的用户喜欢打低分情况的时候,有的用户喜欢乱打分的情况, 这时候consine相似度算出来的结果可能就不是那么准确了, 比如下面这种情况:

x Y z d 4 4 5 e 1 1 2 f 4 1 5 \begin{array}{|l|l|l|l|} \hline & x & Y & z \\ \hline d & 4 & 4 & 5 \\ \hline e & 1 & 1 & 2 \\ \hline f & 4 & 1 & 5 \\ \hline \end{array} defx414Y411z525

这时候, 如果用余弦相似度进行计算, 会发现用户d和用户f比较相似, 而实际上, 如果看这个商品喜好的一个趋势的话, 其实d和e比较相近, 只不过e比较喜欢打低分, d比较喜欢打高分。 所以对于这种用户评分偏置的情况, 余弦相似度就不是那么好了, 可以考虑使用下面的皮尔逊相关系数。

4.协同过滤还存在其他什么缺陷?有什么比较好的思路可以解决(缓解)?

  • 协同过滤的特点就是完全没有利用到物品本身或者是用户自身的属性, 仅仅利用了用户与物品的交互信息就可以实现推荐,比较简单高效, 但这也是它的一个短板所在, 由于无法有效的引入用户年龄, 性别,商品描述,商品分类,当前时间,地点等一系列用户特征、物品特征和上下文特征, 这就造成了有效信息的遗漏,不能充分利用其它特征数据。

  • 为了解决这个问题, 在推荐模型中引用更多的特征,推荐系统慢慢的从以协同过滤为核心到了以逻辑回归模型为核心, 提出了能够综合不同类型特征的机器学习模型。
    在这里插入图片描述

推荐阅读:推荐系统实战(二)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浩波的笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值