数据挖掘与分析(四)推荐模型实战

1 minute read

Published:

在过去的几十年里,随着Youtube、Amazon、Netflix和其他许多此类网络服务的兴起,推荐系统在我们的生活中占据了越来越重要的位置。

从电子商务(向买家推荐他们可能感兴趣的商品)到在线广告(向用户推荐符合他们喜好的正确内容),推荐系统今天在我们的日常生活中是不可避免的。

以一种非常普遍的方式,推荐系统是旨在向用户推荐相关项目的算法。其中项目是指要观看的电影、要阅读的文本、要购买的产品或其他任何取决于行业的东西。

推荐系统在某些行业非常关键,因为它们在高效推荐的同时还可以产生巨大的收入,也是一种从竞争对手中脱颖而出的方式。几年前,Netflix组织了一个挑战(”Netflix奖”),目标是产生一个比自己的算法表现更好的推荐系统,赢得100万美元的奖金。足以见得推荐系统对于这种流媒体提供商的重要性。

推荐系统的目的是向用户推荐相关项目。为了实现这一任务,存在着两大类方法。协作过滤方法和基于内容的方法。在这篇文章中,我们将介绍推荐系统的不同范式。对于其中的每一个,我们将介绍它们是如何工作的,描述它们的理论基础,并讨论它们的优势和劣势。在第一节,我们将概述推荐系统的两个主要范式:协作和基于内容的方法。接下来的两节将介绍协同过滤的各种方法,如用户-用户、项目-项目和矩阵分解。以下部分将专门介绍基于内容的方法以及它们如何工作。最后,我们将讨论如何评估一个推荐系统。

基于协同过滤的推荐

在建立智能推荐系统时,协同过滤是最常用的技术,随着收集到更多的用户信息,它可以学习提供更好的推荐。

推荐系统的协作方法是完全基于用户和项目之间过去的交互记录,以产生新的推荐。 这些交互被储存在所谓的“用户-项目交互矩阵”中。然后,协作方法的主要想法是,这些过去的用户-项目交互可以用来推断出相似的用户和/或类似的项目,并根据这些估计的相似度进行推荐。

协同过滤算法的类别分为两个子类别,一般称为基于存量和基于模型的协作方法。基于存量的方法直接使用记录的交互值,基本上是基于最近的邻居搜索(例如,找到离感兴趣的用户最近的用户,并建议这些邻居中最受欢迎的项目)。 基于模型的方法假定有一个潜在的生成模型来解释用户与项目之间的交互,并试图发现它以便做出新的推荐。

协同过滤方法的主要优点是,它们不需要关于用户或项目的信息,因此,它们可以在许多情况下使用。此外,用户与项目的交互越多,新的推荐就越准确。对于一组固定的用户和项目,随着时间的推移,新的交互记录带来了新的信息,系统就会越来越有效。

然而,由于它只考虑过去的交互来进行推荐,协同过滤受到“冷启动问题”的困扰。协同过滤无法向新用户推荐任何东西,也不可能向任何用户推荐一个新的物品,而且许多用户或项目的交互太少以至于无法有效的进行推荐。这个缺点可以通过以下几种不同的策略解决。

  1. 随机策略:向新用户推荐随机项目或向随机用户推荐新项目。
  2. 最大期望值策略:向新用户推荐热门项目或向最活跃的用户推荐新项目。
  3. 探索性策略:向新用户推荐一组各种项目或向一组各种用户推荐一个新项目。

数据集(Dataset)

要实践推荐算法,需要包含一组项目和一组对一些项目有交互的用户的数据。交互可以是显性的(在1到5的范围内评分,喜欢或不喜欢)或隐性的(查看一个项目,把它加入愿望清单,在一篇文章上花费的时间)。

在处理这类数据时,会看到它以矩阵的形式存在。由一组用户对一组项目中的某些项目的交互组成。每一行将包含用户对每个项目给出的评分,每一列将包含一个项目收到的评分。一个有五个用户和五个项目的矩阵可以是这样的。

评分矩阵

用户\项目i1i2i3i4i5
u150410
u203030
u302441
u444500
u524052

