1. 前言

随着信息技术和互联网的发展, 我们已经步入了一个信息过载的时代,这个时代,无论是信息消费者还是信息生产者都遇到了很大的挑战:

  • 信息消费者:如何从大量的信息中找到自己感兴趣的信息?
  • 信息生产者:如何让自己生产的信息脱颖而出, 受到广大用户的关注?

为了解决这个矛盾, 推荐系统应时而生, 并飞速前进,在用户和信息之间架起了一道桥梁,一方面帮助用户发现对自己有价值的信息, 一方面让信息能够展现在对它感兴趣的用户前面。 推荐系统近几年有了深度学习的助推发展之势迅猛, 从前深度学习的传统推荐模型(协同过滤,矩阵分解,LR, FM, FFM, GBDT)到深度学习的浪潮之巅(DNN, Deep Crossing, DIN, DIEN, Wide&Deep, Deep&Cross, DeepFM, AFM, NFM, PNN, FNN, DRN), 现在正无时无刻不影响着大众的生活。

推荐系统通过分析用户的历史行为给用户的兴趣建模, 从而主动给用户推荐给能够满足他们兴趣和需求的信息, 能够真正的“懂你”。 想上网购物的时候, 推荐系统在帮我们挑选商品, 想看资讯的时候, 推荐系统为我们准备了感兴趣的新闻, 想学习充电的时候, 推荐系统为我们提供最合适的课程, 想消遣放松的时候, 推荐系统为我们奉上欲罢不能的短视频…, 所以当我们淹没在信息的海洋时, 推荐系统正在拨开一层层波浪, 为我们追寻多姿多彩的生活!

这段时间刚好开始学习推荐系统, 通过王喆老师的《深度学习推荐系统》已经梳理好了知识体系, 了解了当前推荐系统领域各种主流的模型架构和技术。 所以接下来的时间就开始对这棵大树开枝散叶,对每一块知识点进行学习总结。 所以接下来一块目睹推荐系统的风采吧!

这次整理重点放在推荐系统的模型方面, 先从传统推荐模型开始, 然后到深度学习模型。 传统模型的演化关系拿书上的一张图片, 便于梳理传统推荐模型的进化关系脉络, 对知识有个宏观的把握:

今天是第一篇, 我们从最经典的协同过滤算法开始, 虽然这个离我们比较久远, 但它是对业界影响力最大, 应用最广泛的一种经典模型, 从1992年一直延续至今, 尽管现在协同过滤差不多都已经融入到了深度学习,但模型的基本原理依然还是基于经典协同过滤的思路, 或者是在协同过滤的基础上进行演化, 所以这个算法模型依然有种“宝刀未老”的感觉, 掌握和学习非常有必要。

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

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

所以本篇文章的核心就是这两个算法, 所以分成两块, 每一块都是先对理论部分进行补充, 包括算法的工作原理和计算方式,相似度的度量方式等。 然后通过一个例子进行解释加深理解,最后基于学习的理论知识进行代码实战, 完成一个推荐小任务, 这样可以对协同过滤的思想有个更深的理解。

大纲如下:

  • 基于用户的协同协同过滤
  • 基于物品的协同过滤算法
  • 应用场景及存在问题分析
  • 总结

Ok, let’s go!

2. 基于用户的协同过滤

基于用户的协同过滤(UserCF)可以追溯到1993年, 可以说是非常早的一种算法了, 这种算法的思想其实比较简单, 当一个用户A需要个性化推荐的时候, 我们可以先找到和他有相似兴趣的其他用户, 然后把那些用户喜欢的, 而用户A没有听说过的物品推荐给A

所以基于用户的协同过滤算法主要包括两个步骤:

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

如果这样说比较抽象的话, 我们可以看一个例子, 了解一下给用户推荐物荐到底是怎么推荐的, 看下面这个表格:


