作者——James Kirk
许多产品使用推荐系统向用户提供相关或个性化的物品(食物、电影、音乐、书籍、新闻等)。为此,它们从用户与项目的互动中学习,以识别用户的品味并作出改进。
这篇文章将带领我们使用TensorRec(https://github.com/jfkirk/tensorrec)在Python中建立一个新的推荐系统原型,包括输入数据操作、算法设计和预测用法。
你可以在这里找到这篇文章的Python代码。(https://github.com/jfkirk/tensorrec/blob/master/examples/getting_started.py)
系统概述
TensorRec是一个用于构建推荐系统的Python包。TensorRec推荐系统使用三个输入数据:用户特性、项目特性和交互。根据用户/项目的特性,系统将预测要推荐的项目。在拟合模型时使用相互作用:将预测与相互作用进行比较,并计算损失/惩罚,然后系统学习减少损失/惩罚。
在我们构建系统原型时,我们将解决三个主要问题:如何处理交互、如何处理特性以及如何构建推荐程序本身。
交互数据
对于此示例,我们将使用MovieLens数据集(https://grouplens.org/datasets/movielens/)。此数据集包含电影的1-5星评级、有关这些电影的元数据以及用户对于电影的标签。对于我们的第一个原型,我们将专注于评级,但我们稍后将返回其他元数据。
原始形式的评级是这样的:
每行代表一个评级:一个用户对一部电影的想法。我们将使用这些评级作为我们的数据。我们训练该系统的第一步是摄取和格式化这个交互数据。首先,我们在CSV文件中读取评级。
在这一点上,我们将通过改组和拆分评级来将评级分解为训练集和测试集。我们的原型将在训练集上进行训练,我们将使用测试集评估它们的程度。像这样随机拆分训练/测试集是粗糙的,还有更严格的模型评估技术,但是对于这个例子而言它是快速而清晰的。
接下来,我们将这些评级重新组织为一个Scipy稀疏矩阵(https://docs.scipy.org/doc/scipy/reference/sparse.html)。在此矩阵中,每一行代表一个用户,每一列代表一部电影。此矩阵中的[i,j]值是用户i与电影j的交互。
协同过滤原型
协同过滤是一种算法,它可以了解哪些用户有相似的品味,并根据相似用户的喜好向用户推荐商品。一种常见的方法是通过矩阵分解。在矩阵分解中,我们必须学习两个矩阵(用户表示和项目表示),当它们相乘时,它们会近似于交互:
在这种情况下,W的行是用户表示,H的列是项目表示,V中的值是交互。我们需要学习W和H来得到V的最佳近似值。
W的宽度和H的高度是相同的——这个共享的维度被称为"组件的数量。"具有更多组件的模型正在学习更复杂的用户和项目表示,但这可能导致过度拟合训练数据。一般来说,我们希望尝试将大量信息压缩到小型表示中。出于这个原因,在我们的原型中,我任意选择使用5个组件。在进一步进行原型设计时,我们应该尝试增加和减少组件的数量,并警惕过度拟合。
如果仅将身份矩阵作为用户/项目特性给出,则TensorRec将默认执行矩阵分解。这些身份矩阵通常被称为"指标特性"。
我们现在已经创建了两个指标特性矩阵,构建了一个包含5个组件的简单协同过滤模型,并且对模型进行了拟合!
接下来,我们希望看看该模型的表现如何。
为此,我们将查看一个名为"recall at K."的指标.Recall @ K表示,对于普通用户来说,他们的测试项目中有多少百分比进入预测排名的前K名。换句话说,如果我们得到recall@ 10值为.06,那么我喜欢的特定电影将有6%的可能性进入我的前10个推荐。
recall@K对于许多推荐系统来说是一个不错的指标,因为它模仿了推荐产品的行为:如果一个电影网站只向我展示我的十大推荐,那么他们的算法将能够有效地将我喜欢的电影放入前十条推荐中。
在计算recall之前,我们要确定哪些交互应该算作"喜欢"。在这种情况下,我选择使用至少4.0的所有评级作为"喜欢"而忽略其余的评分。
我们来看看结果:
不是很好。这些结果告诉我们,在测试集电影中,只有0.1%的机会让喜欢的电影进入前10名。这个推荐系统是无效的。
损失图
我们可以配置TensorRec系统的一种方法是更改损失图。损失图接受预测和交互,并计算系统在学习过程中试图减少的惩罚(损失)。
默认情况下,TensorRec使用RMSE(均方根误差)作为损失图。这意味着TensorRec正试图准确估计交互的值:如果我给电影打4.5分,TensorRec试图得到的分数正好是4.5分。
这很直观,但它并不符合推荐系统在许多产品中的工作方式:电影网站不需要准确预测我的评级,它只需要能够对我喜欢的电影进行排名。出于这个原因,许多系统通过"学习排名"来运行。我们可以通过使用名为WMRB的损失图来使我们的TensorRec系统以这种方式工作。
WMRB(https://arxiv.org/abs/1711.04015),它通过随机抽取用户未与之交互的项目并将其预测与用户喜欢的项目进行比较来工作。
我们可以令TensorRec在构造模型时指定WMRB,在拟合模型时指定样本批量的大小。在这种情况下,我们只想在正评级(≥4.0)上训练模型,以便WMRB将这些评级推到排名的首位。
我们来看看结果:
好多了!已经达到了7.76%。TensorRec允许你指定和自定义自己的损失图,你可以在此处查看更多示例损失图(https://github.com/jfkirk/tensorrec/blob/master/tensorrec/loss_graphs.py)。
添加元数据特性
为了继续试验,我们应该尝试使用我们可用的其他数据。在MovieLens示例中,我们可以使用电影元数据(例如电影的类型)来丰富推荐。
在原始格式中,电影元数据文件如下所示:
首先,我们需要阅读这些数据,将电影映射到我们的内部ID,并跟踪每部电影的类型。然后我们将使用Scikit的MultiLabelBinarizer对类型标签进行二值化。二值化输出将是我们新推荐系统的特性。
运行此命令将打印出原始元数据和二值化类型的示例:
基于内容的推荐
现在我们有关于我们项目的元数据,我们可以尝试的一件事是仅基于项目元数据进行推荐。
为此,我们将配置TensorRec模型以使用项目特性的传递表示图。对我们而言,这意味着项目表示将与传入的项目特性(仅仅是电影类型)相同,并且用户表示将反映用户对该特定类型的喜爱程度。
我们来看看结果:
它不如协同过滤那么好,但是,当recall@ 10为1.3%时,它比我们的第一个协同过滤更有效。
这个系统存在一个主要的缺点:单独的类型不是非常具有描述性,也不足以提供明智的推荐信息。如果我们有更多的描述性元数据(更多标签、演员、子类型等),我们可以通过这个基于内容的推荐系统获得更大的成功。
另一方面,该系统有一个主要优势:仅依靠元数据特性,而不使用指示特性,我们可以推荐在训练模型时不存在的电影。同样,如果我们拥有有价值的用户元数据,我们可以避免使用用户指标特性,并为之前从未与电影进行过交互的用户进行预测。这被称为"冷启动"推荐。
混合模型
我们知道我们的排名协同过滤给了我们最好的结果,但似乎使用电影元数据也有一些价值。让我们结合这两个:我们将使用指标特性来获得协同过滤的优势,我们还将使用内容特性来利用元数据。这种将协同过滤和基于内容的推荐相结合的系统称为"混合"模型。
我们通过将两组特性叠加在一起来实现此目的:
我们来看看结果:
recall@ 10的推荐率为7.94%,这是我们目前为止最强大的推荐系统。与纯协同过滤的7.76%的结果相比,差异并不明显,但仍然是一种改进。如果我们使用更多元数据而不仅仅是类型,我们可能会看到更大的改进变化。
提出建议
我们有一个经过训练的模型,现在我们可以使用这个模型为我们的用户提出建议。我们通过将用户的特性向量和所有项目特性传递给predict_rank()并检查结果的排名:
此代码段将得出用户432的前10个推荐。我选择了用户432,因为我熟悉他们评价过的电影,所以我觉得我可以站在他们的角度判断他们的建议。
让我们看看用户432的训练数据,看看他们喜欢什么类型的电影:
一些犯罪片,一些黑暗喜剧,以及对Tarantino的热爱。让我们看看用户432的混合模型建议:
更多带有黑暗喜剧倾向的犯罪电影!这看起来相当不错,但让我们检查一下用户432的测试电影:
Fargo和Fight Club都在用户432的推荐中——这是一个很好的结果!对于这个特定的用户,我们实现了50%的召回率。
展望未来
为了继续优化我们的推荐系统,我们应该尝试更多的表示、预测和损失图,为系统提供更多元数据,以不同方式设计这些元数据特性,以不同方式管理交互/反馈数据,以及优化系统的各种超参数。