表10-1中的矩阵显示了五个用户对五个项目进行了1到5的评分。 例如,第一个用户给第三个项目的评分是4。在大多数情况下,矩阵中的单元格是空的,因为用户只对几个项目进行过评分。每个用户都对每个项目都进行评价是不可能的。一个大部分单元格为空的矩阵被称为稀疏矩阵,而与此相反的(大部分填充的矩阵)被称为密集矩阵。

有很多数据集已经被收集起来,并提供给公众用于研究和基准测试。 这里有一个高质量的数据源清单,你可以从中选择。

最好的开始是由GroupLens Research收集的MovieLens数据集。 特别是,MovieLens 100k数据集是一个稳定的基准数据集,有943个用户对1682部电影给出的100,000个评价,每个用户至少有20部电影的评价。

  1. 基于存量的协作方法(Memory-based Approach)

第一类包括基于存量的方法,其中统计技术被应用于整个数据集以计算推荐结果。为了找到用户U会给一个项目I的评分R,该方法包括两个步骤。

(1)寻找与U相似的对项目进行过评分的用户I

(2)根据上一步找到的用户的评分计算评分R

考虑使用角度来计算用户相似度,如果向量之间的角度增加,那么相似度就会降低,如果角度为零,那么用户就非常相似。我们需要一个函数,对于较低的角度返回较高的相似度或较小的距离,对于较高的角度返回较低的相似度或较大的距离。向量夹角的余弦值符合恰好符合以上条件,我们采用角度的余弦值来寻找两个用户之间的相似性。角度越大,余弦就越小,用户的相似度就越低。

在确定了一个与用户U相似的用户名单后,需要计算出U会给某个项目I的评分R。同样,就像相似性一样,可以通过多种方式实现。可以预测一个用户对一个项目I的评分R将接近与U最相似的前5名或前10名用户对I的评分的平均值。n个用户给出的平均评分的数学公式看起来是这样的:

$R_{U} = \frac{\left( \sum_{u = 1}^{n}R_{u} \right)}{n}$

这个公式表明,n个相似用户给出的平均评分等于他们给出的评分之和除以相似用户的数量。会有这样的情况:找到的n个相似用户与目标用户U的相似度不一样。其中前3名可能非常相似,其余的可能不像前3名那样与U相似。

在这种情况下,可以考虑一种方法,即最相似的用户的评分比第二个最相似的用户更重要,以此类推。加权平均数可以帮助我们实现这一目标。在加权平均法中,将每个评分乘以一个相似度系数。通过与相似性系数相乘,为评级增加了权重。权重越大,评级就越重要。作为权重的相似性系数,应该是上面讨论的距离的倒数,因为距离越小意味着相似性越高。例如,你可以用1减去余弦距离,得到余弦相似度。有了每个与目标用户U相似的用户的相似度系数S,可以用这个公式计算出加权平均数。

$R_{U} = \frac{\left( \sum_{u = 1}^{n}{R_{u} \times S_{u}} \right)}{\left( \sum_{u = 1}^{n}S_{u} \right)}$

在上述公式中,每个评分都要乘以给予该评分的用户的相似度系数。用户U的最终预测评分将等于加权评分之和除以权重之和。

接下来使用真实的数据集来揭晓基于存量的协同过滤方法的实现过程。推荐功能的算法选择取决于你想使用的技术。对于上面讨论的基于存量的方法,适合的算法是环形k近邻算法,因为该算法非常接近上面解释的余弦相似度公式。下面的程序配置了环形k近邻算法函数。

1. **from** surprise **import** KNNWithMeans
2. # 使用基于项目的余弦相似度
3. sim_options = {
4. "name": "cosine",
5. "user_based": False, # Compute similarities between items
6. }
7. algo = KNNWithMeans(sim_options=sim_options)

上述程序中使用余弦相似度,并使用基于项目的方法寻找相似项目。为了尝试这个推荐器,可以把数据分成几份。其中一些数据将用于训练,一些用于测试。下面是一个例子,预测用户e会如何电影2打几分。

1. trainingSet = data.build_full_trainset()
2. algo.fit(trainingSet)
3. prediction = algo.predict('E', 2)
4. prediction.est

