「猜你喜欢」?摸清套路,本身也来 DIY 个推荐系统

专属图1.png

尽管还没进入六月,可各大电商的暑期大促早已如火如荼地开始了。优惠、满减、秒杀、返券……固然,在你成功清空购物车后,还少不了人民群众们喜闻乐见的「猜你喜欢」环节。常用就会发现,这类推荐内容一般都会很合你本身的口味,电商究竟是怎么作到的?这就涉及到「推荐系统」这个概念了。html

商品推荐系统,究竟是怎样的原理?

推荐系统,是利用电子商务网站向客户提供商品信息和建议,帮助用户决定应该购买什么产品,并模拟销售人员帮助客户完成购买过程。个性化推荐则是根据用户兴趣特色和购买行为,向用户推荐用户感兴趣的信息和商品。
最先的推荐算法一般是用关联规则创建的,如著名的啤酒尿不湿的故事,就是利用关联规则推荐商品促进成交的经典案例。为之而生的算法,如 Apriori算法就是亚马逊所发明的。python

当咱们在 Amazon 上购买图书时,会常常看到下面两个提示:git

  1. 这些书会被消费者一块儿购买,而且价格上有必定的折扣;
  2. 购买了这本书的人,也会购买其余书。

Amazon 会对平台中海量用户记录进行挖掘,发现这些规律,而后将这些规律应用于实际销售工做当中。而结果也显示,使用这种算法优化,对于当时亚马逊的业绩提高起到了很大做用。github

今天,随着电子商务规模的不断扩大,商品种类快速增加,顾客须要花费大量的时间才能找到本身想买的商品。这种浏览大量无关信息和产品的过程无疑会使淹没在信息过载问题中的消费者不断流失。为解决这些问题,个性化推荐系统应运而生。算法

个性化推荐系统是创建在海量数据挖掘基础上的一种高级商务智能平台,可帮助电子商务网站为其顾客购物提供彻底个性化的决策支持和信息服务。区别于传统的规则推荐,个性化推荐算法一般使用机器学习甚至深度学习算法,对于用户信息与其行为信息充分挖掘,进而进行有效的推荐。
经常使用的推荐算法有不少,其中最为经典的就是基于 Matrix Factorization(矩阵分解)的推荐。矩阵分解的思想简单来讲就是:每一个用户和每一个物品都会有本身的一些特性,用矩阵分解的方法能够从评分矩阵中分解出用户—特性矩阵,以及特性—物品矩阵等,这样作的好处是获得了用户的偏好和每件物品的特性。矩阵分解的思想也被扩展和泛化到深度学习及 Embedding 中,这样构建模型可以加强模型的准确率及灵活易用性。apache

试试看,使用 Amazon SageMaker 构建基于 Gluon 的推荐系统

下文介绍的方法将会用到 Amazon SageMaker,它能够帮助开发人员和数据科学家构建、训练并部署ML模型。Amazon SageMaker 是一项彻底托管的服务,涵盖了ML的整个工做流,能够标记和准备数据、选择算法、训练模型、调整和优化模型以便部署、预测和执行操做。json

同时本方案基于 Gluon API,Gluon 是微软联合亚马逊推出的一个开源深度学习库,这是一个清晰、简洁、简单但功能强大的深度学习API,该规范能够提高开发人员学习深度学习的速度,而无需关心所选择的深度学习框架。Gluon API提供了灵活的接口来简化深度学习原型设计、建立、训练以及部署,并且不会牺牲数据训练的速度。后端

下文将介绍如何使用 Amazon SageMaker 的自定义脚本(Bring Your Own Script,简称 BYOS)方式来运行 Gluon 程序(MXNet 后端)的训练任务,而且进行部署调用。api

首先一块儿看看如何在 Amazon SageMaker Notebook 上运行这个项目,在本地运行训练任务,而后再进行部署,直接利用 Amazon SageMaker 的相关接口调用。网络

解决方案概览

在此示例中,咱们将使用 Amazon SageMaker 执行如下操做:

  1. 环境准备
  2. 使用 Jupyter Notebook 下载数据集并将其进行数据预处理
  3. 使用本地机器训练
  4. 使用 Amazon SageMaker BYOS 进行模型训练
  5. 托管部署及推理测试
1.环境准备

首先要建立一个 Amazon SageMaker Notebook,笔记本实例类型最好选择 ml.p3.2xlarge,由于本例中用到了本地机器训练的部分用来测试咱们的代码,卷大小建议改为10GB 或更大,由于运行该项目须要下载一些额外数据。

