龙空技术网

手把手教你基于深度学习构建推荐系统

闻数起舞 4493

前言:

如今姐妹们对“推荐算法movielens”大致比较讲究,大家都想要知道一些“推荐算法movielens”的相关知识。那么小编同时在网络上收集了一些关于“推荐算法movielens””的相关文章,希望各位老铁们能喜欢,兄弟们快快来了解一下吧!

> Photo by freestocks on Unsplash

对现代电影推荐器的简要介绍

传统上,推荐系统基于诸如聚类,最近邻和矩阵分解的方法。 但是,近年来,深度学习在从图像识别到自然语言处理的多个领域都取得了巨大的成功。 推荐系统还受益于深度学习的成功。 实际上,当今的最先进的推荐系统(例如,Youtube和Amazon的推荐系统)由复杂的深度学习系统提供支持,而传统方法则更少。

为什么要使用本教程?

在阅读了许多有用的教程(包括使用矩阵分解等传统方法的推荐系统基础知识)时,我注意到缺少涵盖基于深度学习的推荐系统的教程。 在此笔记本中,我们将进行以下操作:

· 如何使用PyTorch Lightning创建自己的基于深度学习的推荐系统

· 推荐系统的隐式和显式反馈之间的区别

· 如何在不引入偏见和数据泄漏的情况下训练测试拆分数据集以训练推荐系统

· 评估推荐系统的指标(提示:准确性或RMSE不适用!)

本教程的数据集

本教程使用MovieLens 20M数据集提供的电影评论,MovieLens 20M数据集是一个受欢迎的电影评分数据集,其中包含1995年至2015年收集的2000万电影评论。

如果您想按照本教程中的代码进行操作,则可以查看我的Kaggle笔记本,在其中可以运行代码并按照本教程的步骤查看输出。

使用隐式反馈构建推荐系统

在建立模型之前,了解隐式和显式反馈之间的区别以及现代推荐系统为何建立在隐式反馈上非常重要。

明确的反馈

在推荐系统中,显式反馈是从用户那里收集的直接和定量数据。 例如,亚马逊允许用户以1-10的等级对购买的商品进行评分。 这些评级是直接从用户提供的,该量表允许Amazon量化用户的偏好。 明确反馈的另一个示例包括YouTube上的拇指向上/向下按钮,该按钮可捕获用户对特定视频的明确偏好(即喜欢或不喜欢)。

但是,显式反馈的问题在于它们很少。 如果您考虑一下,您最后一次单击YouTube视频上的"赞"按钮或对您的在线购买进行评分是什么时候? 您在YouTube上观看的视频数量有可能远远超过您明确评分的视频数量。

隐式反馈

另一方面,隐式反馈是从用户交互中间接收集的,它们充当用户偏好的代理。 例如。 您在YouTube上观看的视频会用作隐式反馈,以为您量身定制推荐内容,即使您未对视频进行明确评分。 隐式反馈的另一个示例包括您在Amazon上浏览的项目,这些项目用于为您建议其他类似的项目。

隐式反馈的优点是它很丰富。 使用隐式反馈构建的推荐器系统还使我们能够通过每次单击和交互来实时定制建议。 如今,在线推荐系统是使用隐式反馈构建的,该系统允许系统在每次用户交互时实时调整其推荐。

数据预处理

在开始构建和训练模型之前,让我们进行一些预处理,以获取所需格式的MovieLens数据。

为了使内存使用情况易于管理,我们将仅使用此数据集中30%用户的数据。 我们随机选择30%的用户,仅使用所选用户的数据。

import pandas as pdimport numpy as npnp.random.seed(123)ratings = pd.read_csv('rating.csv', parse_dates=['timestamp'])rand_userIds = np.random.choice(ratings['userId'].unique(),                                 size=int(len(ratings['userId'].unique())*0.3),                                 replace=False)ratings = ratings.loc[ratings['userId'].isin(rand_userIds)]

过滤完数据集后,现在有41,547位用户的6,027,314行数据(仍然是很多数据!)。 数据框中的每一行都对应一个用户进行的电影评论,如下所示。

训练数据测试拆分

除了评分之外,还有一个时间戳列,显示提交评论的日期和时间。 在"时间戳记"列中,我们将使用"留一法"方法实施火车测试拆分策略。 对于每个用户,将最新的评论用作测试集(即不做评论),而其余评论将用作训练数据。