该算法预测用户e会给这部电影打4.15分,这说明我们可以将此电影推荐给用户e了。

  1. 基于模型的协作方法(Model-based Approach)

第二类包括基于模型的方法,其中包括减少或压缩大而稀疏的用户-项目矩阵的步骤。 为了理解这一步,我们先对降维有基本理解。

在用户-项目矩阵中,有两个维度。

(1)用户的数量。

(2)项目的数量。

如果矩阵大部分是空的,减少维度可以在空间和时间方面提高算法的性能。可以使用矩阵分解(Matrix Factorization)或自动编码器(Auto Encoder)来做到这一点。

矩阵分解可以看作是将一个大矩阵分解成若干个小矩阵的乘积。这类似于整数的因式分解,12可以写成6 × 2或4 × 3。一个尺寸为m × n的矩阵A可以简化为两个尺寸分别为m × pp × n的因子矩阵XY的乘积。缩减后的矩阵实际上单独代表了用户和项目。第一个矩阵中的m行代表m个用户,p列告诉你用户的特征或特性。有n个项目和p个特征的项目矩阵也是如此。

因子矩阵可以提供关于用户和项目的这种内在关系,但实际上它们通常比上面的解释要复杂得多。这种因子的数量可以从一个到数百个甚至数千个,这个数字是模型训练期间需要优化的参数之一。在这个例子中,有两个关于电影类型的潜在因素,但在实际场景中,这些潜在因素不需要进行过多的分析。这些是数据中的模式,无论你是否探索它们的潜在含义,都会自动发挥它们的作用。潜在因素的数量会影响推荐,因素的数量越多推荐就越个性化。但是过多的因素会导致模型的过度拟合。

同样的,我们使用真实的数据集来揭晓基于模型的协同过滤方法的实现过程。对矩阵进行因式分解的流行算法之一是奇异值分解(SVD)算法。当矩阵分解在Netflix奖竞赛中表现出色时,SVD进入了人们的视线。其他算法包括主成分分析(PCA)等等。 如果你想使用神经网络,自动编码器也可以用于降维。在这里我们将使用奇异值分解,关于其它降维的做法读者可自学尝试。

1. **from** surprise **import** SVD
2. **from** surprise **import** Dataset
3. **from** surprise.model_selection **import** GridSearchCV
4. data = Dataset.load_builtin("ml-100k")
5. param_grid = {
6. "n_epochs": [5, 10],
7. "lr_all": [0.002, 0.005],
8. "reg_all": [0.4, 0.6]
9. }
10. gs = GridSearchCV(SVD, param_grid, measures=["rmse", "mae"], cv=3)
11. gs.fit(data)
12. **print**(gs.best_score["rmse"])
13. **print**(gs.best_params["rmse"])

找到最佳的参数来实现奇异值分解。对于MovieLens数据集,使用10个epochs并使用0.005的学习率和0.4的正则化,SVD算法的效果最好可以达到96%的准确率。按照这些例子,读者可以深入研究这些算法中可以使用的所有参数。并且观察它们背后的数学原理。

基于内容的推荐

与只依赖用户-项目交互的协同过滤方法不同,基于内容的方法使用关于用户和项目的额外信息。考虑一个电影推荐系统的例子,这些额外的信息可以是用户的年龄、性别、工作或任何其他个人信息,以及电影(项目)的类别、主要演员、时间或其他特征。

基于内容的方法的想法是尝试建立一个模型,基于现有的特征来解释观察到的用户-项目交互。仍然考虑电影推荐的例子,模拟年轻女性倾向于对一些电影进行更好的评价,年轻男性倾向于对一些其他电影进行更好的评价,等等。如果我们设法得到这样的模型,那么,为用户做出新的推荐是非常容易的。我们只需要看一下这个用户的资料(年龄、性别等等特征),并根据这些信息,确定要推荐的相关电影。

基于内容的方法受冷启动问题的影响远远小于协同过滤式方法。新的用户或项目可以通过他们的特征(内容)来描述,因此可以为这些新实体做相关的推荐。只有新用户或具有以前未见过的功能的项目才会在逻辑上受到这个缺点的影响,但一旦系统掌握的特征非常多时,这种情况就几乎没有机会发生。