using-amazon-sagemaker-to-build-a-recommendation-system-based-on-gluon1.png

笔记本启动后,打开页面上的终端并执行下列命令下载代码;或者也能够经过 Sagemaker Examples 找到 Introduction to Applying Machine Learning/gluon_recommender_system.ipynb,点击 Use 运行代码。

cd ~/SageMaker
git clone https://github.com/awslabs/amazon-sagemaker-examples/tree/master/introduction_to_applying_machine_learning/gluon_recommender_system
2.使用 Jupyter Notebook 下载数据集并将其进行数据预处理

本文使用了亚马逊官方开源数据集,其中包含2000位亚马逊电商用户对160k个视频的评论打分,打分分数为1-5,您能够访问该数据集主页查看完整的数听说明并下载。因为本数据集很大,咱们将使用临时目录进行存储。

!mkdir /tmp/recsys/
!aws s3 cp s3://amazon-reviews-pds/tsv/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz /tmp/recsys/
3.数据预处理

下载好数据后,能够经过Python的Pandas库进行数据的读取,浏览和预处理。

首先,运行以下代码加载数据:

df=pd.read_csv('/tmp/recsys/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz', delimiter='\t',error_bad_lines=False)
df.head()

随后能够看到以下结果,由于列比较多,这里截图显示的并不完整:

using-amazon-sagemaker-to-build-a-recommendation-system-based-on-gluon2.png

咱们能够看到,该数据集包含了不少特征(列),其中每列具体的含义以下:

  • marketplace:两位数的国家编码,此处都是「US」
  • customer_id:一个表明发表评论用户的随机编码,对于每一个用户惟一
  • review_id:对于评论的惟一编码
  • product_id:亚马逊通用的产品编码
  • product_parent:母产品编码,不少产品有同属于一个母产品
  • product_title:产品的描述
  • product_category:产品品类
  • star_rating:评论星数,从1到5
  • helpful_votes:有用评论数
  • total_votes:总评论数
  • vine:是否为 vine 项目中的评论
  • verified_purchase:该评论是否来源于已购买该产品的客户
  • review_headline:评论标题
  • review_body:评论内容
  • review_date:评论时间

在这个例子中,咱们只准备使用 custermor_id、product_id 和 star_rating 三列构建模型。这也是咱们构建一个推荐系统所须要最少的三列数据。其他特征列若是在构建模型时添加,能够有效提升模型的准确率,但本文不会包括这部份内容。同时咱们会保留 product_title 列用于结果验证。

df = df[['customer_id', 'product_id', 'star_rating', 'product_title']]

同时,由于大部分的视频对大部分人而言都没有看过,因此咱们的数据是很稀疏的。通常来讲,推荐系统的模型对于稀疏数据能够很好地处理,但这通常须要大规模的数据来训练模型。为了实验示例运行更加顺畅,这里咱们将把这种数据稀疏的场景进行验证,而且进行清洗,并使用一个较为稠密的reduced_df进行模型的训练。

随后能够经过以下代码进行稀疏(「长尾效应」)的验证

customers = df['customer_id'].value_counts()
products = df['product_id'].value_counts()
quantiles = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.96, 0.97, 0.98, 0.99, 1]
print('customers\n', customers.quantile(quantiles))
print('products\n', products.quantile(quantiles))

能够看到,只有5%的客户评论了5个及以上的视频,同时只有25%的视频被超过10个用户评论。

接下来咱们将把数据进行过滤,去掉长尾用户和产品:

customers = customers[customers >= 5]
products = products[products >= 10]
 
reduced_df = df.merge(pd.DataFrame({'customer_id': customers.index})).merge(pd.DataFrame({'product_id': products.index}))

随后对用户与产品进行从新编码:

customer_index = pd.DataFrame({'customer_id': customers.index, 'user': np.arange(customers.shape[0])})
product_index = pd.DataFrame({'product_id': products.index, 
                              'item': np.arange(products.shape[0])})
 
reduced_df = reduced_df.merge(customer_index).merge(product_index)

接下来,咱们将准备好的数据集切割为训练集和验证集。其中验证集将做为模型效果的验证使用,并不会在训练中使用:

test_df = reduced_df.groupby('customer_id').last().reset_index()
 
train_df = reduced_df.merge(test_df[['customer_id', 'product_id']], 
                            on=['customer_id', 'product_id'], 
                            how='outer', 
                            indicator=True)