为了说明这一点,下面显示了用户39849审阅的电影。 用户最近看过的电影是2014年的热门电影《银河护卫队》。 我们将把这部电影用作该用户的测试数据,并将其余的已审查电影用作训练数据。

> Movie posters from themoviedb.org (free to use)

在训练和评估推荐系统时,通常会使用这种火车测试拆分策略。 随机分组并不公平,因为我们可能会使用用户的近期评论进行培训,而使用较早的评论进行测试。 这会导致数据泄漏并带有前瞻性偏差,并且训练后的模型的性能无法推广到实际性能。

下面的代码将使用"留一法leave-one-out"方法将我们的收视率数据集划分为训练和测试集。

ratings['rank_latest'] = ratings.groupby(['userId'])['timestamp'].rank(method='first', ascending=False)train_ratings = ratings[ratings['rank_latest'] != 1]test_ratings = ratings[ratings['rank_latest'] == 1]# drop columns that we no longer needtrain_ratings = train_ratings[['userId', 'movieId', 'rating']]test_ratings = test_ratings[['userId', 'movieId', 'rating']]
将数据集转换为隐式反馈数据集

如前所述,我们将使用隐式反馈来训练推荐系统。 但是,我们正在使用的MovieLens数据集基于显式反馈。 要将此数据集转换为隐式反馈数据集,我们只需对评分进行二值化并将其转换为" 1"(即肯定类别)。 值" 1"表示用户已与商品进行了互动。

重要的是要注意,使用隐式反馈会重新构造我们的推荐程序试图解决的问题。 我们不是在使用显式反馈时尝试预测电影收视率,而是在尝试预测用户是否将与每部电影进行交互(即点击/购买/观看),以向用户展示具有最高交互可能性的电影。

train_ratings.loc[:, 'rating'] = 1

我们现在确实有问题。 对数据集进行二值化后,我们看到数据集中的每个样本现在都属于正类。 但是,我们还需要负样本来训练我们的模型,以指示用户尚未与之互动的电影。 我们假设这类电影是用户不感兴趣的电影,尽管这可能是不正确的假设,但通常在实践中效果很好。

下面的代码为每行数据生成4个负样本。 换句话说,阴性样本与阳性样本的比率为4:1。 该比率是任意选择的,但我发现它在实践中效果很好(您可以自行找到最佳比率!)。

# Get a list of all movie IDsall_movieIds = ratings['movieId'].unique()# Placeholders that will hold the training datausers, items, labels = [], [], []# This is the set of items that each user has interaction withuser_item_set = set(zip(train_ratings['userId'], train_ratings['movieId']))# 4:1 ratio of negative to positive samplesnum_negatives = 4for (u, i) in user_item_set:    users.append(u)    items.append(i)    labels.append(1) # items that the user has interacted with are positive    for _ in range(num_negatives):        # randomly select an item        negative_item = np.random.choice(all_movieIds)         # check that the user has not interacted with this item        while (u, negative_item) in user_item_set:            negative_item = np.random.choice(all_movieIds)        users.append(u)        items.append(negative_item)        labels.append(0) # items not interacted with are negative

赞! 现在,我们具有模型所需格式的数据。 在继续之前,我们先定义一个PyTorch数据集以方便训练。 下面的类将我们上面编写的代码简单地封装到PyTorch Dataset类中。

import torchfrom torch.utils.data import Datasetclass MovieLensTrainDataset(Dataset):    """MovieLens PyTorch Dataset for Training        Args:        ratings (pd.DataFrame): Dataframe containing the movie ratings        all_movieIds (list): List containing all movieIds        """    def __init__(self, ratings, all_movieIds):        self.users, self.items, self.labels = self.get_dataset(ratings, all_movieIds)    def __len__(self):        return len(self.users)      def __getitem__(self, idx):        return self.users[idx], self.items[idx], self.labels[idx]    def get_dataset(self, ratings, all_movieIds):        users, items, labels = [], [], []        user_item_set = set(zip(ratings['userId'], ratings['movieId']))        num_negatives = 4        for u, i in user_item_set:            users.append(u)            items.append(i)            labels.append(1)            for _ in range(num_negatives):                negative_item = np.random.choice(all_movieIds)                while (u, negative_item) in user_item_set:                    negative_item = np.random.choice(all_movieIds)                users.append(u)                items.append(negative_item)                labels.append(0)        return torch.tensor(users), torch.tensor(items), torch.tensor(labels)
我们的模型-神经协同过滤(NCF)