在这篇文章的后面,我们将进一步讨论基于内容的方法,并看到根据我们的问题,可以使用各种分类或回归模型,从非常简单的模型到更复杂的模型。

在前上节中,我们主要讨论了环形k近邻算法和矩阵分解方法。这些方法只考虑用户与项目的交互矩阵,属于协同过滤范式。现在我们来描述一下基于内容的范式。在基于内容的方法中,推荐问题被归纳为分类问题(预测用户是否喜欢某个项目)或回归问题(预测用户给某个项目的评分)。在这两种情况下,我们都要建立一个模型,这个模型将基于我们所掌握的用户和项目特征。如果我们的分类(或回归)是基于用户的特征,我们说这个方法是以项目为中心的。建模、优化和计算可以逐项完成。在这种情况下,我们根据用户的特征,按项目学习一个模型,试图回答 “每个用户喜欢这个项目的概率是多少?”(或 “每个用户给这个项目的评分是多少?”,用于回归)。 与每个项目相关的模型自然是在与该项目相关的数据上训练出来的,一般来说,它会训练出相当强大的模型,因为有很多用户与该项目进行了交互。然而,用于学习模型的交互来自不同用户,即使这些用户有类似的特征,他们的偏好也可能不同。这意味着,即使这种方法更加稳健,它也可以被认为是比以用户为中心的方法更有偏见。

这里我们使用以项目为中心的建模,我们将预测一个用户喜欢一个项目的概率。根据用户喜欢的东西,该算法将简单地挑选内容相似的项目来推荐给用户。同样的,我们使用余弦相似度来度量内容的相似性。我们使用来自IMDB的250部最高评分电影的数据集来展示基于内容的推荐过程。

这个数据集收集了250条电影数据和38个电影特征。然而,我们希望推荐器只基于电影导演、主要演员、类型和情节这些特征来建模以降低过拟合的风险。每部电影只获得一列,这一列包含了所有上述的特征以便进行向量化。我们需要使用自然语言处理(NLP)的技术来对抽取上述特征,因为我们需要向量来计算余弦相似度。

为了检测电影之间的相似性,我们需要进行向量化处理。使用CountVectorizer为词袋模型(Bag of Words)中的每个词建立一个简单的频率计数器。一旦我有了包含每个词的计数的矩阵,我们就可以应用余弦相似度函数了

1. # 生成计数矩阵
2. count = CountVectorizer()
3. count_matrix = count.fit_transform(df['bag_of_words'])
4. # 生成余弦相似度矩阵
5. cosine_sim = cosine_similarity(count_matrix, count_matrix)

接下来可以编写模型将电影标题作为输入并返回前10部类似电影创建了一系列简单的带有数字索引的电影标题以便将相似度矩阵中的索引与实际的电影标题相匹配

1. # 输入电影,输出最相似的10部电影
2. **def** recommendations(title, cosine_sim = cosine_sim):
3. recommended_movies = []
4. # 获取电影索引值
5. idx = indices[indices == title].index[0]
6. # 将电影根据余弦相似度以降序排列
7. score_series = pd.Series(cosine_sim[idx]).sort_values(ascending = False)
8. # 获取最相似的10部电影
9. top_10_indexes = list(score_series.iloc[1:11].index)
10. **for** i **in** top_10_indexes:
11. recommended_movies.append(list(df.index)[i])
12. **return** recommended_movies

推荐评价指标

让我们再关注一下前面提到的方法之间的主要区别。特别是让我们看看建模水平对偏差和方差的影响。

在基于存量的协作方法中,没有生成模型。该算法直接使用用户-项目交互矩阵。 例如,用户通过他们与物品的交互来表示,在这些表示上进行近邻搜索来产生建议。由于没有生成模型,这些方法在理论上有较低的偏差,但有较高的方差。

在基于模型的协作方法中,生成潜在的交互模型。该模型经过训练,可以从其自身对用户和项目的表述中重建用户与项目的交互值。然后可以根据这个模型做新的推荐。由模型提取的用户和项目的潜意识表征具有数学意义,对人来说可能很难解释。由于生成了一个用户-项目交互的模型,这种方法在理论上比没有生成模型的方法有更高的偏差,但方差更小。

