Skip to content

Latest commit

 

History

History
445 lines (291 loc) · 24.4 KB

File metadata and controls

445 lines (291 loc) · 24.4 KB

十一、使用 Danfo.js 和 TensorFlow.js 构建推荐系统

在前一章中,我们向您介绍了 TensorFlow.js,并向您展示了如何创建一个简单的回归模型来预测销售价格。在这一章中,我们将更进一步,创建一个推荐系统,可以为不同的用户推荐电影,同时考虑用户的偏好。本章结束时,您将了解推荐系统是如何工作的,以及如何用 JavaScript 构建一个推荐系统。

具体来说,我们将涵盖以下主题:

  • 什么是推荐系统?
  • 创建推荐系统的神经网络方法
  • 构建电影推荐系统

技术要求

要完成本章,您需要以下内容:

Danfo.js、TensorFlow.js 和 Dnotebook 的安装说明分别可以在 第 3 章 【丹 fo.js 入门】第 10 章*【tensorflow . js 入门】*和 第 2 章 中找到,Dnotebook–一个 JavaScript 的交互式计算环境

什么是推荐系统?

推荐系统是能够预测用户给予项目的偏好或有用性分数的任何系统。使用这个偏好分数,它可以向用户推荐项目。

这里的物品可以是数字产品,如电影、音乐、书籍,甚至衣服。每个推荐系统的目标都是能够推荐用户喜欢的项目。

推荐系统非常受欢迎,几乎随处可见;例如:

  • 网飞亚马逊 PrimeHulu**迪士尼+ 等电影流媒体平台使用推荐系统向你推荐电影。
  • 脸书推特insta gram等社交媒体网站使用推荐系统推荐好友进行连接。
  • 亚马逊**全球速卖通等电商网站使用推荐系统推荐衣服、书籍、电子产品等产品。

推荐系统大多是使用来自用户-项目交互的数据构建的。因此,在构建推荐系统时,有三种主要的方法可以遵循。这些是协同过滤基于内容的过滤,以及混合方法。我们将在以下小节中简要解释这些方法。

协同过滤方法

在协同过滤方法中,推荐系统基于用户过去的行为或历史进行建模。也就是说,这种方法利用现有的用户交互,例如对项目的评级、喜欢或评论,来模拟用户的偏好,从而理解用户喜欢什么。下图显示了协作过滤方法如何帮助构建推荐系统:

Figure 11.1 – Collaborative filtering approach to building a recommendation system

图 11.1–构建推荐系统的协同过滤方法

在上图中,你可以看到两个看过同一部电影的用户,可能是以同样的方式评分,被归为相似用户,因为左边人看过的电影已经推荐给右边的人了。在基于内容的过滤方法中,推荐系统是基于项目特征建模的。也就是说,项目可以被预先标记有某些特征,例如类别、价格、流派、大小和接收的评级,并且使用这些特征,推荐系统可以推荐类似的项目。

下图显示了基于内容的过滤方法如何构建推荐系统:

Figure 11.2 – Content-based filtering approach to building a recommendation system

图 11.2–构建推荐系统的基于内容的过滤方法

在前面的图中,您可以观察到彼此相似的电影被推荐给用户。

混合滤波方法

顾名思义,混合方法是协作和基于内容的过滤方法的结合。也就是说,它结合了两种方法的优点来创建一个更好的推荐系统。当今大多数现实世界的推荐系统都使用这种方法来减轻单个方法的缺点。

下图显示了一种将基于内容的过滤方法与协作过滤方法相结合来创建混合推荐系统的方法:

Figure 11.3 – The hybrid approach to building a recommendation system

图 11.3–构建推荐系统的混合方法

在上图中,您可以看到我们有两个输入供给混合动力系统。这些输入进入协作( CF )和基于内容的系统,然后来自这些系统的输出被合并。这种组合可以定制,甚至可以作为神经网络等其他高级系统的输入。这里的总体目标是通过组合多个推荐系统来创建一个强大的混合系统。

值得一提的是,任何用于创建推荐系统的方法都需要某种形式的数据。例如,在协同过滤方法中,您将需要用户-项目交互历史,而对于基于内容的方法,您将需要项目元数据