虽然有很多基于深度学习的推荐系统架构,但我发现He等人提出的框架。 是最简单的方法,它足够简单,可以在这样的教程中实现。

用户嵌入

在深入研究模型的体系结构之前,让我们熟悉一下嵌入的概念。 嵌入是一个低维空间,可捕获高维空间中矢量的关系。 为了更好地理解这一概念,让我们仔细研究一下用户嵌入。

想象一下,我们想根据用户对两类电影的喜好来代表他们—动作和浪漫电影。 假设第一维度是用户喜欢动作片的程度,第二维度是用户喜欢浪漫片的程度。

现在,假设Bob是我们的第一个用户。 鲍勃喜欢动作片,但不喜欢浪漫片。 为了将Bob表示为二维向量,我们根据其偏好将其放置在图形中。

我们的下一个用户是Joe。 乔是动作电影和浪漫电影的忠实粉丝。 就像鲍勃一样,我们使用二维向量表示乔。

该二维空间称为嵌入。 本质上,嵌入减少了我们的用户,因此可以在较低维度的空间中以有意义的方式表示他们。 在此嵌入中,具有相似电影首选项的用户彼此靠近放置,反之亦然。

当然,我们不限于仅使用2个维度来表示我们的用户。 我们可以使用任意数量的维度来表示我们的用户。 更大数量的维度将使我们能够以模型复杂性为代价,更准确地捕获每个用户的特征。 在我们的代码中,我们将使用8个维度(稍后将介绍)。

学习的嵌入

同样,我们将使用单独的项目嵌入层来表示较低维度空间中的项目(即电影)的特征。

您可能想知道,我们如何才能学习嵌入层的权重,以便它可以准确表示用户和项目? 在前面的示例中,我们使用鲍勃和乔的喜剧电影和浪漫电影手动创建了嵌入。 有没有一种方法可以自动学习此类嵌入?

答案是协作过滤-通过使用评级数据集,我们可以识别相似的用户和电影,创建从现有评级中学到的用户和项目嵌入。

模型架构

既然我们对嵌入有了更好的了解,就可以定义模型架构了。 如您所见,用户和项目嵌入是模型的关键。

让我们使用以下训练样本逐步了解模型架构:

模型的输入是userId = 3和movieId = 1的单次编码用户和项目向量。由于这是一个正样本(用户实际对电影进行评级),因此真实标签(互动)为1。

用户输入向量和项目输入向量分别被馈送到用户嵌入和项目嵌入,这导致更小,更密集的用户向量和项目向量。

嵌入的用户和项目向量在经过一系列完全连接的层之前被连接起来,该层将连接的嵌入映射为输出的预测向量。 在输出层,我们应用Sigmoid函数来获取最可能的类。 在上面的示例中,最可能的类别是1(正类别),因为0.8> 0.2。

现在,让我们使用PyTorch Lightning定义此NCF模型!

import torch.nn as nnimport pytorch_lightning as plfrom torch.utils.data import DataLoaderclass NCF(pl.LightningModule):    """ Neural Collaborative Filtering (NCF)            Args:            num_users (int): Number of unique users            num_items (int): Number of unique items            ratings (pd.DataFrame): Dataframe containing the movie ratings for training            all_movieIds (list): List containing all movieIds (train + test)    """        def __init__(self, num_users, num_items, ratings, all_movieIds):        super().__init__()        self.user_embedding = nn.Embedding(num_embeddings=num_users, embedding_dim=8)        self.item_embedding = nn.Embedding(num_embeddings=num_items, embedding_dim=8)        self.fc1 = nn.Linear(in_features=16, out_features=64)        self.fc2 = nn.Linear(in_features=64, out_features=32)        self.output = nn.Linear(in_features=32, out_features=1)        self.ratings = ratings        self.all_movieIds = all_movieIds            def forward(self, user_input, item_input):                # Pass through embedding layers        user_embedded = self.user_embedding(user_input)        item_embedded = self.item_embedding(item_input)        # Concat the two embedding layers        vector = torch.cat([user_embedded, item_embedded], dim=-1)        # Pass through dense layer        vector = nn.ReLU()(self.fc1(vector))        vector = nn.ReLU()(self.fc2(vector))        # Output layer        pred = nn.Sigmoid()(self.output(vector))        return pred        def training_step(self, batch, batch_idx):        user_input, item_input, labels = batch        predicted_labels = self(user_input, item_input)        loss = nn.BCELoss()(predicted_labels, labels.view(-1, 1).float())        return loss    def configure_optimizers(self):        return torch.optim.Adam(self.parameters())    def train_dataloader(self):        return DataLoader(MovieLensTrainDataset(self.ratings, self.all_movieIds),                          batch_size=512, num_workers=4)