最后,在基于内容的方法中,也会生成一些潜在的互动模型。然而,模型被提供了定义用户和项目的表示。例如,用户由给定的特征来表示,我们试图为每个项目建立喜欢或不喜欢这个项目的用户档案。在这里,与基于模型的协作方法一样,生成一个用户-项目互动模型。然而,这个模型更受限制(因为用户和/或项目的表示是给定的),因此,该方法往往有最高的偏差,但方差最低。

对于任何机器学习算法,我们需要能够评估我们的推荐系统的性能,以决定哪种算法最适合我们的情况。推荐系统的评价方法主要可以分为两组。基于明确定义的指标的评价和主要基于人类判断和满意度估计的评价。

  1. 基于指标的评估(Metrics Based Evaluation)

如果我们的推荐系统是基于一个输出数值的模型,如评分预测或匹配概率,我们可以用一个非常经典的方式来评估这些输出的质量,如误差测量指标均方误差(MSE)。在这种情况下,模型只在一部分可用的交互上进行训练,并在剩余的交互上进行测试。

如果我们的推荐系统是基于一个预测数值的模型,我们也可以用经典的阈值处理方法将这些数值二进制化(高于阈值的数值为正值,低于阈值的数值为负值),并以更加“二分类的方式”评估该模型。事实上,由于用户-项目过去的交互数据集也是二进制的或者可以通过阈值化进行二进制化,我们可以在未用于训练的交互测试数据集上评估模型的二进制化输出的准确性以及精确度和召回率。

最后,如果我们现在考虑一个不基于数值的推荐系统,并且只返回一个推荐列表(比如基于k近邻方法的用户-用户或项目-项目推荐),我们仍然可以通过估计真正适合我们用户的推荐项目的比例来定义一个类似精度的指标。为了估计这个精度,我们不能考虑用户没有交互过的推荐项目,我们应该只考虑我们有用户反馈的测试数据集的项目。

  1. 基于人的评价(Human Based Evaluation)

当设计一个推荐系统时,我们不仅想获得能产生我们非常确定的推荐的模型,而且我们还可以期待其他一些好的属性,如推荐的多样性和可解释性。

正如在协同过滤部分提到的,我们绝对要避免让用户陷入信息黑域,也就是说推荐系统会一直给用户推荐同一类型的项目以至于用户无法逃离系统的推荐。“偶然性”的概念经常被用来表达一个模型是否有创造这样一个黑域的倾向。偶然性(Serendipity)可以通过计算推荐项目之间的距离来估计。距离不应该太低,因为它会产生黑域。但也不应该太高,因为这意味着我们在做推荐时没有充分考虑到用户的兴趣。因此,为了使推荐的选择多样化,我们希望推荐既适合我们的用户,又不太相似的项目。例如,与其向用户推荐书籍《三体》1、2和3,不如推荐《三体1》、《三国演义》和《流浪地球》。我们的系统可能会认为后面的两个项目引起用户兴趣的机会较少,但推荐3个看起来过于相似的项目并不是一个好的选择。

可解释性是推荐算法成功的另一个关键点。事实已经证明,如果用户不理解为什么他们被推荐特定的项目,他们往往会对推荐系统失去信心。因此,如果我们设计了一个可以清楚解释的模型,我们就可以在做推荐时加入一个小句子,说明一个项目被推荐的原因(“喜欢这个项目的人也喜欢这个项目”,“你喜欢这个项目,你可能对这个项目感兴趣”,…)。

最后,除了多样性和可解释性在本质上难以评估外,我们可以注意到,评估不属于推荐数据集的推荐的质量也是相当困难。如何在实际向用户推荐之前知道一个新的推荐是否相关?由于所有这些原因,有时会很想在真实条件下测试该模型。由于推荐系统的目标是产生一个行为(观看电影、购买产品、阅读文章等),我们确实可以评估其产生预期行为的能力。例如,系统可以按照A/B测试的方法投入生产,也可以只在用户的样本上进行测试。然而,这样的过程需要对模型有一定程度的信心。

代码

基于协同过滤的推荐

基于内容的推荐