如果你有足够的数据来训练一个推荐系统,你可以利用众多的机器和非机器学习技术在做推荐之前对数据进行建模。你可以使用的一些流行的算法有 K 近邻(https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm)聚类算法(https://en.wikipedia.org/wiki/Cluster_analysis)决策树(https://en.wikipedia.org/wiki/Decision_trees)贝叶斯 分类器(https://en.wikipedia.org/wiki/Naive_Bayes_classifier)甚至还有人工神经网络 (

在本章中,我们将使用神经网络方法来构建一个推荐系统。我们将在下面的部分详细解释这一点。

创建推荐系统的神经网络方法

近年来,神经网络在解决机器学习 ( ML )领域的诸多问题方面,一直是瑞士军刀。这一点在 ML 突破的领域很明显,例如图像分类 / 分割自然语言处理。随着数据的可获得性,神经网络已被成功用于构建大规模推荐系统,例如在网飞(https://research.netflix.com/research-area/machine-learning)和YouTube(https://research.google/pubs/pub45530/)使用的系统。

尽管用神经网络构建推荐系统有不同的方法,但它们都依赖于一个主要事实:它们需要一种有效的方法来学习项目或用户之间的相似性。在本章中,我们将利用一个名为嵌入的概念来有效地学习这些相似之处,以便我们可以轻松地为我们的推荐系统提供动力。

但是首先,什么是嵌入,我们为什么要使用它们?在下一小节中,我们将简要回答这些问题。

什么是嵌入?

嵌入是离散变量到连续或实值变量的映射。也就是说,给定一组变量,如[good, bad],嵌入可以将每个离散项映射到 n 维的连续向量——例如,好的可以表示为[0.1, 0.6, 0.1, 0.8],坏的可以表示为[0.8, 0.2, 0.6, 0.1],如下图所示:

Figure 11.4 – Representing discrete categories with real-valued variables

图 11.4–用实值变量表示离散类别

如果您熟悉编码方案,如一热编码(https://en.wikipedia.org/wiki/One-hot)或标签编码(https://machinelearningmastery . com/一热编码分类数据/ ),那么您可能会想知道嵌入与它们有何不同。

嗯,有两个主要的区别和技术上的嵌入优势:

  • Embedding representations can be small or large, depending on the specified dimension. This is different from encoding schemes such as one-hot encoding, where the dimensionality of the representation increases with the number of discrete classes.

    例如,下图显示了单向编码表示所使用的维度如何随着唯一国家/地区的数量而增加:

Figure 11.5 – Size comparison between embeddings and one-hot encoding

图 11.5–嵌入和一次编码之间的大小比较

  • 嵌入可以和神经网络中的权重一起学习。这是优于其他编码方案的主要优势,因为有了这个特性,学习嵌入就变成了离散类的相似集群,这意味着你可以很容易地找到相似的项目或用户。例如,查看下面的证明,您可以看到我们有两组学习过的单词嵌入:

Figure 11.6 – Embedding words and showing similarity in an embedding space (Redrawn from: https://medium.com/@hari4om/word-embedding-d816f643140)

图 11.6–在嵌入空间中嵌入单词并显示相似性(来自:https://medium.com/@hari4om/word-embedding-d816f643140)

在上图中,您可以看到描绘男子女子国王王后的组正在通过嵌入,并且由此产生的输出是嵌入空间,在该空间中,意义更接近的单词被分组。这可以通过学习单词嵌入来实现。

那么,我们如何利用嵌入来创建推荐系统呢?正如我们前面提到的,嵌入可以有效地表示数据,这意味着我们可以使用它们来学习或表示用户-项目交互。因此,我们可以很容易地使用学习嵌入来找到类似的项目进行推荐。我们甚至可以通过将嵌入与有监督的机器学习任务相结合来更进一步。

这种将学习的嵌入表示与监督的 ML 任务相结合的方法是我们将在下一节中创建我们的电影推荐系统所要做的。

构建电影推荐系统

为了构建电影推荐系统,我们需要某种用户-电影交互数据集。幸运的是,我们可以使用group lens(https://grouplens.org/)提供的的MovieLens 100k数据集(https://grouplens.org/datasets/movielens/100k/)。该数据包含 1000 名用户对 1700 部电影给出的 100000 部电影评分。

下面的截图显示了数据集的前几行:

Figure 11.7 – The first few rows of the MovieLens dataset

图 11.7–电影数据集的前几行

从前面的截图中,可以看到我们有user_iditem_id(电影),以及用户给物品(电影)的评分。通过这种交互和嵌入的使用,我们可以有效地模拟用户的行为,从而了解他们喜欢什么样的电影。

要了解我们将如何利用嵌入和神经网络构建和学习这种交互,请参考以下架构图:

Figure 11.8 – High-level architecture of our recommendation system

图 11.8–我们推荐系统的高级架构

从上图中,您可以看到我们有两个嵌入层,一个用于用户,另一个用于项目(电影)。然后,这两个嵌入层在传递到致密层之前合并。

因此,本质上,我们将嵌入与监督学习任务相结合,其中嵌入的输出被传递到一个密集的层,以预测用户将给某个项目(电影)的评分。

你可能会想,如果我们正在学习预测用户对产品的评价,这如何帮助我们做出推荐?好吧,诀窍在于,如果我们能够有效地预测用户对一部电影的评价,那么,使用所学的相似性嵌入,我们可以预测用户对所有电影的评价。然后,有了这些信息,我们可以向用户推荐预测收视率最高的电影。

那么,如何用 JavaScript 构建这个看似复杂的推荐系统呢?好吧,在下一小节中,我们将向您展示如何轻松地使用 TensorFlow.js,结合 Danfo.js 来实现这一点。

设置项目目录

本章的代码库中提供了成功遵循本章所需的代码和数据集。

您可以将整个项目下载到您的计算机上,以便轻松完成。如果你已经下载了项目代码,那么导航到你的根目录,在那里src文件夹是可见的。

src文件夹中,您有以下文件夹/脚本:

  • book_recommendation_model:这是保存训练模型的文件夹。
  • data:这个文件夹包含我们的训练数据。
  • data_proc.js:这个脚本包含了我们所有的数据处理代码。
  • model.js:这个脚本定义并编译推荐模型。
  • recommend.js:这个脚本包含推荐的代码。
  • train.js:这个脚本包含了训练推荐模型的代码。

要快速测试预训练的推荐模型,首先使用yarn(推荐)或NPM安装所有必要的包,然后运行以下命令:

 yarn recommend

这将为拥有19688013用户标识的用户推荐10520电影。如果成功,您应该会看到类似以下内容的输出:

Figure 11.9 – Recommended movies provided by the trained recommendation system

图 11.9–由训练有素的推荐系统提供的推荐电影

您也可以通过运行以下命令来重新训练模型:

 yarn retrain

在默认情况下,前面的命令将重新训练模型,批量大小为128,纪元大小为5,完成后,将训练好的模型保存到book_recommender_model文件夹中。

现在,您已经在本地设置了项目,我们将逐一介绍每个部分,并解释如何从头开始构建推荐系统。

检索和处理训练数据集

我们正在使用的数据集是从 Grouplens 网站检索的。默认情况下,电影数据(https://files.grouplens.org/datasets/movielens/ml-100k.zip)是包含制表符分隔文件的 ZIP 文件。为了简单起见,我已经下载了这个项目需要的两个文件,并将其转换为CSV格式。你可以从这个项目的代码库中的data文件夹中获取这些文件。

有两个主要文件:

  • movieinfo.csv:该文件包含每部电影的元数据,如标题、描述和链接。
  • movielens.csv:这是用户评分数据集。

要使用movielens数据集,我们必须用 Danfo.js 读取数据集,对其进行处理,然后将其转换为张量,我们可以将其传递给我们的神经网络。

在您项目的代码中,打开data_proc.js脚本。该脚本导出一个名为processData的主函数,代码如下:

...
    const nItem = (moviesDF["item_id"]).max()
    const nUser = (moviesDF["user_id"]).max()
    const moviesIdTrainTensor = (moviesDF["item_id"]).tensor
    const userIdTrainTensor = (moviesDF["user_id"]).tensor
    const targetData = (moviesDF["rating"]).tensor
    return {
        trainingData: [moviesIdTrainTensor, userIdTrainTensor],
        targetData,
        nItem,
        nUser
    }
...

那么,我们在前面的代码中做什么呢?谢天谢地,我们不需要太多的数据预处理,因为user_iditem_idratings列已经是数字形式的了。所以,我们只是在做两件事:

  • 从项目和用户列中检索最大标识号。这个数字,称为词汇大小,将在创建我们的模型时被传递给嵌入层。
  • 检索并返回用户、项目和评级列的基础张量。用户和项目张量将作为我们的训练输入,而评分张量将成为我们的监督学习目标。

现在您已经知道如何处理数据,让我们开始使用 TensorFlow.js 构建神经网络。

建立推荐模型

我们的推荐模型的完整代码可以在model.js文件中找到。这个模型使用了一种混合方法,正如我们在高级架构图中看到的(参见图 11.8 )。

注意

我们正在使用我们在 第 10 章*【tensorflow . js 简介】*中介绍的模型应用编程接口来创建网络。这是因为我们正在创建一个复杂的架构,我们需要在输入和输出方面有更多的控制。

在下面的步骤中,我们将解释模型,并展示创建它的相应代码:

  1. The inputs: Every model will have an entry point, and in our case, we have two inputs: one from user and the other from the items:

    ...
    const itemInput = tf.layers.input({ name: "itemInput", shape: [1] })
    const userInput = tf.layers.input({ name: "userInput", shape: [1] })
    ...

    请注意,输入的形状参数设置为1。这是因为我们的输入张量是维度为1的向量。

  2. The embedding layers: Now, we need to create two separate embedding layers – one for the user and the other for the movies:

    ...
    const itemEmbedding = tf.layers.embedding({
            inputDim: nItem + 1,
            outputDim: 16,
            name: "itemEmbedding",
        }).apply(itemInput)
    
    const userEmbedding = tf.layers.embedding({
            inputDim: nUser + 1,
            outputDim: 16,
            name: "userEmbedding",
        }).apply(userInput)
    ...

    嵌入层接受以下参数:

    a) InputDim:这是嵌入向量的词汇量大小。最大整数指数为+ 1

    b) OutputDim:这是用户指定的输出维度。也就是说,它用于配置嵌入向量的大小。

    接下来,我们将合并这些嵌入层。

  3. Merging the embedding layers: In the following code block, we are merging the output from the user and item embedding layer using a dot product, flattening the output, and passing the output to a dense layer:

    ...
    const mergedOutput = tf.layers.dot({ axes: 0}).apply([itemEmbedding, userEmbedding])
    const flatten = tf.layers.flatten().apply(mergedOutput)
    const denseOut = tf.layers.dense({ units: 1, activation: "sigmoid", kernelInitializer: "leCunUniform" }).apply(flatten)
    ...

    有了前面的输出,我们现在可以使用模型应用编程接口定义我们的模型了。

  4. 最后,我们将定义并编译模型,如下面的代码片段所示:

    ...
    const model = tf.model({ inputs: [itemInput, userInput],  outputs: denseOut })
          model.compile({
            optimizer: tf.train.adam(LEARNING_RATE),
            loss: tf.losses.meanSquaredError
          });
    ...

前面的代码使用模型应用编程接口来定义输入和输出,然后调用编译方法,该方法接受训练优化器(亚当优化器)和损失函数(均方误差)。您可以在model.js文件中查看完整的型号代码。

定义好模型架构后,我们就可以开始训练模型了。

训练并保存推荐模型

模型的训练代码可以在train.js文件中找到。该代码有两个主要部分。我们会在这里看两者。

第一部分,如下面的代码块所示,使用批次大小为128、时期大小为5、训练数据的验证分割为0.1 - 10 %来训练模型,这是为模型验证保留的:

...
await model.fit(trainingData, targetData, {
        batchSize: 128,
        epochs: 5,
        validationSplit: 0.1,
        callbacks: {
            onEpochEnd: async (epoch, logs) => {
                const progressUpdate = `EPOCH (${epoch + 1}): Train MSE: ${Math.sqrt(logs.loss)}, Val MSE:  ${Math.sqrt(logs.val_loss)}\n`
                console.log(progressUpdate);
            }
        }
    });
...

在前面的训练代码中,我们打印了每次训练历元后的损失。这有助于我们跟踪培训进度。

下面的代码块将训练好的模型保存到提供的文件路径中。在我们的例子中,我们将其保存到movie_recommendation_model文件夹:

...
await model.save(`file://${path.join(__dirname, "movie_recommendation_model")}`)
...

请记下这个文件夹的名称,因为我们将在下一小节中提出建议时使用它。

要训练模型,可以在src文件夹中运行以下命令:

yarn train 

或者,您可以直接用node运行train.js:

node train.js

这将为指定数量的时期开始模型训练,一旦完成,将模型保存到指定的文件夹中。培训完成后,您应该会得到类似以下内容的输出:

Figure 11.10 – Training logs of the recommendation model

图 11.10–推荐模型的培训日志

一旦你有了一个经过训练并保存的模型,你就可以开始制作电影推荐了。

用保存的模型制作电影推荐

recommend.js文件包含推荐代码。我们还包括了一个名为getMovieDetails的实用功能。此功能将电影标识映射到电影元数据,以便我们可以显示有用的信息,如电影名称。

但是我们如何提出建议呢?既然我们已经训练了我们的模型来预测用户给一组电影的评分,我们可以简单地将用户标识和所有电影传递给模型来进行评分预测。

有了所有电影的收视率预测,我们可以简单地按降序排序,然后将排名靠前的电影作为推荐返回。

为此,请遵循以下步骤:

  1. 首先,我们必须获得所有要预测的独特电影 id:

    ...
    const moviesDF = await dfd.read_csv(moviesDataPath)
    const uniqueMoviesId = moviesDF["item_id"].unique().values
    const uniqueMoviesIdTensor = tf.tensor(uniqueMoviesId)
    ...
  2. 接下来,我们必须构造一个与电影 ID 张量长度相同的用户张量。张量将在所有条目中具有相同的用户 ID,因为对于每部电影,我们预测相同用户将给出的评级:

    ...
    const userToRecommendForTensor = tf.fill([uniqueMoviesIdTensor.shape[0]], userId)
    ...
  3. Next, we must load the model and call the predict function by passing the movie and user tensors as input:

    ...
    const model = await loadModel()
          const ratings = model.predict([uniqueMoviesIdTensor,
       userToRecommendForTensor])
    ...

    这将返回用户对每部电影的预测评分张量。

  4. 接下来,我们必须构建一个包含两列的数据框,分别叫做movie_id(唯一的电影 id)和ratings(用户给每部电影的预测收视率):

    ...
    const recommendationDf = new dfd.DataFrame({
            item_id: uniqueMoviesId,
            ratings: ratings.arraySync()
         })
    ... 
  5. 将预测的收视率和相应的电影标识存储在数据框中有助于我们轻松地对收视率进行排序,如以下代码所示:

    ...
        const topRecommendationsDF = recommendationDf
            .sort_values({
                by: "ratings",
                ascending: false
            })
            .head(top) //return only the top rows
    ...
  6. 最后,我们必须将排序后的电影 id 数组传递给getMovieDetails实用函数。该函数将每个电影 ID 映射到相应的元数据,并返回一个包含两列(电影标题和电影发行日期)的数据帧,如下面的代码所示:

    ...
    const movieDetailsDF = await getMovieDetails(topRecommendationsDF["movie_id"].values)
    ...

src文件夹中的recommend.js脚本包含建议的完整代码,包括将电影标识映射到其元数据的实用程序功能。

要测试推荐,需要调用recommend函数,传递电影 ID 和想要的推荐数量,如下例所示:

recommend(196, 10) // Recommend 10 movies for user with id 196

前面的代码在控制台中给出了如下输出:

[
  'Remains of the Day, The (1993)',
  'Star Trek: First Contact (1996)',
  'Kolya (1996)',
  'Men in Black (1997)',
  'Hunt for Red October, The (1990)',
  'Sabrina (1995)',
  'L.A. Confidential (1997)',
  'Jackie Brown (1997)',
  'Grease (1978)',
  'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb (1963)'
]

就这样!您已经成功创建了一个使用神经网络嵌入的推荐系统,可以高效地向不同用户推荐电影。使用您在本章中学习的概念,您可以轻松创建不同的推荐系统,这些系统可以推荐不同的产品,如音乐、书籍和视频。

总结

在这一章中,我们成功构建了一个推荐系统,可以根据用户的喜好向用户推荐电影。首先,在简要介绍设计推荐系统的三种方法之前,我们定义了什么是推荐模型。然后,我们讨论了神经网络嵌入,以及为什么我们决定使用它们来创建我们的推荐模型。最后,我们通过构建一个可以向用户推荐指定数量电影的电影推荐模型,将我们所学的所有概念整合在一起。

利用您在本章中获得的知识,您可以轻松创建一个可以嵌入到您的 JavaScript 应用中的推荐系统。

在下一章也是最后一章,您将使用 Danfo.js 和 Twitter API 构建另一个实践应用。