其实, 给用户推荐物品的过程可以形象化为一个猜测用户对商品进行打分的任务,上面表格里面是5个用户对于5件物品的一个打分情况,就可以理解为用户对物品的喜欢程度(这里多说一句,这种表格在实际情况中就是根据用户的行为进行统计出来的, 比如用户购买了某个物品, 那直接量化为5分, 用户收藏了某个物品, 量化为4分, 用户看某个物品很久量化为3分等, 通过这样的量化就相当于把每个用户对物品的行为刻画成了向量的形式, 我们就可以计算相似程度了), 我们的任务是判断到底该不该把物品5推荐给用户Alice呢?

如果是基于用户的协同过滤算法, 根据上面的算法步骤, 其实它会这么做:

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

所以这个过程相信很容易理解了吧, 下面我们就尝试解决这个问题, 但是在解决之前, 还得先补充两个知识点: 第一个就是用户之间的相似性怎么衡量? 第二个就是选出了topn个与Alice最相似的用户来之后, 如果根据他们的相似程度和对物品5的分数计算Alice对物品5的分数?

2.1 计算两个向量之间的相似程度

计算相似度需要根据特点的不同选择不同的相似度计算方法, 比较常用的有下面几种, 更多的关于向量的相似性度量,可以参考这篇文章:

  1. 杰卡德(Jaccard)相似系数
    这个是衡量两个集合的相似度一种指标。 两个集合A和B的交集元素在A,B的并集中所占的比例,称为两个集合的杰卡德相似系数,用符号J(A,B)表示。
    J(A,B)=∣A∩B∣∣A∪B∣\mathrm{J}(\mathrm{A}, \mathrm{B})=\frac{|A \cap B|}{|A \cup B|}J(A,B)=∣A∪B∣∣A∩B∣​
    在项亮老师的《推荐系统实战》中, 计算相似度出现过这个公式。 所以在这里简单的说一下。

  2. 余弦相似度
    余弦相似度这个很熟悉了吧, 它衡量了用户向量iii和jjj之间的向量夹角的大小, 夹角越小, 说明相似度越大, 两个用户越相似。 公式如下:
    sim⁡(i,j)=cos⁡(i,j)=i⋅j∥i∥⋅∥j∥\operatorname{sim}(i, j)=\cos (i, j)=\frac{i \cdot j}{\|i\| \cdot\|j\|}sim(i,j)=cos(i,j)=∥i∥⋅∥j∥i⋅j​
    这里面是向量表示的, 如果不太明白的话, 可以换成具体的数值表示, 假设两个用户的向量是nnn维的, 分别是i(x11,x12,...x1n)、j(x21,x22,..x2n)i(x_{11}, x_{12}, ...x_{1n})、j(x_{21}, x_{22}, ..x_{2n})i(x11​,x12​,...x1n​)、j(x21​,x22​,..x2n​), 那么余弦相似度为:
    cos⁡(θ)=∑k=1nx1kx2k∑k=1nx1k2∑k=1nx2k2\cos (\theta)=\frac{\sum_{k=1}^{n} x_{1 k} x_{2 k}}{\sqrt{\sum_{k=1}^{n} x_{1 k}^{2}} \sqrt{\sum_{k=1}^{n} x_{2 k}^{2}}}cos(θ)=∑k=1n​x1k2​​∑k=1n​x2k2​​∑k=1n​x1k​x2k​​

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

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

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


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

  3. 皮尔逊相关系数
    这个也是非常常用的一种计算相似度的一种方式, 相比余弦相似度, 皮尔逊相关系数通过使用用户平均分对个独立评分进行修正, 减少了用户评分偏置的影响。简单的说, 其实pearson做的就是把两个向量都减去他们的均值, 然后再计算consine值。 用pearson来计算用户相似进行推荐的话, 效果还是好于consine的。公式如下:
    sim⁡(i,j)=∑pϵP(Ri,p−Rˉi)(Rjp−Rˉj)∑pϵP(Ri,p−Rˉi)2∑pϵP(Rj,p−Rˉj)2\operatorname{sim}(i, j)=\frac{\sum_{\mathrm{p} \epsilon P}\left(R_{\mathrm{i}, \mathrm{p}}-\bar{R}_{\mathrm{i}}\right)\left(R_{\mathrm{jp}}-\bar{R}_{\mathrm{j}}\right)}{\sqrt{\sum_{\mathrm{p} \epsilon P}\left(R_{\mathrm{i}, \mathrm{p}}-\bar{R}_{\mathrm{i}}\right)^{2}} \sqrt{\sum_{\mathrm{p} \epsilon P}\left(R_{\mathrm{j}, \mathrm{p}}-\bar{R}_{\mathrm{j}}\right)^{2}}}sim(i,j)=∑pϵP​(Ri,p​−Rˉi​)2​∑pϵP​(Rj,p​−Rˉj​)2​∑pϵP​(Ri,p​−Rˉi​)(Rjp​−Rˉj​)​
    这个式子里面其实就是每个向量先减去了它的平均值, 然后在计算余弦相似度, 其中Ri,pR_{i,p}Ri,p​代表用户iii对物品ppp的评分。 Riˉ\bar{R_i}Ri​ˉ​代表用户iii对所有物品的平均评分, PPP代表所有物品的集合, ppp表示某个物品。 待会计算上面例子的时候就很容易明白了。具体实现, 我们也是可以调包, 这个计算方式很多, 下面是其中的一种:

    from scipy.stats import pearsonri = [1, 0, 0, 0]
    j = [1, 0.5, 0.5, 0]
    pearsonr(i, j)
    