train_df = train_df[(train_df['_merge'] == 'left_only')]

最后,咱们将数据集由 Pandas DataFrame 转换为 MXNet NDArray,这是由于即将使用基于 MXNe 的 Gluon 接口进行模型训练:
b

atch_size = 1024
 
train = gluon.data.ArrayDataset(nd.array(train_df['user'].values, dtype=np.float32),
                                nd.array(train_df['item'].values, dtype=np.float32),
                                nd.array(train_df['star_rating'].values, dtype=np.float32))
test  = gluon.data.ArrayDataset(nd.array(test_df['user'].values, dtype=np.float32),
                                nd.array(test_df['item'].values, dtype=np.float32),
                                nd.array(test_df['star_rating'].values, dtype=np.float32))
 
train_iter = gluon.data.DataLoader(train, shuffle=True, num_workers=4, batch_size=batch_size, last_batch='rollover')
test_iter = gluon.data.DataLoader(train, shuffle=True, num_workers=4, batch_size=batch_size, last_batch='rollover')
4.使用本地机器训练

首先经过本身定义一个简单的网络结构在本地进行训练,这里咱们会继承 Gluon 接口构建 MFBlock 类,须要设置以下的主要网络结构。更多关于如何使用 Gluon 构建自定义网络模型的信息,能够参考动手深度学习。简单说明以下:

  • embeddings 将输入的稠密向量转换为固定长度。这里咱们选取「64」,可调节
  • dense layers 全连接层,使用 ReLU 激活函数
  • dropout layer 用于防止过拟合
class MFBlock(gluon.HybridBlock):
    def __init__(self, max_users, max_items, num_emb, dropout_p=0.5):
        super(MFBlock, self).__init__()
        
        self.max_users = max_users
        self.max_items = max_items
        self.dropout_p = dropout_p
        self.num_emb = num_emb
        
        with self.name_scope():
            self.user_embeddings = gluon.nn.Embedding(max_users, num_emb)
            self.item_embeddings = gluon.nn.Embedding(max_items, num_emb)
            
            self.dropout_user = gluon.nn.Dropout(dropout_p)
            self.dropout_item = gluon.nn.Dropout(dropout_p)
 
            self.dense_user   = gluon.nn.Dense(num_emb, activation='relu')
            self.dense_item = gluon.nn.Dense(num_emb, activation='relu')
            
    def hybrid_forward(self, F, users, items):
        a = self.user_embeddings(users)
        a = self.dense_user(a)
        
        b = self.item_embeddings(items)
        b = self.dense_item(b)
 
        predictions = self.dropout_user(a) * self.dropout_item(b)     
        predictions = F.sum(predictions, axis=1)
        return predictions
##set up network
num_embeddings = 64
net = MFBlock(max_users=customer_index.shape[0], 
              max_items=product_index.shape[0],
              num_emb=num_embeddings,
              dropout_p=0.5)
# Initialize network parameters
ctx = mx.gpu()
net.collect_params().initialize(mx.init.Xavier(magnitude=60),
                                ctx=ctx,
                                force_reinit=True)
net.hybridize()
 
# Set optimization parameters
opt = 'sgd'
lr = 0.02
momentum = 0.9
wd = 0.
 
trainer = gluon.Trainer(net.collect_params(),
                        opt,
                        {'learning_rate': lr,
                         'wd': wd,
                         'momentum': momentum})

同时咱们还须要构建一个评估函数用来评价模型。这里将使用 MSE:

def eval_net(data, net, ctx, loss_function):
    acc = MSE()
    for i, (user, item, label) in enumerate(data):
        
            user = user.as_in_context(ctx)
            item = item.as_in_context(ctx)
            label = label.as_in_context(ctx)
            predictions = net(user, item).reshape((batch_size, 1))
            acc.update(preds=[predictions], labels=[label])
   
return acc.get()[1]

接下来定义训练的代码而且示例一下进行几个轮次的训练:

def execute(train_iter, test_iter, net, epochs, ctx):
    
    loss_function = gluon.loss.L2Loss()
    for e in range(epochs):
        
        print("epoch: {}".format(e))
        
        for i, (user, item, label) in enumerate(train_iter):
                user = user.as_in_context(ctx)
                item = item.as_in_context(ctx)
                label = label.as_in_context(ctx)
                
                with mx.autograd.record():
                    output = net(user, item)               
                    loss = loss_function(output, label)
                    
                loss.backward()
                trainer.step(batch_size)
 
        print("EPOCH {}: MSE ON TRAINING and TEST: {}. {}".format(e,
                                                                   eval_net(train_iter, net, ctx, loss_function),
                                                                   eval_net(test_iter, net, ctx, loss_function)))
    print("end of training")
    return net
