百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

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

toyiye 2024-06-21 12:20 8 浏览 0 评论

> 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 pd
import numpy as np
np.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 need
train_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 IDs
all_movieIds = ratings['movieId'].unique()

# Placeholders that will hold the training data
users, items, labels = [], [], []

# This is the set of items that each user has interaction with
user_item_set = set(zip(train_ratings['userId'], train_ratings['movieId']))

# 4:1 ratio of negative to positive samples
num_negatives = 4

for (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 torch
from torch.utils.data import Dataset

class 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 nn
import pytorch_lightning as pl
from torch.utils.data import DataLoader

class 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()+1
num_items = ratings['movieId'].max()+1
all_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 testing
test_user_item_set = set(zip(test_ratings['userId'], test_ratings['movieId']))

# Dict of all items that are interacted with by each user
user_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》,参考:https://towardsdatascience.com/deep-learning-based-recommender-systems-3d120201db7e)

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码