常用的就是这几种, 其余方法, 例如欧式距离, 曼哈顿距离, 马氏距离等参考上面给出的那篇文章, 在协同过滤这里衡量相似性往往喜欢用余弦相似度或者皮尔逊相关系数。第一个问题解决。

谈到距离,这里再补充一个问题为什么在一些场景中要使用余弦相似度而不是欧式距离呢?

有些时候,我们其实并不关心两个向量的绝对大小,而是关注向量之间的夹角大小,此时就要用余弦相似度。 比如,当一对文本相似度的长度差距很大,但是内容相似时,如果是使用词频或者词向量作为特征,它们在特征空间中的欧式距离非常大,而使用余弦相似度的时候,可能很小,相似度高,这正是我们想要的。 此外,在文本,图像,视频领域,研究对象的特征维度往往很高,余弦相似度在高维情况仍然保持“相同时为1,正交时为0,相反时-1”的性质,而欧式距离的数值则受维度的影响,范围不固定,并且含义也比较模糊。不过,向量的模长归一化了之后,就成了皮尔逊相关系数了,这时候欧式距离与余弦相似度就有着单调关系了。

总体的来说,欧式距离体现数值上的绝对差异,而余弦距离体现方向上的相对差异。具体使用中,看需求决定。

  • 如果要统计两部剧的用户观看行为,用户A的观看向量(0,1), 用户B为(1,0), 此时二者的余弦距离很大,而欧式距离很小。我们分析两个用户对于不同视频的偏好,更关注相对差异,显然应当用余弦距离。
  • 而当我们分析用户活跃度,以登录次数和平均观看时长作为特征时, 余弦距离会认为(1,10)和(10,100)两个用户距离很近,但显然这两个用户活跃度是有着极大差异的。此时我们关注的是数值绝对差异,应当使用欧式距离。

2.2 最终结果的预测

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