%%time
epochs = 3
trained_net = execute(train_iter, test_iter, net, epochs, ctx)

能够看到,打印出来的训练日志以下图所示,显示训练成功进行,而且 Loss 随着迭代次数的增长在降低:

using-amazon-sagemaker-to-build-a-recommendation-system-based-on-gluon3.png

5.使用 Amazon SageMaker BYOS 进行模型训练

在上文范例中,咱们使用本地环境一步步训练了一个较小的模型,验证了咱们的代码。如今,咱们须要把代码进行整理,在 Amazon SageMaker 上进行可扩展至分布式的托管训练任务。

首先要将上文的代码整理至一个 Python 脚本,而后使用 SageMaker 上预配置的 MXNet 容器,咱们能够经过不少灵活的使用方式来使用该容器,详情可参考 mxnet-sagemaker-estimators。

接下来将执行以下几步操做:

  • 将全部数据预处理的工做封装至一个函数,本文中为prepare_train_data
  • 将全部训练相关的代码(函数或类)复制粘贴
  • 定义一个名为 Train 的函数,用于:
    ➢ 添加一段 Sagemaker 训练集群读取数据的代码
    ➢ 定义超参数为一个字典做为入参,在以前的例子咱们是全局定义的
    ➢ 建立网络而且执行训练

咱们能够在下载的代码目录中看到 Recommender.py脚本,是编辑后的范例。

如今,咱们须要将数据从本地上传至 S3,这样 Amazon SageMaker 后台运行时能够直接读取。这种方式一般对于大数据量的场景和生产环境的实践十分常见。

boto3.client('s3').copy({'Bucket': 'amazon-reviews-pds', 
                         'Key': 'tsv/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz'},
                        bucket,
                        prefix + '/train/amazon_reviews_us_Digital_Video_Download_v1_00.tsv.gz')

最后,咱们能够经过 SageMaker Python SDK 建立一个 MXNet estimator,须要传入如下设置:

  • 训练的实例类型和实例个数,SageMaker 提供的 MXNet 容器支持单机训练也支持多 GPU 训练,只须要指定训练机型个数便可切换
  • 模型存储的 S3路径及其对应的权限设置
  • 模型对应的超参数,这里咱们将 Embedding 的个数提高,后面能够看到这个结果会优于以前的结果,这里的超参数配置能够进一步进行调优,从而获得更为准确的模型

完成以上配置后,能够用.fit () 来开启训练任务,这会建立一个 SageMaker 训练任务加载数据和程序,运行咱们 Recommender.py 脚本中的 Train 函数,将模型结果保存至传入的 S3路径。

m = MXNet('recommender.py', 
          py_version='py3',
          role=role, 
          train_instance_count=1, 
          train_instance_type="ml.p2.xlarge",
          output_path='s3://{}/{}/output'.format(bucket, prefix),
          hyperparameters={'num_embeddings': 512, 
                           'opt': opt, 
                           'lr': lr, 
                           'momentum': momentum, 
                           'wd': wd,
                           'epochs': 10},
         framework_version='1.1')
 
m.fit({'train': 's3://{}/{}/train/'.format(bucket, prefix)})

训练启动后,咱们能够在 Amazon SageMaker 控制台看到这个训练任务,点进详情能够看到训练的日志输出,以及监控机器的 GPU、CPU、内存等使用率等状况,以确认程序能够正常工做。

using-amazon-sagemaker-to-build-a-recommendation-system-based-on-gluon4.png

using-amazon-sagemaker-to-build-a-recommendation-system-based-on-gluon5.png

6.托管部署及推理测试

在本地完成训练后,便可轻松地将上述模型部署成一个实时可在生产环境中调用的端口:

predictor = m.deploy(initial_instance_count=1, 
                     instance_type='ml.m4.xlarge')