让我们使用GPU训练5个时期的NCF模型。

注意:与普通的PyTorch相比,PyTorch Lightning的一个优势是您无需编写自己的样板训练代码。 注意,Trainer类是如何允许我们仅用几行代码来训练模型的。

num_users = ratings['userId'].max()+1num_items = ratings['movieId'].max()+1all_movieIds = ratings['movieId'].unique()model = NCF(num_users, num_items, train_ratings, all_movieIds)trainer = pl.Trainer(max_epochs=5, gpus=1, reload_dataloaders_every_epoch=True,                     progress_bar_refresh_rate=50, logger=False, checkpoint_callback=False)trainer.fit(model)
评估我们的推荐系统

既然我们已经训练了模型,就可以使用测试数据对其进行评估了。 在传统的机器学习项目中,我们使用诸如准确性(针对分类问题)和RMSE(针对回归问题)的指标评估模型。 但是,这样的指标对于评估推荐系统太简单了。

为了设计评估推荐系统的良好指标,我们首先需要了解如何使用现代推荐系统。

在Netflix上,我们看到以下建议列表:

同样,亚马逊使用建议列表:

这里的关键是我们不需要用户与推荐列表中的每个项目进行交互。 取而代之的是,我们只需要用户与列表中的至少一个项目进行交互-只要用户这样做,建议就起作用了。

为了模拟这一点,让我们运行以下评估协议,为每个用户生成推荐的前10个项目的列表。

· 对于每个用户,随机选择该用户未与之交互的99个项目。

· 将这99个项目与测试项目(用户最后一次与之交互的实际项目)结合起来。 我们现在有100个项目。

· 对这100个项目运行模型,并根据其预测的概率对它们进行排名。

· 从100个项目的列表中选择前10个项目。 如果测试项目位于前10个项目中,那么我们说这是一个成功。

· 对所有用户重复该过程。 命中率就是平均命中率。

此评估协议称为"命中率@ 10",通常用于评估推荐系统。

命中率@ 10

现在,让我们使用所描述的协议评估我们的模型。

# User-item pairs for testingtest_user_item_set = set(zip(test_ratings['userId'], test_ratings['movieId']))# Dict of all items that are interacted with by each useruser_interacted_items = ratings.groupby('userId')['movieId'].apply(list).to_dict()hits = []for (u,i) in test_user_item_set:    interacted_items = user_interacted_items[u]    not_interacted_items = set(all_movieIds) - set(interacted_items)    selected_not_interacted = list(np.random.choice(list(not_interacted_items), 99))    test_items = selected_not_interacted + [i]        predicted_labels = np.squeeze(model(torch.tensor([u]*100),                                         torch.tensor(test_items)).detach().numpy())        top10_items = [test_items[i] for i in np.argsort(predicted_labels)[::-1][0:10].tolist()]        if i in top10_items:        hits.append(1)    else:        hits.append(0)        print("The Hit Ratio @ 10 is {:.2f}".format(np.average(hits)))

我们在10分时获得了不错的点击率! 为了说明这一点,这意味着向86%的用户推荐了最终与他们互动的实际商品(包括10个商品)。 不错!

下一步是什么?

我希望这对创建基于深度学习的推荐器系统很有用。 要了解更多信息,我建议以下资源:

· 广泛和深度学习-Google为推荐系统引入的模型

· Microsoft的推荐库—推荐系统的最佳做法

· 基于深度学习的推荐系统-有用的调查报告

(本文翻译自James Loy的文章《Deep Learning based Recommender Systems》,参考:)

标签: #推荐算法movielens