这里常用的方式之一是利用用户相似度和相似用户的评价加权平均获得用户的评价预测, 用下面式子表示:
Ru,p=∑s∈S(wu,s⋅Rs,p)∑s∈Swu,sR_{\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∈S​wu,s​∑s∈S​(wu,s​⋅Rs,p​)​
这个式子里面, 权重wu,sw_{u,s}wu,s​是用户uuu和用户sss的相似度, Rs,pR_{s,p}Rs,p​是用户sss对物品ppp的评分。

还有一种方式如下, 这种方式考虑的更加全面, 依然是用户相似度作为权值, 但后面不单纯的是其他用户对物品的评分, 而是该物品的评分与此用户的所有评分的差值进行加权平均, 这时候考虑到了有的用户内心的评分标准不一的情况, 即有的用户喜欢打高分, 有的用户喜欢打低分的情况。
Pi,j=Rˉi+∑k=1n(Si,k(Rk,j−Rˉk))∑k=1nSj,kP_{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=1n​Sj,k​∑k=1n​(Si,k​(Rk,j​−Rˉk​))​
所以这一种计算方式更为推荐。下面的计算将使用这个方式。这里的Sj,kS_{j,k}Sj,k​是用户iii和用户kkk的相似度, 和上面的wu,sw_{u,s}wu,s​代表的是一个意思。

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

2.3 拿一个例子来具体看一下

下面我们解决上面的问题, 把图拿过来:

猜测Alice对物品5的得分:

  1. 计算Alice与其他用户的相似度(这里使用皮尔逊相关系数)

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

    从这里看出, Alice用户和用户2, 用户3, 用户4的相似度是0.7, 0, -0.79。 所以如果n=2, 找到与Alice最相近的两个用户是用户1, 和Alice的相似度是0.85, 用户2, 和Alice相似度是0.7
  2. 根据相似度用户计算Alice对物品5的最终得分
    用户1对物品5的评分是3, 用户2对物品5的打分是5, 那么根据上面的计算公式, 可以计算出Alice对物品5的最终得分是
    PAlice,物品5=RˉAlice+∑k=12(SAlice,userk(Ruserk,物品5−Rˉuserk))∑k=12SAlice,userk=4+0.85∗(3−2.4)+0.7∗(5−3.8)0.85+0.7=4.87P_{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.87PAlice,物品5​=RˉAlice​+∑k=12​SAlice,userk​∑k=12​(SAlice,userk​(Ruserk,物品5​−Rˉuserk​))​=4+0.85+0.70.85∗(3−2.4)+0.7∗(5−3.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。

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

2.4 编程实现

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

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

  1. 首先, 先把数据表给建立起来
    这里我采用了字典的方式, 之所以没有用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,usersitems, 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就是我们的用户相似性矩阵, 张下面这样:

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

  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
    

    结果如下:

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

这里其实本来想把项亮老师推荐系统实践里面的那个UserCF例子放过来, 但是那个可能不是太好理解, 因为那里做的是TopN的推荐, 那里的任务是预测用户会不会对某个物品进行评分, 而不是预测用户在准备为物品打分的情况下打多少分。 所以那一个类似于找到用户可能打分的物品然后对用户推荐, 而这里是预测出用户对物品打多少分, 然后根据分数推荐物品。 这俩还是有区别的。 所以那一个实战的例子放在了GitHub, 后面会给出链接。

2.5 优缺点

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

  1. 数据稀疏性。
    一个大型的电子商务推荐系统一般有非常多的物品,用户可能买的其中不到1%的物品,不同用户之间买的物品重叠性较低,导致算法无法找到一个用户的邻居,即偏好相似的用户。这导致UserCF不适用于那些正反馈获取较困难的应用场景(如酒店预订, 大件商品购买等低频应用)

  2. 算法扩展性。
    基于用户的协同过滤需要维护用户相似度矩阵以便快速的找出Topn相似用户, 该矩阵的存储开销非常大,存储空间随着用户数量的增加而增加,不适合用户数据量大的情况使用

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

3. 基于物品的协同过滤

基于物品的协同过滤(ItemCF)的基本思想是预先根据所有用户的历史偏好数据计算物品之间的相似性,然后把与用户喜欢的物品相类似的物品推荐给用户。比如物品a和c非常相似,因为喜欢a的用户同时也喜欢c,而用户A喜欢a,所以把c推荐给用户A。ItemCF算法并不利用物品的内容属性计算物品之间的相似度, 主要通过分析用户的行为记录计算物品之间的相似度, 该算法认为, 物品a和物品c具有很大的相似度是因为喜欢物品a的用户大都喜欢物品c

基于物品的协同过滤算法主要分为两步:

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

3.1 还是前面的例子

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

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

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

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


由于计算比较麻烦, 这里直接用python计算了:


根据皮尔逊相关系数, 可以找到与物品5最相似的2个物品是item1和item4(n=2), 下面基于上面的公式计算最终得分:
PAlice,物品5=Rˉ物品5+∑k=12(S物品5,物品k(RAlice,物品k−Rˉ物品k))∑k=12S物品k,物品5=134+0.97∗(5−3.2)+0.58∗(4−3.4)0.97+0.58=4.6P_{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.6PAlice,物品5​=Rˉ物品5​+∑k=12​S物品k,物品5​∑k=12​(S物品5,物品k​(RAlice,物品k​−Rˉ物品k​))​=413​+0.97+0.580.97∗(5−3.2)+0.58∗(4−3.4)​=4.6

这时候依然可以向Alice推荐物品5。

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

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[itemId][otheritemId] = cosine_similarity(np.array(vec_item), np.array(vec_otheritem))[0][1]

这里就是物品的相似度矩阵了, 张下面这个样子:

然后也是得到与物品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

结果如下:

同样的, 这里还是实现了上面例子中的那个过程。 项亮老师书里面的那个基于物品的协同过滤放在了GitHub。

3.3 优缺点分析

上面说道UserCF存在两个问题, 数据稀疏性和算法扩展性问题, 而ItemCF算法因为物品直接的相似性相对比较固定,所以可以预先在线下计算好不同物品之间的相似度,把结果存在表中,当推荐时进行查表,计算用户可能的打分值,可以同时解决上面两个问题。在Item-to-Item论文中, 作者得出结论:

  1. Item-based算法的预测结果比User-based算法的质量要高一点。
  2. 由于Item-based算法可以预先计算好物品的相似度,所以在线的预测性能要比User-based算法的高。
  3. 用物品的一个小部分子集也可以得到高质量的预测结果。

至于存在的问题, 应该是CF存在的共性问题了, 放在下面一块。

4. 应用场景和存在问题分析

4.1 应用场景

这里首先介绍UserCF和ItemCF的应用场景, 因为其原理不同, 应用上也会有所区别。

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

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

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

下面是UserCF和ItemCF的优缺点对比: (来自项亮推荐系统实践)

4.2 问题分析

这里分析一下协同过滤算法存在的共性问题, 协同过滤的特点就是完全没有利用到物品本身或者是用户自身的属性, 仅仅利用了用户与物品的交互信息就可以实现推荐,是一个可解释性很强, 非常直观的模型, 但是也存在一些问题。

4.2.1 较差的稀疏向量处理能力

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

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

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

4.2.2 无法利用更多的信息

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

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

演化图左边的时间线梳理完毕:

5. 总结

这篇文章的主要内容就是围绕着推荐系统比较经典的协同过滤算法进行的展开, 这个算法虽然比较古老了, 但是思想和原理还是值得我们研究研究的, 毕竟这个算法不依赖于任何物品本身或者用户自身的属性, 而仅仅靠用户和物品的交互信息就可以完成推荐任务, 所以还是非常powerful的, 并且后面的很多算法都是基于该算法存在的问题进行的改进, 比如矩阵分解算法, 解决了其泛化能力和稀疏能力弱的问题, LR或者GBDT+LR那些机器学习模型解决了无法利用其它用户属性, 物品属性和上下文属性的问题, 即使近几年的深度学习模型, 也都是在前面的那些模型上进行改进过来的。 如果把推荐系统模型的发展看成一棵树, 协同过滤算法可是当之无愧的根

协同过滤算法比较常用的是基于邻域的方法, 主要包括UserCF和ItemCF, 这篇文章首先介绍了它们的原理,UserCF的基本思想是如果用户A喜欢物品a,用户B喜欢物品a、b、c,用户C喜欢a和c,那么认为用户A与用户B和C相似,因为他们都喜欢a,而喜欢a的用户同时也喜欢c,所以把c推荐给用户A。该算法用最近邻居(nearest-neighbor)算法找出一个用户的邻居集合,该集合的用户和该用户有相似的喜好,算法根据邻居的偏好对该用户进行预测。而ItemCF的基本思想是如果用户A, 用户B喜欢物品a, 物品c, 用户C喜欢物品a, 那么就认为物品a和物品c相似,因为用户C喜欢物品a, 所以也可以把物品c推荐给C。

然后从一个例子出发进行解释这两种算法,又通过了编程进行实现。 然后分析了它们各自的优缺点和应用场景, UserCF比较适用于实时性强的任务, 偏向于社会化推荐, 容易推进出新事物, 而ItemCF比较适合推荐兴趣比较固定的产品, 偏向于个性化推荐,推荐的大都类似产品。

最后再回顾这张图感受一下两者的不同:

参考

  • 王喆 - 深度学习推荐系统
  • 项亮 - 推荐系统实践
  • 协同过滤推荐算法的原理及实现
  • 协同过滤推荐算法总结
  • 【推荐系统算法实战】协同过滤 CF 算法(Collaborative Filtering)
  • 机器学习中的相似性度量
  • 协同过滤算法(collaborative filtering)

论文:

  • Using collaborative filtering to weave an information tapestry, 1992
  • Item-to-Item collaborative filtering, 2003

整理这篇文章的同时, 也刚建立了一个GitHub项目, 准备后面把各种主流的推荐模型复现一遍,并用通俗易懂的语言进行注释和逻辑整理,协同过滤算法是一个开头,主要是把项亮推荐系统实践里面的协同过滤算法(UserCF和ItemCF)实现一遍, 并进行解释。 由于也是小白起步, 学习起来比较吃力, 但后面会坚持进行更新, 所以有对推荐系统感兴趣的, 欢迎一块探讨和交流, 如果有幸遇到大神, 也希望能带一下

AI上推荐 之 协同过滤相关推荐

  1. 计算机系统应用的书,基于个性化图书推荐的协同过滤算法

    摘 要本文对基于个性化图书推荐的协同过滤算法的设计方案进行实验,目的是为证实在真实用户的多标准评估过程中怎样产生数据集,从而找到一种科学的算法.并通过图书推荐的应用案例来说明算法,以验证其是否有效. ...

  2. AI上推荐 之 AFM与DIN模型(当推荐系统遇上了注意力机制)

    1. 前言 随着信息技术和互联网的发展, 我们已经步入了一个信息过载的时代,这个时代,无论是信息消费者还是信息生产者都遇到了很大的挑战: 信息消费者:如何从大量的信息中找到自己感兴趣的信息? 信息生产 ...

  3. Python+Django+Mysql开发在线美食推荐网 协同过滤推荐算法在美食网站中的运用 基于用户、物品的协同过滤推荐算法 个性化推荐算法、机器学习、分布式大数据、人工智能开发

    Python+Django+Mysql开发在线美食推荐网 协同过滤推荐算法在美食网站中的运用 基于用户.物品的协同过滤推荐算法 个性化推荐算法.机器学习.分布式大数据.人工智能开发 FoodRecom ...

  4. AI上推荐 之 逻辑回归模型与GBDT+LR(特征工程模型化的开端)

    1. 前言 随着信息技术和互联网的发展, 我们已经步入了一个信息过载的时代,这个时代,无论是信息消费者还是信息生产者都遇到了很大的挑战: 信息消费者:如何从大量的信息中找到自己感兴趣的信息? 信息生产 ...

  5. 92 推荐算法——相似性推荐和协同过滤

    1 基于相似性的推荐流程 用户偏好如何收集 用户偏好如何整合 大多数情况我们提取的用户行为都多于一种,如何组合这些不同的用户行为,基本上有以下两种方式: 不同的行为分组 一般可以分为"查看& ...

  6. Python+Django+Mysql开发在线购物推荐网 协同过滤推荐算法在购物网站中的运用 个性化推荐算法开发 基于用户、物品的协同过滤推荐算法 机器学习、分布式大数据、人工智能开发

    Python+Django+Mysql开发在线购物推荐网 协同过滤推荐算法在购物网站中的运用 个性化推荐算法开发 基于用户.物品的协同过滤推荐算法 机器学习.分布式大数据.人工智能开发 ShopRec ...

  7. 基于神经网络的推荐算法,协同过滤推荐算法python

    大数据运维的主要工作内容是什么? . 大数据相关工作岗位很多,有大数据分析师.大数据挖掘算法工程师.大数据研发工程师.数据产品经理.大数据可视化工程师.大数据爬虫工程师.大数据运营专员.大数据架构师. ...

  8. AI上推荐 之 基于内容的推荐(ContentBasedRecommend)

    1. 前言 随着信息技术和互联网的发展, 我们已经步入了一个信息过载的时代,这个时代,无论是信息消费者还是信息生产者都遇到了很大的挑战: 信息消费者:如何从大量的信息中找到自己感兴趣的信息? 信息生产 ...

  9. 经典推荐算法-协同过滤

    协同过滤算法 1.传统推荐模型的演化关系图 2. 协同过滤 2.1 什么是协同过滤 2.2 计算用户相似度 2.3 最终结果排序 2.4 存在缺点 2.5 ItemCF 2.6 各自的应用场景 1.传 ...

  10. 个性化推荐系统原理介绍(基于内容推荐/协同过滤/关联规则/序列模式/基于社交推荐)...

    个性化推荐根据用户兴趣和行为特点,向用户推荐所需的信息或商品,帮助用户在海量信息中快速发现真正所需的商品,提高用户黏性,促进信息点击和商品销售.推荐系统是基于海量数据挖掘分析的商业智能平台,推荐主要基 ...

最新文章

  1. jmeter web监听结果_JMeter 性能测试基本过程及示例
  2. Android: 创建一个AlertDialog对话框,必须按确定或取消按钮才能关闭对话框,禁止按[返回键]或[搜索键]关闭
  3. 穿透内网,连接动态ip,内网ip打洞-----p2p实现原理(转)
  4. php将get传参解析成数组,php解析url (parse_url) 参数成数组 (parse_str)
  5. 程序员减压方法100%有效
  6. sql如何避免插入并发_SQL批量插入并发和性能注意事项
  7. 睡眠者效应(Sleeper Effect):当下被拒绝,但其实已经
  8. Android Studio 4.1一键生成代码Template
  9. N1网络命令-ping
  10. Atitit word结构化提取考试试题读取 poi读取word nlp aiprj 目录 1.1. 结构化后数据 1 1.2. 文字读取 1 1.3. 图片读取 1 1.4. Doc的表格读取 /
  11. 用计算机表白教程,抖音短视频vbs表白代码使用教程
  12. Java开发的学生考勤系统多人脸识别签到二维码签到
  13. 【JVM · 调优】监控及诊断工具
  14. 什么是计算机剪贴板介绍,剪贴板在哪里,详细教您如何打开电脑剪贴板
  15. codevs1253 超级市场(dp)
  16. 老年机打不出电话拨号失败服务器无响应,老年机为什么打不出去电话
  17. 101 Ruby Code Factoids
  18. BlackHoleDAO能否点燃DeFi3.0用户激情
  19. 诺丁汉大学计算机专业介绍,诺丁汉大学计算机科学本科专业.pdf
  20. [JavaSE] 数据类型(变量、整形、长整型、双精度浮点、单精度浮点、字符、字节)

热门文章

  1. 老毛桃u盘装系统linux,老毛桃U盘PE重装系统教程
  2. python 类和对象的实例简介
  3. Specification使用
  4. 计算机专业保研面试备考:数据库系统理论
  5. linux 系统找回密码,Linux系统密码忘记后的五种恢复方法
  6. Chrome源码chromium下载编译详细步骤
  7. java获取手机型号
  8. mybatis源码之MapperMethod
  9. ADL平台上传数据前的准备工作
  10. matlab 压缩采样恢复,MATLAB|测试信号压缩采样