predictor.serializer = None
<!-- /\* Font Definitions \*/ @font-face {font-family:"Cambria Math"; panose-1:2 4 5 3 5 4 6 3 2 4; mso-font-charset:0; mso-generic-font-family:roman; mso-font-pitch:variable; mso-font-signature:3 0 0 0 1 0;} @font-face {font-family:"Segoe UI"; panose-1:2 11 5 2 4 2 4 2 2 3; mso-font-charset:0; mso-generic-font-family:swiss; mso-font-pitch:variable; mso-font-signature:-469750017 -1073683329 9 0 511 0;} @font-face {font-family:微软雅黑; panose-1:2 11 5 3 2 2 4 2 2 4; mso-font-charset:134; mso-generic-font-family:swiss; mso-font-pitch:variable; mso-font-signature:-2147483001 718224464 22 0 262175 0;} @font-face {font-family:Consolas; panose-1:2 11 6 9 2 2 4 3 2 4; mso-font-charset:0; mso-generic-font-family:modern; mso-font-pitch:fixed; mso-font-signature:-536869121 64767 1 0 415 0;} @font-face {font-family:"\\@微软雅黑"; mso-font-charset:134; mso-generic-font-family:swiss; mso-font-pitch:variable; mso-font-signature:-2147483001 718224464 22 0 262175 0;} /\* Style Definitions \*/ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-unhide:no; mso-style-qformat:yes; mso-style-parent:""; margin-top:2.5pt; margin-right:0cm; margin-bottom:2.5pt; margin-left:0cm; mso-para-margin-top:.5gd; mso-para-margin-right:0cm; mso-para-margin-bottom:.5gd; mso-para-margin-left:0cm; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; layout-grid-mode:char; word-break:break-all; font-size:10.5pt; mso-bidi-font-size:11.0pt; font-family:"Segoe UI",sans-serif; mso-fareast-font-family:微软雅黑; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi; mso-font-kerning:1.0pt; layout-grid-mode:line;} p.a, li.a, div.a {mso-style-name:代码; mso-style-update:auto; mso-style-unhide:no; mso-style-qformat:yes; mso-style-link:"代码 字符"; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; layout-grid-mode:char; background:#BFBFBF; mso-background-themecolor:background1; mso-background-themeshade:191; word-break:break-all; font-size:10.5pt; mso-bidi-font-size:11.0pt; font-family:Consolas; mso-fareast-font-family:微软雅黑; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi; mso-font-kerning:1.0pt; layout-grid-mode:line;} span.a0 {mso-style-name:"代码 字符"; mso-style-unhide:no; mso-style-locked:yes; mso-style-link:代码; font-family:Consolas; mso-ascii-font-family:Consolas; mso-fareast-font-family:微软雅黑; mso-hansi-font-family:Consolas; background:#BFBFBF; mso-shading-themecolor:background1; mso-shading-themeshade:191; layout-grid-mode:both;} .MsoChpDefault {mso-style-type:export-only; mso-default-props:yes; font-family:等线; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi;} /\* Page Definitions \*/ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page WordSection1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.WordSection1 {page:WordSection1;} -->

上述命令运行后,咱们会在控制面板中看到相应的终结点配置。当成功建立后,能够测试一下,为此能够发出一个 HTTP Post 请求,也能够简单地调用 SDK 的.predict () 直接调用,随后会获得返回的预测结果:[5.446407794952393, 1.6258208751678467]

predictor.predict(json.dumps({'customer\_id': customer\_index\[customer\_index\['user'\] == 6\]\['customer\_id'\].values.tolist(),

 'product\_id': \['B00KH1O9HW', 'B00M5KODWO'\]}))

在此基础上,咱们还能够计算模型在测试集上的偏差,结果为1.27。这个结果优于咱们以前本地 Embedding 设置为64时的1.65,这也体现了经过调节网络结构,咱们能够不断优化模型。

test_preds = []
for array in np.array_split(test_df[['customer_id', 'product_id']].values, 40):
    test_preds += predictor.predict(json.dumps({'customer_id': array[:, 0].tolist(), 
                                                'product_id': array[:, 1].tolist()}))
 
test_preds = np.array(test_preds)
print('MSE:', np.mean((test_df['star_rating'] - test_preds) ** 2))

总结
本文介绍了如何利用Amazon SageMaker基于Gluon构建一个简单的推荐系统,而且将它进行部署调用。这能够是你们入手推荐系统的很好的入门教程。但值得注意的是:本文做为基础示例,并无包含超参数调优,网络结构的优化,多特征的引入等工做,这都是后续提高准确率构建一个完备推荐系统所必需的工做。若是须要使用更复杂深刻的推荐系统模型,或是基于 Gluon 构建其余应用,请关于咱们后续发布地相关内容,或参阅 Amazon SageMaker 官方文档。

参考资料

底图2.png

相关文章
相关标签/搜索