精通-AWS-机器学习-全-

精通 AWS 机器学习(全)

原文:annas-archive.org/md5/837d819f8bef8ba992e2d85e9b0ed5bc

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

AWS 不断推动新的创新,使数据科学家能够探索各种机器学习云服务。这本书是您了解和实施 AWS 上的高级机器学习算法的全面参考。

随着你阅读这本书,你将深入了解这些算法如何在 AWS 上使用 Apache Spark 在 Elastic MapReduce、SageMaker 和 TensorFlow 上进行训练、调整和部署。当你专注于 XGBoost、线性模型、因子分解机以及深度网络等算法时,本书还将为你提供 AWS 的概述,以及帮助你解决现实世界问题的详细实际应用。每个实际应用都包括一系列配套笔记本,其中包含在 AWS 上运行所需的所有代码。在接下来的几章中,你将学习如何使用 SageMaker 和 EMR 笔记本来执行一系列任务,从智能分析和预测建模到情感分析。

到这本书的结尾,你将掌握处理机器学习项目所需的技能,并在 AWS 上实现和评估算法。

本书面向的对象

这本书是为数据科学家、机器学习开发者、深度学习爱好者以及希望使用 AWS 和其集成服务在云上构建高级模型和智能应用的 AWS 用户而编写的。对机器学习概念、Python 编程和 AWS 的了解将有所帮助。

本书涵盖的内容

第一章,AWS 机器学习入门,向读者介绍了机器学习。它解释了为什么数据科学家有必要学习机器学习,以及 AWS 如何帮助他们解决各种现实世界的问题。我们还讨论了本书中将涵盖的 AWS 服务和工具。

第二章,使用朴素贝叶斯分类 Twitter 流,介绍了朴素贝叶斯算法的基础,并展示了一个将通过使用此算法和语言模型解决的问题。我们将提供示例,解释如何使用 scikit-learn 和 Apache Spark 在 SageMaker 的 BlazingText 上应用朴素贝叶斯。此外,我们还将探讨如何在更复杂的情况下使用贝叶斯推理背后的思想。我们将使用 Twitter API 从两位不同的政治候选人那里实时获取推文,并预测是谁写的。我们将使用 scikit-learn、Apache Spark、SageMaker 和 BlazingText。

第三章,使用回归算法预测房价,介绍了回归算法的基础知识,并将其应用于根据多个特征预测房价。我们还将介绍如何使用逻辑回归进行分类问题。将提供 SageMaker 中 scikit-learn 和 Apache Spark 的示例。我们将使用波士顿房价数据集www.kaggle.com/c/boston-housing/,以及 scikit-learn、Apache Spark 和 SageMaker。

第四章,使用基于树的算法预测用户行为,介绍了决策树、随机森林和梯度提升树。我们将探讨如何使用这些算法来预测用户何时会点击广告。此外,我们还将解释如何使用 AWS EMR 和 Apache Spark 在大规模上构建模型。我们将使用 Adform 点击预测数据集(doi.org/10.7910/DVN/TADBY7,哈佛数据集,V2)。我们将使用 xgboost、Apache Spark、SageMaker 和 EMR 库。

第五章,使用聚类算法进行客户细分,通过探索如何根据消费者模式应用这些算法进行客户细分,介绍了主要的聚类算法。通过 AWS SageMaker,我们将展示如何在 skicit-learn 和 Apache Spark 中运行这些算法。我们将使用来自 Fabien Daniel 的电子商务数据(www.kaggle.com/fabiendaniel/customer-segmentation/data)以及 scikit-learn、Apache Spark 和 SageMaker。

第六章,分析访问模式以制定推荐策略,提出了基于用户导航模式寻找相似用户的问题,以便推荐定制营销策略。我们将介绍协同过滤和基于距离的方法,并在 AWS SageMaker 上的 scikit-learn 和 Apache Spark 中提供示例。我们将使用 Kwan Hui Lim 的游乐场景点访问数据集(sites.google.com/site/limkwanhui/datacode)、Apache Spark 和 SageMaker。

第七章,实现深度学习算法,向读者介绍了深度学习背后的主要概念,并解释了为什么它在今天的 AI 产品中变得如此重要。本章的目的是不讨论深度学习的理论细节,而是通过示例解释算法,并提供对深度学习算法的高级概念理解。这将给读者提供一个平台,以了解他们在下一章中将要实现的内容。

第八章, 《在 AWS 上使用 TensorFlow 实现深度学习,通过一系列实用的图像识别问题,并解释如何使用 AWS 上的 TensorFlow 来解决这些问题。TensorFlow 是一个非常流行的深度学习框架,可以用来训练深度神经网络。本章将解释读者如何安装 TensorFlow,并使用玩具数据集来训练深度学习模型。在本章中,我们将使用 MNIST 手写数字数据集 (yann.lecun.com/exdb/mnist/),以及 TensorFlow 和 SageMaker。

第九章, 《使用 SageMaker 进行图像分类和检测,回顾了我们在前几章中处理过的图像分类问题,但这次使用 SageMaker 的图像分类算法和目标检测算法。我们将使用以下数据集:

我们还将使用 AWS Sagemaker。

第十章, 《与 AWS Comprehend 协作,解释了 AWS 工具 Comprehend 的功能,这是一个执行各种有用任务的 NLP 工具。

第十一章, 《使用 AWS Rekognition,解释了如何使用 Rekognition,这是一个使用深度学习的图像识别工具。读者将学习一种简单的方法,将图像识别应用于他们的应用程序中。

第十二章, 《使用 AWS Lex 构建对话界面,解释了 AWS Lex 是一个允许程序员构建对话界面的工具。本章向读者介绍了诸如使用深度学习进行自然语言理解等主题。

第十三章, 《在 AWS 上创建集群,讨论了深度学习中的一个关键问题,即理解如何在多台机器上扩展和并行化学习。在本章中,我们将探讨创建学习者集群的不同方法。特别是,我们将关注如何通过分布式 TensorFlow 和 Apache Spark 并行化深度学习管道。

第十四章, 《在 Spark 和 SageMaker 中优化模型,解释了在 AWS 上训练的模型可以进一步优化,以便在生产环境中平稳运行。在本节中,我们将讨论读者可以使用的一些技巧,以改善他们算法的性能。

第十五章,调整集群以适应机器学习,解释了许多数据科学家和机器学习实践者在尝试大规模运行机器学习数据管道时面临规模问题。在本章中,我们主要关注 EMR,这是一个运行非常大的机器学习作业的非常强大的工具。配置 EMR 有许多方法,并不是每一种设置都适用于每一种场景。我们将介绍 EMR 的主要配置,并解释每种配置如何适用于不同的目标。此外,我们还将介绍其他通过 AWS 运行大数据管道的方法。

第十六章,在 AWS 上构建的模型部署,讨论了部署问题。到这时,读者将已经在 AWS 上构建了模型,并希望将它们部署到生产环境中。我们理解模型应该部署的上下文有很多种。在某些情况下,这就像生成一个将输入到某些系统的动作的 CSV 文件一样简单。通常,我们只需要部署一个能够进行预测的 Web 服务。然而,有许多时候我们需要将这些模型部署到复杂、低延迟或边缘系统中。我们将介绍您可以将机器学习模型部署到生产环境的不同方法。

要充分利用本书

本书涵盖了多个不同的框架,例如 Spark 和 Tensorflow。但它并不是针对每个框架的全面指南。相反,我们关注 AWS 如何通过使用不同的框架来赋予实际机器学习能力。我们鼓励读者在需要时参考其他具有特定框架内容的书籍。

下载示例代码文件

您可以从www.packt.com的账户下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packt.com/support并注册,以便将文件直接通过电子邮件发送给您。

您可以通过以下步骤下载代码文件:

  1. www.packt.com登录或注册。

  2. 选择“支持”标签。

  3. 点击“代码下载与勘误”。

  4. 在搜索框中输入书名,并遵循屏幕上的说明。

一旦文件下载完成,请确保您使用最新版本的软件解压或提取文件夹:

  • WinRAR/7-Zip for Windows

  • Zipeg/iZip/UnRarX for Mac

  • 7-Zip/PeaZip for Linux

该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Mastering-Machine-Learning-on-AWS。如果代码有更新,它将在现有的 GitHub 仓库中更新。

我们还有其他来自我们丰富的图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!

下载彩色图像

我们还提供了一份包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:www.packtpub.com/sites/default/files/downloads/9781789349795_ColorImages.pdf

使用的约定

本书使用了多种文本约定。

CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“以下截图显示了我们的df数据框的前几行。”

代码块设置如下:

vectorizer = CountVectorizer(input=dem_text + gop_text,
                             stop_words=stop_words,
                             max_features=1200)

任何命令行输入或输出都按以下方式编写:

wget -O /tmp/adform.click.2017.01.json.gz https://dataverse.harvard.edu/api/access/datafile/:persistentId/?persistentId=doi:10.7910/DVN/TADBY7/JCI3VG

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“您还可以在 AWS Comprehend 中使用自定义 NER 算法进行训练,方法是在左侧菜单中选择自定义 | 自定义实体识别选项。”

警告或重要注意事项显示如下。

技巧和窍门显示如下。

联系我们

我们欢迎读者的反馈。

一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中提及书名,并给我们发送邮件至customercare@packtpub.com

勘误表:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告这个错误。请访问www.packt.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。

盗版:如果您在互联网上发现我们作品的任何非法副本,我们将不胜感激,如果您能提供位置地址或网站名称。请通过copyright@packt.com与我们联系,并提供材料的链接。

如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com

评论

请留下评论。一旦您阅读并使用过这本书,为何不在您购买它的网站上留下评论呢?潜在读者可以查看并使用您的客观意见来做出购买决定,Packt 公司可以了解您对我们产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!

如需了解更多关于 Packt 的信息,请访问packt.com

第一部分:AWS 上的机器学习

本节的目标是向读者介绍在 AWS 云计算和服务背景下进行机器学习。我们期望我们的受众对机器学习有一些基本了解。然而,我们将描述典型成功的机器学习项目的性质以及通常面临的挑战。我们将概述不同的 AWS 服务,并提供典型机器学习管道的示例,以及创建智能 AI 驱动产品时需要考虑的关键方面。

本节包含以下章节:

  • 第一章,AWS 机器学习入门

第一章:AWS 机器学习的入门

在这本书中,我们通过解释商业应用中的机器学习(ML)算法,关注数据科学的三个方面,展示它们如何在可扩展的环境中实施,以及如何评估模型和展示评估指标作为业务关键绩效指标(KPI)。本书展示了如何有效地使用亚马逊网络服务(AWS)机器学习工具处理大数据集。我们提出了各种场景,在这些场景中,掌握 AWS 中的机器学习算法有助于数据科学家更有效地完成他们的工作。

让我们来看看本章将涵盖的主题:

  • AWS 如何赋能数据科学家

  • 确定可以使用机器学习解决的问题候选问题

  • 机器学习项目生命周期

  • 部署模型

AWS 如何赋能数据科学家

在过去十年中,存储在互联网上的数字数据记录数量大幅增加。由于存储成本的下降和新的数字数据来源,预计到 2025 年,可用的数字数据量将达到 163 泽字节(163,000,000,000 太字节)。此外,每天生成的数据量正在以惊人的速度增长,其中近 90%的数据仅在过去两年内生成。随着超过 35 亿人能够访问互联网,这些数据不仅由专业人士和大型公司生成,而且由每一个互联网用户生成。

此外,由于公司认识到数据的重要性,他们存储了所有交易数据,希望分析它们并揭示可能帮助其业务做出重要决策的有趣趋势。金融投资者也渴望存储和理解他们能得到的关于公司的每一丁点信息,并训练他们的定量分析师或量化分析师(quants)做出投资决策。

分析这些数据并从中发现信息宝石的责任落在了世界各地的数据科学家身上。在过去十年中,数据科学团队已成为每个组织中最重要的团队之一。当数据科学团队最初成立时,大部分数据都能适应 Microsoft Excel 表格,任务是在数据中找到统计趋势并向业务团队提供可操作的见解。然而,随着数据量的增加和机器学习算法变得更加复杂和强大,数据科学团队的职责范围也扩大了。

在以下图中,我们可以看到数据科学家需要的三项基本技能:

图片

数据科学家在不同公司中的职位描述可能会有所不同。然而,总的来说,数据科学家需要以下三个关键技能:

  • 机器学习:机器学习算法提供了分析和从大量数据中学习的方法,并从这些数据中提供预测或推荐。它是分析结构化(数据库)和非结构化(文本文档)数据并从中推断可操作见解的重要工具。数据科学家应该精通众多机器学习算法,并应了解在特定情况下应该应用哪种算法。由于数据科学家可以访问大量可以解决特定问题的算法库,他们应该知道在每种情况下应该使用哪些算法。

  • 计算机编程:数据科学家应该是一位熟练的程序员,能够编写代码来访问各种机器学习库和统计库。有大量的编程语言,如 Scala、Python 和 R,它们提供了许多库,使我们能够在数据集上应用机器学习算法。因此,了解这些工具有助于数据科学家在可行的时间内完成复杂任务。这在商业环境中至关重要。

  • 沟通:除了发现数据中的趋势和构建复杂的机器学习模型外,数据科学家还负责向业务团队解释这些发现。因此,数据科学家不仅必须具备良好的沟通技巧,还必须具备良好的分析和可视化技巧。这将帮助他们以易于理解的方式向不熟悉机器学习的人展示复杂的数据模型。这也帮助数据科学家向业务团队传达他们的发现,并为他们提供预期结果的指导。

使用 AWS 工具进行机器学习

机器学习研究跨越了几十年,其根源深深植根于数学和统计学。机器学习算法可以用于解决许多商业应用中的问题。在广告等应用领域,预测算法被用来根据先前购买者的趋势预测发现更多客户的位置。回归算法被用来根据先前趋势预测股价。Netflix 等服务使用推荐算法来研究用户的历史记录,并提高他们可能感兴趣的新节目的可发现性。人工智能AI)应用,如自动驾驶汽车,严重依赖于利用深度学习有效发现和标记道路上的对象的图像识别算法。对于数据科学家来说,理解不同机器学习算法的细微差别以及它们应该应用在哪里非常重要。使用现有的库有助于数据科学家探索特定应用领域的各种算法,并对其进行评估。AWS 提供了大量可用于执行机器学习任务的库,正如本书中关于机器学习算法和深度学习算法部分所解释的。

确定可以使用机器学习解决的问题的候选问题

对于数据科学家来说,理解他们正在处理的数据规模也很重要。可能有与医学研究相关的任务,涉及数千名患者和数百个特征,这些特征可以在单个节点设备上处理。然而,像广告这样的任务,公司根据向用户提供的每一则在线广告收集数 PB 的客户数据,可能需要数千台机器来计算和训练机器学习算法。深度学习算法对 GPU 密集型,需要不同于其他机器学习算法的机器。在这本书中,对于每个算法,我们提供如何仅使用 Python 库简单实现它的描述,然后是如何使用 Spark 和 AWS SageMaker 等技术在大 AWS 集群上扩展它的描述。我们还讨论了 TensorFlow 在深度学习应用中的使用。

理解机器学习相关任务的目标客户至关重要。尽管数据科学家找到适用于特定应用领域的算法具有挑战性,但收集有关该算法如何增强应用领域的证据并将其呈现给产品所有者也很重要。因此,我们还讨论了如何评估每个算法,并在必要时可视化结果。AWS 为评估机器学习算法和呈现结果提供了一系列工具。

最后,数据科学家还需要能够决定在 AWS 上最适合他们需求的是哪种类型的机器。一旦算法得到实施,如何以最经济的方式在大集群上部署它就变得非常重要。AWS 提供了超过 25 种硬件选择,称为实例 类型,可以选择。我们将讨论案例研究,说明如何将应用程序部署到生产集群,以及数据科学家在这个过程中可能遇到的各种问题。

机器学习项目生命周期

一个典型的机器学习项目生命周期始于理解当前的问题。通常,组织中的某个人(可能是数据科学家或业务利益相关者)认为,通过使用机器学习可以改善他们业务的一部分。例如,一家音乐流媒体公司可能会推测,提供与用户播放的歌曲相似的歌曲推荐可以改善用户与平台的互动。一旦我们理解了业务背景和可能采取的业务行动,数据科学团队在项目生命周期中需要考虑几个方面。

以下图表描述了机器学习项目生命周期中的各个步骤:

图片

数据收集

我们需要获取数据并适当地组织它以解决当前问题(在我们的例子中,这可能意味着构建一个将用户与过去听过的歌曲联系起来的数据集)。根据数据的大小,我们可能会选择不同的技术来存储数据。例如,如果我们正在处理几百万条记录,那么在本地机器上使用scikit-learn进行训练可能就足够了。然而,如果数据无法适应单个计算机,那么我们必须考虑 AWS 解决方案,如 S3 存储和 Apache Spark,或者 SageMaker 内置的算法进行模型构建。

评估指标

在应用机器学习算法之前,我们需要考虑如何评估我们策略的有效性。在某些情况下,我们可以使用我们数据的一部分来模拟算法的性能。然而,在其他情况下,评估算法应用的有效性的唯一可行方式是通过进行一些受控测试(A/B 测试),并确定算法应用的使用案例是否导致了更好的结果。在我们的音乐流媒体示例中,这可能意味着选择一组用户,并使用新算法向他们推荐歌曲。我们可以运行统计测试来确定这些用户是否有效地在平台上停留了更长的时间。评估指标应根据业务 KPI 确定,并应显示出与现有流程相比的显著改进。

算法选择

我们需要对创建算法的复杂问题进行迭代。这包括探索数据以深入了解潜在变量。一旦我们有了想要应用的算法类型,我们就需要进一步准备数据,可能还需要将其与其他数据源(例如,人口普查数据)结合。在我们的例子中,这可能意味着创建一个歌曲相似度矩阵。一旦我们有了数据,我们就可以训练一个模型(能够进行预测)并测试该模型在保留数据上的表现。在这个过程中有许多考虑因素,使得它变得复杂:

  • 数据的编码方式(例如,歌曲矩阵是如何构建的)

  • 使用的算法(例如,协同过滤或基于内容的过滤)

  • 您的模型所采用的参数值(例如,平滑常数或先验分布的值)

我们在这本书中的目标是,通过展示数据科学家在创建成功模型的过程中会经历的迭代步骤,以及以现实应用为例,使这一步对你来说更容易。

模型部署

一旦我们生成一个符合我们初始 KPI 要求的模型,我们需要将其部署到生产环境中。这可能像创建一个需要解决每个社区和政冶问题的社区列表这样简单,也可能像将模型发送到数千台机器上,以实时决定为特定营销活动购买哪些广告这样复杂。一旦部署到生产环境,持续监控那些 KPIs 以确保我们仍在解决最初目标的问题非常重要。有时,由于趋势的变化,模型可能产生负面影响,需要训练另一个模型。例如,随着时间的推移,听众可能对不断听到相同的音乐风格失去兴趣,这个过程必须从头开始。

摘要

在本章中,我们首先学习了 AWS 如何赋能机器学习实践者和数据科学家。然后我们了解了可用于机器学习的各种 AWS 工具,之后我们学习了机器学习生命周期。最后,我们学习了如何部署模型。

在下一章中,我们将讨论各种流行的机器学习算法,并了解如何在 AWS 上大规模实现它们。在继续下一章之前,我们建议对 AWS 新手读者阅读附录,即“AWS 入门”,它涵盖了创建新 AWS 账户的过程。

练习

  1. 在你的手机上,定义三个你可以识别的、实现机器学习的应用程序。对于每个应用程序,根据本章中提供的步骤定义其项目生命周期。

  2. 搜索三个数据科学家职位,并仔细审查职位要求。对于每个要求,分类该技能属于沟通、机器学习还是计算机编程。

  3. 作为数据科学家,了解你周围生成可用于机器学习的数据的应用程序非常重要。基于你使用的电子设备,列出你每天生成的数据。定义三个可以使用你生成数据的机器学习应用程序。

第二部分:在 AWS 上大规模实现机器学习算法

在本节中,我们将讨论各种流行的机器学习算法及其工作原理。我们将提供它们在哪些情况下表现良好以及何时应避免使用它们的例子。读者将了解不同的机器学习算法,并能够通过 scikit-learn 中的简单示例进行操作,并在 AWS 的背景下将它们扩展到 Apache Spark。阅读本节后,我们期望读者能够掌握机器学习算法的工作原理以及如何在 AWS 上大规模实现它们。

本节包含以下章节:

  • 第二章,使用朴素贝叶斯对 Twitter 推文进行分类

  • 第三章,使用回归算法预测房屋价值

  • 第四章,使用基于树的预测用户行为

  • 第五章,使用聚类算法进行客户细分

  • 第六章,分析访问模式以提出推荐

第二章:使用朴素贝叶斯分类 Twitter 流

机器学习(ML)在分析大数据集和从数据中提取可操作见解方面发挥着重要作用。ML 算法执行预测结果、聚类数据以提取趋势和构建推荐引擎等任务。了解 ML 算法有助于数据科学家了解他们处理的数据的性质,并计划应用哪些算法以从数据中获得期望的结果。尽管有多种算法可用于执行任何任务,但数据科学家了解不同 ML 算法的优缺点非常重要。应用 ML 算法的决定可以基于各种因素,例如数据集的大小、用于训练和部署 ML 模型的集群预算以及错误率成本。尽管 AWS 在选择和部署 ML 模型方面提供了大量选项,但数据科学家必须了解在不同情况下应使用哪些算法。

在本书的这一部分,我们介绍了各种流行的 ML 算法以及它们可以有效地应用的示例。我们将解释每个算法的优点和缺点,以及在这些算法应该在 AWS 中选择的情况。鉴于本书是为数据科学学生和专业人士编写的,我们将通过一个简单的示例展示如何使用简单的 Python 库实现这些算法,然后使用 Spark 和 AWS SageMaker 在更大的数据集上部署。这些章节应有助于数据科学家熟悉流行的 ML 算法,并帮助他们了解在 AWS 集群的大数据环境中实现这些算法的细微差别。

第二章,使用朴素贝叶斯分类 Twitter 流,第三章,使用回归算法预测房屋价值,第四章,使用基于树的算法预测用户行为,以及第五章,使用聚类算法进行客户细分,介绍了四种分类算法,这些算法可以根据特征集预测结果。第六章,分析访问模式以提供建议,解释了聚类算法,并展示了它们如何用于客户细分等应用。第七章,实现深度学习算法,介绍了一种推荐算法,可以根据用户的购买历史向用户推荐新项目。

本章将介绍朴素贝叶斯算法的基础知识,并展示一个将通过使用此算法和语言模型来解决的问题。我们将提供如何在 scikit-learn、Apache Spark 和 SageMaker 的 BlazingText 上应用它的示例。此外,我们还将探讨如何在更复杂的场景中进一步使用贝叶斯推理背后的思想。

在本章中,我们将涵盖以下主题:

  • 分类算法

  • 朴素贝叶斯分类器

  • 使用语言模型进行文本分类

  • 朴素贝叶斯 — 优点和缺点

分类算法

机器学习算法中流行的一个子集是分类算法。它们也被称为监督学习算法。对于这种方法,我们假设我们有一个丰富的特征和事件数据集。算法的任务是根据一组特征预测一个事件。事件被称为类别变量。例如,考虑以下与天气相关的特征数据集,以及那天是否下雪:

表 1:样本数据集

温度 (华氏度) 天空状况 风速 (英里/小时) 降雪量
小于 20 晴朗 30
20-32 度 晴朗 6
32-70 度 多云 20
70 度以上 多云 0
20-32 度 多云 10
32-70 度 晴朗 15
小于 20 多云 8
32-70 度 晴朗 7
20-32 度 多云 11
小于 20 晴朗 13

在数据集中,气象站有关于该日温度、天空状况和风速的信息。他们还有关于何时收到降雪的记录。他们正在工作的分类问题是根据温度、天空状况和风速等特征预测降雪。

让我们讨论一下在机器学习数据集中使用的术语。对于示例表,如果分类问题是预测降雪,那么降雪特征被称为类别目标变量。非类别值被称为属性或特征变量。数据集中的每一行被称为一个观测值。

特征类型

在分类数据集中有三种类型的特征可用。数据科学家需要能够区分不同特征的原因是,并非每个机器学习算法都支持每种类型的特征。因此,如果特征集的类型与期望的算法不匹配,那么特征就需要进行预处理,以转换成分类算法可以处理的特征。

名义特征

名义分类特征是具有有限个分类值的特征,这些值不能按任何特定顺序排列。在示例数据集中,天空状况特征是一个名义特征。在表中,名义特征的值要么是晴朗,要么是多云。其他名义特征的例子包括性别和颜色。名义特征可以通过使用如独热编码等技术转换为连续变量。

序列特征

序列特征,类似于名义特征,也具有有限个分类值。然而,与名义特征不同,这些分类值可以按特定顺序排列。在先前的例子中,温度特征是一个序列特征。这个类别的标签可以从最冷到最暖排列。序列特征可以通过将范围值插值到定义的尺度来转换为连续变量。

连续特征

连续特征可以有无穷多个可能的值。与只能有离散值集合的名义和序列特征不同,连续变量是数值变量,并且与某些机器学习算法不兼容。然而,可以使用称为离散化的技术将连续特征转换为序列特征。

尽管我们在这里不会讨论将特征从一种形式转换为另一种形式的技巧,但我们将通过示例部分展示如何实现。在这本书中,我们选择了需要特征转换的示例数据集。您不仅应该从这本书中了解这些不同的转换技巧,还应该观察数据科学家如何分析数据集,并根据应用使用特定的特征转换技巧。我们还提供了在 Python 和 AWS SageMaker 中大规模应用这些技巧的示例。

朴素贝叶斯分类器

基于贝叶斯定理的朴素贝叶斯分类器是一种机器学习算法。该算法与信念系统的发展方式相似。贝叶斯定理最初由英国数学家托马斯·贝叶斯在 1776 年提出。这个算法有各种应用,并且被用于超过两个世纪的许多历史任务。这个算法最著名的应用之一是艾伦·图灵在第二次世界大战期间的应用,他使用贝叶斯定理来解密德国恩尼格玛密码。贝叶斯定理在机器学习中也为诸如贝叶斯网络和朴素贝叶斯算法等算法找到了一个重要的位置。朴素贝叶斯算法因其低复杂性和预测原因的透明度而在机器学习中非常受欢迎。

贝叶斯定理

在本节中,我们将首先介绍贝叶斯定理,并展示它在机器学习中的应用。

贝叶斯定理计算在给定条件下事件发生的概率,这样我们就有关于事件、条件和事件发生时条件概率的先验知识。在我们的雪预测例子中,事件是下雪。条件是温度在 20°F 到 32°F 之间。根据数据,我们可以计算下雪时温度在 20°F 和 32°F 之间的似然。使用这些数据,我们可以预测在温度在 20°F 到 32°F 之间时下雪的概率。

假设我们有一个类别变量 C 和一个条件变量 x。贝叶斯定理在公式 1 中给出。我们还在公式 2 中提供了一个简单的方法来记住算法的不同组成部分。

公式 1

图片

**公式 2 **

图片

从这个公式中,你需要记住四个术语。

后验

后验概率是在特征变量 x 存在的情况下事件发生的概率。

似然

似然是指给定事件发生时某个条件出现的概率。在我们的例子中,似然指的是在下雪时温度在 20°F 到 32°F 之间的概率。根据数据集中的数据,下雪时温度在 20°F-30°F 之间的概率是 66.66%。训练数据可以用来计算特征集中每个离散值的概率。

先验概率

先验概率是数据集中事件的总体概率。在我们的例子中,这将是数据集中下雪的总体概率。先验概率在数据集不平衡的情况下很重要,即数据集中一个类别变量的实例数量显著高于另一个。这会导致似然变量的偏差。先验概率通过考虑数据集中的偏差来重新规范化这些概率。例如,在我们的数据集中,雪事件的先验概率是 30%,不下雪的先验概率是 70%。下雪时多云的概率是 66%,而不下雪时多云的似然是 42.8%。

然而,考虑到先验概率,尽管下雪时多云的条件比不下雪时更可能,但在乘以先验概率后,多云时的后验概率为 19%,多云时不下雪的概率为 30%。通过将先验概率与似然事件相乘,我们告知我们的后验概率是它不下雪的概率比下雪的概率更高。

证据

证据变量是数据集中一个条件的概率。在我们的例子中,温度为 70°F 或以上的概率仅为 10%。罕见事件具有低的证据概率。证据概率会提高罕见事件的后验概率。对于 Naïve Bayes 分类器来说,我们不需要考虑证据变量,因为它不依赖于类变量。

因此,贝叶斯定理用于计算给定单个条件的事件的概率。然而,当我们训练机器学习算法时,我们使用一个或多个特征来预测事件的概率。在下一节中,我们将解释 Naive Bayes 算法以及它是如何利用多个特征变量的后验概率的。

Naive Bayes 算法的工作原理

Naive Bayes 算法使用贝叶斯定理来计算数据集中每个条件的后验概率,并使用这些概率来计算给定一组条件的事件的条件概率。Naive Bayes 算法假设每个条件特征是相互独立的。这是一个重要的假设,有助于简化条件概率的计算方式。独立性假设是算法得名 Naive Bayes 的原因。

在本节中,我们不是考虑一个 x 特征变量,而是考虑一个特征向量,图片,其中 n 是用于计算类概率的特征变量的数量。我们在公式 3 中表示 x 向量的类变量的条件概率:

公式 3

图片

由于我们假设每个特征变量是相互独立的,因此可以按以下方式计算类变量的条件概率:

公式 4

图片

根据前几节中所示的后验概率计算,此公式可以重写如下:

公式 5

图片

公式 5 解释了如何根据图片特征变量计算事件 C 的概率。在这个公式中值得注意的是,如何轻松地从数据集中计算每个元素。此外,由于贝叶斯定理中的证据概率不依赖于类变量,因此它没有用于 Naive Bayes 公式中。

Naive Bayes 算法在训练阶段只需要遍历一次数据集,就可以计算每个事件的特征值的概率。在预测阶段,我们根据特征实例计算每个事件的概率,并预测概率最高的那个事件。公式 6 展示了当有 k 个可能事件时,Naive Bayes 分类器的预测是如何计算的。公式中的 Argmax 表示选择概率最大的事件作为预测:

** 公式 6**

Naive Bayes 分类器是一种多类分类器,可以用于在需要预测两个或更多类变量的数据集上进行训练。在下一章中,我们将展示一些仅适用于需要预测两个类变量的二元分类器的示例。然而,我们将向您展示将二元分类器应用于多类问题的方法。

使用语言模型进行文本分类

文本分类是分类算法的应用。然而,文本是按特定顺序组合的单词。因此,您可以看到,具有类变量的文本文档与我们之前在 分类算法 部分中展示的表 1 中的数据集并不相似。

文本数据集可以表示如表 2 所示。

表 2:Twitter 数据集示例

推文 账户
保护美国人民免受枪支暴力的最简单方法就是真正讨论常识性的枪支法律。 民主党
这不能是我们作为一个国家的样子。我们需要找出发生了什么,并确保它永远不会再次发生 (t.co/RiY7sjMfJK)) 民主党
周末,特朗普总统访问了阿灵顿国家公墓,向阵亡士兵致敬。 共和党
这位总统已经明确表示他将保护这个国家——@SecNielsen 共和党

对于本章,我们基于两个不同账户的推文构建了一个数据集。我们还在以下章节中提供了代码,以便您可以创建自己的数据集来尝试这个示例。我们的目的是构建一个智能应用程序,能够仅通过阅读推文文本就能预测推文的来源。我们将收集美国共和党(@GOP)和民主党(@TheDemocrats)的多个推文来构建一个模型,可以预测哪个政党撰写了给定的推文。为了做到这一点,我们将从每个政党随机选择一些推文并通过模型提交,以检查预测是否与实际情况相符。

收集推文

我们将首先使用 Twython 库访问 Twitter API 并收集一系列推文,并给它们标注起源的政治党派。

实现的细节可以在以下 Jupyter Notebook 中的我们的 GitHub 仓库中找到:

chapter2/collect_tweets.ipynb

我们需要在 Twython 库中调用以下方法来将来自 @GOP@TheDemocrats 的推文分别保存到一些文本文件中,gop.txtdems.txt

twitter.get_user_timeline(screen_name='GOP', tweet_mode='extended', count=500)

每个文件包含 200 条推文。以下是从 dems.txt 文件中的一些摘录:

  • 这不能代表我们作为一个国家的样子。我们需要找出发生了什么,并确保它不再发生。

  • RT @AFLCIO: Scott Walker. 永远是国家耻辱。

准备数据

现在我们已经将源数据保存在文本文件中,我们需要将其转换为可以用于机器学习库输入的格式。大多数通用机器学习包,如 scikit-learn 和 Apache Spark,只接受数字矩阵作为输入。因此,需要对文本数据集进行特征转换。一种常见的方法是使用语言模型,如词袋模型BoW)。在这个例子中,我们为每条推文构建一个 BoW,并构建一个矩阵,其中每一行代表一条推文,每一列表示特定单词的存在。我们还有一个用于区分来自 共和党1)或 民主党0)的推文的标签列,如下表所示:

表 3:将文本数据集转换为结构化数据集

移民 医疗补助 恐怖主义 阶级
推文 1 0 1 0 0
推文 2 1 0 1 1
推文 3 0 0 1 0

表 2 表示可以从推文中导出的矩阵。然而,在生成此类矩阵时有许多需要注意的点。由于语言词汇表中的术语数量,矩阵的列数可能非常高。这给机器学习带来了一个称为维度诅咒的问题(见第 X 节)。有几种方法可以解决这个问题;然而,由于我们的示例在数据量上相对较小,我们只会简要讨论减少列数的方法。

  • 停用词:某些常见的单词可能对我们的任务没有价值(例如,单词 theforas)。我们称这些单词为停用词,我们将从 dems.txtgop.txt 中删除这些单词。

  • 词干提取:文本中可能有许多单词的变体。例如,argue、argued、argues 和 arguing 都来自单词 argue。可以使用词干提取和词形还原等技术来找到单词的词干,并用该词的变体替换词干。

  • 分词:分词可以将各种单词组合成短语,从而减少特征的数量。例如,“tea party”在政治上与单独的两个词有完全不同的含义。我们不会在我们的简单示例中考虑这一点,但分词技术有助于找到这样的短语。

另一个需要考虑的问题是,在推文中出现多次的单词在训练行中具有同等的重要性。可以通过使用多项式或词频-逆文档频率(TFIDF)模型来利用这些信息。由于推文相对较短,我们不会在我们的实现中考虑这个方面。

表 2 矩阵描述了每个类别(即每个政党)中可能找到的单词。然而,当我们想要预测推文的来源时,就提出了逆问题。给定一个特定的词袋,我们感兴趣的是评估这些术语被一个政党或另一个政党使用的可能性。换句话说,我们知道给定一个特定政党,词袋的概率,而我们感兴趣的是相反的情况:给定一个词袋,推文被一个政党撰写的概率。这就是朴素贝叶斯算法应用的地方。

通过 SageMaker 笔记本构建朴素贝叶斯模型

让我们开始使用 SageMaker 笔记本。这个工具将帮助我们运行训练模型的代码。SageMaker 除了其他功能外,还允许我们创建笔记本实例,这些实例托管 Jupyter 笔记本。Jupyter 是一个 Web UI,允许数据科学家或程序员通过创建代码段落进行交互式编码,这些代码段落按需执行。它作为一个 IDE,但具有将代码输出以视觉相关形式(例如图表、表格和 Markdown)呈现的附加功能,并且还支持在同一个笔记本中用不同语言编写段落。我们将在这本书中广泛使用笔记本,并推荐将其用作分享和展示数据科学发现的方法。它允许用户实现可重复研究,因为特定研究目标所需的代码可以通过重新运行笔记本中的代码段落进行验证和重现。

您可以在 SageMaker 的 AWS 控制台页面了解更多信息,请访问console.aws.amazon.com/sagemaker/home?region=us-east-1#/dashboard

让我们看看 SageMaker 的 AWS 控制台页面在以下截图中的样子:

图片

点击“添加仓库”,选择您的身份验证机制,并添加位于github.com/mg-um/mastering-ml-on-aws的仓库:

图片

在创建笔记本实例之前,您可能希望附加一个 Git 仓库,以便本书提供的笔记本可以附加到笔记本上,并立即可用,正如您稍后将会看到的:

图片

我们现在可以继续启动笔记本实例。为将要托管笔记本的服务器配置硬件、网络和安全有多种选项。然而,我们现在不会深入细节,而是接受默认设置。如果你想要限制访问或增强你的机器,AWS 文档是一个极好的资源。

由于我们附加了 Git 仓库,一旦你打开 Jupyter,你应该能看到我们为这本书创建的笔记本,你可以重新运行它们、修改它们或改进它们:

图片

在本节中,我们关注 train_scikit Python 笔记本,并回顾代码片段来解释我们如何构建和测试用于推文分类问题的模型。我们鼓励你运行这个笔记本的所有段落,以了解这个笔记本的目的。

我们首先将加载停用词和两组推文到变量中:

import pandas as pd
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
from scipy import sparse

SRC_PATH = '/home/ec2-user/SageMaker/mastering-ml-on-aws/chapter2/'
stop_words = [word.strip() for word in open(SRC_PATH + 'stop_words.txt').readlines()]
with open(SRC_PATH + 'dem.txt', 'r') as file:
   dem_text = [line.strip('\n') for line in file]
with open(SRC_PATH + 'gop.txt', 'r') as file:
   gop_text = [line.strip('\n') for line in file]

我们将接着使用 scikit-learn 中的工具来构建我们的矩阵。为了做到这一点,我们将使用一个 CountVectorizer 类,这是一个知道如何将不同的单词分配到列同时过滤掉停用词的类。我们将考虑两组推文;在我们的例子中,我们将只使用前 1200 个单词:

vectorizer = CountVectorizer(input=dem_text + gop_text,
                             stop_words=stop_words,
                             max_features=1200)

通过 vectorizer 我们现在可以构建两个矩阵,一个用于共和党推文,另一个用于民主党推文:

dem_bow = vectorizer.fit_transform(dem_text)
gop_bow = vectorizer.fit_transform(gop_text)

这两个词袋矩阵(dem_bowgop_bow)以稀疏数据结构表示,以最小化内存使用,但可以通过将它们转换为数组来检查:

>>> gop_bow.toarray()

array([[0, 0, 1, ..., 0, 1, 0],
      [0, 0, 0, ..., 0, 0, 1],
      [0, 1, 0, ..., 0, 0, 0],
      ...,
      [0, 0, 0, ..., 0, 0, 0],
      [0, 1, 0, ..., 0, 0, 0],
      [0, 0, 0, ..., 0, 1, 0]], dtype=int64)

为了训练我们的模型,我们需要提供两个数组。一个是 BoWs 矩阵(针对双方),我们将称之为 x,另一个是每条推文的标签(类别变量)。为了构建这个,我们将垂直堆叠两个矩阵(针对每个政党):

x = sparse.vstack((dem_bow, gop_bow))

为了构建标签向量,我们只需组装一个向量,其中 Democrat 位置为 ones,而 Republican 位置为 zeros

ones = np.ones(200)
zeros = np.zeros(200)
y = np.hstack((ones, zeros))

在训练我们的模型之前,我们将随机分割推文(x 矩阵的行),以便一些用于构建模型,而另一些用于检查模型是否正确预测政治党派(标签):

from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.25, random_state=42)

现在我们已经有了训练集和测试集,我们继续使用朴素贝叶斯(由于我们的矩阵是 ones 或 zeros,因此是伯努利朴素贝叶斯)来训练我们的模型:

from sklearn.naive_bayes import BernoulliNB
naive_bayes = BernoulliNB()
model = naive_bayes.fit(x_train, y_train)

如前述代码所示,拟合朴素贝叶斯模型非常简单。我们需要提供训练矩阵和标签。现在,模型能够预测任意推文的标签(政治党派)(只要我们以 BoWs 矩阵表示形式拥有它们)。幸运的是,我们已经为测试分离了一些推文,因此我们可以将这些推文通过模型,看看模型预测正确标签的频率(注意,我们知道测试数据集中每条推文的实际党派)。

要获取预测结果,只需调用模型的predict方法:

y_predictions = model.predict(x_test)

现在,我们可以看到有多少预测结果与真实值相匹配:

from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_predictions)

代码块的输出分数为0.95

在这个例子中,我们使用准确率作为评估指标。准确率可以使用公式 7 计算:

公式 7

数据科学家可以使用各种评估指标来评估机器学习算法。在下一章中,我们将介绍如精度、召回率、F1 度量、均方根误差RMSE)和曲线下面积AUC)等评估指标,以不同的示例进行展示。评估指标应根据实现算法的业务需求进行选择,并应表明机器学习算法是否达到了完成任务所需的标准。

由于这是我们正在处理的第一个例子,我们将使用最简单的评估指标,即准确率。根据公式 7,准确率是正确预测数与分类器做出的总预测数之比。结果证明,我们的朴素贝叶斯模型非常准确,准确率为 95%。可能有些词,如每个党派的成员名称,可以迅速使模型给出正确的预测。我们将使用决策树在第四章,基于树的预测用户行为方法中探讨这一点。

注意,在这个过程中,我们必须准备和转换数据以适应模型。这个过程非常常见,scikit-learn和 Spark 都支持管道的概念,允许数据科学家声明构建模型所需的必要转换,而无需手动获取中间结果。

在下面的代码片段中,我们可以看到通过创建以下两个阶段的管道来产生相同模型的一种替代方法:

  • 计数向量器

  • 朴素贝叶斯训练器

from sklearn.pipeline import Pipeline
x_train, x_test, y_train, y_test = train_test_split(dem_text + gop_text, y, test_size=0.25, random_state=5)
pipeline = Pipeline([('vect', vectorizer), ('nb', naive_bayes)])
pipeline_model = pipeline.fit(x_train, y_train)
y_predictions = pipeline_model.predict(x_test)
accuracy_score(y_test, y_predictions)

这使得我们的建模更加简洁和声明性。通过调用pipeline.fit()方法,库应用任何必要的转换或估计。请注意,在这种情况下,我们分割了原始文本(而不是矩阵),因为fit()方法现在接收的是原始输入。正如我们将在下一节中看到的,管道可以包含两种类型的阶段,Transformers 和 Estimators,这取决于该阶段是否需要从数据中计算模型,或者只是声明性地转换数据。

在 SageMaker 笔记本中使用 Apache Spark 的朴素贝叶斯模型

在上一节使用语言模型对文本进行分类中,我们看到了如何在 SageMaker 笔记本实例上使用scikit-learn训练模型。对于像我们从 Twitter 收集的例子那样小的例子来说,这是可行的。如果我们有数百 TB 的推文数据呢?首先,我们无法在单个机器上存储这些数据。即使我们能够做到,在如此大的数据集上训练可能也需要太长时间。Apache Spark 通过实现可以读取分布式数据集(如 AWS S3)的 ML 算法并可以在多台机器上分配计算来解决我们的问题。AWS 提供了一个名为弹性映射减少EMR)的产品,它能够启动和管理我们可以在其上执行大规模 ML 的集群。

许多 ML 算法需要对数据进行多次遍历(尽管朴素贝叶斯不是这种情况)。Apache Spark 提供了一种将数据集缓存到内存中的方法,这样就可以高效地运行需要多次遍历数据的算法(如逻辑回归决策树,我们将在下一章中看到)。我们将在第四章,使用基于树的算法预测用户行为中展示如何启动 EMR 集群,然而,在本节中,我们将展示与 Apache Spark 相比,与scikit-learn一起工作的相似性。事实上,Apache Spark 中的许多接口(如管道、转换器和估计器)都受到了scikit-learn的启发。

Apache Spark 支持四种主要语言:R、Python、Scala 和 Java。在这本书中,我们将使用 Python 版本,也称为 PySpark。尽管我们的 Spark 代码将在单个机器上运行(即在 SageMaker 笔记本实例上运行),但如果我们的数据更大,并且我们有 Spark 集群(在第四章使用基于树的算法预测用户行为中,我们将深入了解使用 EMR 创建 Spark 集群),它可以在不更改任何代码的情况下在多台机器上运行。

在 Spark 中,我们首先需要做的是创建一个 Spark 会话。我们通过首先创建一个 Spark 上下文,然后创建一个用于类似 SQL 数据操作会话来完成此操作:

from pyspark.context import SparkContext
from pyspark.sql import SQLContext

sc = SparkContext('local', 'test')
sql = SQLContext(sc)

由于我们将本地运行 Spark(在单个机器上),我们指定local。然而,如果我们要在集群上运行此操作,我们需要指定集群的主机地址。Spark 使用称为 DataFrame 的抽象,允许我们使用类似 SQL 的操作来操作大量数据表。

我们的第一项任务将是为我们的原始数据定义 DataFrame:

from pyspark.sql.functions import lit

dems_df = sql.read.text("file://" + SRC_PATH + 'dem.txt')
gop_df = sql.read.text("file://" + SRC_PATH + 'gop.txt')
corpus_df = dems_df.select("value", lit(1).alias("label")).union(gop_df.select("value", lit(0).alias("label")))

在前两行中,我们将原始推文创建为 DataFrame。我们还创建了corpus_df,它包含推文的两个来源,并通过创建一个包含1的列来标记民主党人,0来标记共和党人

>>> corpus_df.select("*").limit(2).show()

+--------------------+-----+
|               value|label|
+--------------------+-----+
|This ruling is th...| 1 . |
|No president shou...| 1 . |
+--------------------+-----+

Spark 以惰性方式工作,因此,尽管我们定义并联合了 DataFrame,但在我们执行数据上的第一个操作之前,实际上不会发生任何处理。在我们的例子中,这将是将 DataFrame 分割为测试集和训练集:

train_df, test_df = corpus_df.randomSplit([0.75, 0.25])

现在,我们已准备好训练我们的模型。Spark 支持相同的管道概念。我们将构建一个包含模型所需转换的管道。它与我们的上一个例子非常相似,只是 Spark 有两个单独的阶段用于分词和停用词去除:

from pyspark.ml import Pipeline
from pyspark.ml.feature import CountVectorizer, Tokenizer, StopWordsRemover
tokenizer = Tokenizer(inputCol="value", outputCol="words")
stop_words_remover = StopWordsRemover(inputCol="words", outputCol="words_cleaned")
vectorizer = CountVectorizer(inputCol="words_cleaned", outputCol="features")
cleaning_pipeline = Pipeline(stages = [tokenizer, stop_words_remover, vectorizer])
cleaning_pipeline_model = cleaning_pipeline.fit(corpus_df)
cleaned_training_df = cleaning_pipeline_model.transform(train_df)
cleaned_testing_df = cleaning_pipeline_model.transform(test_df)

Spark ML 管道由一系列阶段组成。每个阶段可以是 Transformer 或 Estimator。Transformers 在数据集上应用一个定义良好的转换,而 Estimators 通过遍历数据集具有生成模型的能力。NaiveBayesCountVectorizer 是 Estimator 的例子,而分词器和 StopWordsRemover 是 Transformer 的例子。反过来,模型也是 Transformers,因为它们可以提供数据集中所有元素的预测作为转换。

如前述代码所示,我们定义了一个包含所有必要阶段的管道来清理数据。每个阶段都将转换原始 DataFrame(仅包含两个列 value,分别是原始推文文本和标签)并添加更多列。

在以下代码中,训练时使用的相关列是特征(一个稀疏向量,表示与我们的 scikit-learn 示例中完全相同的 BoWs)和标签:

>>> cleaned_training_df.show(n=3)

+-----------+------------------+-------------+--------------------+
| value     |label| . words .  |words_cleaned| features           |
+-----------+------------------+-------------+--------------------+
|#Tuesday...| 1 . |[#tuesday...|[#tuesday... |(3025,[63,1398,18...|
|#WorldAI...| 1 . |[#worlda....|[#worldai... |(3025,[37,75,155,...|
|@Tony4W....| 1 . |[.@tony4w...|[.@tony4w... |(3025,[41,131,160...|
+-----------------+------------+-------------+--------------------+

通过指定这些列到 NaiveBayes 分类器,我们可以训练一个模型:

from pyspark.ml.classification import NaiveBayes
naive_bayes = NaiveBayes(featuresCol="features", labelCol="label")

该模型是一个 Transformer,可以为我们的训练 DataFrame 中的每一行提供预测:

naive_bayes_model = naive_bayes.fit(cleaned_training_df)
predictions_df = naive_bayes_model.transform(cleaned_testing_df)

>>> predictions_df.select("features", "label", "prediction").limit(3).show()
+--------------------+-----+----------+
| features           |label|prediction|
+--------------------+-----+----------+
|(3025,[1303,1858,...| 1 . | 1.0      |
|(3025,[1,20,91,13...| 1 . | 1.0      |
|(3025,[16,145,157...| 1 . | 1.0      |
+--------------------+-----+----------+

与我们的上一个例子类似,我们可以评估我们模型的准确性。通过使用 MulticlassClassificationEvaluator 类并指定实际和预测标签,我们可以获得 accuracy

from pyspark.ml.evaluation import MulticlassClassificationEvaluator
evaluator = MulticlassClassificationEvaluator(
   labelCol="label", predictionCol="prediction", metricName="accuracy")
evaluator.evaluate(predictions_df)

输出结果是 0.93,这与我们在 scikit-learn 上得到的结果相似。

使用 SageMaker 的 BlazingText 内置 ML 服务

我们看到了如何使用scikit-learn和 Apache Spark 库执行 ML 任务。然而,有时使用 ML 服务更合适。SageMaker 提供了创建、调整和部署模型的方法,这些模型支持多种内置的 ML 算法,只需调用服务即可。简而言之,您需要将数据放在 S3(一个用于存储大量数据的亚马逊服务)中,并调用 SageMaker 服务,提供所有必要的详细信息(实际的 ML 算法、数据的位置、用于训练的机器的类型和数量)。在本节中,我们将通过 SageMaker 的 BlazingText ML 服务的过程来训练我们的模型,以预测推文。BlazingText 是一种支持使用 word2vec 进行文本分类的算法,word2vec 是一种将单词转换为向量的方式,可以捕捉精确的语法和语义单词关系。我们不会在本节中深入探讨 SageMaker 架构的细节,但我们将向读者展示如何使用这个 AWS 服务作为scikit-learn或 Spark 的替代方案。

我们将首先导入 SakeMaker 库,创建一个会话,并获取一个角色(这是笔记本实例正在使用的角色(见[https://aws.amazon.com/blogs/aws/iam-roles-for-ec2-instances-simplified-secure-access-to-aws-service-apies-from-ec2]))。

此外,我们指定我们将使用的 S3 存储桶来存储所有我们的数据和模型:

import sagemaker
from sagemaker import get_execution_role
import json
import boto3

sess = sagemaker.Session()
role = get_execution_role()
bucket = "mastering-ml-aws"
prefix = "chapter2/blazingtext"

下一步是将一些数据放入 S3 进行训练。BlazingText 期望的格式是每行以__label__X TEXT 格式存在。在我们的例子中,这意味着在每个推文前加上代表原始方的标签:

__label__1 We are forever g..
 __label__0 RT @AFLCIO: Scott Walker.
 __label__0 Democrats will hold this
 __label__1 Congratulations to hundreds of thousands ...

为了做到这一点,我们对我们的推文进行一些预处理,并添加正确的标签:

with open(SRC_PATH + 'dem.txt', 'r') as file:
    dem_text = ["__label__0 " + line.strip('\n') for line in file]

with open(SRC_PATH + 'gop.txt', 'r') as file:
    gop_text = ["__label__1 " + line.strip('\n') for line in file]

corpus = dem_text + gop_text

我们接下来将创建用于训练和测试的集合,以文本文件的形式:

from sklearn.model_selection import train_test_split
corpus_train, corpus_test = train_test_split(corpus, test_size=0.25, random_state=42) 

corpus_train_txt = "\n".join(corpus_train)
corpus_test_txt = "\n".join(corpus_test)

with open('tweets.train', 'w') as file:
    file.write(corpus_train_txt) 
with open('tweets.test', 'w') as file:
    file.write(corpus_test_txt)

一旦我们有了训练和验证的文本文件,我们将它们上传到S3

train_path = prefix + '/train'
validation_path = prefix + '/validation'

sess.upload_data(path='tweets.train', bucket=bucket, key_prefix=train_path)
sess.upload_data(path='tweets.test', bucket=bucket, key_prefix=validation_path)

s3_train_data = 's3://{}/{}'.format(bucket, train_path)
s3_validation_data = 's3://{}/{}'.format(bucket, validation_path)

我们接下来通过指定所有必要的详细信息来实例化Estimator:用于训练的机器的类型和数量,以及模型将在 S3 中存储的路径位置:

container = sagemaker.amazon.amazon_estimator.get_image_uri('us-east-1', "blazingtext", "latest")

s3_output_location = 's3://{}/{}/output'.format(bucket, prefix)
bt_model = sagemaker.estimator.Estimator(container,
                                         role, 
                                         train_instance_count=1, 
                                         train_instance_type='ml.c4.4xlarge',
                                         train_volume_size = 30,
                                         train_max_run = 360000,
                                         input_mode= 'File',
                                         output_path=s3_output_location,
                                         sagemaker_session=sess)

如我们在上一节中讨论的在 SageMaker 笔记本上使用 Apache Spark 的朴素贝叶斯模型部分,estimator 能够通过处理训练数据来创建模型。下一步将是提供训练数据来拟合模型:

bt_model.set_hyperparameters(mode="supervised", epochs=10, min_count=3, learning_rate=0.05, vector_dim=10, early_stopping=False, patience=5, min_epochs=5, word_ngrams=2) train_data = sagemaker.session.s3_input(s3_train_data, distribution='FullyReplicated', content_type='text/plain', s3_data_type='S3Prefix') validation_data = sagemaker.session.s3_input(s3_validation_data, distribution='FullyReplicated', content_type='text/plain', s3_data_type='S3Prefix') data_channels = {'train': train_data, 'validation': validation_data}
bt_model.fit(inputs=data_channels, logs=True)

在我们训练模型之前,我们需要指定超参数。我们不会在本节中详细介绍这个算法,但读者可以在[https://docs.aws.amazon.com/sagemaker/latest/dg/blazingtext.html]中找到详细信息。

这个特定的算法也会使用验证数据,因为它会在数据上运行多次(epochs)以减少错误。一旦我们拟合了模型,我们就可以将模型部署为 Web 服务,以便应用程序可以使用它:

predictor = bt_model.deploy(initial_instance_count = 1,instance_type = 'ml.m4.xlarge')

在我们的案例中,我们只需点击端点即可获取预测结果并评估准确性:

corpus_test_no_labels = [x[11:] for x in corpus_test]
payload = {"instances" : corpus_test_no_labels}
response = predictor.predict(json.dumps(payload))
predictions = json.loads(response)
print(json.dumps(predictions, indent=2))

在运行前面的代码后,我们得到以下输出:

[ { "prob": [ 0.5003 ], "label": [ "__label__0" ] }, { "prob": [ 0.5009 ], "label": [ "__label__1" ] }...

如您在前面代码中看到的,每个预测都伴随着一个概率(我们现在将忽略它)。接下来,我们计算有多少这样的标签与原始标签匹配:

predicted_labels = [prediction['label'][0] for prediction in predictions]
predicted_labels[:4]

在运行前面的代码后,我们得到以下输出:

['__label__0', '__label__1', '__label__0', '__label__0']

然后运行下一行代码:

actual_labels = [x[:10] for x in corpus_test]
actual_labels[:4]

如您在以下代码块输出的结果中可以看到,一些标签与实际匹配,而一些则没有匹配:

['__label__1', '__label__1', '__label__0', '__label__1']

接下来,我们运行以下代码来构建一个布尔向量,该向量包含真实或假,取决于实际是否与预测结果匹配:

matches = [(actual_label == predicted_label) for (actual_label, predicted_label) in zip(actual_labels, predicted_labels)]
matches[:4]

在运行前面的代码后,我们得到以下输出:

[False, True, True, False]

在运行前面的输出后,我们将运行以下代码来计算匹配的案例与总实例的比例:

matches.count(True) / len(matches)

以下是从前一个代码块输出的准确性分数:

0.61

我们可以看到,准确性低于我们之前的示例。这有很多原因。首先,在这个案例中,我们没有在数据准备上投入太多(例如,这个案例中没有使用停用词)。然而,准确性较低的主要原因是我们使用了如此少的数据。这些模型在大型数据集上表现最佳。

Naive Bayes – 优点和缺点

在本节中,我们介绍了选择朴素贝叶斯算法进行分类问题的优缺点:

优点

  • 训练时间:朴素贝叶斯算法只需要在整个数据集上遍历一次来计算数据集中每个特征值的后续概率。因此,当我们处理大型数据集或预算有限的硬件时,朴素贝叶斯算法对于大多数数据科学家来说是一个可行的选择。

  • 预测时间:由于朴素贝叶斯算法中所有概率都是预先计算的,因此该算法的预测时间非常高效。

  • 透明度:由于朴素贝叶斯算法的预测基于每个条件特征的后续概率,因此很容易理解哪些特征正在影响预测。这有助于用户理解预测。

缺点

  • 预测准确性:朴素贝叶斯算法的预测准确性低于本书中将要讨论的其他算法。算法的预测准确性依赖于数据集,许多研究工作已经证明,例如随机森林、支持向量机SVMs)和深度神经网络DNNs)在分类准确性方面优于朴素贝叶斯算法。

  • 独立性假设:由于我们假设每个特征相互独立,因此此算法可能会丢失相互依赖的特征的信息。其他高级算法在计算预测时确实会使用这种依赖信息。

摘要

在本章中,我们向您介绍了为什么机器学习是数据科学家工具箱中的关键工具。我们讨论了结构化机器学习数据集的外观以及如何识别数据集中的特征类型。

我们深入研究了朴素贝叶斯分类算法,并研究了贝叶斯定理在朴素贝叶斯算法中的应用。利用贝叶斯定理,我们可以根据每个特征值预测事件发生的概率,并选择概率最高的那个事件。

我们还提供了一个 Twitter 数据集的示例。我们希望您学会了如何思考文本分类问题,以及如何构建朴素贝叶斯分类模型来预测推文的来源。我们还展示了如何在 SageMaker 中实现该算法,以及如何使用 Apache Spark 实现。这个代码库应该能帮助您在未来解决任何文本分类问题。由于实现使用了 SageMaker 服务和 Spark,它可以扩展到可以容纳吉字节或太字节规模的数据集。

我们将在后面的章节中探讨如何在实际生产集群上部署机器学习模型。

练习

  1. 贝叶斯定理不仅对朴素贝叶斯算法有用,还被用于其他目的。找出另外两个应用贝叶斯定理的算法,并解释它们与朴素贝叶斯算法的不同之处。

  2. 在本章中,我们提供了一个二元分类器的示例。基于我们下载推文的代码,创建一个新的数据集,从五个不同的来源下载推文,并构建一个可以预测每个推文来源的朴素贝叶斯模型。

  3. 确定何时使用scikit-learn、Apache Spark 或 SageMaker 服务解决特定问题的场景。

第三章:使用回归算法预测房屋价值

本章将介绍回归算法的基础知识,并将它们应用于根据多个特征预测房屋价格。我们还将介绍如何使用逻辑回归解决分类问题。将提供 SageMaker Notebooks 中 scikit-learn、Apache Spark 和 SageMaker 的线性学习器的示例。

在本章中,我们将涵盖以下主题:

  • 预测房屋价格

  • 理解线性回归

  • 评估回归模型

  • 通过 scikit-learn 实现线性回归

  • 通过 Apache Spark 实现线性回归

  • 通过 SageMaker 的线性学习器实现线性回归

  • 理解逻辑回归

  • 线性模型的优缺点

预测房屋价格

在本章中,我们将考虑尝试根据多个变量(如房间数量和房屋年龄)预测波士顿郊区的房屋价值的问题。数据集的详细信息可以在此处找到:www.kaggle.com/c/boston-housing/。与上一章考虑的问题不同,因为我们试图预测的变量(美元价格)是连续的。能够预测连续量的模型被称为回归器回归算法。有许多这样的算法,但本章我们将专注于最简单(但非常流行)的一种,即线性回归器。

理解线性回归

回归算法是数据科学家工具箱中的重要算法,因为它们可以用于各种非二值预测任务。线性回归算法模型了我们试图预测的因变量与独立变量向量之间的关系。在回归算法的上下文中,变量向量也被称为回归器。线性回归假设独立变量向量与我们试图预测的因变量之间存在线性关系。因此,线性回归模型使用训练数据学习线性函数的未知变量和常数,使得线性函数最佳地拟合训练数据。

线性回归可以应用于目标是根据回归变量预测或预测因变量的情况。我们将通过一个示例来解释线性回归如何根据数据训练。

下表展示了一个样本数据集,目标是根据三个变量预测房屋价格:

楼层面积 卧室数量 浴室数量 房屋价格
2500 4 2 600,000
2800 4 2 650,000
2700 4 3 650,000
4500 6 4 800,000
3500 4 2 750,000
3000 5 4 760,000
2000 3 2 500,000
4100 4 3 810,000

在这个数据集中,变量 Floor SizeNumber of BedroomsNumber of Bathrooms 在线性回归中被假定为相互独立。我们的目标是根据这些变量预测 House Price 的值。

让我们简化这个问题。让我们只考虑 Floor Size 变量来预测房价。仅从一个变量或回归器创建线性回归被称为简单线性回归。如果我们从两个列创建散点图,我们可以观察到这两个变量之间存在关系:

虽然这两个变量之间没有精确的线性关系,但我们可以创建一条近似线来表示趋势。建模算法的目的是最小化创建这条近似线时的误差。

如我们所知,一条直线可以用以下方程表示:

因此,前面图表中近似线性关系也可以用相同的公式表示,线性回归模型的任务是学习 的值。此外,由于我们知道预测变量和回归器之间的关系不是严格线性的,我们可以在表示数据集中噪声的方程中添加一个随机误差变量。以下公式表示简单线性回归模型是如何表示的:

现在,让我们考虑具有多个回归器的数据集。我们不仅表示一个变量 之间的线性关系,我们还将表示一组回归器为 。我们将假设因变量 和回归器 之间存在线性关系。因此,具有多个回归器的线性回归模型可以用以下公式表示:

如我们之前所讨论的,线性回归假设回归变量和我们要预测的因变量之间存在线性关系。这是一个重要的假设,可能并不适用于所有数据集。因此,对于数据科学家来说,由于线性回归的训练时间快,使用线性回归可能看起来很有吸引力。然而,如果数据集变量与因变量之间没有线性关系,可能会导致显著的误差。在这种情况下,数据科学家也可能尝试伯努利回归、泊松回归或多项式回归等算法来提高预测精度。我们将在本章后面讨论逻辑回归,它用于因变量为二进制的情况。

在训练阶段,线性回归可以使用各种参数估计技术来学习 、 、 和  的值。然而,我们不会在本书中详细介绍这些技术。但是,我们建议你在接下来的示例中尝试使用这些参数估计技术,并观察它们对算法训练时间和预测准确性的影响。

为了将线性模型拟合到数据中,我们首先需要能够确定线性模型拟合数据的好坏。线性回归中的参数估计正在开发出各种模型。参数估计是估计 、 、 和  的值的过程。在接下来的章节中,我们将简要介绍这两种估计技术。

线性最小二乘估计

线性最小二乘LLS)是一种基于给定数据估计参数的估计方法。LLS 估计的优化问题可以解释如下:

LLS 是一组公式,用于通过估计  和  的值来解决线性回归的统计问题。LLS 是获取线性回归解决方案的优化方法。它使用观察到的xy的值来估计  和  的值。我们鼓励你探索 LLS 解决方案,以了解它是如何估计线性回归参数的。然而,由于本书的重点是介绍这些概念并帮助你将它们应用于 AWS,我们不会详细介绍这种方法。

最大似然估计

最大似然估计MLE)是一种流行的模型,用于估计线性回归的参数。MLE 是一个概率模型,可以预测哪些参数值具有最大的可能性来重新创建观察到的数据集。这可以通过以下公式表示:

图片

对于线性回归,我们的假设是因变量与模型之间存在线性关系。最大似然估计(MLE)假设因变量的值服从正态分布。其思路是预测每个观察到的 X 值的参数,以便它能够模拟 y 的值。我们还估计每个观察到的值的误差,以模拟线性预测的 y 值与实际值之间的差异。

梯度下降

梯度下降算法也常用于估计线性回归的参数。梯度下降算法用于最小化一个函数。根据我们的预测,我们从参数的初始值集合开始,迭代地移动到参数,以最小化函数中的误差。用于迭代地减小误差的函数称为梯度。其思路是沿着梯度平面中的最低点下降梯度。不同的梯度下降算法包括批量梯度下降,它查看每个例子中的所有观察到的例子,以及随机梯度下降,其中我们一次迭代一个观察值。因此,批量梯度下降比随机梯度下降更准确,但速度要慢得多,因此不适合大型数据集。

由于回归算法非常适合预测连续变量,因此关于回归算法的研究正在进行得非常广泛。我们鼓励您了解更多关于线性回归库的信息,并尝试库中提供的不同变体,以计算测试数据集的效率和效果。

评估回归模型

与朴素贝叶斯分类模型不同,回归模型提供数值输出作为预测。这个输出可以通过预测两个事件并使用最大值来进行二分类。然而,在例如基于回归器预测房屋价值等例子中,我们不能使用仅依赖于预测是否正确或错误的评估指标。当我们预测数值时,评估指标还应量化预测中的误差值。例如,如果房屋价值为 600,000,模型 A 预测为 700,000,模型 B 预测为 1,000,000,则精确度和召回率等指标会将这两个预测都计为假阳性。然而,对于回归模型,我们需要能够告诉我们模型 A 比模型 B 更接近实际值的评估指标。因此,在本节中,我们将介绍用于此类数值预测的三个指标。

平均绝对误差

平均绝对误差MAE)是误差绝对值的平均值。它可以表示为以下公式:

图片

MAE 提供了两个向量之间的平均误差。在我们的例子中,MAE 是实际值 图片 和预测值 图片 之间的差异。由于它为模型预测中的误差提供了清晰的解释,MAE 被许多研究人员使用。

均方误差

平均平方误差MSE)是误差值平方的平均值,表示为以下公式:

图片

MSE 在误差非常小的情况下很有用。MSE 结合了预测值与真实值之间的距离以及预测值中的方差。

根均方误差

根均方误差RMSE)是均方误差的平方根,表示为以下公式:

图片

RMSE 与 MSE 类似,捕捉预测中的方差。然而,在 RMSE 中,由于我们取平方误差值的平方根,误差可以与 MSE 相比较,同时也保留了 MSE 的优点。

另一个在回归问题中常用的流行指标是 R² 分数,或确定系数。这个分数衡量了从独立变量可预测的因变量方差的比例:

图片

在这里, 代表实际值的向量,而 代表预测值的向量。实际值的平均值是 。商的分子衡量实际值通常与平均值的不同程度,而分母衡量实际值与预测值的不同程度。请注意,差异是平方的,类似于均方误差(MSE),因此大的差异会受到严重惩罚。

在完美的回归器中,分子为 0,因此的最佳可能值为 1.0。然而,当预测误差显著时,我们可以看到任意大的负值。

机器学习包中实现了所有四种类型的评估指标,并在以下代码示例中进行了演示。

通过 scikit-learn 实现线性回归

如我们在上一章中所做的那样,我们将向您展示如何快速使用scikit-learn从 SageMaker 笔记本实例中直接训练线性模型。首先,您必须创建笔记本实例(选择conda_python3作为内核)。

  1. 我们将首先将训练数据加载到一个pandas数据框中:
housing_df = pd.read_csv(SRC_PATH + 'train.csv')
housing_df.head()

上述代码显示以下输出:

  1. 最后一列(medv)代表中值,表示我们试图根据剩余列(自变量)的值来预测的变量(因变量)。

如同往常一样,我们将数据集分为训练集和测试集:

from sklearn.model_selection import train_test_split

housing_df_reordered = housing_df[[label] + training_features]

training_df, test_df = train_test_split(housing_df_reordered, 
                                        test_size=0.2)
  1. 一旦我们有了这些数据集,我们将继续构建一个线性回归器:
from sklearn.linear_model import LinearRegression

regression = LinearRegression()

training_features = ['crim', 'zn', 'indus', 'chas', 'nox', 
                     'rm', 'age', 'dis', 'tax', 'ptratio', 'lstat']

model = regression.fit(training_df[training_features], 
                       training_df['medv'])

我们首先构建一个估计器(在这种情况下,线性回归),并通过提供训练值的矩阵(training_df[training_features])和标签(raining_df['medv'])来拟合模型。

  1. 在拟合模型后,我们可以用它来预测测试数据集中的每一行。我们通过向现有的测试数据框中添加一个新列来完成此操作:
test_df['predicted_medv'] = model.predict(test_df[training_features])
test_df.head()

上述代码显示以下输出:

  1. 总是检查我们的预测结果非常有用。一种实现方式是将预测值与实际值作为散点图进行绘制:
test_df[['medv', 'predicted_medv']].plot(kind='scatter', 
                                         x='medv', 
                                         y='predicted_medv')

上述代码显示以下输出:

注意值大多位于对角线上。这是一个好兆头,因为完美的回归器会将所有数据点精确地放在对角线上(每个预测值都会与实际值完全相同)。

  1. 除了这种图形验证之外,我们还获得了一个评估指标,它告诉我们我们的模型在预测值方面有多好。在这个例子中,我们使用 R 平方评估指标,如前所述,它在 scikit-learn 中可用。

让我们看看以下代码块:

from sklearn.metrics import r2_score

r2_score(test_df['medv'], test_df['predicted_medv'])

0.695

接近 0.7 的值是一个不错的值。如果您想了解什么是好的 R2 相关系数,我们建议您玩这个游戏:guessthecorrelation.com/.

我们的线性模型将通过将每个特征的值乘以一个系数并将所有这些值加起来,再加上一个独立项或截距来创建一个预测价格。

我们可以通过访问模型实例变量中的数据成员来找到这些系数和截距的值:

model.coef_

array([-7.15121101e-02, 3.78566895e-02, -4.47104045e-02, 5.06817970e+00,
 -1.44690998e+01, 3.98249374e+00, -5.88738235e-03, -1.73656446e+00,
 1.01325463e-03, -6.18943939e-01, -6.55278930e-01])

model.intercept_
32.20

通常,检查不同变量的系数非常方便,因为它们可以表明特征在独立预测能力方面的相对重要性。

默认情况下,大多数线性回归算法,如scikit-learn或 Spark,将自动进行一定程度的预处理(例如,它将变量缩放以防止具有大值的特征引入偏差)。此外,这些算法支持正则化参数,并提供选项供您选择用于高效搜索最大化 R2 分数(或最小化某些损失函数)的优化器。

通过 Apache Spark 实现线性回归

您可能对训练可以接受大量数据集作为输入的回归模型感兴趣,这超出了scikit-learn所能做到的。Apache Spark 是这种场景下的一个很好的选择。正如我们在上一章中提到的,Apache Spark 可以很容易地在 AWS 上使用弹性映射减少EMR)在机器集群上运行训练算法。我们将在下一章中解释如何设置 EMR 集群。在本节中,我们将解释您如何使用 Spark ML 库来训练线性回归算法。

  1. 第一步是从我们的训练数据创建一个 dataframe:
housing_df = sql.read.csv(SRC_PATH + 'train.csv', header=True, inferSchema=True)

以下图像显示了数据集的前几行:

图片

  1. 通常,Apache Spark 需要输入数据集有一个单列,该列是一个向量,代表所有训练特征。在第二章中,使用朴素贝叶斯分类 Twitter 帖子,我们使用了CountVectorizer来创建这样的列。在本章中,由于向量值已经存在于我们的数据集中,我们只需要使用VectorAssembler转换器构建这样一个列:
from pyspark.ml.feature import VectorAssembler

training_features = ['crim', 'zn', 'indus', 'chas', 'nox', 
                     'rm', 'age', 'dis', 'tax', 'ptratio', 'lstat']

vector_assembler = VectorAssembler(inputCols=training_features, 
                                   outputCol="features")

df_with_features_vector = vector_assembler.transform(housing_df)

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

图片

注意向量组装器创建了一个名为 features 的新列,它将用于训练的所有特征组装成向量。

  1. 如同往常,我们将我们的 dataframe 分为测试集和训练集:
train_df, test_df = df_with_features_vector.randomSplit([0.8, 0.2], 
                                                        seed=17)
  1. 我们现在可以实例化我们的回归器并拟合一个模型:
from pyspark.ml.regression import LinearRegression

linear = LinearRegression(featuresCol="features", labelCol="medv")
linear_model = linear.fit(train_df)
  1. 通过使用此模型,我们对测试数据集中的每个值进行预测:
predictions_df = linear_model.transform(test_df)
predictions_df.show(3)

上述show()命令的输出是:

图片

  1. 我们可以通过使用 RegressionEvaluator 容易地找到 R2 值:
from pyspark.ml.evaluation import RegressionEvaluator

evaluator = RegressionEvaluator(labelCol="medv", 
                                predictionCol="prediction", 
                                metricName="r2")
evaluator.evaluate(predictions_df)

在这种情况下,我们得到 R20.688,这与 scikit-learn 的结果相似。

通过 SageMaker 的线性学习器实现线性回归

AWS 中用于训练回归模型的另一种选择是使用 SageMaker 的 API 来构建线性模型。在前一章中,当我们考虑如何使用 BlazingText 解决我们的文本分类问题时,我们解释了该服务的基本原理。同样,在本节中,我们将使用线性学习器,并遵循相同的过程,这基本上包括三个步骤:

  1. 将训练和测试数据存档到 S3

  2. 调用 API 来训练模型

  3. 使用模型获取预测

与我们在第二章,使用朴素贝叶斯分类 Twitter 流中所做的不一样,不是部署一个端点(即,一个网络服务)来获取预测,我们将使用批处理转换器,这是一种能够根据模型和 S3 中的某些数据获取大量预测的服务。让我们看看以下步骤:

  1. 假设我们已经以与前面章节类似的方式准备好了训练和测试数据集,我们将创建一个 SageMaker 会话并将我们的训练和测试数据上传到 S3:
import sagemaker
from sagemaker import get_execution_role
import json
import boto3

sess = sagemaker.Session()
role = get_execution_role()

bucket = "mastering-ml-aws"
prefix = "chapter3/linearmodels"

train_path = prefix + '/train'
validation_path = prefix + '/validation'

sess.upload_data(path='training-housing.csv', 
                 bucket=bucket, 
                 key_prefix=train_path)
sess.upload_data(path='testing-housing.csv', 
                 bucket=bucket, 
                 key_prefix=validation_path)

s3_train_data = 's3://{}/{}'.format(bucket, train_path)
s3_validation_data = 's3://{}/{}'.format(bucket, validation_path)
  1. 一旦数据存入 S3,我们可以继续实例化估计器:
from sagemaker.amazon.amazon_estimator import get_image_uri
from sagemaker.session import s3_input

container = get_image_uri(boto3.Session().region_name, 'linear-learner')
s3_output_location = 's3://{}/{}/output'.format(bucket, prefix)

linear = sagemaker.estimator.Estimator(container,
                                       role,
                                       train_instance_count=1, 
                                       train_instance_type='ml.c4.xlarge',
                                       output_path=s3_output_location,
                                       sagemaker_session=sess)
  1. 接下来,我们需要设置超参数。SageMaker 中的线性学习器接受大量选项,可以在以下链接中找到:docs.aws.amazon.com/sagemaker/latest/dg/ll_hyperparameters.html。在第十四章,优化 SageMaker 和 Spark 机器学习模型中,我们将深入了解如何找到这些参数的合适值:
linear.set_hyperparameters(feature_dim=len(training_features),
predictor_type='regressor',
mini_batch_size=1)

linear.fit({'train': s3_input(s3_train_data, 
content_type='text/csv'), 
'test': s3_input(s3_validation_data, 
content_type='text/csv')})
  1. 一旦我们拟合了模型,我们可以实例化一个转换器,它能够计算测试数据集在 S3 中的预测:
transformer = linear.transformer(instance_count=1, instance_type='ml.m4.xlarge', output_path=s3_output_location)

transformer.transform(s3_validation_data, content_type='text/csv')
transformer.wait()

这将在 S3 中创建一个名为 testing-housing.csv.out 的文件,其格式如下:

 {"score":18.911674499511719}
 {"score":41.916255950927734}
 {"score":20.833599090576172}
 {"score":38.696208953857422}
  1. 我们可以下载此文件并使用预测构建一个 pandas 数据框:
predictions = pd.read_json('testing-housing.csv.out',lines=True)

以下截图显示了前几个预测:

图片

  1. 由于这些分数与测试数据集中的顺序完全一致,我们可以通过合并数据系列来组合实际和预测列:
evaluation_df = pd.DataFrame({'actual':list(test_df[label]),
                              'predicted':list(predictions['score'])})

上述代码显示以下输出:

图片

  1. 使用这个数据框,我们可以计算 R2 分数:
from sklearn.metrics import r2_score

r2_score(evaluation_df['actual'], evaluation_df['predicted'])

结果为 0.796,与之前的估计一致,略有改进。

理解逻辑回归

逻辑回归是一个广泛使用的统计模型,可以用来模拟二元因变量。在线性回归中,我们假设因变量是我们试图预测的数值。考虑一个二元变量具有真和假值的情况。在逻辑回归中,我们不是使用我们在线性回归部分使用的公式来计算数值输出,而是使用相同的公式估计标记为 True 的二元事件的 log odds。将 log odds 转换为事件标记为 1 发生的概率的函数称为逻辑函数

对数似然尺度测量的单位称为logit。对数似然是通过以下公式计算的:

图片

因此,使用与线性回归相同的方法,逻辑回归通过计算真实事件发生的概率来用于二元因变量。线性回归与逻辑回归之间的主要区别在于,线性回归用于预测因变量的值,而逻辑回归用于预测因变量值的概率。因此,正如我们在本书的大部分内容中所强调的,数据科学家应该看看他们想要预测什么,并相应地选择算法。

逻辑回归算法在大多数流行的机器学习包中都有实现,我们将在下一节提供一个如何在 Spark 中使用它的示例。

Spark 中的逻辑回归

chapter3/train_logistic笔记本展示了我们如何实例化一个LogisticRegression Spark Trainer,而不是使用NaiveBayes来处理我们在第二章中处理的 Twitter 数据集,即使用朴素贝叶斯分类 Twitter 帖子,并获得一个与我们所构建的模型一样好的模型:

from pyspark.ml.classification import LogisticRegression
logistic_regression = LogisticRegression(featuresCol="features", 
                                         labelCol="label")
logistic_model = logistic_regression.fit(cleaned_training_df)

线性模型的优缺点

回归模型在机器学习中非常受欢迎,并在许多领域得到广泛应用。线性回归的主要优势是它简单地将数据集表示为简单的线性模型。因此,线性回归的训练时间很快。同样,模型可以被数据科学家检查,以了解哪个变量对整体模型的决策有贡献。在问题陈述简单且用于预测的变量较少的情况下,建议使用线性回归。随着数据集复杂性的增加,如果数据中有很多噪声,线性回归可能会产生显著的错误。

线性回归大胆地假设因变量与回归变量之间存在线性关系。如果这个假设不成立,那么线性回归算法可能无法很好地拟合数据。有一些变体,如二次回归,可以解决这个问题。然而,这会导致模型复杂化,从而显著增加训练时间。

摘要

在本章中,我们首先介绍了回归算法的基础,并将其应用于预测房价。然后,我们学习了如何评估回归模型,通过如scikit-learn、Apache Spark 和 SageMaker 的线性学习器等库介绍了线性回归,最后,我们看到了如何使用逻辑回归来解决分类问题,以及线性模型的优缺点。

在下一章中,我们将使用基于树的算法来预测用户行为。

第四章:使用基于树的算法预测用户行为

本章将介绍决策树、随机森林和梯度提升树。决策树方法是一种在数据科学中广泛使用的流行技术,它以可视化的方式展示了训练集中的信息如何表示为一个层次结构。根据观察结果遍历层次结构可以帮助你预测该事件发生的概率。我们将探讨如何使用这些算法来预测用户可能会点击在线广告的时间,基于现有的广告点击记录。此外,我们还将展示如何使用 AWS 弹性映射减少EMR)与 Apache Spark 以及 SageMaker XGBoost 服务在大数据环境中构建模型。

在本章中,我们将涵盖以下主题:

  • 理解决策树

  • 理解随机森林算法

  • 理解梯度提升算法

  • 预测日志流中的点击

理解决策树

决策树图形化地展示了将要做出的决策、可能发生的观察事件以及给定一组特定的可观察事件同时发生时的结果概率。决策树作为一种流行的机器学习算法,基于一组可观察事件的数据集和已知的输出结果,我们可以构建一个决策树来表示事件发生的概率。

下表展示了决策树如何生成的一个非常简单的例子:

汽车品牌 年份 价格
宝马 2015 >$40K
宝马 2018 >$40K
本田 2015 <$40K
本田 2018 >$40K
尼桑 2015 <$40K
尼桑 2018 >$40K

这是一个非常简单的数据集,它由以下决策树表示:

机器学习算法的目标是生成最能代表数据集中观测值的决策树。对于一个新观测值,如果我们遍历决策树,叶节点代表最有可能发生的类变量或事件。在先前的例子中,我们有一个包含关于二手车品牌和年份的信息的数据集。类变量(也称为特征标签)是汽车的价格。我们可以在数据集中观察到,无论年份变量的值如何,宝马汽车的价格都超过 40,000 美元。然而,如果汽车的品牌不是宝马,汽车的成本将由汽车生产的年份决定。这个例子基于非常少量的数据。然而,决策树代表了数据集中的信息,如果我们必须确定品牌为宝马且年份为 2015 的新车的成本,那么我们可以预测其成本将超过 40,000 美元。对于更复杂的决策树,叶节点还与一个概率相关联,该概率代表类值发生的概率。在本章中,我们将研究可以用来生成此类决策树的算法。

递归分割

决策树可以通过递归地将数据集划分为子集来构建。在每次分割过程中,我们根据所有输入属性评估分割,并使用成本函数来确定哪个分割的成本最低。成本函数通常评估将数据集分割成两个分支时的信息损失。这种将数据集分割成更小子集的过程也被称为递归分割。分割数据集的成本通常由每个数据集中具有相似类变量的记录如何分组在一起来决定。因此,最优的分割是在每个子集中的观测值将具有相同的类变量值时。

这种决策树的递归分割是一种自上而下的生成方法。这同样是一种贪婪算法,因为我们在每个点上做出了如何划分数据集的决定,而没有考虑它可能对后续分割的影响。

在先前的例子中,我们是根据汽车的品牌进行第一次分割的。这是因为我们的一个子集,其中品牌是宝马,有 100%的概率汽车的价格将超过 40,000 美元。同样,如果我们根据年份进行分割,我们也会得到一个年份等于 2018 的子集,该子集也有 100%的概率汽车的成本将超过 40,000 美元。因此,对于相同的数据集,我们可以生成多个代表数据集的决策树。我们将查看各种成本函数,它们基于相同的数据集生成不同的决策树。

决策树的类型

根据数据集中的类变量,大多数数据科学家必须处理以下两种主要的决策树类型:

  • 分类树:分类树是用于预测离散值的决策树。这意味着用于生成分类树的数据库中的类别变量是离散值。本节开头关于汽车价格的例子就是一个分类树,因为它只有两个类别变量的值。

  • 回归树:回归树是用于预测实数的决策树,例如在第三章中的例子,使用回归算法预测房价,我们预测的是房价。

术语分类与回归树CART)用于描述生成决策树的算法。CART 是一种流行的决策树算法。其他流行的决策树算法包括 ID3 和 C4.5。这些算法在用于分割数据集的成本函数以及确定何时停止分割的准则方面各不相同。

成本函数

如在递归分割章节中所述,我们需要成本函数来确定在给定输入变量上分割是否比其他变量更好。这些成本函数的有效性对于构建决策树的质量至关重要。在本节中,我们将讨论生成决策树时两种流行的成本函数。

Gini 不纯度

Gini 不纯度定义为在随机观测值被基于数据集中类别变量的分布进行分类的情况下,对随机观测值进行错误分类的可能性的度量。考虑一个包含  图片 类别变量的数据集,其中  图片 是数据集中标记为  图片 的观测值的比例。Gini 不纯度可以使用以下公式计算:

图片  .. 4.1

Gini 不纯度告诉我们数据集中存在的噪声量,这是基于各种类别变量的分布情况。

例如,在理解决策树部分开头展示的汽车价格数据集中,我们有两个类别变量:大于 40,000 和小于 40,000。如果我们必须计算数据集的 Gini 不纯度,它可以按照以下公式计算:

图片

因此,基础数据集中存在大量的噪声,因为每个类别变量都有 50% 的观测值。

然而,当我们创建一个以汽车品牌为分支时,该数据集子集的 Gini 不纯度计算如下:

图片

图片

图片

由于宝马分支只包含>40K的类值,因此该分支没有噪声,基尼不纯度的值为0。请注意,当数据子集只有一个类值时,基尼不纯度的值总是0

基尼不纯度用于计算每个属性的基尼指数。基尼指数是我们创建分支的属性所有值的加权总和。对于具有图片个唯一值的属性,基尼增益是使用以下公式计算的。图片是数据集中属性值图片图片的观察值的比例:

图片

因此,在我们前面的例子中,对于具有三个不同值的Make属性,基尼指数的计算如下:

图片

同样,我们计算其他属性的基尼指数。在我们的例子中,Year属性的基尼指数为 0.4422。我们鼓励您自己计算这个值。我们的目标是选择产生最低基尼指数得分的属性。对于完美的分类,即每个分支中的所有类值都相同,基尼指数得分将为 0。

信息增益

信息增益基于熵的概念,这在物理学中常用来表示随机变量的不可预测性。例如,如果我们有一个无偏的硬币,硬币的熵表示为1,因为它具有最高的不可预测性。然而,如果一个硬币是偏的,并且有 100%的机会出现正面,那么硬币的熵为 0。

熵的概念也可以用来确定给定分支中类变量的不可预测性。分支的熵,用H表示,是根据以下公式计算的。图片代表属性的熵。图片是数据集中类变量的数量。图片是数据集中属于该类的观察值的比例,图片

图片

步骤 1:在我们的例子中,对于整个数据集,我们可以按照以下方式计算数据集的熵。

图片

步骤 2:在我们的决策树中,我们根据汽车的型号来分割树。因此,我们也计算了树的每个分支的熵,如下所示:

图片

图片

图片

步骤 3:基于父节点和分支的熵,我们可以使用称为信息增益的度量来评估分支。对于父分支图片和属性图片,信息增益表示如下:

图片

图片是子节点熵的加权总和。在我们的例子中,Make属性的图片计算如下:

图片

因此,Make属性的信息增益计算如下:

图片

同样,我们可以计算其他属性的信息增益分数。应该使用信息增益最高的属性来分割数据集,以获得最高质量的决策树。信息增益在 ID3 和 C4.5 算法中使用。

停止分割树的准则

由于决策树生成算法是递归的,我们需要一个准则来指示何时停止分割树。我们可以设置各种准则来停止分割树。现在让我们看看常用准则的列表:

  • 节点中的观测数:我们可以设置标准,如果分支中的观测数少于预先指定的数量,则停止分支的递归。一个很好的经验法则是当分支中有少于 5%的总训练数据时停止递归。如果我们过度分割数据,以至于每个节点只有一个数据点,这会导致决策树过度拟合训练数据。任何之前未见过的新观察结果将无法在这些树中得到准确分类。

  • 节点的纯度:在基尼不纯度部分,我们学习了如何计算对随机观察进行分类的错误概率。我们也可以使用相同的方法来计算数据集的纯度。如果一个分支中的子集纯度大于预先指定的阈值,我们可以停止基于该分支的分割。

  • 树的深度:我们也可以预先指定树的深度限制。如果任何分支的深度超过限制,我们可以停止进一步分割该分支。

  • 修剪树木:另一种策略是让树木充分生长。这样做可以避免分支过早终止,而不考虑未来。然而,在完全构建了整棵树之后,树可能很大,某些分支可能存在过拟合。因此,应用修剪策略来评估树的每一分支;任何引入的杂质少于预先指定的父分支杂质量的分支将被消除。修剪决策树有多种技术。我们鼓励读者在实现决策树的库中进一步探索这个主题。

理解随机森林算法

使用决策树有两个主要缺点。首先,决策树使用基于成本函数选择在属性上分割的算法。决策树算法是一种贪婪算法,在做出关于将数据集分割成两个子集的每个决策时,都趋向于局部最优。然而,它并没有探索在属性上做出次优决策是否会导致未来有更优的决策树。因此,当我们运行此算法时,我们不会得到全局最优的树。其次,决策树倾向于过度拟合训练数据。例如,数据集中可用的少量观察结果可能导致一个分支,该分支提供了非常高的某个类事件发生的概率。这导致决策树在生成用于训练的数据集的正确预测方面表现得非常好。然而,对于他们以前从未见过的观察结果,由于过度拟合训练数据,决策树可能不准确。

为了解决这些问题,可以使用随机森林算法来提高现有决策树算法的准确性。在这种方法中,我们将训练数据划分为随机子集,并创建一个决策树集合,每个决策树基于一个子集。这解决了过拟合的问题,因为我们不再依赖于一棵树来做出对整个训练集过度拟合的决策。其次,这也帮助解决了仅基于成本函数在一个属性上分割的问题。随机森林中的不同决策树可能会基于它们训练的随机样本,基于不同的属性做出分割决策。

在预测阶段,随机森林算法从每个分支获取事件的概率,并使用投票方法生成预测。这有助于我们抑制可能过度拟合或做出次优决策的树的预测。将训练集划分为随机子集并训练多个机器学习模型的方法被称为Bagging。Bagging 方法也可以应用于其他机器学习算法。

理解梯度提升算法

梯度提升算法也被用来解决决策树算法的缺点。然而,与基于训练数据随机子集训练多个树的随机森林算法不同,梯度提升算法通过减少决策树中的错误来顺序地训练多个树。梯度提升决策树基于一种流行的机器学习技术,称为自适应提升,其中我们学习为什么机器学习模型会出错,然后训练一个新的机器学习模型来减少先前模型的错误。

梯度提升算法在数据中发现了难以在决策树中表示的规律,并给训练示例添加了更大的权重,这可能导致正确的预测。因此,与随机森林类似,我们从训练数据的一个子集中生成多个决策树。然而,在每一步中,训练数据的子集并不是随机选择的。相反,我们创建一个训练数据子集,其中优先考虑那些会导致决策树中错误更少的示例。当我们无法观察到可能导致更多优化的错误模式时,我们停止这个过程。

下一个部分提供了随机森林算法和梯度提升算法实现的示例。

预测日志流中的点击

在本节中,我们将向您展示如何使用基于树的方法来预测在给定一系列条件(如地区、广告展示的位置、一天中的时间、横幅的位置以及提供广告的应用程序)的情况下,谁会点击移动广告。

我们将在本章的其余部分使用的数据集来自 Shioji, Enno, 2017, Adform 点击预测数据集doi.org/10.7910/DVN/TADBY7,哈佛数据集,V2。

主要任务是构建一个分类器,能够根据条件预测用户是否会点击广告。拥有这样的模型对于选择向用户展示哪些广告以及何时展示的广告技术平台非常有用。这些平台可以使用这些模型只为可能点击所提供广告的用户展示广告。

数据集足够大(5 GB),足以证明使用跨多台机器的技术来执行训练是合理的。我们首先将探讨如何使用 AWS EMR 结合 Apache Spark 来完成这项任务。我们还将展示如何使用 SageMaker 服务来完成这项任务。

弹性 MapReduce (EMR) 简介

EMR 是 AWS 服务,允许我们运行和扩展 Apache Spark、Hadoop、HBase、Presto、Hive 和其他大数据框架。我们将在第十五章中详细介绍 EMR 的更多细节,调整集群以进行机器学习。然而,现在让我们将 EMR 视为一种服务,允许我们启动几个运行软件(如 Apache Spark)的相互连接的机器,这些软件协调分布式处理。EMR 集群有一个主节点和几个从节点。主节点通常协调作业,而从节点处理和合并数据,为主节点提供结果。这个结果可以从一个简单的数字(例如,行数计数)到一个能够进行预测的机器学习模型不等。Apache Spark Driver 是协调完成操作所需作业的机器。驱动程序通常在主节点上运行,但它也可以配置在从节点上运行。Spark executors(Spark 用于处理数据的恶魔)通常在 EMR 从节点上运行。

EMR 还可以托管连接到集群的笔记本服务器。这样,我们可以运行笔记本段落,这将触发通过 Apache Spark 的任何分布式处理。在 Apache Spark 上托管笔记本有两种方式:EMR 笔记本和 JupyterHub EMR 应用程序。我们将在本章中使用第一种方法,并在第十五章“针对机器学习调整集群”中介绍 JupyterHub。

通过 EMR 笔记本,你可以通过控制台上的EMR 笔记本链接同时启动集群和笔记本(console.aws.amazon.com/elasticmapreduce/home)。

你可以通过点击“创建笔记本”按钮同时创建集群和笔记本,如下截图所示:

截图

创建笔记本后,它将点击“打开”按钮,如下截图所示:

截图

点击“打开”按钮将打开笔记本,以便我们开始编码。正如以下截图所示,笔记本是一个标准的 Jupyter Notebook:

截图

或者,你可以单独创建集群,然后将笔记本附加到集群上。这样做的好处是你可以访问额外的先进选项。

我们建议至少使用 10 台机器(例如,10 个 m5.xlarge 节点)来及时运行本章中的代码。另外,如果你的作业完成时间超过一小时,我们建议你增加 Livy 会话超时时间。对于此类作业,笔记本可能会与集群断开连接。Livy 是负责笔记本与集群之间通信的软件。以下截图显示了创建集群选项,包括扩展 Livy 会话超时时间的方法:

截图

在第十五章“针对机器学习调整集群”中,我们将详细介绍集群配置的更多细节。

在 EMR 上使用 Apache Spark 进行训练

现在我们来探索在 EMR 上使用 Apache Spark 进行训练。

获取数据

第一步是将数据上传到 EMR。你可以直接从笔记本中这样做,或者将数据集本地下载后,使用 AWS 的命令行工具(awscli)将其上传到 S3。为了使用 AWS 的命令行工具,你需要在 IAM 控制台上创建 AWS 访问密钥。有关如何操作的详细信息,请参阅此处:docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html

一旦你有了 AWS 访问密钥和秘密密钥,你可以在命令行上执行aws configure来配置它们。

首先,我们将通过以下wget命令获取数据集的一部分:

wget -O /tmp/adform.click.2017.01.json.gz https://dataverse.harvard.edu/api/access/datafile/:persistentId/?persistentId=doi:10.7910/DVN/TADBY7/JCI3VG

接下来,我们将解压并将 CSV 数据集上传到名为 mastering-ml-awss3 存储桶,如下命令所示:

gunzip /tmp/adform.click.2017.01.json.gz

aws s3 cp /tmp/adform.click.2017.01.json s3://mastering-ml-aws/chapter4/training-data/adform.click.2017.01.json

一旦数据在 S3 中,我们就可以回到我们的笔记本并开始编写代码来训练分类器。

准备数据

与我们在前几章中在本地运行的示例相比(第二章,使用朴素贝叶斯分类 Twitter 推文和* 第三章,*使用回归算法预测房屋价值),EMR 笔记本具有隐式变量以访问 Spark 上下文。特别是,Spark 会话被命名为 spark。第一次运行的第一段将始终初始化上下文并触发 Spark 驱动器。

在下面的屏幕截图中,我们可以看到 Spark 应用程序启动以及 Spark UI 的链接:

图片

下一步是加载我们的数据集并通过运行以下代码片段来探索前几行:

ctr_df = spark.read.json(s3_train_path)
ctr_df.show(5)

上面的 show 命令的输出如下:

图片

spark.read.json 方法,上一段代码块中的第一个命令,将 JSON 数据读取到一个数据集,类似于我们之前使用 spark.read.csv 处理 CSV 数据所做的那样。我们可以观察到我们的数据集有 10 个特征和一个 l 列,表示我们试图预测的标签,即用户是否点击了(1)或未点击(0)广告。你可能意识到一些特征是多值的(单元格中有多个值)并且一些是空的。为了简化本章的代码示例,我们将只选择前五个特征,通过构建一个新的数据集并将这些特征命名为 f0f4,同时将空特征替换为值 0,并在多值特征的情况下只取第一个值:

df = ctr_df.selectExpr("coalesce(c0[0],0) as f0",
                       "coalesce(c1[0],0) as f1",
                       "coalesce(c2[0],0) as f2",
                       "coalesce(c3[0],0) as f3",
                       "coalesce(c4[0],0) as f4",
                       "l as click")

上面的 selectExpr 命令允许我们使用类似 SQL 的操作。在这种情况下,我们将使用 coalesce 操作,该操作将任何空表达式转换为值 0。此外,请注意,我们总是只取多值特征的第一个值。

通常,丢弃特征是一个坏主意,因为它们可能携带重要的预测价值。同样,用固定值替换空值也可能不是最优的。我们应该考虑常见的缺失值填充技术,例如用点估计(中位数、众数和平均值是常用的)。或者,可以训练一个模型来从剩余特征中填充缺失值。为了保持本章专注于使用树,我们不会深入探讨缺失值的问题。

我们的 df 数据集现在看起来如下:

图片

现在我们做一件相当特定于 Spark 的事情,即将 CSV 的不同部分重新洗牌到不同的机器上并在内存中缓存它们。执行此操作的命令如下:

df = df.repartition(100).cache()

由于我们将反复迭代处理相同的数据集,通过将其加载到内存中,这将显著加快对df进行的任何未来操作。重新分区有助于确保数据在整个集群中分布得更好,从而提高并行化程度。

describe() 方法构建了一个包含我们数据集中不同字段的基本统计信息(minmaxmeancount)的数据框,如下截图所示:

图片

我们可以观察到,大多数特征的范围从较低的负值到非常大的整数,这表明这些是经过匿名处理的特征值,对它们应用了哈希函数。我们试图预测的字段是click,当用户点击广告时为1,未点击时为0。点击列的均值告诉我们存在一定程度的标签不平衡(大约 18%的实例是点击)。此外,count行告诉我们我们的数据集中总共有 1,200,000,000 行。

另一个有用的检查是了解分类值的基数。以下是我们笔记本中的截图,显示了每个特征得到的唯一值的数量:

图片

如您所见,f4 特征是一个具有许多不同值的分类的例子。这类特征通常需要特别注意,正如我们将在本节后面看到的那样。

决策树和 Spark ML 库中的大多数库都需要我们的特征是数值型的。碰巧我们的特征已经是数值形式,但它们实际上代表的是被哈希成数字的分类。在第二章中,我们学习了为了训练分类器,我们需要提供一个数字向量。因此,我们需要将我们的分类转换成数字,以便将它们包含在我们的向量中。这种转换通常被称为特征编码。有两种流行的方法可以实现这一点:通过独热编码或分类编码(也称为字符串索引)。

在以下通用示例中,我们假设site_id特征只能取最多三个不同的值:siteAsiteBsiteC。这些示例还将说明我们如何将字符串特征编码成数字(不同于我们数据集中的整数哈希)的情况。

分类编码

分类编码(或字符串索引)是最简单的一种编码方式,其中我们为每个站点值分配一个数字。让我们看看以下表中的例子:

site_id site_id_indexed
siteA 1
siteB 2
siteC 3

独热编码

在这种编码中,我们为每个可能的站点值创建新的二进制列,并在值存在时将其设置为1,如下表所示:

site_id siteA siteB siteC
siteA 1 0 0
siteB 0 1 0
siteC 0 0 1

分类编码很简单;然而,它可能会创建特征的虚假排序,并且一些机器学习算法对这一点很敏感。独热编码有额外的优点,可以支持多值特征(例如,如果某行有两个站点,我们可以在两个列中都设置1)。然而,独热编码会增加我们的数据集特征数量,从而增加维度。向数据集中添加更多维度会使训练更加复杂,并可能降低其预测能力。这被称为维度诅咒

让我们看看我们如何在数据集的一个样本上使用分类编码,将 C1 特征(一个分类特征)转换为数值:

from pyspark.ml.feature import StringIndexer

string_indexer = StringIndexer(inputCol="f0", outputCol="f0_index")
string_indexer_model = string_indexer.fit(df)
ctr_df_indexed = string_indexer_model.transform(df).select('f0','f0_index')
ctr_df_indexed.show(5)

上述代码首先实例化一个StringIndexer,该索引器将在拟合时将列f0编码到新的列f0_index中,遍历数据集并找到不同的特征值,根据这些值的流行度分配索引。然后我们可以使用transform()方法为每个值获取索引。上述最后的show()命令的输出如下所示:

在上述截图中,我们可以看到每个原始(散列)分类值被分配的数值。

要对值执行独热编码,我们使用OneHotEncoder转换器:

from pyspark.ml.feature import OneHotEncoder

encoder = OneHotEncoder(inputCol="f0_index", outputCol="f0_encoded")
encoder.transform(ctr_df_indexed).distinct().show(5)

上述命令生成了以下输出:

注意不同的f0值是如何映射到相应的布尔向量的。我们只为一个特征进行了编码;然而,为了训练,我们需要对多个特征执行相同的过程。因此,我们构建了一个函数,该函数构建了我们管道所需的所有索引和编码阶段:

def categorical_one_hot_encoding_stages(columns):
    indexers = [StringIndexer(inputCol=column, 
                              outputCol=column + "_index", 
                              handleInvalid='keep') 
                for column in columns]
    encoders = [OneHotEncoder(inputCol=column + "_index", 
                              outputCol=column + "_encoded") 
                for column in columns]
    return indexers + encoders

以下代码构建了一个包含DecisionTree估计器的训练管道:

from pyspark.ml.feature import OneHotEncoder
from pyspark.ml.feature import StringIndexer
from pyspark.ml.feature import ChiSqSelector
from pyspark.ml import Pipeline
from pyspark.ml.feature import VectorAssembler
from pyspark.ml.classification import DecisionTreeClassifier

categorical_columns = ['f0','f1','f2','f3','f4']
encoded_columns = [column + '_encoded' for column in categorical_columns] 

categorical_stages = categorical_one_hot_encoding_stages(categorical_columns) vector_assembler = VectorAssembler(inputCols=encoded_columns,
                                   outputCol="features")
selector = ChiSqSelector(numTopFeatures=100, featuresCol="features",
                         outputCol="selected_features", labelCol="click")
decision_tree = DecisionTreeClassifier(labelCol="click",                                       
                                       featuresCol="selected_features")

pipeline = Pipeline(stages=categorical_stages + [vector_assembler, selector, 
                                                 decision_tree])

在前面的代码中,VectorAssembler构建了一个包含所有需要编码的特征以及数值特征的向量(VectorAssembler可以接受作为输入的列,这些列可以是向量或标量,因此如果您的数据集中存在数值特征,您可以直接使用它们)。由于独热编码值数量众多,特征向量可能非常大,这会使训练器非常慢或需要大量的内存。减轻这种情况的一种方法是用卡方特征选择器。在我们的管道中,我们选择了最佳的 100 个特征。这里的“最佳”是指具有更多预测能力的特征——注意卡方估计器如何同时考虑特征和标签来决定最佳特征。最后,我们包括决策引擎估计器阶段,这是实际创建分类器的阶段。

如果我们尝试将具有非常大的基数(cardinality)的特征索引串联起来,驱动程序将收集所有可能的值(为了保留一个值到索引的字典以进行转换)。在这种尝试中,驱动程序很可能会耗尽内存,因为我们正在查看数百万个不同的值来保留。对于这些情况,你需要其他策略,例如只保留最具预测能力的特征,或者只考虑最受欢迎的值。查看我们的文章,其中包含了解决这个问题的方案,请参阅medium.com/dataxutech/how-to-write-a-custom-spark-classifier-categorical-naive-bayes-60f27843b4ad

训练一个模型

我们的数据管道现在已构建完成,因此我们可以继续将数据集分割为测试和训练集,然后拟合模型:

train_df, test_df = df.randomSplit([0.8, 0.2], seed=17)
pipeline_model = pipeline.fit(train_df)

一旦执行,Spark 驱动程序将确定在多台机器上分配训练模型所需处理的最佳方案。

通过跟随本节开头显示的 Spark UI 链接,我们可以查看在 EMR 上运行的不同作业的状态:

图片

一旦模型训练完成,我们可以探索其背后的决策树。我们可以通过检查管道的最后一个阶段(即决策树模型)来实现这一点。

以下代码片段显示了以文本格式输出决策树的结果:

print(pipeline_model.stages[-1].toDebugString)

DecisionTreeClassificationModel (uid=DecisionTreeClassifier_3cc3252e8007) of depth 5 with 11 nodes
  If (feature 3 in {1.0})
   Predict: 1.0
  Else (feature 3 not in {1.0})
   If (feature 21 in {1.0})
    Predict: 1.0
   Else (feature 21 not in {1.0})
    If (feature 91 in {1.0})
     Predict: 1.0
    Else (feature 91 not in {1.0})
     If (feature 27 in {1.0})
      Predict: 1.0
     Else (feature 27 not in {1.0})
      If (feature 29 in {1.0})
       Predict: 1.0
      Else (feature 29 not in {1.0})
       Predict: 0.0 

注意每个决策都是基于一个取值为01的特征。这是因为我们在管道中使用了 one-hot 编码。如果我们使用了分类编码(字符串索引),我们会看到涉及多个索引值的条件,例如以下示例:

 If (feature 1 in {3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,17.0,27.0})
       Predict: 0.0
 Else (feature 1 not in {3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,17.0,27.0})
       Predict: 1.0

评估我们的模型

与我们在第二章中讨论的 Twitter 分类问题第二章,“使用朴素贝叶斯分类 Twitter 帖子”,这个数据集的标签非常倾斜。这是因为用户决定点击广告的情况很少。我们在第二章中使用的准确性度量第二章,“使用朴素贝叶斯分类 Twitter 帖子”,将不适用,因为一个从未预测点击的模型仍然会有非常高的准确性(所有非点击都会导致正确预测)。对于这种情况,两种可能的替代方案是使用 ROC 或精确率-召回率曲线衍生出的度量,这些将在下一节中展示。

ROC 曲线下的面积

接收者操作特征ROC)是真正例率和假正例率之间权衡的表示。真正例率描述了当实际类别为正时,模型预测正类别的良好程度。真正例率是模型预测的真正例与真正例和假负例之和的比率。假正例率描述了模型预测正类别的频率,当实际类别为负时。假正例率是假正例与假正例和真正例之和的比率。ROC 是一个图表,其中x轴表示范围在 0-1 之间的假正例率,而y轴表示真正例率。曲线下面积AUC)是 ROC 曲线下面积的措施。AUC 是分类模型预测性的衡量指标。

下面的屏幕截图显示了三个接收者操作曲线的例子:

图片

在前面的图表中,虚线代表当AUC1的情况。这种AUC出现在所有正结果都被正确分类时。实线代表AUC0.5的情况。对于二元分类器来说,当机器学习模型的预测结果与随机生成结果相似时,AUC0.5。这表明机器学习模型在预测结果方面并不比随机数生成器更好。虚线代表AUC0.66的情况。这发生在机器学习模型正确预测了一些例子,但并非全部。然而,如果二元分类器的AUC高于0.5,则说明模型比随机猜测结果更好。但如果它低于0.5,这意味着机器学习模型比随机结果生成器更差。因此,AUC 是衡量机器学习模型和评估其有效性的良好指标。

精确率-召回率曲线下的面积

精确率-召回率曲线表示预测模型中精确率和召回率之间的权衡。精确率定义为模型做出的所有正预测中真正例的比例。召回率定义为正预测与实际正预测总数的比例。

注意,精确率-召回率曲线不模拟真正负值。这在数据集不平衡的情况下很有用。如果模型擅长分类真正负值并生成较少的假阳性,ROC 曲线可能会提供一个非常乐观的模型视图。

下面的图表展示了一个精确率-召回率曲线的例子:

图片

在前面的截图上,虚线表示当精确度-召回率曲线下的面积是 0.5 的时候。这表明精确度始终为 0.5,类似于随机数生成器。实线代表比随机更好的精确度-召回率曲线。精确度-召回率曲线也可以用来评估机器学习模型,类似于 ROC 面积。然而,当数据集不平衡时,应使用精确度-召回率曲线,而当数据集平衡时,应使用 ROC。

因此,回到我们的示例,我们可以使用 Spark 的BinaryClassificationEvaluator通过提供测试数据集上的实际和预测标签来计算分数。首先,我们将模型应用于我们的测试数据集以获取预测和分数:

test_transformed = pipeline_model.transform(test_df)

通过应用前面的转换 test_transformed,将包含在 test_df 中的所有列以及一个额外的列 rawPrediction,该列将有一个用于评估的分数:


from pyspark.ml.evaluation import BinaryClassificationEvaluator

evaluator = BinaryClassificationEvaluator(rawPredictionCol="rawPrediction", 
                                          labelCol="click")
evaluator.evaluate(test_transformed, 
                   {evaluator.metricName: "areaUnderROC"})

前一个命令的输出是 0.43. 我们得到一个低于 0.5 的 ROC 指标意味着我们的分类器甚至比随机分类器还要差,因此它不是一个好的点击预测模型!在下一节中,我们将展示如何使用集成模型来提高我们的预测能力。

在 EMR 上训练树集成

决策树可以用来理解我们的分类器所做的决策,尤其是在决策树较小且可读时。然而,决策树往往会对数据进行过拟合(通过学习训练数据集的细节而无法对新数据进行泛化)。因此,机器学习从业者倾向于使用树集成,如随机森林和梯度提升树,这些在本书的 理解梯度提升算法理解随机森林算法 部分中已有解释。

在我们的代码示例中,要使用随机森林或梯度提升树,我们只需将我们的管道的最后一个阶段替换为相应的构造函数:

from pyspark.ml.classification import RandomForestClassifier

random_forest = RandomForestClassifier(labelCol="click",                                                                                        
                                       featuresCol="features")

pipeline_rf = Pipeline(stages=categorical_stages + \
                              [vector_assembler, random_forest])

注意我们如何在我们的样本数据集上通过随机森林获得更好的 ROC 值:

rf_pipeline_model = pipeline_rf.fit(train_df)

evaluator.evaluate(rf_pipeline_model.transform(test_df), 
                   {evaluator.metricName: "areaUnderROC"})

>> 0.62

我们可以看到现在我们得到了一个大于 0.5 的 ROC 值,这意味着我们的模型已经改进,现在比随机猜测要好。同样,你可以使用pyspark.mllib.tree.GradientBoostedTrees类训练梯度提升树。

使用 SageMaker 服务训练梯度提升树

在 训练模型评估我们的模型 部分中,我们学习了如何在 EMR 上使用 Spark 构建和评估随机森林分类器。在本节中,我们将通过 SageMaker 笔记本了解如何使用 SageMaker 服务训练梯度提升树。XGBoost SageMaker 服务允许我们以分布式方式训练梯度提升树。鉴于我们的点击数据相对较大,使用此类服务将非常方便。

准备数据

为了使用 SageMaker 服务,我们需要将我们的训练和测试数据放在 S3 上。docs.aws.amazon.com/sagemaker/latest/dg/xgboost.html 中的文档要求我们将数据作为 CSV 文件降级,其中第一列表示训练标签(目标特征),其余列表示训练特征(支持其他格式,但我们将使用 CSV 作为示例)。为了以这种方式分割和准备数据,EMR 仍然是最佳选择,因为我们希望我们的数据准备也是分布式的。鉴于上一节“准备数据”中的测试和训练 Spark 数据集,我们可以应用管道模型,在这种情况下不是用于获取预测,而是用于获取每行的选定编码特征。

在以下片段中,我们对 test_dftrain_df 都应用了模型转换:

test_transformed = model.transform(test_df)
train_transformed = model.transform(train_df)

以下截图显示了 test_transformed 数据框的最后三列:

图片

转换后的数据集包括特征向量列(命名为 selected_features,大小为 100)。我们需要将这些两列转换成一个包含 101 列的 CSV 文件(clickselected_features 向量展开)。Spark 中的一个简单转换使我们能够做到这一点。我们定义了一个 deconstruct_vector 函数,我们将使用它来获取一个 Spark 数据框,其中标签和每个向量组件作为单独的列。然后我们将这些数据保存到 S3,用于训练和测试,作为没有标题的 CSV 文件,因为 SageMaker 需要。

在以下代码片段中,我们提供了 deconstruct_vector 函数以及保存数据框所需的系列转换:

def deconstruct_vector(row):
    arr = row['selected_features'].toArray()
    return tuple([row['click']] + arr.tolist())

df_for_csv = train_transformed.select("click", "selected_features") \
                .rdd.map(deconstruct_vector).toDF() 

df_for_csv.write.csv('s3://mastering-ml-aws/chapter4/train-trans-vec-csv-1/', 
                     header=False)

以类似的方式,我们将在 s3://mastering-ml-aws/chapter4/test-trans-vec-csv-no-label 路径下保存一个额外的 CSV 文件,该文件不包括标签(只有特征)。我们将在下一节“使用 SageMaker XGBoost 训练”中,通过 SageMaker 批量转换作业使用这个数据集来评分测试数据集。

使用 SageMaker XGBoost 训练

现在我们训练和测试的数据集已经以正确的格式保存在 S3 上,我们可以启动我们的 SageMaker 笔记本实例并开始编写我们的训练器代码。让我们执行以下步骤:

  1. 使用我们数据集的位置实例化 SageMaker 会话、容器和变量:
import sagemaker
from sagemaker import get_execution_role
import boto3

sess = sagemaker.Session()
role = get_execution_role()
container = sagemaker.amazon.amazon_estimator.get_image_uri('us-east-1', 
                                                            'xgboost', 
                                                            'latest')

s3_validation_data = \
    's3://mastering-ml-aws/chapter4/test-trans-vec-csv-1/'
s3_train_data = \
    's3://mastering-ml-aws/chapter4/train-trans-vec-csv-1/'
s3_test_data = \
    's3://mastering-ml-aws/chapter4/test-trans-vec-csv-no-label/'
s3_output_location = \
    's3://mastering-ml-aws/chapter4/sagemaker/output/xgboost/'
  1. 通过实例化 SageMaker 估算器并提供基本参数(例如要使用的机器数量和类型等详细信息可在 AWS 文档中找到,链接为 sagemaker.readthedocs.io/en/stable/estimators.html)来创建一个分类器:
sagemaker_model = sagemaker.estimator.Estimator(container,
    role,
    train_instance_count=1,
    train_instance_type='ml.c4.4xlarge',
    train_volume_size=30,
    train_max_run=360000,
    input_mode='File',
    output_path=s3_output_location,
    sagemaker_session=sess)
  1. 设置训练器的超参数。详细信息可以在文档中找到(我们将在第十四章中更详细地介绍,优化 SageMaker 和 Spark 机器学习模型)。这里要查看的主要参数是目标,我们将其设置为二元分类(使用逻辑回归分数,这是 XGBoost 执行分类的标准方式)。XGBoost 也可以用于其他问题,如回归或多类分类:
sagemaker_model.set_hyperparameters(objective='binary:logistic',
    max_depth=5,
    eta=0.2,
    gamma=4,
    min_child_weight=6,
    subsample=0.7,
    silent=0,
    num_round=50)
  1. 在拟合模型之前,我们需要指定输入的位置和格式(接受几种格式;在我们的示例中我们选择了 CSV):
train_data = sagemaker.session.s3_input(s3_train_data, 
    distribution='FullyReplicated',
    content_type='text/csv', 
    s3_data_type='S3Prefix')

validation_data = sagemaker.session.s3_input(s3_validation_data, 
    distribution='FullyReplicated',
    content_type='text/csv', 
    s3_data_type='S3Prefix')

data_channels = {'train': train_data, 
                 'validation': validation_data}

sagemaker_model.fit(inputs=data_channels, 
                    logs=True)
  1. 调用fit函数将使用提供的数据(即,我们通过 EMR/Spark 准备存储在 S3 中的数据)训练模型:
INFO:sagemaker:Creating training-job with name: xgboost-2019-04-27-20-39-02-968
2019-04-27 20:39:03 Starting - Starting the training job...
2019-04-27 20:39:05 Starting - Launching requested ML instances......
...
train-error:0.169668#011validation-error:0.169047
2019-04-27 20:49:02 Uploading - Uploading generated training model
2019-04-27 20:49:02 Completed - Training job completed
Billable seconds: 480

日志将显示 XGBoost 正在优化的训练和验证错误的一些细节,以及作业状态和训练成本。

应用和评估模型

以下步骤将展示如何使用sagemaker创建批量预测,以便您可以评估模型。

为了获得预测,我们可以使用批量转换作业:

transformer = sagemaker_model.transformer(instance_count=1, 
                                          instance_type='ml.m4.2xlarge',
                                          output_path=s3_output_location)
transformer.transform(s3_test_data, 
                      content_type='text/csv', 
                      split_type='Line')
transformer.wait()

对于输入s3目录中的每个文件,批量转换作业将生成一个包含分数的文件:

aws s3 ls s3://mastering-ml-aws/chapter4/sagemaker/output/xgboost/ | head

前面命令生成了以下输出:

2019-04-28 01:29:58 361031 part-00000-19e45462-84f7-46ac-87bf-d53059e0c60c-c000.csv.out
2019-04-28 01:29:58 361045 part-00001-19e45462-84f7-46ac-87bf-d53059e0c60c-c000.csv.out

然后,我们可以将这个单列 CSV 文件加载到pandas数据框中:

import pandas as pd

scores_df = pd.read_csv(output_path + \
   'part-00000-19e45462-84f7-46ac-87bf-d53059e0c60c-c000.csv.out',
    header=None, 
    names=['score'])

这些分数代表概率(通过逻辑回归得出)。如果我们把目标设置为二元:hinge,我们将得到实际的预测。选择使用哪种类型取决于应用类型。在我们的案例中,收集概率似乎很有用,因为任何表明特定用户更有可能进行点击的迹象都将有助于提高营销定位。

SageMaker XGBoost 的一个优点是它提供了与 Python 标准序列化库(pickle)兼容的 XGBoost 模型的 S3 序列化。作为一个例子,我们将从 S3 中的测试数据的一部分运行模型以获取分数。通过这样做,我们可以通过以下步骤计算 ROC 曲线下的面积:

  1. s3中定位模型 tar 包:
aws s3 ls --recursive s3://mastering-ml-aws/chapter4/sagemaker/output/xgboost/ | grep model

输出如下所示:

chapter4/sagemaker/output/xgboost/xgboost-2019-04-27-20-39-02-968/output/model.tar.gz

将模型从 S3 复制到我们的本地目录并解压 tar 包:

aws s3 cp s3://mastering-ml-aws/chapter4/sagemaker/output/xgboost/xgboost-2019-04-27-20-39-02-968/output/model.tar.gz /tmp/model.tar.gz
tar xvf model.tar.gz

以下是前面命令的输出,显示了从 tar 包中解压的文件名:

xgboost-model
  1. 一旦模型被本地下载并解压,我们可以通过pickle序列化库将模型加载到内存中:
import xgboost
import pickle as pkl

model_local = pkl.load(open('xgboost-model', 'rb'))
  1. 定义我们列的名称(f0f99为特征,click为标签)并从 S3 加载验证数据:
column_names = ['click'] + ['f' + str(i) for i in range(0, 100)]
validation_df = pd.read_csv(s3_validation_data + \
                            'part-00000-25f35551-ffff-41d8-82a9-75f429553035-c000.csv',
                            header=None, 
                            names=column_names)
  1. 要使用xgboost创建预测,我们需要从我们的pandas数据框组装一个矩阵。选择除第一个(标签)之外的所有列,然后构建一个 DMatrix。从xgboost模型调用 predict 方法以获取每行的分数:
import xgboost
matrix = xgboost.DMatrix(validation_df[column_names[1:]])
validation_df['score'] = model_local.predict(matrix)

在下面的屏幕截图中,读者可以看到数据框的外观:

图片

  1. 给定点击列和得分列,我们可以构建 ROC AUC:
from sklearn.metrics import roc_auc_score
roc_auc_score(validation_df['click'], validation_df['score'])

对于我们的样本,我们得到一个 AUC 值为 0.67,这与我们使用 Spark 的随机森林得到的结果相当。

在本章中,我们没有专注于为我们的数据集构建最优模型。相反,我们专注于提供简单且流行的转换和树模型,您可以使用它们来分类大量数据。

摘要

在本章中,我们介绍了理解树集成的基本理论概念,并展示了如何通过 Apache Spark 以及 SageMaker XGBoost 服务在 EMR 中训练和评估这些模型。决策树集成是最受欢迎的分类器之一,原因有很多:

  • 它们能够在相对较短的训练时间和少量资源中找到复杂的模式。XGBoost 库被认为是 Kaggle 竞赛获胜者中最受欢迎的分类器(这些竞赛是为了寻找开放数据集的最佳模型而举办的)。

  • 有可能理解为什么分类器正在预测一个特定的值。遵循决策树路径或只是查看特征重要性是快速理解树集成决策背后的理由的快捷方式。

  • 通过 Apache Spark 和 XGBoost 可以提供分布式训练的实现。

在下一章中,我们将探讨如何使用机器学习根据客户的行为模式对客户进行聚类。

练习

  1. 随机森林和梯度提升树之间主要区别是什么?

  2. 解释为什么 Gini 不纯度可以解释为误分类率。

  3. 解释为什么对分类特征进行特征编码是必要的。

  4. 在本章中,我们提供了两种进行特征编码的方法。找出另一种对分类特征进行编码的方法。

  5. 解释为什么我们在第二章中使用的准确度指标,“使用朴素贝叶斯分类 Twitter 帖子”不适合预测我们数据集中的点击。

  6. 找出我们可以用于 XGBoost 算法的其他目标。你会在什么时候使用每个目标?

第五章:使用聚类算法进行客户细分

本章将通过探讨如何根据他们的行为模式将聚类算法应用于客户细分来介绍主要的聚类算法。特别是,我们将演示 Apache Spark 和 Amazon SageMaker 如何无缝交互以执行聚类。在本章中,我们将使用Kaggle 数据集 E-Commerce,这是由Fabien Daniel提供的,可以从www.kaggle.com/fabiendaniel/customer-segmentation/data下载。

让我们来看看我们将要讨论的主题:

  • 理解聚类算法的工作原理

  • 弹性映射减少EMR)上使用Apache Spark进行聚类

  • 通过 Spark 集成使用SageMaker进行聚类

理解聚类算法的工作原理

聚类分析,或聚类,是一个基于观察值的相似性将一组观察值分组的过程。其思想是,一个簇中的观察值彼此之间比来自其他簇的观察值更相似。因此,该算法的输出是一组簇,可以识别数据集中的模式并将数据安排到不同的簇中。

聚类算法被称为无监督学习算法。无监督学习不依赖于预测真实值,旨在发现数据中的自然模式。由于没有提供真实值,比较不同的无监督学习模型比较困难。无监督学习通常用于探索性分析和降维。聚类是探索性分析的一个例子。在这个任务中,你正在寻找数据集中的模式和结构。

这与我们在书中迄今为止所研究的算法不同。朴素贝叶斯线性回归决策树算法都是监督学习的例子。有一个假设,即每个数据集都有一组观察结果和与这些观察结果相关的事件类别。因此,数据已经根据每个观察结果的实际结果事件进行分组。然而,并非每个数据集都与每个事件相关联有标记的结果。例如,考虑一个包含有关电子商务网站上每个交易的信息的数据集:

SKU 商品名称 客户 ID 国家
12423 iPhone 10 美国
12423 iPhone 11 美国
12423 iPhone 12 美国
11011 三星 S9 13 英国
11011 三星 S9 10 美国
11011 三星 S9 14 英国

此数据集是一系列交易记录,但没有任何类别变量告诉我们哪些用户购买了特定的产品。因此,如果任务是识别数据集中的模式,我们不能使用任何可以预测特定事件的算法。这就是聚类算法发挥作用的地方。我们希望探索是否可以根据数据集找到网站交易的趋势。让我们看看一个简单的例子。假设数据集只有一个特征:商品名称。我们将发现数据可以排列成三个簇,即 iPhone、Samsung S9 和 Pixel 2。同样,如果我们考虑的唯一聚类特征是国家,数据可以聚类成两个簇:USA 和 UK。一旦生成了簇,你就可以分析每个簇中的统计数据,以了解购买特定商品的观众类型。

然而,在大多数经验中,你将不得不根据多个特征对数据进行聚类。有许多聚类算法可以用来将数据聚类成簇。以下图表显示了数据集簇的示例:

图片

聚类帮助我们得到一个结果,我们可以将数据分成两个簇,并理解每个簇中的模式。我们可能能够将客户聚类为购买某种手机的用户。通过分析簇,我们可以了解购买 iPhone 或 Samsung S9 手机的用户模式。

在本章中,我们将研究两种常见的聚类方法:

  • k-means 聚类

  • 层次聚类

k-means 聚类

k-means 聚类算法旨在通过在数据集中选择 k 个质心来将数据集聚类成 k 个簇。每个记录根据其到质心的距离进行评估,并分配到一个簇中。质心是位于每个簇中心的观测值。为了正式定义 k-means 聚类,我们正在优化观测值的簇内平方和(*WCSS)距离。因此,最理想的聚类将确保每个簇中的观测值都靠近其质心,并且尽可能远离其他质心。

k-means 聚类中有两个重要的参数。首先,我们需要在我们的数据集中发现质心。选择质心的流行方法之一称为随机划分。这种方法使用称为期望最大化EM)的技术来获得高质量的簇。在第一步中,我们随机将一个簇分配给每个观测值。一旦每个观测值被分配到一个簇,我们就使用 WCSS 方法计算簇的质量:

图片

J代表生成具有M个观察值和K个聚类的聚类的 WCSS 得分。如果观察值i属于聚类k,则为 1,如果不属于,则为 0。是观察值,而是聚类k的质心。之间的差异表示观察值和质心之间的距离。我们的目标是使J的值最小化。

在下一步中,我们根据第一步中的当前聚类再次计算新的质心。这是 EM 算法中的最大化步骤,我们试图为记录尝试更优的聚类分配。新的质心值使用以下公式计算:

图片

这表示我们根据前几步中创建的聚类均值重新计算质心。根据新的质心,我们根据每个观察值与质心的距离将数据集中的每个观察值分配给一个质心。距离是两个观察值之间相似度的度量。我们将在本节后面讨论如何计算距离的概念。我们重新计算新聚类的 WCSS 得分,并再次重复最小化步骤。我们重复这些步骤,直到聚类中观察值的分配不再改变。

尽管随机划分算法允许 k-means 算法以较低的 WCSS 得分发现质心,但它们并不能保证全局最优解。这是因为 EM 算法可能会贪婪地找到一个局部最优解并停止探索,以寻找更优的解。此外,在第一步中选择不同的随机质心可能会导致算法结束时得到不同的最优解。

为了解决这个问题,有其他算法,例如Forgy 算法,我们在第一步中选择数据集中的随机观察值作为质心。与随机划分算法相比,这导致第一步中的质心分布更分散。

正如我们之前讨论的,我们必须计算观察值和聚类质心之间的距离。有各种方法来计算这个距离。两种流行的方 法是欧几里得距离曼哈顿距离

欧几里得距离

两个点之间的欧几里得距离是连接它们的线段的长度。对于具有n个值的 n 维点PQ,欧几里得距离使用以下公式计算:

图片

如果数据点的值是分类值,那么 图片 如果两个观测值对于某个特征具有相同的值则为 1,如果观测值具有不同的值则为 0。对于连续变量,我们可以计算属性值之间的归一化距离。

曼哈顿距离

曼哈顿距离是两个数据点之间绝对差值的总和。对于具有 n 个值的 n 维点 PQ,我们使用以下公式计算曼哈顿距离:

图片

曼哈顿距离减少了数据中异常值的影响,因此,当我们有噪声数据且异常值很多时,应该使用曼哈顿距离。

层次聚类

层次聚类旨在从观测值构建聚类的层次结构。有两种生成层次结构的方法:

  • 聚合聚类:在这种方法中,我们使用自下而上的方法,其中每个观测值最初是其自己的聚类,并在生成层次结构的每个阶段合并聚类。

  • 划分聚类:在这种方法中,我们使用自上而下的方法,随着我们向下移动层次结构的阶段,我们将观测值划分为更小的聚类。

聚合聚类

聚合聚类 中,我们以每个观测值作为其自己的聚类开始,并根据某些标准将这些聚类合并,以便我们最终得到一个包含所有观测值的聚类。类似于 k-means 聚类,我们使用距离度量,如曼哈顿距离和欧几里得距离来计算两个观测值之间的距离。我们还使用 连接标准,它可以表示两个聚类之间的距离。在本节中,我们研究了三种连接标准,即 完全连接聚类单连接聚类平均连接聚类

完全连接聚类计算两个聚类之间的距离为两个聚类中观测值的最大距离,表示如下:

图片

单连接聚类计算两个聚类之间的距离为两个聚类中观测值的最近距离,表示如下:

图片

平均连接聚类计算来自聚类 A 的每个观测值与聚类 B 之间的距离,并根据聚类 A 和 B 中的观测值进行归一化。这表示如下:

图片

因此,在层次聚类法的第一步中,我们使用距离方法计算每个观测值之间的距离,并将距离最小的观测值合并。对于第二步,我们使用刚刚介绍的方法的链接标准来计算每个簇之间的距离。我们运行必要的迭代,直到只剩下一个包含所有观测值的簇。

下图显示了对于只有一个连续变量的观测值,聚合聚类将如何工作:

在这个例子中,我们有五个观测值和一个连续特征。在第一次迭代中,我们查看每个观测值之间的欧几里得距离,可以推断出记录 1 和 2 彼此最接近。因此,在第一次迭代中,我们将观测值 1 和 2 合并。在第二次迭代中,我们发现观测值 10 和 15 是最接近的记录,并从中创建一个新的簇。在第三次迭代中,我们观察到(1,2)簇和(10,15)簇之间的距离小于这些簇和观测值 90 因此,我们在第三次迭代中创建了一个包含(1,2,10,15)的簇。最后,在最后一次迭代中,我们将元素 90 添加到簇中,并终止聚合聚类的过程。

分层聚类

分层聚类是一种自上而下的方法,我们首先从一个包含所有观测值的大簇开始,并在迭代过程中将簇分割成更小的簇。这个过程类似于使用距离和链接标准,如聚合聚类。目标是找到一个在较大簇中与簇内其他部分距离最远的观测值或簇。在每次迭代中,我们查看一个簇,并通过找到彼此之间距离最远的簇来递归地分割较大的簇。最后,当每个观测值都是其自己的簇时,停止这个过程。分层聚类使用穷举搜索在每个簇中找到完美的分割,这可能在计算上非常昂贵。

与 k-means 方法相比,层次聚类方法在计算上更昂贵。因此,即使在中等规模的数据集上,层次聚类方法可能比 k-means 方法更难以生成结果。然而,由于我们不需要在层次聚类的开始时从一个随机的分区开始,它们消除了 k-means 方法中的风险,即一个糟糕的随机分区可能会损害聚类过程。

在 EMR 上使用 Apache Spark 进行聚类

在本节中,我们将逐步创建一个聚类模型,该模型能够将消费者模式分为三个不同的簇。第一步将是启动一个 EMR 笔记本和一个小簇(由于我们选择的数据集不是很大,一个m5.xlarge节点就足够了)。只需遵循以下步骤:

  1. 第一步是加载数据框并检查数据集:
df = spark.read.csv(SRC_PATH + 'data.csv', 
                    header=True, 
                    inferSchema=True)

以下截图显示了我们的df数据框的前几行:

图片

正如你所见,数据集涉及不同客户在不同时间、不同地点购买的产品交易。我们尝试通过查看三个因素来使用 k-means 对这些客户交易进行聚类:

  • 产品(由StockCode列表示)

  • 购买产品的国家

  • 客户在所有产品上的总消费金额

注意,最后一个因素在数据集中不可直接获得,但它似乎是一个直观上有价值的特征(客户是否是大额消费者)。在特征准备过程中,我们经常需要找到汇总值并将它们插入到我们的数据集中。

  1. 在这次事件中,我们首先通过将QuantityUnitPrice列相乘来找到每个客户的总消费金额:
df = df.selectExpr("*",
                   "Quantity * UnitPrice as TotalBought")

以下截图显示了我们的修改后的df数据框的前几行:

图片

  1. 然后,我们继续按客户聚合TotalBought列:
customer_df = df.select("CustomerID","TotalBought")
   .groupBy("CustomerID")
   .sum("TotalBought")
   .withColumnRenamed('sum(TotalBought)','SumTotalBought')

以下截图显示了customer_df数据框的前几行:

图片

  1. 然后,我们可以根据客户将这个新列重新连接到我们的原始数据集:
from pyspark.sql.functions import *
joined_df = df.join(customer_df, 'CustomerId')

以下截图显示了joined_df数据框的前几行:

图片

注意,我们感兴趣的用于聚类的两个特征(国家StockCode)是分类的。因此,我们需要找到一种方法来编码这两个数字,类似于我们在上一章中所做的。在这种情况下,对这些特征进行字符串索引是不合适的,因为 k-means 通过计算数据点之间的距离来工作。分配给字符串值的人工索引之间的距离不包含很多信息。相反,我们对这些特征应用独热编码,以便向量距离表示有意义的含义(注意,两个在大多数向量组件上巧合的数据点具有接近 0 的余弦或欧几里得距离)。

我们的流程将包括两个独热编码步骤(用于国家产品),以及一个表示客户是大额、正常还是小额消费者的列。为了确定这一点,我们使用QuantileDiscretizerSumTotalBought列离散化为三个值,这将根据每个客户所在的分位数产生三个桶。我们使用向量组装器来编译特征向量。鉴于 k-means 算法通过计算距离来工作,我们规范化特征向量,以便第三个特征(支出桶)不会对距离有更高的影响,因为它在向量组件中有更大的绝对值。最后,我们的流程将运行 k-means 估计器。

  1. 在以下代码块中,我们定义了流程的阶段并拟合了一个模型:
from pyspark.ml import Pipeline
from pyspark.ml.clustering import KMeans
from pyspark.ml.feature import Normalizer
from pyspark.ml.feature import OneHotEncoder
from pyspark.ml.feature import QuantileDiscretizer
from pyspark.ml.feature import StringIndexer
from pyspark.ml.feature import VectorAssembler

stages = [ 
   StringIndexer(inputCol='StockCode', 
                 outputCol="stock_code_index", 
                 handleInvalid='keep'),
   OneHotEncoder(inputCol='stock_code_index', 
                 outputCol='stock_code_encoded'),
   StringIndexer(inputCol='Country', 
                 outputCol='country_index', 
                 handleInvalid='keep'),
   OneHotEncoder(inputCol='country_index', 
                 outputCol='country_encoded'),
   QuantileDiscretizer(numBuckets=3,
                       inputCol='SumTotalBought',
                       outputCol='total_bought_index'),
   VectorAssembler(inputCols=['stock_code_encoded', 
                              'country_encoded', 
                              'total_bought_index'],
                   outputCol='features_raw'),
   Normalizer(inputCol="features_raw",         
              outputCol="features", p=1.0),
   KMeans(featuresCol='features').setK(3).setSeed(42) ]

pipeline = Pipeline(stages=stages)
model = pipeline.fit(joined_df)
  1. 一旦我们有了模型,我们就将该模型应用于我们的数据集以获得每个交易所属的簇:
df_with_clusters = model.transform(joined_df).cache()

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

  1. 注意新的预测列,它包含每行所属簇的值。我们使用轮廓指标评估簇的形成情况,该指标衡量数据点在其簇内与其他簇的相似性:
from pyspark.ml.evaluation import ClusteringEvaluator

evaluator = ClusteringEvaluator()
silhouette = evaluator.evaluate(df_with_clusters)

在这个例子中,我们得到了 0.35 的值,这是一个平均的聚类分数(理想情况下它接近 1.0,但至少是正的)。没有更大值的主要原因是我们没有减少向量的维度。通常,在聚类之前,我们应用一些变换来减少特征向量的基数,例如主成分分析PCA)。为了简化,我们没有在这个例子中包含这一步。

  1. 现在,我们可以检查每个簇,以了解数据是如何聚类的。首先要注意的是每个簇的大小。正如我们下面可以看到的,簇的大小各不相同,其中一个簇捕获了超过一半的数据点:
df_with_clusters
.groupBy("prediction")
.count()
.toPandas()
.plot(kind='pie',x='prediction', y='count')

下面的图显示了不同簇的相对大小:

  1. 如果我们查看每个簇包含的国家,我们可以看到有两个簇仅包含来自英国的数据点,第三个簇只包含来自其他国家的数据点。我们首先检查簇 0 的计数:
df_with_clusters \
.where(df_with_clusters.prediction==0) \
.groupBy("Country") \
.count() \
.orderBy("count", ascending=False) \
.show()

+--------------+------+
| Country      | count|
+--------------+------+
|United Kingdom|234097|
+--------------+------+

同样,显示簇 1 的计数:

df_with_clusters \
.where(df_with_clusters.prediction==1) \
.groupBy("Country") \
.count() \
.orderBy("count", ascending=False) \
.show()

+--------------+------+
| Country .    | count|
+--------------+------+
|United Kingdom|127781|
+--------------+------+

最后,显示簇 2 的计数:

df_with_clusters \
.where(df_with_clusters.prediction==2) \
.groupBy("Country") \
.count() \
.orderBy("count", ascending=False) \
.show()

+---------------+-----+
| Country       |count|
+---------------+-----+
| Germany       | 9495|
| France        | 8491|
.
.
| USA           | 291 |
+---------------+-----+
  1. 一个有趣的观察是,不同的簇似乎有非常不同的消费模式:
pandas_df = df_with_clusters \
   .limit(5000) \
   .select('CustomerID','InvoiceNo','StockCode',
           'Description','Quantity','InvoiceDate',
           'UnitPrice','Country','TotalBought',
            'SumTotalBought','prediction') \
   .toPandas()

pandas_df.groupby('prediction') \
.describe()['SumTotalBought']['mean'] \
.plot(kind='bar', 
      title = 'Mean total amount bought per cluster')

前面的plot()命令生成了以下图:

  1. 为了了解每个簇如何分类产品,我们查看不同簇的产品描述字段。一个很好的视觉表示是使用词云,其中包含每个簇产品描述中出现的单词。使用 Python 的wordcloud库,我们可以创建一个函数,该函数去除产品的单词并构建一个词云:
import itertools
import re
from functools import reduce
import matplotlib.pyplot as plt
from wordcloud import WordCloud, STOPWORDS

def plot_word_cloud(description_column):
   list_of_word_sets = description_column \
        .apply(str.split) \
        .tolist()
   text = list(itertools.chain(*list_of_word_sets))
   text = map(lambda x: re.sub(r'[^A-Z]', r'', x), text)
   text = reduce(lambda x, y: x + ' ' + y, text)
   wordcloud = WordCloud(
       width=3000,
       height=2000,
       background_color='black',
       stopwords=STOPWORDS,
       collocations=False).generate(str(text))
   fig = plt.figure(
       figsize=(10, 5),
       facecolor='k',
       edgecolor='k')
   plt.imshow(wordcloud, interpolation='bilinear')
   plt.axis('off')
   plt.tight_layout(pad=0)
   plt.show()
  1. 我们在每个簇上调用此函数并得到以下结果:
plot_word_cloud(pandas_df[pandas_df.prediction==0].Description)

簇 0 的结果词云如下:

  1. 看看下面的代码:
plot_word_cloud(pandas_df[pandas_df.prediction==1].Description)

簇 1 的结果词云如下:

看看下面的代码:

plot_word_cloud(pandas_df[pandas_df.prediction==2].Description)

簇 2 的结果词云如下:

我们可以在词云中看到,尽管有一些非常常见的单词,但像Christmasretrospot*这样的几个单词的相对重要性在簇中的一个权重更高。

在 EMR 上使用 Spark 和 SageMaker 进行聚类

在本节中,我们将展示 SparkSageMaker 如何通过代码集成无缝协作。

在上一章关于决策树的内容中,我们通过 Apache Spark 进行数据准备,通过 EMR 上传准备好的数据到 S3,然后使用 SageMaker Python 库打开 SageMaker 笔记本实例以执行训练。有一种做同样事情的不同方法,在很多情况下更为方便,那就是使用 sagemaker_pyspark 库。这个库允许我们通过在我们的管道中添加一个特殊阶段,通过 SageMaker 服务执行训练阶段。为此,我们将定义一个与上一节中写的管道相同的管道,区别在于最后一个阶段。

我们将使用 KMeansSageMakerEstimator 而不是包含 Apache Spark 的 KMeans 实现:

  1. 首先,我们将导入所有必要的依赖项:
from pyspark.ml import Pipeline
from pyspark.ml.clustering import KMeans
from pyspark.ml.feature import Normalizer
from pyspark.ml.feature import OneHotEncoder
from pyspark.ml.feature import QuantileDiscretizer
from pyspark.ml.feature import StringIndexer
from pyspark.ml.feature import VectorAssembler
from sagemaker_pyspark import IAMRole
from sagemaker_pyspark.algorithms import KMeansSageMakerEstimator
  1. 接下来,我们首先定义要使用的 IAM 角色和完整管道:
role = 'arn:aws:iam::095585830284:role/EMR_EC2_DefaultRole'

kmeans = KMeansSageMakerEstimator(
  sagemakerRole=IAMRole(role),
  trainingInstanceType="ml.m4.xlarge",
  trainingInstanceCount=1,
  endpointInstanceType="ml.m4.xlarge",
  endpointInitialInstanceCount=1)
kmeans.setK(3)
kmeans.setFeatureDim(3722)

stages = [
   StringIndexer(inputCol='StockCode', outputCol="stock_code_index", handleInvalid='keep'),
   OneHotEncoder(inputCol='stock_code_index', outputCol='stock_code_encoded'),
   StringIndexer(inputCol='Country', outputCol='country_index', handleInvalid='keep'),
   OneHotEncoder(inputCol='country_index', outputCol='country_encoded'),
   QuantileDiscretizer(numBuckets=3, inputCol='SumTotalBought',outputCol='total_bought_index'),
   VectorAssembler(inputCols=['stock_code_encoded', 'country_encoded', 'total_bought_index'],
                   outputCol='features_raw'),
   Normalizer(inputCol="features_raw", 
              outputCol="features", p=1.0),
              kmeans ]

pipeline = Pipeline(stages=stages)
model = pipeline.fit(joined_df)

KMeansSageMakerEstimator 实现了 Apache Spark 的估算器接口,因此它被包含在我们的管道上的任何其他估算器或转换器中。通过 KMeansSageMakerEstimator 构造函数,我们定义要使用的机器的数量和类型以及 IAM 角色。我们将在下一小节中解释该角色的目的,理解 IAM 角色的目的。此外,我们设置我们想要创建的集群数量(k 的值)以及我们将用于训练的向量的长度(我们通过检查上一节输出的行来找到它)。

让我们看看当我们对管道调用 fit() 时幕后发生了什么。管道的前一部分工作方式与之前完全相同,即不同的阶段启动 Spark 作业,通过添加列(例如,编码向量或离散特征)对数据集执行一系列转换。最后一个阶段,作为一个 SageMaker 估算器,工作方式略有不同。它不是使用 EMR 集群资源来计算和训练聚类模型,而是将数据保存到 S3,并通过指向该 S3 临时位置的 API 调用 SageMaker KMeans 服务。SageMaker 服务随后启动 EC2 服务器以执行训练,并创建一个 SageMaker 模型和端点。一旦训练完成,KMeansSageMakerEstimator 存储对新创建的端点的引用,每次调用模型的 transform() 方法时都会使用该端点。

您可以通过检查 SageMaker AWS 控制台在 console.aws.amazon.com/sagemaker/ 来找到由 KMeansSageMakerEstimator 创建的模型和端点。

现在,按照以下步骤操作:

  1. 让我们来看看当我们调用管道的 transform() 方法时会发生什么:
df_with_clusters = model.transform(joined_df)

第一组转换(数据准备阶段)将通过 Spark 作业在 EMR 集群上运行。作为最终转换的 SageMaker 模型依赖于 SageMaker 端点来获取预测(在我们的案例中,是集群分配)。

应该记住,一旦不再需要,就要删除端点(例如使用控制台)。

  1. 然后,运行以下代码:
df_with_clusters.show(5)

看看下面的截图:

图片

(图片已被截断,仅显示最后几列.)

注意,distance_to_clusterclosest_cluster管道是如何添加这两个列的。

  1. 通过指示集群评估器使用此列,我们可以评估聚类能力:
evaluator = ClusteringEvaluator()
evaluator.setPredictionCol("closest_cluster")
silhouette = evaluator.evaluate(df_with_clusters)

我们得到的轮廓值几乎与使用 Spark 算法得到的一样。

理解 IAM 角色的目的

SageMaker 是 AWS 上的一种托管服务,负责管理训练和推理所需的硬件。为了使 SageMaker 能够代表您执行此类任务,您需要通过 IAM 配置允许它。例如,如果您在 EMR 上运行,集群中的 EC2 实例(即计算机)正在运行具有特定角色的配置。您可以通过访问 EMR 控制台上的集群页面来找到此角色:console.aws.amazon.com/elasticmapreduce/

以下截图显示了集群的详细信息,包括安全和访问信息:

图片

在之前的截图中的 EC2 实例配置文件下的角色就是我们正在使用的,即EMR_EC2_DefaultRole

然后,我们转到 IAM 控制台console.aws.amazon.com/iam/home来编辑该角色的权限,以授予对 SageMaker 资源的访问权限,并允许该角色被假定:

图片

在信任关系部分,我们点击编辑信任关系按钮以打开对话框,允许我们添加设置:

图片

您可以如下编辑并允许角色被假定:

图片

之前的更改是必要的,以便我们的 EMR 集群能与 SageMaker 通信,并启用本节中描述的集成类型。

摘要

在本章中,我们研究了监督学习和无监督学习之间的区别,并探讨了无监督学习应用的情况。我们研究了无监督学习的探索性分析应用,其中使用了聚类方法。我们详细研究了 k-means 聚类和层次聚类方法,并探讨了它们的应用示例。

我们还研究了如何在 AWS 集群上实现 Apache Spark 的聚类方法。根据我们的经验,聚类任务通常在较大的数据集上完成,因此,对于此类任务考虑集群的设置是很重要的。我们在本章中讨论了这些细微差别。

作为数据科学家,我们遇到过很多只为了从数据中提取价值而分析数据的情况。在这些情况下,你应该考虑聚类方法,因为它将帮助你理解数据中的固有结构。一旦你发现了数据中的模式,你就可以通过数据排列的事件和类别来识别事件和类别。一旦你建立了你的聚类,你还可以根据它可能属于哪个聚类来评估任何新的观察,并预测该观察将表现出与聚类中其他观察相似的行为。

在下一章中,我们将探讨一个非常有趣的问题:如何通过找到相似用户认为相关的产品来通过机器学习进行推荐。

练习

  1. 与层次聚类相比,你会在什么情况下应用 k-means 算法?

  2. 正常的 Spark 估计器与调用 SageMaker 的估计器之间有什么区别?

  3. 对于训练时间太长的数据集,为什么使用 SageMaker 估计器启动此类作业不是一个好主意?

  4. 研究并建立其他用于聚类评估的替代指标。

  5. 为什么在为 k-means 编码特征时,字符串索引不是一个好主意?

第六章:通过分析游客模式来制定推荐

本章将专注于基于游客参加的主题公园景点寻找相似游客的问题,以制定改进的营销推荐。我们将介绍协同过滤方法,并通过示例展示如何在 Apache Spark(EMR)和通过 AWS SageMaker 内置算法中训练和获取定制推荐。许多公司利用我们在本章中描述的算法类型来通过推荐与类似客户相关联的产品来提高客户的参与度。

在本章中,我们将涵盖以下主题:

  • 通过 Flickr 数据推荐主题公园景点

  • 通过 Apache Spark 的交替最小二乘法寻找推荐

  • 通过 SageMaker 因子分解机推荐景点

通过 Flickr 数据推荐主题公园景点

在本章中,我们将使用来自sites.google.com/site/limkwanhui/datacode的数据集,该数据集包含在不同地点拍照的用户上传的 Flickr 数据,这些照片随后被映射到已知的主题公园景点。Flickr 是一个图像托管服务。假设 Flickr 希望在他们的移动应用上创建一个插件,当用户在各个景点拍照时,能够识别用户偏好并提供可能对他们感兴趣的其它景点的推荐。

让我们再假设用户在特定景点拍摄的照片数量是他们对该景点兴趣的一个指标。我们的目标是分析包含用户 ID、景点、拍摄照片数量的三元组数据集,以便给定用户访问的任意景点集合,模型能够推荐类似用户发现有趣的景点。

协同过滤

协同过滤是一种基于分析大量用户行为来向用户提供推荐的过程。我们在日常生活中的大量应用中观察到这种算法的影响。例如,当你使用流媒体服务,如 Netflix 或 YouTube 时,它会根据你的观看历史推荐你可能感兴趣的视频。社交媒体,如 Twitter 和 LinkedIn,会根据你的当前联系人建议你关注或连接的人。Instagram 和 Facebook 等服务会根据你阅读或赞过的帖子来编辑你朋友的帖子,并定制你的时间线。作为一名数据科学家,当你在基于大量用户数据构建推荐系统时,协同过滤算法非常有用。

协同过滤可以在数据集上有多种实现方式。在本章中,我们将讨论基于记忆的方法和基于模型的方法。

基于记忆的方法

在基于记忆的方法中,我们分两个阶段生成推荐。考虑这样一种情况,即我们试图根据用户的兴趣为特定用户生成推荐。在第一阶段,我们根据兴趣发现与给定用户相似的用户。我们根据用户与给定用户的相似程度对所有用户进行排名。在第二阶段,我们在与给定用户最相似的用户组中找到最感兴趣的兴趣点。这些顶级兴趣根据它们与顶级用户集的相似性进行排名。然后,这个按排名排列的兴趣列表被呈现给原始用户作为推荐。

例如,在电影推荐的过程中,我们查看用户感兴趣或最近观看的电影,并发现观看过类似电影的其他用户。基于相似用户的高排名列表,我们查看他们最近观看的电影,并根据与排名用户列表的相似性进行排名。然后,将排名最高的电影作为推荐呈现给用户。

为了找到用户之间的相似性,我们使用称为相似度度量的函数。相似度度量在搜索引擎中广泛用于对查询词和文档之间的相似性进行排序。在本节中,我们讨论了余弦相似度度量,它在协同过滤中常用。我们将每个用户的兴趣视为一个向量。为了发现具有相似兴趣的用户,我们计算两个用户兴趣向量之间的余弦角。它可以表示如下:

图片

根据给定用户与数据集中所有用户的相似性,我们选择前 k 个用户。然后,我们聚合所有用户的兴趣向量,以发现顶级兴趣并推荐给用户。

注意,基于记忆的模型不使用前几章中讨论的任何建模算法。它们仅依赖于简单的算术来根据用户的兴趣为用户生成推荐。

基于模型的方法

对于基于模型的方法,我们使用机器学习技术训练一个模型,该模型可以预测每个兴趣与给定用户的相关概率。可以应用各种算法,如贝叶斯模型或聚类模型,用于基于模型的协同过滤。然而,在本章中,我们专注于基于矩阵分解的方法。

矩阵分解

矩阵分解方法通过分解用户和用户兴趣的矩阵来工作。在本方法中,我们将用户数据和关于兴趣的信息映射到一组因子。用户对兴趣的评分是通过计算用户和兴趣的向量评分的点积来计算的。这些因子可以从用户评分或从算法中关于兴趣的外部信息中推断出来。

例如,考虑一个矩阵,其中一个维度代表用户,另一个维度代表用户已评分的电影:

复仇者联盟 勇敢者游戏 蜘蛛侠
用户 1 4 4
用户 2 1 5 2
用户 n 5 2 4

矩阵中的值是用户提供的评分。矩阵分解方法将电影映射到更短的向量,这些向量代表概念,例如电影的类型。这些向量被称为潜在因素。我们将电影映射到类型,并根据用户对每个类型的电影评分来映射用户感兴趣的类型。使用这些信息,我们可以根据两个向量之间的点积(将用户对类型的兴趣乘以电影属于类型的可能性)来计算用户和电影之间的相似性。因此,矩阵中的未知评分可以通过通过将用户和兴趣合并到更粗粒度的项目(即类型)来使用已知评分的知识进行预测。在我们的前一个例子中,我们假设我们已经有了一个已知的电影到类型的映射。然而,我们不能假设我们总是有显式数据来生成这样的映射到潜在因素。

因此,我们探索可以基于数据自动生成此类映射的方法。因此,矩阵分解模型需要能够通过潜在因素向量在用户和兴趣之间生成映射。为了确保我们可以生成用户潜在因素和项目潜在因素之间的点积,潜在因素长度被设置为固定值。每个兴趣项目,图片,由一个向量,图片,表示,每个用户,图片,由一个向量,图片,表示。向量图片图片都是从数据中导出的潜在因素。用户对项目的评分,图片,对用户的评分,图片,表示如下:

图片

通常,如果我们已经从用户到兴趣的部分评分集合,我们可以使用它来建模其他用户和兴趣之间的评分。我们使用优化技术来计算这个。我们的目标是预测图片图片的值。因此,我们通过最小化预测这些向量时使用的已知评分的正则化误差来实现这一点。这可以用以下公式表示:

图片

在这里,k 是一个包含 的集合,其中已知的评分 。现在,让我们看看两种最小化前面方程的方法。

随机梯度下降

我们在第三章,“使用回归算法预测房屋价值”中研究了随机梯度下降算法,关于线性回归。类似的方法用于最小化预测每个用户和兴趣的正确潜在因子的函数。我们使用迭代方法,在每次迭代中,我们根据所有已知的评分计算预测 的误差:

图片

根据误差的大小,我们更新 的值,方向与误差相反:

图片

图片

的值收敛后,我们停止迭代。随机梯度下降也用于如因子分解机FMs)等算法中,它使用它来计算向量的值。FMs 是 支持向量机SVM)模型的变体,可以在协同过滤框架中应用。我们在这本书中不会详细解释支持向量机或 FMs,但鼓励你了解它们是如何工作的。

交替最小二乘法

最小化预测 值的优化函数的一个挑战是,方程不是凸的。这是因为我们试图同时优化两个值。然而,如果我们为其中一个值使用常数,或者 ,我们可以为另一个变量最优地解决方程。因此,在交替最小二乘技术中,我们交替地将 的值设为常数,同时优化另一个向量。

因此,在第一步中,我们为两个向量设定了基础值。假设其中一个值是常数,我们使用线性规划来优化另一个向量。在下一步中,我们将优化向量的值设为常数,并优化另一个变量。我们不会解释如何使用线性规划来优化二次问题,因为这是一个完整的研究领域,并且超出了本书的范围。这种方法将优化每个向量,直到收敛。

随机梯度下降的优点是它比 ALS 方法更快,因为它在修改向量时基于错误比例预测每个步骤中每个向量的值。然而,在 ALS 方法中,系统独立地计算每个向量的值,因此导致更好的优化。此外,当矩阵密集时,梯度下降方法必须从每一组数据中学习,这使得它比 ALS 方法效率更低。

通过 Apache Spark 的 ALS 找到推荐

在本节中,我们将通过 Apache Spark 使用交替最小二乘法ALS)创建推荐的过程。

数据收集和探索

第一步是从sites.google.com/site/limkwanhui/datacode下载数据。我们将使用poiList-sigir17数据集,其中包含用户在不同主题公园景点(由 Flickr 识别为兴趣点)拍摄的图片。我们感兴趣的以下两个数据集:

  • 该兴趣点列表,它捕捉了每个景点的名称和其他属性:
poi_df = spark.read.csv(SRC_PATH + 'data-sigir17/poiList-sigir17', 
                        header=True, inferSchema=True, sep=';')

以下截图显示了poi_df数据框的前几行:

图片

  • Flickr 用户在不同兴趣点拍摄的图片:
visits_path = SRC_PATH+'data-sigir17/userVisits-sigir17'
visits_df = spark.read.csv(visits_path, 
                           header=True,
                           inferSchema=True, sep=';')

以下截图显示了visits_df数据框的一个样本:

图片

在这个数据集中,我们将使用nsid字段(表示拍照的用户)和poiID,它表示拍照时实际访问的兴趣点或景点。为了我们的目的,我们将忽略其余的字段。

让我们对我们的数据集做一些基本的检查。数据集大约有 30 万行数据。通过抽取 1000 个条目的样本,我们可以看到有 36 个独特的 Flickr 用户:

sample_df = visits_df.limit(1000).toPandas()
sample_df.describe()

前一个describe()命令的输出如下:

count 1000
 unique 36
 top 10182842@N08
 freq 365

这很重要,因为我们需要每个用户有足够多的条目来确保我们有足够的信息关于用户来做出预测。此外,了解用户是否访问了不同的景点实际上更为相关。Apache Spark 的一个优点是,可以使用 SQL 在数据集上工作。使用 SQL 轻松地找到用户平均看到的独特景点的数量。

为了使用 SQL,我们首先需要给数据集一个表名。这是通过注册一个临时表来完成的:

poi_df.createOrReplaceTempView('points')
visits_df.createOrReplaceTempView('visits')

一旦我们注册了表,我们就可以进行查询,例如找到独特景点的数量:

spark.sql('select distinct poiID from visits').count()
31

或者我们可以将 SQL 与其他数据集操作相结合,例如.describe()

spark.sql('select nsid,count(distinct poiID) as cnt from visits group by nsid').describe().show()

以下截图包含了show()命令输出的结果:

图片

前面的 SQL 命令找出每个用户访问的独特景点的数量。describe数据集操作找出这些用户的统计数据,这告诉我们,平均而言,用户访问大约五个不同的地点。这很重要,因为我们需要每个用户有足够的景点,以便能够正确识别用户模式。

同样,我们应该查看用户在每个地点拍摄的照片数量,以验证实际上我们可以使用拍摄照片的数量作为用户兴趣的指标。我们通过以下命令来完成:

spark.sql('select nsid,poiID,count(*) from visits group by nsid,poiID').describe().show()

前面命令的输出如下所示:

图片

SQL 命令计算每个用户和景点的条目数量,然后我们使用describe找到统计摘要。因此,我们可以得出结论,平均而言,每个用户在每个访问的地点大约拍摄八张照片。

训练模型

为了训练我们的模型,我们将构建一个数据集,该数据集计算每个用户在每个地点拍摄的照片数量:

train_df = spark.sql('select hash(nsid) as user_hash_id, poiID, count(*) as pictures_taken from visits group by 1,2')

以下截图显示了train_df数据框的前几行:

图片

我们对用户进行哈希处理,因为 ALS 训练器只支持数值作为特征。

要训练模型,我们只需构建一个 ALS 实例,并提供用户列、项目列(在这种情况下是景点 ID)和评分列(在这种情况下,pictures_takes用作评分的代理)。coldStartStrategy设置为丢弃,因为我们不感兴趣为数据集中不存在(即,预测此类条目将被丢弃而不是返回 NaN)的用户或景点进行预测:

from pyspark.ml.recommendation import ALS

recommender = ALS(userCol="user_hash_id", 
                  itemCol="poi_hash_id", 
                  ratingCol="pictures_taken", 
                  coldStartStrategy="drop")

model = recommender.fit(train_df)

获取推荐

一旦我们构建了一个模型,我们就可以为我们数据集中的所有用户生成预测:

recommendations = model.recommendForAllUsers(10)

前面的命令将为每个用户选择前 10 个推荐。请注意,由于 ALS 的工作方式,它实际上可能会推荐用户已经访问过的景点,因此我们需要在我们的目的中丢弃这些推荐,正如我们稍后将要看到的。

推荐看起来如下:

图片

每个用户都会得到一个包含推荐景点以及推荐得分的元组列表。在这种情况下,得分代表我们预计每个用户在推荐地点会拍摄的照片数量。尽管模型只提供景点的 ID,但我们想检查其中的一些推荐,以确保它们是好的。为了做到这一点,我们将通过收集查询结果来构建一个 ID 到景点名称(兴趣点名称)的字典,该查询找出每个景点在景点表中的名称:

row_list = spark.sql('select distinct p.poiName, p.poiID from visits v join points p on (p.poiID=v.poiID) ').collect()
id_to_poi_name = dict(map(lambda x: (x.poiID, x.poiName), row_list))

地图包含以下条目:

{1: 'Test Track',
 10: 'Golden Zephyr',
 19: "Tarzan's Treehouse",
 22: 'Country Bear Jamboree'
 ....
 }

对于每个用户,我们希望移除已访问地点的推荐并输出推荐。为此,我们需要处理每行的元组列表。Apache Spark 通过允许用户创建自定义 SQL 函数或用户定义函数UDFs)提供了一种方便的方式来做到这一点。我们将定义并注册一个 UDF,它能够通过使用前面的映射提取每个推荐景点的名称:

def poi_names(recommendations, visited_pois):
   visited_set = set([id_to_poi_name[poi] for poi in visited_pois])
   recommended = str([(id_to_poi_name[poi], weight) \
                      for (poi,weight) in recommendations
                      if id_to_poi_name[poi] not in visited_set])
   return "recommended: %s ; visited: %s "%(recommended, visited_set)

spark.udf.register("poi_names", poi_names)

poi_names函数接收用户的推荐元组以及访问的景点,然后返回一个包含所有未在访问集合中的推荐景点名称以及已访问景点枚举的字符串。

然后,我们将推荐注册为一个表,以便在下一个查询中使用:

recommendations.createOrReplaceTempView('recommendations')

recommendation_sample = spark.sql('select user_hash_id, collect_list(poiID), poi_names(max(recommendations), collect_list(poiID)) as recommendation from recommendations r join visits v on (r.user_hash_id = hash(v.nsid)) group by 1')\
   .sample(fraction=0.1, withReplacement=False) \
   .collect()

前面的查询将用户推荐表与访问表连接,通过用户进行连接,收集每个用户访问的所有兴趣点,并通过 UDF 输出推荐的景点以及已访问景点的名称。我们采样并收集一些表实例以进行检查。在配套的笔记本中,我们可以观察到以下条目:

print(recommendation_sample[0].recommendation)

recommended: [("It's A Small World", 31.352962493896484), ('Walt Disney World Railroad', 23.464025497436523), ('Pirates of the Caribbean', 21.36219596862793), ('Buzz Lightyear Astro Blasters', 17.21680450439453), ('Haunted Mansion', 15.873616218566895), ('Country Bear Jamboree', 9.63521957397461), ('Astro Orbiter', 9.164801597595215), ('The Great Movie Ride', 8.167647361755371)] ; visited: {"California Screamin'", 'Sleeping Beauty Castle Walkthrough', 'Voyage of The Little Mermaid', "Tarzan's Treehouse", 'Main Street Cinema', 'The Many Adventures of Winnie the Pooh', 'Jungle Cruise', 'Tom Sawyer Island', 'Test Track', 'The Twilight Zone Tower of Terror'}

我们可以观察到这位用户访问了许多类似冒险的景点,模型推荐了更多。在这里,读者可以检查更多推荐:

print(recommendation_sample[200].recommendation)

recommended: [('Splash Mountain', 0.9785523414611816), ('Sleeping Beauty Castle Walkthrough', 0.8383632302284241), ("Pinocchio's Daring Journey", 0.7456990480422974), ('Journey Into Imagination With Figment', 0.4501221477985382), ("California Screamin'", 0.44446268677711487), ('Tom Sawyer Island', 0.41949236392974854), ("It's A Small World", 0.40130260586738586), ('Astro Orbiter', 0.37899214029312134), ('The Twilight Zone Tower of Terror', 0.3728359639644623)] ; visited: {"Snow White's Scary Adventures"}

print(recommendation_sample[600].recommendation)

recommended: [('Fantasmic!', 20.900590896606445), ('Pirates of the Caribbean', 9.25596809387207), ("It's A Small World", 8.825133323669434), ('Buzz Lightyear Astro Blasters', 5.474684715270996), ('Main Street Cinema', 5.1001691818237305), ('Country Bear Jamboree', 4.3145904541015625), ("California Screamin'", 3.717888832092285), ("It's A Small World", 3.6027705669403076), ('The Many Adventures of Winnie the Pooh', 3.429044246673584)] ; visited: {'Haunted Mansion', 'The Twilight Zone Tower of Terror', 'Journey Into Imagination With Figment'}

通过 SageMaker 因子分解机推荐景点

当涉及到非常稀疏的输入时,FM(因子分解机)是制作推荐中最广泛使用的算法之一。它类似于我们在基于模型的矩阵分解方法下讨论的随机梯度下降SGD)算法。在本节中,我们将展示如何使用 AWS 内置的 FM 算法实现来为我们主题公园的游客获取推荐。

准备学习数据集

为了使用这样的算法,我们需要以不同的方式准备我们的数据集。我们将推荐问题设定为一个回归问题,其中输入是一个用户和吸引力的配对,输出是这个用户对吸引力的预期兴趣水平。训练数据集必须包含每个用户和吸引力配对的实际经验兴趣(通过拍照数量衡量)。有了这些数据,FM 模型就能预测任何用户对任意吸引力的兴趣。因此,为了获得用户的推荐,我们只需找到那些能产生最高预测兴趣水平的吸引力列表。

那么我们如何在数据集中编码用户和景点呢?

由于 FM 在处理高维特征方面非常出色,我们可以对输入进行 one-hot 编码。由于有 8,903 个用户和 31 个景点,我们的输入向量长度将为 8,934,其中前 31 个向量分量将对应于 31 个不同的景点,其余位置对应于每个用户。向量除了对应于用户和景点的位置外,其余位置都将为零。我们模型中使用的目标特征(标签)将是兴趣水平,我们将通过根据相应的分位数对拍照数量进行归一化将其离散化到 1 到 5 的值。

下图显示了这样一个训练数据集可能的样子:

图片

如您所想象,这个矩阵非常稀疏,因此我们需要使用稀疏表示来编码我们的行。像大多数 SageMaker 算法一样,我们必须将数据放入 S3,以便 SageMaker 进行训练。在过去的章节中,我们使用了 CSV 作为输入。然而,CSV 并不是我们数据集的好表示;鉴于其稀疏性,它将占用太多空间(有很多重复的零!)。实际上,在撰写本文时,SageMaker 甚至不支持 CSV 作为输入格式。在稀疏表示中,每个向量必须指示以下三个值:

  • 向量的尺寸

  • 我们有非零值的位位置

  • 这些非零位置上的值

例如,前图中第一行的稀疏表示如下:

  • 向量大小 = 8934

  • 非零位置 = [1, 33]

  • 非零位置上的值 = [1, 1]

FM(特征矩阵)目前支持的唯一输入格式称为 protobuf recordIO。protobuf,即协议缓冲区,是一种语言无关、平台无关的可扩展机制,用于序列化结构化数据,最初由谷歌开发。在我们的情况下,结构将是矩阵的稀疏表示。我们存储在 S3 中的 protobuf 文件中的每个记录都将包含稀疏表示所需的所有三个项目,以及目标特征(标签)。

接下来,我们将介绍准备数据集并将其上传到 S3 的过程。

我们将从上一节中用于训练的 Spark dataframe(train_df)开始,并应用一个Pipeline,该Pipeline执行 one-hot 编码以及归一化拍照目标特征:

from pyspark.ml.feature import OneHotEncoder
from pyspark.ml.feature import StringIndexer
from pyspark.ml import Pipeline
from pyspark.ml.feature import QuantileDiscretizer
from pyspark.ml.feature import VectorAssembler

pipeline = Pipeline(stages = [
   StringIndexer(inputCol='user_hash_id', 
                 outputCol="user_hash_id_index", 
                 handleInvalid='keep'),
   OneHotEncoder(inputCol='user_hash_id_index', 
                 outputCol='user_hash_id_encoded'),
   StringIndexer(inputCol='poiID', 
                 outputCol='poi_id_indexed', 
                 handleInvalid='keep'),
   OneHotEncoder(inputCol='poi_id_indexed', 
                 outputCol='poi_id_encoded'),
   QuantileDiscretizer(numBuckets=5, 
                       inputCol='pictures_taken', 
                       outputCol='interest_level'),
   VectorAssembler(inputCols=['poi_id_encoded', 'user_hash_id_encoded'],
                   outputCol='features'),
])

model = pipeline.fit(train_df)

管道与我们在前几章中构建的管道类似,不同之处在于我们没有将机器学习算法作为最终步骤(因为一旦数据集存入 S3,这一阶段将通过 SageMaker 的 FMs 运行)。我们首先对用户和吸引力(兴趣点)特征进行字符串索引,然后将它们链接到一个独热编码器。分位数离散化器将拍摄照片特征根据其百分位数减少到五个桶中。我们将此特征命名为interest_level。此外,我们将这些编码的吸引力和用户向量组装成一个向量。

接下来,我们通过应用模型来转换训练集:

sparse_df = model.transform(train_df)

这将生成一个数据集:

图片

注意编码字段(user_hash_id_encodedpoi_id_encodedfeatures)显示了向量的稀疏表示。

一旦我们有了这个编码后的数据集,我们可以将其分为测试集和训练集。SageMaker 将使用训练集进行拟合,并在训练过程中每个 epoch 使用测试集来查找验证错误。我们需要将这些数据集转换为 recordio 格式并上传到 s3。

如果我们使用 Scala(Spark 使用的本地编程语言),我们可以做类似这样的事情:

sagemaker_train_df.write.format("sagemaker") \
  .option("labelColumnName", "interest_level") \
  .option("featuresColumnName", "features") \
  .save("s3://mastering-ml-aws/chapter6/train-data")

不幸的是,在撰写本文时,pyspark不支持直接将数据框写入 recordio 格式。相反,我们将收集所有 spark 数据框到内存中,并将每一行转换为稀疏向量,然后上传到 S3。

下面的spark_vector_to_sparse_matrix函数正是这样做的。它接受一个 Spark 数据框行并将其转换为scipy稀疏矩阵(来自scipy,一个具有科学工具的 Python 库)。upload_matrices_to_s3函数接收一个 Spark 数据集(无论是训练集还是测试集),收集每一行,使用特征构建一个稀疏向量,并将它们堆叠成一个矩阵。此外,它构建一个包含所有兴趣级别的目标特征向量。给定这个矩阵和标签向量,我们使用sagemaker库的实用函数write_spmatrix_to_sparse_tensor将数据写入 recordio 格式。最后,我们将该对象上传到 S3。为此,让我们首先导入所有必要的依赖项:

from scipy.sparse import csr_matrix
import numpy as np
import boto3
import io
import numpy as np
import scipy.sparse as sp
import sagemaker.amazon.common as smac

接下来,让我们定义两个辅助函数:spark_vector_to_sparse_matrix,它将接受一行并生成一个scipy稀疏矩阵,以及upload_matrices_to_s3,它负责将测试集或训练集上传到 s3:

def spark_vector_to_sparse_matrix(row):
   vect = row['features']
   return csr_matrix((vect.values, vect.indices, np.array([0, vect.values.size])),
                     (1, vect.size), 
                      dtype=np.float32)

def upload_matrices_to_s3(dataframe, dataset_name):
   features_matrices = 
        dataframe.select("features") \
                 .rdd.map(spark_vector_to_sparse_matrix).collect()
   interest_levels = 
        dataframe.select("interest_level") \
                 .rdd.map(lambda r: r['interest_level']).collect()

   interest_level_vector = np.array(interest_levels, dtype=np.float32)
   buffer = io.BytesIO()
   smac.write_spmatrix_to_sparse_tensor(buffer, \
                                        sp.vstack(features_matrices), \
                                        interest_level_vector)
   buffer.seek(0)
   bucket = boto3.resource('s3').Bucket('mastering-ml-aws')
   bucket.Object('chapter6/%s-data.protobuf'%dataset_name).upload_fileobj(buffer)

最后,我们需要通过在两个变量上调用upload_matrices_to_s3方法来上传训练集和测试集:

upload_matrices_to_s3(sagemaker_train_df, 'train')
upload_matrices_to_s3(sagemaker_test_df, 'test')

训练模型

现在我们已经将数据以适合学习的正确格式存入 S3,我们可以开始训练我们的模型以获取推荐。

我们将实例化 SageMaker 会话并定义读取和写入数据的路径:

import sagemaker
from sagemaker import get_execution_role
import json
import boto3

sess = sagemaker.Session()
role = get_execution_role()
container = sagemaker.amazon.amazon_estimator.get_image_uri('us-east-1', 
     "factorization-machines", 
     "latest")

s3_train_data = 's3://mastering-ml-aws/chapter6/train-data.protobuf'
s3_test_data = 's3://mastering-ml-aws/chapter6/train-data.protobuf'
s3_output_location = 's3://mastering-ml-aws/chapter6/sagemaker/output/'

通过会话,我们可以通过设置要使用的计算机的数量和类型来实例化 SageMaker 估计器。我们还可以指定超参数。两个需要考虑的重要参数是特征维度(这是我们训练向量的长度)和预测器类型。由于我们的问题是设定为回归,我们将使用回归器。如果我们不是将兴趣水平建模为存在/不存在兴趣,而是将其建模为binary_classifier值:

from sagemaker.session import s3_input

recommender = sagemaker.estimator.Estimator(container,
                                            role,
                                            train_instance_count=1,
                                            train_instance_type='ml.c4.xlarge',
                                            output_path=s3_output_location,
                                            sagemaker_session=sess)

recommender.set_hyperparameters(predictor_type='regressor',
                                feature_dim=8934,
                                epochs=200,
                                mini_batch_size=100,
                                num_factors=128)

recommender.fit({'train': s3_input(s3_train_data), \
                 'test': s3_input(s3_test_data)})

日志将显示一些验证统计信息和模型完成时的确认信息:

[02/23/2019 22:01:02 INFO 140697667364672] #test_score (algo-1) : ('rmse', 0.19088356774389661)
2019-02-23 22:01:11 Uploading - Uploading generated training model
 2019-02-23 22:01:11 Completed - Training job completed

获取推荐

一旦模型拟合完成,我们可以启动预测器网络服务:

predictor = recommender.deploy(instance_type='ml.c5.xlarge', initial_instance_count=1)

这将启动托管训练模型的网络服务端点,现在可以接收带有预测请求的请求。让我们从 Spark 的 ALS 推荐中选取一个用户,并将其与 SageMaker 的预测进行比较:

print(recommendation_sample[1].user_hash_id)
-525385694

我们可以收集那个用户的特征:


sagemaker_test_df.select('features').where('user_hash_id=-525385694') \
                 .rdd.map(build_request).collect()

[{'data': {'features': {'shape': [8934],
   'keys': [4, 3297],
   'values': [1.0, 1.0]}}}]

在这里,build_request是一个方便的函数,用于创建与 SageMaker 期望的稀疏编码请求兼容的 JSON 请求:

def build_request(row):
   vect = row['features']
   return {'data':{ 'features': {'shape':[int(vect.size)], 
                                 'keys':list(map(int,vect.indices)),
                                 'values':list(vect.values)}}}

如我们所知,用户 ID 在向量中的位置是3297,景点位置是4。我们可以调用该服务以获取服务的预测:

import json

predictor.content_type = 'application/json'
predictor.predict(json.dumps({'instances': [
    {'data': {'features': {'shape': [8934], 'keys': [4, 3297], 
              'values': [1, 1]}}}]}))

这里是输出:

{'predictions': [{'score': 0.8006305694580078}]}

关于 JSON 请求和响应格式的更多详细信息,请在此处查看:docs.aws.amazon.com/sagemaker/latest/dg/cdf-inference.html

由于我们可以向预测器询问任意一对(用户,景点)的分数,因此我们将找到问题用户的所有 31 个景点的分数,然后按分数排序:

def predict_poi(poi_position):
   prediction = predictor.predict( 
           json.dumps({'instances': [{'data': 
                        {'features': {'shape': [8934], 
                                      'keys': [poi_position, 3297], 
                                      'values': [1, 1]}}}]}))
   return prediction['predictions'][0]['score']

predictions = [(poi_position, predict_poi(poi_position)) for poi_position in range(0,31)]
predictions.sort(key=lambda x:x[1], reverse=True)

给定这些分数,我们可以找到排名最高的景点名称,排除那些已经访问过的:

user_visited_pois = 
     [id_to_poi_name[x] for x in set(recommendation_sample[1]['collect_list(poiID)'])]

for (poi_position, score) in predictions[:10]:
  recommended_poi = id_to_poi_name[int(model.stages[2].labels[poi_position])]
  if recommended_poi not in user_visited_pois:
       print(recommended_poi)

输出如下:

Test Track
 Walt Disney World Railroad
 Main Street Cinema
 Tom Sawyer Island
 Tarzan's Treehouse
 Mark Twain Riverboat
 Sleeping Beauty Castle Walkthrough
 Snow White's Scary Adventures

让我们将其与 Spark 提供的推荐进行比较:

print(recommendation_sample[1].recommendation)
recommended: [("Pinocchio's Daring Journey", 3.278768539428711), ('Tom Sawyer Island', 2.78713321685791), ('Splash Mountain', 2.114530324935913), ("Tarzan's Treehouse", 2.06896710395813), ('Fantasmic!', 1.9648514986038208), ("Snow White's Scary Adventures", 1.8940000534057617), ('Main Street Cinema', 1.6671074628829956), ('Mark Twain Riverboat', 1.314055323600769), ('Astro Orbiter', 1.3135600090026855)] ; visited: {'The Many Adventures of Winnie the Pooh', 'Rose & Crown Pub Musician', 'Golden Zephyr', "It's A Small World"}

如读者可能注意到的,有许多重叠的建议。为了更深入地分析模型的质量及其预测能力,我们可以使用第三章中讨论的评估方法,即使用回归算法预测房价,因为这个问题被设定为回归问题。

摘要

在本章中,我们研究了称为协同过滤的新类型机器学习算法。该算法用于推荐系统。我们研究了基于相似度度量来寻找与给定用户相似的用户并基于排名最高的相似用户的集体兴趣发现推荐的基于内存的方法。我们还研究了称为矩阵分解的基于模型的方法,该方法将用户和兴趣映射到潜在因子,并基于这些因子生成推荐。我们还研究了 Apache Spark 和 SageMaker 中各种协同过滤方法的实现。

在下一章中,我们将关注一个非常热门的主题:深度学习。我们将涵盖这个高级领域背后的理论以及一些现代应用。

练习

  1. 找到一个在本章中没有描述的推荐系统示例。评估哪种协同过滤方法适合那种方法。

  2. 对于电影推荐引擎,探讨数据稀疏性问题如何影响本章中列出的每个算法。

第三部分:深度学习

人工智能和机器学习领域的主要创新之一是深度学习算法的出现。我们将本书的这一部分奉献给深度学习算法,并解释读者如何使用 AWS 中的各种技术来实现它们。我们采用一种实用的方法来解释深度学习算法,而不是理论方法,并借助几个现实世界的例子来解释深度学习是如何工作的。读者将了解深度学习是什么,深度学习的应用,以及如何在 AWS 上实现深度学习系统。

本节包含以下章节:

  • 第七章,实现深度学习算法

  • 第八章,在 AWS 上使用 TensorFlow 实现深度学习

  • 第九章,使用 SageMaker 进行图像分类和检测

第七章:实现深度学习算法

深度学习是近年来在受欢迎程度上显著增长的机器学习领域。深度学习,也被称为深度结构学习或分层学习,指的是使用多层人工神经网络从数据中训练。在过去的几年里,已经能够执行某些任务,如图像识别,比人类做得更好。

我们将在本章中涵盖以下主题:

  • 理解深度学习

  • 深度学习的应用

  • 理解深度神经网络

  • 理解卷积神经网络

理解深度学习

深度学习算法在过去十年中获得了流行。自动驾驶汽车、语音识别和机器人技术等技术由于深度学习算法的改进而取得了显著进步。深度学习帮助研究人员在训练模型执行此类任务时显著减少了错误数量,并在某些任务上超越了人类。然而,最有趣的是,深度学习算法的灵感来源于人类大脑的工作方式。

让我们以图像识别为例。我们看到物体并能够根据我们过去看到这些物体的经验来识别它们。然而,让我们分解这个过程,看看具体发生了什么。首先,光线击中物体,进入我们的眼睛,并击中视网膜。视网膜是一种感觉膜,将光线转换为神经信号。然后,这个信号通过视网膜后面的各个层级传递到大脑。我们的大脑识别在我们眼前场景中存在的物体数量。根据过去的参考,我们的大脑可以识别这些物体。

我们观察物体并识别它的过程中没有单一的过程。从光线进入我们的眼睛到我们的大脑识别物体之间有各种抽象层级。当我们的大脑停止并决定它试图解释的信号中的哪些特征时,没有特定的过程。这样的特征提取过程是自动发生的。

深度学习算法也遵循一个类似的过程。深度学习将获取数据到各个抽象层级的任务分解,使得每一层解释输入数据,并为下一层抽象提供有意义的输出。例如,在图像识别任务中,输入可能是一组来自图像的像素。在第一层,像素可以被处理以找到图像中的边缘。在第二层,关于边缘的信息可以被处理以检测这些边缘之间的角。在下一层,这些角和边缘可以被用来检测图像中的对象。下一层可能预测每个对象是什么。这些抽象层不需要我们定义,但可以自动训练。

在诸如朴素贝叶斯和线性回归等算法中,我们总是使用手工制作的特征。我们已经有分析师查看传入的数据集,并根据数据定义特征集。我们将每个类别标记为分类或连续。然而,在深度学习方法中,我们只需要具有简单特征的数据集,并使用抽象层来创建额外的抽象特征。因此,在如图像识别这样的任务中,数据集是像素集,传统算法在学会如何分类之前需要帮助识别图像中的对象。我们还需要从对象中提取特征,如颜色和大小,然后才能将这些特征输入到分类模型中。然而,对于深度学习算法,我们使用图像的像素作为算法的输入,并带有标记的对象,这样深度学习模型就可以识别错误并执行自我纠正。

深度学习算法可以执行监督学习和无监督学习算法。

深度学习的应用

在本节中,我们将展示一些流行的深度学习算法应用实例。

自动驾驶汽车

自动驾驶汽车已成为汽车行业的主流,每个主要公司都在投资建设下一代自动驾驶汽车。大多数公司在其最新车型中提供一定程度的自动驾驶功能。这些算法主要是由深度学习算法驱动的。让我们看看如何使用深度学习开发自动驾驶算法。

自动驾驶算法的任务是分析道路状况并正确地对其做出反应,以便将汽车从起点开到目的地地址。这个算法的输入是他们从汽车四面安装的摄像头接收到的视频流。算法的输出是转向、油门和刹车的信号。

这个任务极其复杂,因为驾驶员在处理道路状况时需要做出瞬间的决策。驾驶员不仅需要记住到达目的地需要转弯的位置或道路上的速度限制,还必须监控道路上其他车辆和可能横穿道路的行人。

为此类任务创建基于规则的算法非常困难,因为道路上可能发生的大量排列组合。此外,生成具有良好定义特征的任何标记数据集也很困难,因为可能出现的情景数量很难在一个全面的数据集中进行标记。

深度学习算法在这种情况下非常完美,因为从视频流中自动提取特征并根据奖励函数训练模型可以帮助我们抽象自动驾驶汽车中的问题。我们可以将深度学习算法的输入设置为视频流中的像素,将奖励函数设置为在遵守所有交通规则的同时向目的地前进的进度。模拟器用于训练这些模型。这种模拟模仿了道路上的实际条件。

深度学习算法可以自动确定如何生成抽象层的层次,将视频流中的像素转换为检测边缘和对象,类似于图像识别模型。一旦检测到对象,基于汽车所做的错误和纠正,我们训练模型学习如何输出油门、刹车和方向盘指令。最初,在运行模型时,自动驾驶汽车会犯错误并撞到物体。然而,经过足够的迭代,深度学习模型可以学会如何避免这样的错误,并在预定的路线上行驶。因此,无需从视频流中手动提取特征,也不需要生成任何结构化数据集,深度学习算法可以自动学习实现某些目标。

使用深度学习算法学习玩视频游戏

深度学习的另一个流行例子是训练机器玩电脑游戏。世界各地的研究人员通过训练模型来玩 2D 平台游戏,如超级马里奥,来测试他们的深度学习算法。模型的输入是屏幕上的像素,而模型生成的输出是一系列控制器指令,这些指令控制角色并在游戏中完成目标。

我们不需要教深度学习模型这是款视频游戏,以及有一个名叫马里奥的角色需要跳上平台来完成关卡。我们只需要定义一个奖励函数,使得如果角色在没有死亡的情况下移动到下一个平台,我们就奖励深度学习模型,如果角色死亡,我们就惩罚模型。如前所述,深度学习模型会自动将问题划分为多个抽象层次。

模型学习如何自动检测屏幕上的边缘和平台。它首先通过角色进行随机移动,并迅速学习当按下不同的控制器按钮时屏幕上的像素是如何被操作的。基于角色的移动,模型学习如何使主要角色前进。类似于自动驾驶汽车,它也会自动学习触摸屏幕上的某些物体会导致惩罚,以及跳上屏幕上的某些边缘会导致玩家掉入坑中。因此,基于这些给模型提供反馈的奖励函数,模型学习如何导航关卡中的障碍物,将玩家移动到正确的方向。经过进一步训练,它还可以学习如何解决游戏中的谜题。

因此,只需向深度学习模型提供屏幕像素,我们就可以训练一台机器来玩电子游戏。你可以在周围看到这些实现的例子。不久,机器将学会如何通过基于这些机器学习模型的理性思考来解决复杂的谜题。

理解深度学习算法

在下一节中,我们将研究最流行的深度学习算法之一,称为深度神经网络。

在我们探讨深度神经网络之前,我们将研究神经网络是什么。然后,我们将学习深度神经网络算法是什么,以及为什么它们比神经网络有改进。最后,我们将研究卷积神经网络——这是在图像识别领域使用的一种神经网络变体——并展示我们如何能够从图像的像素中自动学习抽象层。

神经网络算法

神经网络算法是受生物神经网络算法启发的机器学习算法。神经网络模仿我们大脑中的神经元是如何工作的。它们有输入节点,信息被输入到网络中,以及一个输出层,它传输特定的动作或预测。神经网络定义了一个结构,其中存储了机器学习模型的信息。

以下截图显示了神经网络结构:

神经网络结构

来自数据集的输入特征被输入到神经网络输入节点中。例如,如果我们有一个包含诸如温度、云条件和风速等特征的数据库,并且我们的任务是预测某一天是否会下雨,那么这些特征就被作为输入输入到神经网络中。这些特征可以是二进制或连续值。请注意,每个输入特征对应一个输入节点。

模型的信息存储在隐藏层中的边和节点上。有各种算法可以用来训练神经网络。大多数算法迭代地在神经网络中传递输入参数,并根据隐藏节点中的权重预测输出节点的值。基于预测中的误差,这些权重被调整以改进模型。

输出节点对应于神经网络算法需要做出的预期动作或预测。我们的目标是训练隐藏节点中的权重,使得输出节点的值是准确的。

因此,神经网络松散地基于可以处理输入信号并基于该神经元的函数产生输出的生物神经元。

激活函数

现在,让我们看看神经网络算法是如何训练来计算每个隐藏节点的权重的。在我们开始训练神经网络模型之前,我们需要定义每个隐藏节点将如何处理输入信号并产生输出。用于根据输入函数计算隐藏节点输出的函数称为激活函数。激活函数定义了隐藏节点可以生成的输出范围。在其最简单形式中,激活函数可以是一个步骤函数,其中节点输出基于输入是 0 或 1。在我们天气数据集中的简单例子是这样的:如果天空多云,隐藏节点的输出可能是 1,作为预测降雨的预测,如果天空晴朗,隐藏节点的输出是 0。

这样的步骤激活函数定义为以下:

类似地,如果我们计划使用逻辑或 Sigmoid 步骤函数,输出范围从  到 

逻辑步骤函数定义为以下:

基于我们使用的学习算法,我们可以选择激活函数。大多数支持神经网络学习的机器学习库也支持使用各种激活函数。

每个节点之间的边都分配一个权重, ,使得该链接位于神经元 和神经元 之间。

反向传播

一旦我们确定了神经网络中连接的权重和激活函数,神经网络就能够根据给定的输入有效地产生输出。然而,这是一个未训练的神经网络,需要一个算法根据它在预测输出时犯的错误来修改和调整神经网络。

使用随机梯度下降进行反向传播的权重更新可以使用以下方程执行。

反向传播算法是能够实现这一结果的流行机制之一。反向传播算法定义了一种方法,通过修改连接的值,如何将输出中的错误传播到连接中。算法背后的直觉非常简单。考虑一个孩子触摸到一个非常热的平底锅并学会不要触摸放在炉子上的锅。孩子犯了一个错误,但从中学习并避免再次犯同样的错误。反向传播算法也允许神经网络犯错误。预测输出和期望输出之间的差异可以使用公式计算,例如均方误差。一旦我们量化了错误,我们就可以使用梯度下降等算法来确定如何修改连接的权重。我们还使用了梯度下降算法来处理第三章中的线性回归算法,使用回归算法预测房价。反向传播过程类似于我们学习线性回归算法系数的方式。然而,我们不是学习回归器的值,而是在神经网络中估计连接权重的值。

使用随机梯度下降进行反向传播的权重更新可以使用以下方程进行:

图片

在这个方程中,是神经网络的 学习率。这是一个可调参数,定义了神经网络能够多快适应训练数据集。权重是基于连接的先前权重计算的。权重变化值由学习率决定,即错误、先前权重和一个随机项之间的差异。

我们通过将训练数据传递到神经网络中并在每次迭代中修改连接的权重来遍历训练数据。权重被修改,以便每次迭代时错误率都会降低。尽管随机梯度下降不能达到全局最大值,但它对于训练神经网络以减少错误是有效的。当错误低于某个可接受的值或收敛到准确性改进最小的时候,我们终止迭代。

神经网络可以用于训练监督学习以及无监督学习。

深度神经网络简介

深度神经网络DNN)是神经网络的一种变体,其中我们使用一个以上的隐藏层。数据必须通过一个以上的隐藏层,网络才能被认定为深度神经网络。这增加了神经网络模型的复杂性,因为它极大地增加了网络中的连接,从而增加了学习时间。

这里展示了深度神经网络的一个表示:

图片

然而,增加额外的隐藏层也允许网络通过多个层次的模式识别传递输入数据。每个隐藏层都从前一个隐藏层接收输入。因此,它们可以识别比前一层更复杂的模式。这是因为在之前的层中,特征被聚合和重新组合。这被称为特征层次。DNN 中更深层的特征可以识别更复杂的模式。因此,DNN 更擅长处理具有复杂模式的数据库。此外,由于隐藏层自动生成这些抽象层,因此不需要领域专业知识进行特征提取。例如,在图像识别中,我们不需要对图像中物体的边缘进行标记,因为初始层可以学会识别边缘,而深层层则学会识别可能由这些边缘生成的物体。

深度学习和 DNN 是数据科学家在业界经常听到的热门词汇。对于大多数应用,如自动驾驶汽车或机器人技术,DNN 与人工智能同义。由于 GPU 架构的进步,这些 DNN 结构的生成非常适合,因此这些算法无法处理大量数据集以训练高度准确的机器学习算法。

理解卷积神经网络

在本节中,我们将探讨一种 DNNs 的变体,其中网络结构被修改以适应图像识别任务。

在本章迄今为止讨论的神经网络中,我们看到了所有输入层都是一维的。然而,图像是二维的。为了捕捉图像如何被输入神经网络进行训练,我们必须修改输入层的结构。传统算法需要人类对图像中物体的边缘进行标记。卷积神经网络CNNs)可以通过足够的训练自动检测图像中的物体,并且根据图像的标签,它们可以学习如何在不显式标记图像边缘的情况下识别图像中的物体。

CNNs 需要一个预处理阶段,其中图像必须被准备成特定的数据结构,该结构用作前馈 DNNs 的输入。预处理阶段的第一项任务是分解图片成更小的图像,这样我们就不丢失图像中的任何信息。CNN 的灵感来源于人类视觉皮层的组织。我们的神经元对特定视野中看到的视觉做出反应。这被称为局部感受野。这些局部感受野相互重叠。同样,在 CNN 中,我们以图像作为输入,并将图像的重叠子区域表示为局部感受野。

以下图表展示了如何使用局部感受野的概念,通过滑动窗口从图像中生成特征图:

图片

使用这种方法的优点是它消除了图像中对象的大小和位置问题。例如,想象图像中有一只猫。根据我们的训练示例,我们已对包含猫的图片进行了标记。使用局部感受野,我们检测到这只猫并将特征图标记为图像中有猫的图片。在新的图像中,无论猫在图像中的位置如何,我们都会找到一个包含猫的图像的特征图,因为我们使用这种滑动窗口方法创建了多个子图像。从图像中生成的这一层特征图被称为卷积层。

我们还可以通过应用各种过滤器到过程中,从同一组像素生成多个特征图。例如,我们可以应用颜色过滤器到像素,并从同一组像素生成三个特征图。作为一个数据科学家,你必须根据你想要从图像中提取的信息量以及我们生成这些网络时可以使用的处理能力来设计 CNN。

一旦生成了卷积层,我们通过使用称为池化的过程从图像中创建压缩特征图。这有助于我们以更小的特征图来表示特征图。在压缩特征图时,可以应用两种流行的池化过程。在最大池化方法中,通过仅从每个网格中选择最大值来降低特征图的维度。

以下截图展示了最大池化如何从每个特征图中取最大值,并将特征图的维度从 4x4 矩阵降低到 2x2:

图片

另一种池化类型称为平均池化,这是在池化数据时选择网格中值的平均值。以下图表展示了平均池化是如何工作的:

图片

最大池化通常比平均池化更受欢迎,因为它在降低特征图维度时起到噪声抑制的作用,并移除了非主导特征。与卷积层类似,池化层也可以使用重叠窗口来创建更小的特征图。请注意,这些决策可以根据你想要从图像中捕获的细节程度来做出。

CNN 的另一个组成部分是卷积层。当我们设计 CNN 时,一组图像可能会决定我们从图像中提取哪些特征图。然而,根据应用需求,我们可能希望从图像中提取不同的特征。例如,如果我们的图像识别软件正在检测由地震仪(一种检测地震的设备)生成的图表,我们的特征图将包含黑白图表,其中我们的算法需要对时间序列中的边缘进行敏感检测。在这种情况下,我们可以设计一个卷积核,将特征图中的一定模式转换为另一个可以注释这些模式的特征图。同样,如果你正在处理带有对象的彩色图像,为每种颜色创建三个特征图,检测边缘,然后合并特征图,这很有帮助。因此,卷积层帮助设计此类神经网络的科学家根据特定应用对其进行调整。我们不会解释如何设置卷积层的细节,因为大多数库都允许你使用预设计的 CNN 应用于你的应用。

因此,通过使用局部感受野、卷积层和池化操作,我们构建以下结构将图像展平为 DNN 的输入:

图片

通过使用局部感受野方法,第一层卷积将图像转换为特征图。然后,我们通过池化数据来减少特征图的维度,将特征图的维度从 20x20 减少到 10x10。在下一阶段,我们根据可能选择的核将池化后的特征图转换为更多的特征图。这些核可能将检测直线或交叉的特征图转换为其他特征图。然后,我们将卷积层的输出池化到 4x4 特征图。此时,原始图像被转换为 DNN 任务特定信息。这些特征图也代表了图像的空间成分。然后,DNN 根据这些数据训练,并学习根据特征图可能表示的内容来预测输出。

摘要

在本章中,我们解释了深度学习的含义以及它在现实世界中的应用。我们还研究了应用案例,例如自动驾驶汽车和视频游戏机器人,以及它们如何使用深度学习自动学习执行任务。我们解释了神经网络是什么,以及 DNN 是如何改进它们的。我们还研究了 DNN 的一个变体,称为 CNN,并介绍了 CNN 的各个组成部分。

本章的目标是向您提供有关深度学习算法的信息,以便您了解它们如何在现实世界中应用。尽管我们没有深入探讨深度学习的数学原理,也没有提供关于激活函数等概念的所有细节,但我们希望您在深度学习领域获得了实际的知识。对于那些好奇心旺盛的人,这个领域正在进行着大量的研究,我们敦促您更多地了解您感兴趣的算法。

在下一章中,我们将探讨如何使用流行的技术,如 TensorFlow 和 MXNet,来实现深度学习。这些知识将帮助您实现一系列深度学习算法。

练习

  1. 如果你拥有一部智能手机,你的手机上就有很多应用使用了深度学习。探索你的手机上哪些应用使用了本章中列出的算法之一,并研究如何设计这样的算法。

  2. 列出 CNN 的各种组件,并设计一个能够检测人脸特征的 CNN。

第八章:在 AWS 上使用 TensorFlow 实现深度学习

TensorFlow 是一个非常流行的深度学习框架,可以用来训练深度神经网络,例如前一章中描述的那些。

在本章中,我们将涵盖以下主题:

  • 关于 TensorFlow

  • TensorFlow 作为通用机器学习库

  • 通过 SageMaker 训练和部署 TensorFlow 模型

  • 使用 TensorFlow 创建自定义神经网络

关于 TensorFlow

TensorFlow 是一个深度学习库,由 Google 于 2015 年首次发布。最初,它包含一个核心库,允许用户以符号形式处理张量(多维数组),从而实现高性能的低级神经网络设计和训练。如今,它是一个完整的深度学习库,允许数据科学家使用高级原语构建用于复杂问题的模型,例如图像识别。您还可以使用 TensorFlow 解决标准机器学习问题,例如我们在过去章节中考虑过的问题。TensorFlow 具有与我们在 scikit-learn、Apache Spark 和 SageMaker 中使用的类似抽象。例如,它允许用户使用高级抽象,如估计器、预测器和评估器,来创建分类模型或回归模型。

TensorFlow 作为通用机器学习库

在本节中,我们将展示如何使用 TensorFlow 为 第三章 中的房屋估值问题创建回归模型,即 使用回归算法预测房屋价值。为了开始,我们首先启动一个 SageMaker 笔记本,并选择 TensorFlow 内核 (conda_tensorflow_p36),它包含本节所需的全部 TensorFlow 依赖项:

图片

现在,让我们考虑 第三章 中的估值问题,使用回归算法预测房屋价值。回想一下,我们有一组指标(房屋年龄、距离最近中心等)来估计房屋的中值(在 medv 列中表达,这是我们的目标特征),如下截图所示:

图片

在 第三章 的 使用回归算法预测房屋价值 中,我们确定了 11 个学习特征用于预测目标特征 (medv):

training_features = ['crim', 'zn', 'indus', 'chas', 'nox',             'rm', 'age', 'dis', 'tax', 'ptratio', 'lstat']

label = 'medv'

基于这些信息,我们定义了一个 TensorFlow 线性回归器,它能够使用预构建的神经网络解决我们的回归问题:

tf_regressor = tf.estimator.LinearRegressor(
    feature_columns=[tf.feature_column.numeric_column('inputs', 
                                  shape=(11,))])

对于回归器,我们决定创建一个单特征输入,它将其他特征组装成一个代表输入层的数字向量。也有可能为每个训练特征创建一个命名特征(就像我们在第三章中做的,使用回归算法预测房屋价值),但我们将只有一个向量特征来简化本节末尾讨论的预测服务。

要构建一个回归器,我们需要传递 TensorFlow 特征列,这些列可以是几种不同类型之一。tf.feature_column 包提供根据模型使用的编码构建不同类型列的函数(例如,分类、分桶等)。特征列通知模型提交为输入的数据的预期格式。在我们的情况下,我们只需告诉模型期望长度为 11 的向量行。

为了构建要传递给模型的实际数据,我们需要创建一个矩阵。pandas 库有一个方便的方法,as_matrix(),因此我们将切片训练特征并构建一个矩阵:

training_df[training_features].as_matrix()

同样,我们将创建特征向量:

training_df[label].as_matrix()

一旦我们有了这两样东西,我们就可以开始将数据插入到模型中。TensorFlow 期望通过定义一个知道如何将数据源到张量(TensorFlow 的多维数组的基本构建块)的函数来提供数据。

以下是将数据插入的代码块:

training_input_fn = tf.estimator.inputs.numpy_input_fn(
    x={'inputs': training_df[training_features].as_matrix()},
    y=training_df[label].as_matrix(),
    shuffle=False,
    batch_size=1,
    num_epochs=100,
    queue_capacity=1000,
    num_threads=1)

tf.estimator.inputs.numpy_input_fn 工具能够通过提供训练矩阵和目标特征向量来构建这样一个函数。它还将创建数据分区,以便在多个 epoch 中运行网络。它还允许用户选择批次的尺寸(回想一下第三章中提到的迷你批次方法,使用回归算法预测房屋价值,用于随机梯度下降)。本质上,底层回归器的神经网络依赖于 training_input_fn 函数在每个算法阶段创建输入张量。

同样,我们创建一个类似的函数来提供测试数据,为模型评估做准备:

test_input_fn = tf.estimator.inputs.numpy_input_fn(
    x={'inputs': test_df[training_features].as_matrix()},
    y=test_df[label].as_matrix(),
    shuffle=False,
    batch_size=1)

要训练模型,我们调用常规的 fit() 方法,提供我们创建的数据来源函数:

tf_regressor.train(input_fn=training_input_fn, steps=50000)

steps 参数是我们可以对总步数施加的限制。在这里,一个步骤是指对一个批次进行一次梯度下降更新。因此,每个 epoch 运行一定数量的步骤。

一旦完成训练,TensorFlow 将在 final epoch 输出损失度量:

INFO:tensorflow:Loss for final step: 1.1741621.

我们可以通过运行测试数据集(通过提供测试数据集来源函数)来评估我们模型的准确性:

tf_regressor.evaluate(input_fn=test_input_fn)

上述代码生成以下输出:

{'average_loss': 37.858795, 'label/mean': 22.91492, 'loss': 37.858795, 'prediction/mean': 21.380392, 'global_step': 26600}

平均损失取决于目标特征的单位,因此让我们看看构建一个类似于我们在第三章中创建的散点图,使用回归算法预测房价,以比较实际与预测的房价。为此,我们首先需要获得predictions

我们只需调用predict()函数来获取predictions,再次提供测试数据集来源函数:

predictions = tf_regressor.predict(input_fn=test_input_fn)

predictions返回了一个实际上是单值向量的 Python 生成器,因此我们可以通过构造列表来获取predictions

predicted_values = [prediction['predictions'][0] for prediction in predictions]

因此,我们可以检查predicted_values

predicted_values[:5]

之前的代码生成了以下输出:

[22.076485, 23.075985, 17.803957, 20.629128, 28.749748]

我们可以将预测值作为列插入到原始的pandas测试数据框中:

test_df['prediction'] = predicted_values

这允许我们使用 pandas 绘图方法来创建图表:

test_df.plot(kind='scatter', x=label, y='prediction')

我们可以在以下屏幕截图中看到结果:

注意到存在明显的相关性。为了提高性能,我们可能需要调整我们的回归模型、批大小、步骤、周期等。

通过 SageMaker 训练和部署 TensorFlow 模型

与在笔记本实例中训练模型不同,我们使用 SageMaker 基础设施来训练模型。在之前的章节中,我们使用了内置的估计器,如 BlazingText、XGBoost 和因子分解机FMs)。在本节中,我们将展示如何构建自己的 TensorFlow 模型并通过 SageMaker 训练它们,就像我们处理这些预构建模型一样。为此,我们只需教会 SageMaker 如何构建我们的 TensorFlow 模型,并遵守一些关于数据格式、位置和结构的约定。通过一个 Python 脚本,我们指定所有这些。

SageMaker 将依赖于这个 Python 脚本来在 SageMaker 训练实例中执行训练:

import sagemaker
from sagemaker import get_execution_role
import json
import boto3
from sagemaker.tensorflow import TensorFlow

sess = sagemaker.Session()
role = get_execution_role()
tf_estimator = TensorFlow(entry_point='tf_train.py', role=role,
                          train_instance_count=1,      train_instance_type='ml.m5.large',
                          framework_version='1.12', py_version='py3')
tf_estimator.fit('s3://mastering-ml-aws/chapter8/train-data/')

上述代码块的前几行是启动 SageMaker 所需的常规导入和会话创建。下一个重要的事情是创建一个 TensorFlow 估计器。注意我们如何向构造函数提供 Python 脚本、TensorFlow 版本和 Python 版本,以及实例数量和类型的常规参数。

当调用tf_estimator.fit(training_data_s3_path)函数时,SageMaker 将执行以下任务:

  1. 启动一个 EC2 实例(服务器)。

  2. 将 S3 数据下载到本地目录。

  3. 调用tf_train.py Python 脚本来训练模型。Python 脚本预计将模型存储在 EC2 实例的某个本地目录中。

  4. 将存储的模型打包成.tar.gz文件并上传到 S3。此外,它还将创建一个 Amazon 容器和 SageMaker 模型标识符。

因此,训练是在 SageMaker 管理的服务器上进行的,但它产生的模型是 SageMaker 兼容的模型,可以用于提供预测或运行批量转换作业,就像我们在前几章中所做的那样。

让我们看看 tf_train.py Python 脚本,它负责模型训练和保存模型。

这个 Python 脚本必须从 SageMaker 容器接收一些信息。特别是,它必须接收以下信息:

  • SageMaker 下载数据的本地目录(从 S3)

  • Python 脚本需要存储训练好的模型的位置

  • 模型需要的其他超参数(我们目前不会深入探讨,而只是使用固定值,但我们将展示在 第十四章,在 Spark 和 SageMaker 中优化模型,如何使用这些参数进行超参数调整)

看看下面的代码:

import pandas as pd
import argparse
import os
import tensorflow as tf

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--epochs', type=int, default=100)
    parser.add_argument('--batch_size', type=int, default=1)
    parser.add_argument('--steps', type=int, default=12000)
    parser.add_argument('--model_dir', type=str)
    parser.add_argument('--local_model_dir', type=str,                                 
default=os.environ.get('SM_MODEL_DIR'))
    parser.add_argument('--train', type=str,       default=os.environ.get('SM_CHANNEL_TRAINING'))

    args, _ = parser.parse_known_args()
    housing_df = pd.read_csv(args.train + '/train.csv')
    training_features = ['crim', 'zn', 'indus', 'chas', 'nox', 
                         'rm', 'age', 'dis', 'tax', 'ptratio', 'lstat']
    label = 'medv'
    tf_regressor = tf.estimator.LinearRegressor(
        feature_columns=[tf.feature_column.numeric_column('inputs', 
                                  shape=(11,))])
    training_input_fn = tf.estimator.inputs.numpy_input_fn(
        x={'inputs': housing_df[training_features].as_matrix()},
        y=housing_df[label].as_matrix(),
        shuffle=False,
        batch_size=args.batch_size,
        num_epochs=args.epochs,
        queue_capacity=1000,
        num_threads=1)
    tf_regressor.train(input_fn=training_input_fn, steps=args.steps)

    def serving_input_fn():
        feature_spec = tf.placeholder(tf.float32, shape=[1, 11])
        return tf.estimator.export.build_parsing_serving_input_receiver_fn(
                {'input': feature_spec})()

    tf_regressor.export_savedmodel(export_dir_base=args.local_model_dir + '/export/Servo',
                                   serving_input_receiver_fn=serving_input_fn)

脚本的前一部分只是设置一个参数解析器。由于 SageMaker 将此脚本作为黑盒调用,它需要能够将此类参数注入脚本中。有了这些参数,它可以训练 TensorFlow 模型。你可能注意到,训练与我们在前一部分所做的是一样的。唯一的新部分是保存模型和定义一种新的函数(serving_input_fn)。这个函数与我们在训练和测试中使用的函数有类似的目的,但它在服务时间(即每次向服务发出预测请求时)将被使用。它负责定义从输入张量占位符到模型期望的特征的必要转换。tf.estimator.export.build_parsing_serving_input_receiver_fn 工具可以方便地构建用于此类目的的函数。它构建一个期望 tf.Example(特征的一个 protobuf 序列化字典)被输入到字符串占位符中的函数,以便它可以解析这样的示例到特征张量。在我们的例子中,我们只有一个向量作为输入,所以转换是直接的。我们脚本中的最后一行将模型保存到 SageMaker 通过 local_model_dir 参数请求的位置。为了使反序列化和解包工作,惯例是将模型保存到 /export/Servo 子目录中。

一旦我们运行 fit() 命令,我们就可以像往常一样部署模型:

predictor = tf_estimator.deploy(instance_type='ml.m5.large', initial_instance_count=1)

对于这个例子,我们使用了一个非 GPU 实例类型,但这些在很大程度上被推荐用于严肃的服务和训练。我们将在 第十五章,调整机器学习集群 中深入探讨这一点。

deploy() 命令将启动一个容器,该容器能够为我们构建的模型提供服务。然而,构造要发送给此类服务的有效负载并不像前一章中的示例那样简单,因为我们需要构造 tf.Example

在预测时,我们希望根据特定的特征向量获得价格。假设我们想要找到以下特征的价格:

features_vector = [0.00632, 18.0, 2.31, 0.0, 0.538, 6.575, 65.2, 4.09, 296.0, 15.3, 4.98]

第一步是构建一个 tf.train.Example 实例,在我们的情况下,它由一个名为 inputs 的单个特征组成,该特征包含 features_vector 的浮点值:

model_input = tf.train.Example(features=tf.train.Features(

    feature={"inputs": tf.train.Feature(float_list=tf.train.FloatList(value=features_vector))}))

下一步是使用 SerializeToStringmodel_input protobuf 消息序列化:

model_input = model_input.SerializeToString()

由于这实际上是一个字节字符串,我们需要进一步编码 model_input,以便它可以作为字符串发送到负载中,而不包含特殊字符。我们使用 base64 编码来完成这项工作:

encoded = base64.b64encode(model_input).decode()

最后,我们通过组装一个 JSON 请求来调用我们的 predictor 服务:

predictor.predict('{"inputs":[{"b64":"%s"}]}' % encoded)

注意,在发送通过创建以 b64 为键的字典来编码的 base64 protobuf 示例时,有一个特殊的约定。从 JSON 解码的输出是一个包含以下预测的字典:

{'outputs': [[24.7537]]}

inputsoutputs 负载 JSON 键是 SageMaker 的合约的一部分,不应与我们的单个特征 inputs 的名称混淆,inputs 可以是任意字符串。

使用 TensorFlow 创建自定义神经网络

在上一节“通过 SageMaker 训练和部署 TensorFlow 模型”中,我们使用了 TensorFlow 的高级库,使用 LinearRegressor 构建了一个回归模型。在本节中,我们将展示如何使用 TensorFlow 的 Keras 库构建一个实际的神经网络。Keras 通过隐藏核心(低级)TensorFlow 库背后的某些复杂性来简化神经网络的设计。

在本章中,我们将使用无处不在的 MNIST 数据集,它包含一系列手写数字的图像以及真实的标签(0 到 1 之间的值)。MNIST 数据集可以从 www.kaggle.com/c/digit-recognizer/data 下载。

数据集以 CSV 格式提供,包含 784 列,对应于 28 x 28 图像中的每个像素。每列的值代表像素在 0 到 255 的灰度强度。它还有一个额外的列用于标签,其值介于 0 到 9 之间,对应于实际的数字。

让我们下载数据集,并使用 pandasscikit-learn 进行我们通常的测试和训练分割:

import pandas as pd
from sklearn.model_selection import train_test_split

mnist_df = pd.read_csv('mnist/train.csv')
train_df, test_df = train_test_split(mnist_df, shuffle=False) 

我们可以通过 train.head() 检查数据集:

如我们所见,列被标记为 pixelX,其中 x 是介于 0783 之间的数字。让我们将这些列的名称定义为不同的变量:

pixel_columns = ['pixel' + str(i) for i in range(0, 784)]
label_column = 'label'

数据集中的每一行都成为了一个训练示例,因此代表了我们的网络输入层。在网络的另一端,我们将有 10 个节点,每个节点代表给定每个输入向量的每个数字的概率。在我们的例子中,我们将只使用一个中间层。

以下图展示了我们的网络结构:

在 Keras 中定义这样的网络非常简单:

import tensorflow as tf
from tensorflow import keras

model = keras.Sequential([
    keras.layers.InputLayer(input_shape=(784,), batch_size=5),
    keras.layers.Dense(256, activation=tf.nn.relu),
    keras.layers.Dense(10, activation=tf.nn.softmax)
])

注意定义这样一个模型是多么容易。它由三个层组成:

  • 一个输入层,其中每个向量的大小为 784,并且每个梯度下降更新将提供五个示例的迷你批次

  • 一个中间密集层(意味着每个节点将连接到下一层的每个节点)在每个节点上具有Rectified Linear UnitReLU)激活函数

  • 一个大小为 10 的输出层,使用 softmax 激活函数(因为我们想要数字的概率分布)

除了通过一系列层定义网络之外,TensorFlow 还需要编译模型。这基本上意味着提供要使用的优化方法、loss函数和所需的指标:

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

下一个阶段将是将模型与我们的数据拟合。为了将数据集输入到 TensorFlow 中,我们需要创建numpy矩阵,其中每一行是一个训练实例,每一列代表输入层中的一个节点。方便的是,pandas方法dataframe.as_matrix()正好能完成这个任务,因此我们将数据集切片以包含训练列并构建这样一个矩阵。此外,我们将矩阵归一化,以便每个灰度值介于 0 和 1 之间:

import numpy as np

vectorized_normalization_fn = np.vectorize(lambda x: x / 255.0)
normalized_matrix = 
      vectorized_normalization_fn(train_df
[pixel_columns].as_matrix())

同样,我们通过将pandas序列转换为数字向量来获得labels向量:

labels = train_df[label_column].as_matrix()

现在我们已经有了训练矩阵和标签,我们准备拟合我们的模型。我们通过简单地调用fit()并提供标记的训练数据来完成此操作:

model.fit(normalized_matrix, labels, epochs=3)

训练将以训练数据集中的损失和准确度指标结束:

Epoch 3/3 31500/31500 [==============================] - 16s 511us/sample - loss: 0.0703 - acc: 0.9775

为了确定我们的模型是否过拟合(即,它只是学会了如何分类我们的训练数据集中的图像,但无法推广到新的图像),我们需要在测试数据集中测试我们的模型。为此,我们将对测试数据集执行我们在训练数据集上所做的相同转换。

我们模型的evaluate()函数将提供准确度评估指标:

normalized_test_matrix = vectorized_normalization_fn(test_df[pixel_columns].as_matrix())
test_labels = test_df[label_column].as_matrix()
_, test_acc = model.evaluate(normalized_test_matrix, test_labels)

print('Accuracy on test dataset:', test_acc)

上述代码生成以下输出:

Accuracy on test dataset: 0.97

注意,我们的简单模型实际上相当准确。让我们检查测试数据集中的几个图像,看看预测是否与实际数字匹配。为此,我们将绘制图像,并通过以下步骤将它们与预测数字进行比较:

  1. 首先,我们将定义一个函数,该函数可以获得测试数据集矩阵中特定行(index)的预测标签:
def predict_digit(index):
    predictions = model.predict(normalized_test_matrix[index:index + 1])
    return np.argmax(predictions, axis=1)[0]

model.predict()将根据特征矩阵获得预测。在这种情况下,我们只需要一行,所以我们切片我们的矩阵为单行以获得特定索引的预测。预测将是一个包含 10 个组件的向量,每个组件代表每个数字的强度。我们使用argmax函数找到强度最大的数字(即,找到最可能的数字)。

  1. 接下来,我们定义一个函数,show_image(),它将根据索引绘制图像:
from IPython.display import display
from PIL import Image

def show_image(index):
    print("predicted digit: %d" % predict_digit(index))
    print("digit image:")
    vectorized_denormalization_fn = np.vectorize(lambda x: np.uint8(x * 255.0))
    img_matrix = normalized_test_matrix[index].reshape(28, 28)
    img_matrix = vectorized_denormalization_fn(img_matrix)
    img = Image.fromarray(img_matrix, mode='L')
    display(img)

我们依赖PIL库来进行绘图。为了绘制图像,我们需要将我们的值反归一化到 0-255 的范围,并将 784 个像素重塑为 28x28 的图像。

让我们通过以下截图中的几个实例来考察一下:

图片

第二个例子:

图片

以下图像无法被模型正确识别:

图片

第二个例子:

图片

你可能同意,即使是人类也可能犯类似的错误。

那么,我们如何在我们的model之上构建一个服务呢?

做这件事的一个简单方法是从我们的model创建一个estimator实例:

estimator = tf.keras.estimator.model_to_estimator(model)

记得我们在上一节中使用的LinearRegressor也是一个estimator实例,因此从这个estimator实例开始,训练、序列化和部署模型的过程是相同的。

摘要

在本章中,我们介绍了创建两个不同的 TensorFlow 模型的过程:一个使用高级 estimator 库,另一个使用 Keras 构建自定义神经网络。在这个过程中,我们还展示了 SageMaker 如何无缝处理 TensorFlow 模型的训练和部署。

在下一章,使用 SageMaker 进行图像分类和检测中,我们将展示如何在 AWS 上使用深度学习直接检测和识别图像。

练习

本章的问题如下:

  • 一个 epoch、batch 和 step 之间的区别是什么?

  • 你会如何设计一个网络,能够为第六章中考虑的主题公园数据集提供推荐,该章节名为分析游客模式以提供推荐

  • 你会如何构建一个能够将第五章中提到的广告分类为点击/未点击的网络,该章节名为使用聚类算法进行客户细分

第九章:使用 SageMaker 进行图像分类和检测

我们研究了一种称为卷积神经网络CNN)的深度学习算法,它能够对图像进行分类。然而,在实际中实现这样的算法极其复杂,需要大量的专业知识。Amazon SageMaker 提供了功能,允许您使用深度学习能力训练机器学习模型,例如图像分类算法。

本章我们将涵盖以下主题:

  • 介绍 Amazon SageMaker 用于图像分类

  • 使用 Amazon SageMaker 训练深度学习模型

  • 使用 Amazon SageMaker 对图像进行分类

介绍 Amazon SageMaker 用于图像分类

由于 Tensorflow 和 SageMaker 等服务,数据科学领域已经发生了革命。过去,复杂的算法,如深度学习,只有大公司和研究实验室才能访问。然而,得益于 SageMaker 等服务,任何能够编写代码调用这些服务的人都可以训练和使用复杂的机器学习算法。这使得对机器学习有实际了解的青少年能够创建能够执行复杂机器学习任务的应用程序。通过访问 SageMaker 市场中的最先进机器学习模型,您将能够以与世界顶级科学家相同的能力执行机器学习任务。

Amazon SageMaker 提供了大量的算法,数据科学家可以使用这些算法来训练他们的机器学习模型,并且它还提供了工具来对一批测试数据进行预测或创建一个端点以将模型作为服务使用。当我们处理较小的测试数据集时,我们可以使用 Python 机器学习库,如 scikit-learn。然而,当我们处理较大的数据集时,我们必须依赖框架,如 Apache Spark,并使用库,如 MLLib

亚马逊在 SageMaker 中提供了一系列机器学习库,我们可以使用来自不同供应商的预调优模型来训练我们的机器学习模型。因此,当您处理问题时,您可以在 Amazon SageMaker 市场中搜索已可用的算法。如果有来自不同供应商的多个算法和模型可供选择,您可以根据它们的定价模型和准确性来选择算法。

SageMaker 市场可以用来选择亚马逊以外的供应商提供的模型。因此,如果您需要一个针对遗传工程领域功能进行优化的专用算法,或者像建筑工人检测器这样的图像分类算法的专用版本,您可以选择一个预训练模型并直接获取预测。

亚马逊 SageMaker 还提供调整市场算法参数的作业,以便它们可以适应您的集群大小和应用。这类作业被称为超参数调整作业。您可以为参数提供各种值来检查一个算法。然后,亚马逊 SageMaker 可以自动训练以选择最适合您应用的调整参数。您也可以手动设置这些参数的值。

在本章中,我们将通过一个图像分类器的示例来展示如何使用亚马逊 SageMaker。该算法从标记的图像集中学习,然后通过为测试图像中的每个对象分配存在概率来检测测试数据集中的对象。对于这次测试,我们使用了一个公开的数据集,称为 Caltech265 (www.vision.caltech.edu/Image_Datasets/Caltech256/)。该数据集包含 30,608 张图像。数据集用 256 个对象进行了标记。

请将以下数据集文件下载到您的 AWS S3 桶中:data.mxnet.io/data/caltech-256/caltech-256-60-train.recdata.mxnet.io/data/caltech-256/caltech-256-60-val.rec

为了我们的实验目的,我们将训练数据文件存储在 AWS 桶中的 image-classification-full-training/train 文件夹下。该文件包含 15,420 个图像文件,这些图像已调整为 224 x 224 像素。

使用亚马逊 SageMaker 训练深度学习模型

在本节中,我们将展示如何使用此数据集训练图像分类模型。同样,将 validation 文件下载到 image-classification-full-training/validation 文件夹下的 AWS 桶中。

在 第七章,实现 AWS 上的深度学习算法中,我们研究了一种称为 CNN 的算法,该算法使用深度神经网络构建对象检测模型。该模型在标记图像上训练,并学习如何使用各种深度神经网络层识别图像中的对象。从头开始构建这个深度学习模型是困难的。亚马逊 SageMaker 提供了一种简单的方法,使用您自己的数据集来训练图像分类算法,然后将该模型部署到检测图像中的对象。我们将提供一个使用 caltech256 数据集训练模型的代码示例,然后在下一节,使用亚马逊 SageMaker 分类图像中对其进行测试。

与第八章,在 AWS 上使用 TensorFlow 实现深度学习类似,您将需要启动一个新的 SageMaker 实例并使用 Jupyter Notebooks 来开始测试。亚马逊 SageMaker 已经为您提供了大量的示例代码以供开始。要访问这些示例,请参考 SageMaker 示例标签页:

图片

本章中使用的代码也是对 SageMaker 提供的图像分类示例的修改。您可以使用conda_python3内核创建一个新的笔记本:

在第五章“使用聚类算法进行客户细分”和第六章“分析访客模式以提供建议”等章节中,我们使用了亚马逊提供的 Amazon SageMaker 高级sagemaker Python 库。在这里,我们选择展示如何从boto3库中使用 SageMaker 通用客户端。这个库提供了一个声明式接口,更接近 SageMaker 背后的 API。希望读者您能通过本章的示例理解 API 的低级调用。

我们在此提供了一个代码示例,说明如何使用 boto3 客户端通过 Amazon Sagemaker 创建图像分类模型。

  1. 初始化我们想在 SageMaker 中使用的角色和image-classification图像,然后指定我们桶的名称:
import boto3
import re
from sagemaker import get_execution_role
from sagemaker.amazon.amazon_estimator import get_image_uri

role = get_execution_role()

bucket='mastering-ml-aws'

training_image = get_image_uri(boto3.Session().region_name, 'image-classification')

被称为image-classification的训练图像是图像分类算法的 Docker 镜像。Amazon SageMaker 提供了大量此类镜像,您可以使用它们来训练您的分类器。每个镜像都有自己的调整参数,您也可以在训练该算法时提供。

  1. 我们将在以下代码块中声明这些调整参数:
# Define Parameters

num_layers = "18" 
image_shape = "3,224,224"
num_training_samples = "15420"
num_classes = "257"
mini_batch_size =  "64"
epochs = "2"
learning_rate = "0.01"

图像分类算法使用深度神经网络;这些参数对我们来说很熟悉,因为我们已经在第七章,“实现深度学习算法”中研究过。

我们定义深度学习算法将使用的隐藏层数量。我们还需要指定通道数和每个图像的大小。我们定义训练图像的数量和类(对象类型)的数量。迭代次数定义了我们将在训练数据集上迭代的次数。深度学习分类器的准确率随着我们对数据集迭代的次数增加而提高。学习率定义了深度学习算法允许对权重进行的更改次数。

我们建议您使用不同的参数运行此算法,以观察对评估和训练时间的影响。

  1. 一旦我们定义了参数,我们就会初始化用于 S3 的 boto3 客户端,我们在那里存储了我们的训练和验证文件。
import time
import boto3
from time import gmtime, strftime

# caltech-256
s3_train_key = "image-classification-full-training/train"
s3_validation_key = "image-classification-full-training/validation"
s3_train = 's3://{}/{}/'.format(bucket, s3_train_key)
s3_validation = 's3://{}/{}/'.format(bucket, s3_validation_key)

s3 = boto3.client('s3')
  1. 我们构建了一个 JSON,其中包含了训练我们的图像分类器所需的全部参数:
# create unique job name 
job_name_prefix = 'example-imageclassification'
timestamp = time.strftime('-%Y-%m-%d-%H-%M-%S', time.gmtime())
job_name = job_name_prefix + timestamp
training_params = \
{
    # specify the training docker image
    "AlgorithmSpecification": {
        "TrainingImage": training_image,
        "TrainingInputMode": "File"
    },
    "RoleArn": role,
    "OutputDataConfig": {
        "S3OutputPath": 's3://{}/{}/output'.format(bucket, job_name_prefix)
    },
    "ResourceConfig": {
        "InstanceCount": 1,
        "InstanceType": "ml.p2.xlarge",
        "VolumeSizeInGB": 50
    },
    "TrainingJobName": job_name,
    "HyperParameters": {
        "image_shape": image_shape,
        "num_layers": str(num_layers),
        "num_training_samples": str(num_training_samples),
        "num_classes": str(num_classes),
        "mini_batch_size": str(mini_batch_size),
        "epochs": str(epochs),
        "learning_rate": str(learning_rate)
    },
    "StoppingCondition": {
        "MaxRuntimeInSeconds": 360000
    },
    "InputDataConfig": [
        {
            "ChannelName": "train",
            "DataSource": {
                "S3DataSource": {
                    "S3DataType": "S3Prefix",
                    "S3Uri": s3_train,
                    "S3DataDistributionType": "FullyReplicated"
                }
            },
            "ContentType": "application/x-recordio",
            "CompressionType": "None"
        },
        {
            "ChannelName": "validation",
            "DataSource": {
                "S3DataSource": {
                    "S3DataType": "S3Prefix",
                    "S3Uri": s3_validation,
                    "S3DataDistributionType": "FullyReplicated"
                }
            },
            "ContentType": "application/x-recordio",
            "CompressionType": "None"
        }
    ]
}

在这个 JSON 中有很多东西要学习。我们在AlgorithmSpecification部分定义了我们想要用于训练的算法。OutputDataConfig定义了模型将存储的位置。ResourceConfig定义了用于训练作业的实例类型。请注意,在 AWS 上,基于 GPU 的实例可以更快地运行图像分类等任务。所有算法参数都在HyperParameters部分定义。我们在 JSON 的InputDataConfig部分下设置了训练数据集和验证数据集。此 JSON 配置将在下一个代码块中用于设置训练作业的参数。

以下代码块启动了一个sagemaker训练作业:

# create the Amazon SageMaker training job

sagemaker = boto3.client(service_name='sagemaker')
sagemaker.create_training_job(**training_params)

在您开始训练作业后,您可以在 Amazon SageMaker 仪表板上观察训练作业的进度:

此仪表板还显示了您模型的统计数据,包括 CPU 和 GPU 的使用情况,以及内存利用率。您还可以在此仪表板上观察我们正在训练的模型的训练和验证准确率。

由于我们只使用了两个 epoch,因此此模型的训练准确率较低:

您已成功使用 SageMaker 训练了一个图像分类模型。SageMaker 非常易于使用,您只需选择算法镜像,选择训练数据集,并设置算法的参数。SageMaker 将根据这些信息自动训练模型,并将模型存储在您的 S3 桶中。

使用 Amazon SageMaker 对图像进行分类

您已训练的 SageMaker 模型现在可用于预测图像中的对象。正如我们在本章开头所讨论的,SageMaker 提供了一个市场,您可以直接使用许多模型来执行您的任务。

  1. 由于我们训练了自己的机器学习模型,我们必须创建一个 SageMaker 模型,该模型可用于预测。以下代码展示了如何在 Amazon Sagemaker 中生成一个可用的模型。
import boto3
from time import gmtime, strftime

sage = boto3.Session().client(service_name='sagemaker') 

model_name="example-full-image-classification-model"

info = sage.describe_training_job(TrainingJobName=job_name)
model_data = info['ModelArtifacts']['S3ModelArtifacts']

hosting_image = get_image_uri(boto3.Session().region_name, 'image-classification')

primary_container = {
    'Image': hosting_image,
    'ModelDataUrl': model_data,
}

create_model_response = sage.create_model(
    ModelName = model_name,
    ExecutionRoleArn = role,
    PrimaryContainer = primary_container)

要在 SageMaker 中创建一个模型,我们必须指定在之前步骤中生成的模型名称。在我们的例子中,模型名称被设置为example-full-image-classification-model。我们还需要指定模型将存储的容器。由于我们使用了图像分类 Docker 镜像来生成此模型,我们必须将其指定为一个参数。此镜像将帮助 SageMaker 读取训练好的模型并定义如何用于预测。

create_model函数将创建模型并返回模型的Amazon 资源名称ARN)。这可以用于调用模型以生成预测。

对于测试,我们将从Caltech256数据集下载原始图像并将它们存储在一个S3桶中。我们将使用这些图像来生成预测:

!wget -r -np -nH --cut-dirs=2 -P /tmp/ -R "index.html*" http://www.vision.caltech.edu/Image_Datasets/Caltech256/images/008.bathtub/

batch_input = 's3://{}/image-classification-full-training/test/'.format(bucket)
test_images = '/tmp/images/008.bathtub'

!aws s3 cp $test_images $batch_input --recursive --quiet

一旦我们下载了所有图像并将它们存储在 S3 桶中,我们就会指定运行批量预测任务的参数。这个任务将预测图像中 256 个对象中每个对象存在的概率:

timestamp = time.strftime('-%Y-%m-%d-%H-%M-%S', time.gmtime())
batch_job_name = "image-classification-model" + timestamp
request = \
{
    "TransformJobName": batch_job_name,
    "ModelName": model_name,
    "MaxConcurrentTransforms": 16,
    "MaxPayloadInMB": 6,
    "BatchStrategy": "SingleRecord",
    "TransformOutput": {
        "S3OutputPath": 's3://{}/{}/output'.format(bucket, batch_job_name)
    },
    "TransformInput": {
        "DataSource": {
            "S3DataSource": {
                "S3DataType": "S3Prefix",
                "S3Uri": batch_input
            }
        },
        "ContentType": "application/x-image",
        "SplitType": "None",
        "CompressionType": "None"
    },
    "TransformResources": {
            "InstanceType": "ml.p2.xlarge",
            "InstanceCount": 1
    }
}

print('Transform job name: {}'.format(batch_job_name))
print('\nInput Data Location: {}'.format(s3_validation))

正如您所猜测的,我们必须在ModelName参数中指定模型名称,在TransformInput参数中指定输入文件夹。我们还需要指定存储预测的output文件夹。我们必须在TransformResources参数中指定我们使用的实例类型,并在MaxConcurrentTransforms参数中指定要处理的文件的最大数量。

以下代码使用参数并启动create_transform_job

sagemaker = boto3.client('sagemaker')
sagemaker.create_transform_job(**request)

您可以在 SageMaker 仪表板上的推理 | 批量转换作业部分监控您的转换作业。一旦任务完成,您就可以访问您指定的作为output文件夹的 S3 桶中的预测结果。

预测结果可以按照以下格式查看:

{
  "prediction": [
    0.0002778972266241908,
    0.05520012229681015,
...
    ]
}

由于我们的模型有 256 个对象类别,输出指定了每个对象在图像中存在的概率。您可以在各种数据集上运行模型,以检查您的模型是否可以正确预测数据集中的对象。

SageMaker 提供了一个非常易于使用的服务,不仅可以训练深度学习模型,还可以在应用程序中使用它们来生成预测。尽管该服务非常直观,但当您在大数据集上使用预构建的模型生成预测时,SageMaker 的成本很高。根据正在开发的应用程序,数据科学家应该始终考虑使用此类服务时相比在 Apache Spark 自己的集群上构建相同模型所承担的整体成本。

摘要

在本章中,我们研究了 Amazon SageMaker 如何提供各种现成的机器学习模型来生成预测,以及可以用于训练您的模型的算法镜像。Amazon SageMaker 在您和设置自己的集群以训练和创建自己的机器学习模型这些杂乱的细节之间生成了一层抽象。Amazon SageMaker 仪表板还提供了一个存储您的训练模型和监控您的预测批量处理作业的地方。

您还可以使用 SageMaker 和自己的数据集训练自己的机器学习模型。我们展示了如何训练一个能够在图像中执行对象检测的机器学习模型。我们演示了如何将此模型部署在 SageMaker 上,并用于运行批量预测任务。您将能够使用这个模板在 Amazon SageMaker 上工作其他算法。

在这本书中,我们的目标是向您解释机器学习算法是如何工作的,以及您如何利用 Apache Spark、Tensorflow 和 SageMaker 等强大工具来部署大规模的训练和预测任务。

练习

  1. 对于前几章中提供的每个示例,在 Amazon SageMaker 市场中找到一个适用于解决该问题的算法。

  2. Amazon SageMaker 还提供了一种创建端点以生成预测的服务。对于前面的示例,为训练好的模型创建一个端点,并为一张图片生成预测。

第四部分:集成现成的 AWS 机器学习服务

本节的目标是向读者介绍 AWS 提供的各种机器学习服务,以执行特定的机器学习任务。由于读者到本书的这一部分已经对机器学习有了一定的了解,他们将学习如何使用 AWS 提供的工具来执行图像识别和自然语言处理等机器学习任务。

本节包含以下章节:

  • 第十章,使用 AWS Comprehend

  • 第十一章,使用 AWS Rekognition

  • 第十二章,使用 AWS Lex 构建对话界面

第十章:使用 AWS Comprehend

作为数据科学家,了解机器学习算法的工作原理非常重要。然而,构建自己的机器学习模型来执行某些任务可能并不高效,因为这需要大量的努力和时间来设计一个最优算法。在第十章 使用 AWS Comprehend、第十一章 使用 AWS Rekognition 和第十二章 使用 AWS Lex 构建对话界面 中,我们将探讨您可以在 AWS 中访问的 机器学习即服务(MLaaS)产品。这些产品允许您使用在 AWS 中预先训练的模型,无论是通过 AWS 仪表板还是 API 调用。

在本章中,我们将涵盖以下主题:

  • 介绍 Amazon Comprehend

  • 访问 Amazon Comprehend

  • 使用 Comprehend 测试实体识别

  • 使用 Comprehend 测试情感分析

  • 使用 Comprehend API 实现文本分类

介绍 Amazon Comprehend

Amazon Comprehend 是 AWS 上提供的一项服务,它提供了自然语言处理(NLP)算法。NLP 是机器学习中的一个领域,它分析人类(自然)语言,并可以识别这些语言的多种属性。在我们之前的多数章节中,我们查看了一些结构化数据的示例。这些数据具有预定义的特征,并按观察值的行组织。然而,自然语言数据集更难以处理。这样的数据集被称为 非结构化数据集,因为特征的结构没有很好地定义。

因此,需要算法从文本文档中提取结构和信息。例如,自然语言中的单词是按照语法结构排列的。自然语言句子也有关键词,它们包含有关地点、人物和其他细节的更多信息。它们还有一个上下文,这非常难以学习,并且相同的单词根据它们的排列方式可能传达不同的含义。

NLP 领域研究如何处理这些文本文档并从中提取信息。NLP 不仅涉及对文档进行聚类和分类,还包括对数据进行预处理,以从文本中提取重要的关键词和实体信息。根据文本文档的领域,需要不同的预处理,因为书面文档的风格会变化。例如,医学和法律文本包含大量术语,并且结构良好。然而,如果您使用 NLP 算法处理 Twitter 数据,文本可能由语法较差的句子和标签组成。因此,根据数据的领域,您需要单独的过程来预处理数据以及如何训练模型。在训练 NLP 模型时通常需要领域专业知识。

AWS Comprehend 提供了训练机器学习模型和使用预训练模型执行自然语言处理任务的工具。它提供了实时仪表板来分析文本数据,同时也提供了使用其用户界面训练机器学习算法的工具。

在本章中,我们将探讨可以使用 AWS Comprehend 完成的四个自然语言处理任务。我们还将建议数据科学家何时应使用现成的工具,何时应投入时间构建自己的机器学习算法。

访问 AmazonComprehend

Amazon Comprehend 可在 AWS 控制台中使用。当您登录 AWS 管理控制台时,在 AWS 服务框中搜索 Amazon Comprehend。选择 Amazon Comprehend 将带您进入 AWS Comprehend 启动屏幕,如下面的截图所示:

图片

当您到达此屏幕时,请单击“启动 Comprehend”,这将带您进入 AWS Comprehend 仪表板。您应该能够从该页面访问以下部分使用的算法。

使用 Comprehend 进行命名实体识别

命名实体识别NER)是自然语言处理中的一个领域,它对非结构化文本中提到的命名实体进行标记。命名实体包括人名、地名、组织等名称。例如,考虑以下句子:

Tim Cook 前往纽约参加苹果商店的开业。

在这个句子中,有三个命名实体。Tim Cook 是一个人的名字,New York 是一个城市的名称(位置),Apple 是一个组织的名称。因此,我们需要一个能够检测这些实体的 NER 模型。请注意,Apple 是一个歧义名词,因为它可以是公司或水果的名称。NER 算法应理解术语使用的上下文,并据此识别。

AWS Comprehend 提供了一个很好的 NER 工具,可以用来识别实体。此工具可以通过他们的仪表板或使用他们的 API 在实时中使用。AWS Comprehend 检测以下实体:

  • 商品:品牌名称

  • 日期:不同格式的日期

  • 事件:音乐会、节日、选举等的名称

  • 位置:城市、国家等的名称

  • 组织:公司和国有组织的名称

  • 人物:人名

  • 数量:用于量化数字的常用单位

  • 标题:电影、书籍等的名称

要访问 AWS 仪表板中的 NER,请转到菜单中的实时分析选项卡。然后您可以在页面提供的文本框中添加输入文本。以下截图展示了 Amazon Comprehend 如何执行 NER 任务:

图片

您可以看到,Amazon Comprehend 中的 NER 工具会自动标记句子中的实体。除了标记实体的类别外,它还给出了一个置信度分数。这个分数可以用来确定我们是否信任工具的结果。

亚马逊 Comprehend 中的 NER 工具也可以通过 AWS 提供的 API 进行访问。

以下代码展示了如何调用 Comprehend 工具来获取实体分数:

import boto3
import json

comprehend = boto3.client(service_name='comprehend') 

text = "Tim Cook traveled to New York for an Apple store opening"

print(json.dumps(comprehend.detect_entities(Text=text, LanguageCode='en'), sort_keys=True, indent=4)) 

你使用的是boto3包,这是一个 Python 的 AWS 工具包。我们首先初始化 Comprehend 客户端,然后将我们的文本传递给客户端以获取包含有关命名实体信息的 JSON 响应。在以下代码块中,我们可以看到我们从客户端收到的响应:

{
  "Entities": [
    {
      "Score": 0.9999027252197266,
      "Type": "PERSON",
      "Text": "Tim Cook",
      "BeginOffset": 0,
      "EndOffset": 8
    },
    {
      "Score": 0.992688775062561,
      "Type": "LOCATION",
      "Text": "New York",
      "BeginOffset": 21,
      "EndOffset": 29
    },
    {
      "Score": 0.9699087738990784,
      "Type": "ORGANIZATION",
      "Text": "Apple",
      "BeginOffset": 37,
      "EndOffset": 42
    }
  ]
}

因此,解析 JSON 可以让我们了解文本中的实体信息。

你还可以在 AWS Comprehend 中使用自定义 NER 算法,通过左侧菜单中的自定义实体识别选项进行训练。你可以添加训练样本文档和实体标注列表。算法会自动学习如何在正确的上下文中标记这些实体,并更新现有模型。

NER 算法在各种应用中被应用。它们的一个重要应用领域是新闻聚合。你可以自动为文档生成标签,以便用户可以根据文档中的实体进行搜索。NER 在推荐算法领域也非常有用,其中 NER 用于检测关键词,我们可以创建一个新闻推荐算法。我们可以构建一个协同过滤模型,推荐关于当前文章读者可能感兴趣的实体的文章。

使用 Comprehend 进行情感分析

情感分析算法分析文本并根据文本中的情感或观点对其进行分类。情感分析检测文本中表达的主观观点。例如,亚马逊市场中的评论给出了对产品的良好或不良评价。使用情感分析,我们可以检测评论是正面还是负面。我们还可以识别评论中的情感细微差别,例如评论者对特定产品是愤怒、兴奋还是中立。在这个社交媒体时代,我们有大量途径来表达我们对产品、电影、政治等的观点。数据科学家使用情感分析算法分析大量数据,并根据非结构化文本数据提取关于某个实体的观点。

亚马逊 Comprehend 通过提供实时仪表板来分析文本中的情感,使得情感分析任务变得简单。你可以像访问 NER 算法一样访问情感分析仪表板。我们将提供两个示例,说明 Comprehend 如何对我们的数据进行情感分析。我查看了两篇亚马逊的评论,一篇是正面的,另一篇是负面的,并使用 Comprehend 对它们进行了情感分析。考虑以下截图中的第一个示例:

图片

在这个例子中,评论者使用了诸如失望之类的词语。这些术语具有负面含义。然而,情感分析算法可以检测到用户在这个词之前也使用了负面词汇,并正确预测这段文本具有积极的情感。同样,考虑以下例子:

图片

您可以看到,评论者最初对产品感到满意,但后来出现了问题。因此,评论者对产品不满意。因此,情感分析算法正确预测评论为负面的置信度为 70%。然而,它还预测在这篇评论中存在一些混合情感,并提供了 22% 的置信度。我们使用 softmax 方法对具有最高置信度的情感进行像素化。

情感分析也可以通过 Amazon API 获取。在这里,我们提供了示例代码,展示了如何使用 boto3 Python 包调用情感分析 API:

import boto3
import json

comprehend = boto3.client(service_name='comprehend') 

text = " It worked fine for the first 6 weeks, then I lost battery power I turned the phone off at night while charging, did not help. Then every else started to fail."

print(json.dumps(comprehend.detect_sentiment(Text=text, LanguageCode='en'), sort_keys=True, indent=4))

此 API 调用返回以下 JSON,其中包含有关文本情感的资料:

{
    "Sentiment": {
        "Sentiment": "NEGATIVE",
        "SentimentScore": {
            "Positive": 0.03148878738284111,
            "Negative": 0.6730570793151855,
            "Neutral": 0.047707948833703995,
            "Mixed": 0.24774616956710815
        }
    }
}

您可以使用 API 对大量评论进行分类,以检测给定产品的整体情感。

情感分析是一个非常强大的工具,公司用它来分析社交媒体数据,以检测对其产品的整体情感,并确定用户为何对其产品不满意。电影评论聚合器,如烂番茄,也使用它来检测评论是正面还是负面,以便它们可以对它们进行分类并生成汇总评分。

使用 Comprehend 进行文本分类

文本分类是将文本文档分类到类别中的过程。与我们在第二章使用朴素贝叶斯分类 Twitter 流和第六章分析访问模式以提供建议中研究的分类算法类似,文本分类算法也基于标记的训练观察结果生成模型。然后,分类模型可以应用于任何观察结果以预测其类别。此外,我们在前几章中研究的相同算法,如第二章使用朴素贝叶斯分类 Twitter 流、第三章使用回归算法预测房屋价值和第四章使用基于树的预测用户行为,也可以用于文本分类。

文本数据是无结构数据。因此,我们需要从文本文档中生成特征,以便这些特征可以作为我们分类模型的输入。对于文本数据集,特征通常是文档中的术语。例如,考虑以下句子:

蒂姆·库克前往纽约参加苹果商店的开业。

让我们考虑该文档的类别为Technology。此句子将被翻译成以下结构化数据:

Tim Cook traveled to New York Apple Store Opening Microsoft Google Class
1 1 1 1 1 1 1 0 0 Technology

每个术语都将被视为数据集中的特征。因此,对于包含许多文档的大型数据集,特征集可以与该语言的词汇表一样大。特征值根据该术语是否存在于该文档中设置为01。由于我们的示例包含诸如Tim CookNew York之类的单词,这些特征的观察值设置为1。由于 Microsoft 和 Google 这两个术语在句子中不存在,这些特征的值设置为0Class变量设置为Technology

在本节中,我们将展示如何在 Comprehend 上逐步训练自定义分类器的方法。我们将使用一个流行的文本分类数据集20 Newsgroups来生成一个机器学习模型,该模型可以标记评论为正面或负面。数据集可以从archive.ics.uci.edu/ml/datasets/Twenty+Newsgroups下载。

数据集可以下载为单独的文本文件,这些文件组织在 20 个文件夹中。每个文件夹的名称代表文件夹中文档的类别。该数据集是一个公开可用的数据集,它包含被分类到以下类别的新闻文章:

  • alt.atheism

  • comp.graphics

  • comp.os.ms-windows.misc

  • comp.sys.ibm.pc.hardware

  • comp.sys.mac.hardware

  • comp.windows.x

  • misc.forsale

  • rec.autos

  • rec.motorcycles

  • rec.sport.baseball

  • rec.sport.hockey

  • sci.crypt

  • sci.electronics

  • sci.med

  • sci.space

  • soc.religion.christian

  • talk.politics.guns

  • talk.politics.mideast

  • talk.politics.misc

  • talk.religion.misc

您可以使用以下步骤来训练分类器:

  1. 第一步是将数据下载并预处理成 Comprehend 工具可读的格式。Comprehend 要求训练数据以以下 CSV(逗号分隔值)格式:
类别 文档

因此,一旦您下载了数据集,将其转换为上述格式并上传到您的 S3 存储桶。

  1. 您可以在 Comprehend 仪表板的左侧自定义标签页下访问自定义分类工具。要训练模型,您必须点击“训练分类器”选项。请注意,Comprehend 允许您在此仪表板上训练您的机器学习模型并将它们存储起来,以便您将来使用。

当您点击“训练分类器”选项时,您将看到以下截图:

  1. 为分类器命名并选择文档的语言。添加存储训练 CSV 文档的 S3 位置。选择正确的角色后,您可以给分类器添加相关标签,这有助于您在将来搜索它们。一旦您添加了所有信息,请点击训练分类器:

  1. 您将被带回到仪表板屏幕,您将看到分类器训练正在进行。一旦训练完成,分类器的状态将被标记为已训练:

  1. 然后,您可以点击分类器查看模型的评估指标。如您所见,我们的分类模型准确率为 90%:

  1. 由于我们现在有一个经过训练的分类器,您可以使用此模型对任何文档进行预测。我们创建一个包含 100 个文档的test.csv文件,以从该模型获取预测。为了开始预测过程,请点击前一个屏幕上显示的“创建作业”选项。

这将带您进入另一个屏幕,您可以在其中添加有关您想要用于测试的文件以及输出应存储位置的详细信息:

在创建分析作业的屏幕上,添加有关要使用的分类器的详细信息:输入数据存储的位置(在 S3 上)以及输出存储的 S3 位置。您可以指定每行一个文档或每个文件一个文档的输入数据,并将输入数据指向包含所有文件的目录。在我们的示例中,由于test.csv文件每行包含一个文档,我们使用该格式。

  1. 一旦您点击创建作业,它将自动对文档进行分类并将输出存储在输出位置。输出以 JSON 格式存储,其中output文件的每一行都包含对该行的分析。

以下是一个生成的输出示例:

{
  "File": "test_2.csv",
  "Line": "0",
  "Classes": [
    {
      "Name": "alt.atheism",
      "Score": 0.8642
    },
    {
      "Name": "comp.graphics",
      "Score": 0.0381
    },
    {
      "Name": "comp.os.ms-windows.misc",
      "Score": 0.0372
    },
    ...
    {
      "Name": "talk.religion.misc",
      "Score": 0.0243
    }
  ]
}

因此,您可以看到我们的模型将输入文件的第一行标记为"alt.atheism",置信度为 86.42%。

您还可以使用 Amazon Comprehend API 创建文档分类器和预测作业:

import boto3

client = boto3.client('comprehend')
response = client.create_document_classifier(
    DocumentClassifierName='20NG-test',
    DataAccessRoleArn='Data Access ARN value',
     InputDataConfig={
         'S3Uri': 's3://masteringmlsagemaker/comprehend/train.csv'
     },
     OutputDataConfig={
         'S3Uri': 's3://masteringmlsagemaker/comprehend/'
     },
    LanguageCode='en')

运行此函数将自动生成我们在前一步骤中创建的相同分类器。您可以从“我的安全凭证”页面上的角色选项卡访问您的 ARN 值。这是我们在第 3 步中创建的相同 IAM 角色的 ARN 值。输出数据配置位置将自动获取分类器评估的混淆度量,响应字符串将返回如下:

{
    'DocumentClassifierArn': 'string'
}

字符串将是标识分类器的 Amazon 资源名称。您还可以使用 API 运行预测作业。以下代码可以用于生成输入文件的预测:

import boto3

client = boto3.client('comprehend')
response = client.start_document_classification_job( JobName='Testing Model', DocumentClassifierArn='<ARN of classifier returned in the previous step>', InputDataConfig={ 'S3Uri': 's3://masteringmlsagemaker/comprehend/test.csv', 'InputFormat': 'ONE_DOC_PER_LINE' }, OutputDataConfig={ 'S3Uri': 's3://masteringmlsagemaker/comprehend/', }, DataAccessRoleArn='<Data Access ARN value>')

上述代码将启动与我们在仪表板上创建的完全相同的分类作业。因此,您可以控制何时使用某个分类器,并根据需要在不同数据集上生成预测。函数的响应将是作业的状态。作业还将生成一个作业 ID,您可以使用describe_document_classification_job()函数 ping 该 ID 来检查作业的状态。

因此,我们已经在 AWS 上使用 Comprehend 工具生成了一个自定义文档分类器。这些工具将帮助您快速创建这些分类器,无需担心选择哪些分类算法、如何调整参数等问题。亚马逊会根据其研究团队的专长自动更新 Comprehend 使用的算法。然而,主要缺点是如果您在大数据集上运行操作,Comprehend 工具可能会很昂贵,因为它们按预测收费。您可以在aws.amazon.com/comprehend/pricing/访问 AWS Comprehend 的定价信息。

摘要

在本章中,我们研究了如何在 AWS 中使用内置的机器学习工具 Comprehend。我们简要讨论了自然语言处理(NLP)领域,并介绍了其子领域,如命名实体识别(NER)和情感分析。我们还研究了如何使用 Comprehend 提供的仪表板创建自定义文档分类器。此外,我们还研究了如何使用 Python 中的boto3包访问 Comprehend 的 API。

这些工具非常吸引人,因为它们将帮助您快速创建复杂的机器学习模型,并开始将它们应用于您的应用程序中。现在,对 NLP 领域只有初步了解的数据科学家现在可以训练复杂的机器学习模型,并使用它们做出最优决策。然而,大多数数据科学家面临的问题是,这些工具提供的定价是否比使用 Python 包自行构建算法更经济。请注意,Comprehend 通过让数据科学家关注底层集群配置,在数据科学家和机器学习模型之间添加了一层抽象。根据我们的经验,我们在项目的快速原型阶段使用这些工具来评估产品。如果我们决定投入生产,很容易计算出使用 AWS 工具与自行构建算法并在我们的集群上维护它们之间的成本差异。

我们将在下一章介绍亚马逊 Rekognition。这项服务用于图像识别,是对象检测和类似应用的即用型解决方案。

练习

  1. 您的任务是使用亚马逊 Comprehend 提供的 API 在大型数据集上执行命名实体识别(NER)。使用 Kaggle 竞赛中提供的标注 NER 数据集在 Comprehend 中创建自定义实体识别(www.kaggle.com/abhinavwalia95/chemdner-iob-annotated-chemical-named-etities)。

  2. 在 Kaggle 的 Yelp 数据集上应用情感分析,然后评估您的预测是否与评论评分相匹配(www.kaggle.com/yelp-dataset/yelp-dataset)。

第十一章:使用 AWS Rekognition

我们在第七章,实现深度学习算法,和第九章,使用 SageMaker 进行图像分类和检测中研究了深度学习算法及其使用 SageMaker 的实现方法。你一定已经意识到训练一个好的卷积神经网络CNN)需要大量的专业知识和资源。此外,它还需要大量的带有对象的标记图像。亚马逊提供了一个现成的图像识别解决方案,称为Amazon Rekognition,它提供了各种使用预训练图像识别模型的图像识别工具。

在本章中,我们将涵盖以下主题:

  • 介绍 Amazon Rekognition

  • 实现对象和场景检测

  • 实现面部分析

介绍 Amazon Rekognition

使用深度学习构建图像识别模型非常具有挑战性。首先,你需要一个大型、标记的数据集来训练深度学习模型以执行特定任务。其次,你需要了解如何设计网络和调整参数以获得最佳精度。最后,在规模上训练这样的深度学习模型需要昂贵的基于 GPU 的集群来训练这些模型。

Amazon Rekognition (aws.amazon.com/rekognition/) 是 AWS 提供的一个工具,它提供了已经预训练并可用于你应用程序中的图像识别模型。Amazon Rekognition 模型基于对数十亿个视频和图像的分析。类似于 Amazon Comprehend 提供作为服务的 NLP 模型,Rekognition 提供了各种可以执行特定任务的图像识别模型。使用 Amazon Rekognition 的优势在于,你可以简单地使用仪表板和 API 以高精度执行图像识别任务,而无需训练此类机器学习模型所需的高级专业知识。

Amazon Rekognition 只提供有限数量的模型,用于执行特定任务。在本节中,我们将查看 Amazon Rekognition 仪表板中可用的各种工具。我们还将探讨如何使用 Python 中的 AWS API 访问这些功能。

实现对象和场景检测

对象和场景检测算法可以识别图像中的各种对象,并为每个预测分配置信度。此算法使用标签层次结构来标记对象,并在检测到对象时返回所有叶节点。对象检测是图像识别的经典应用。它使我们能够识别图像中的内容并将其标记。例如,考虑一个新闻编辑室,摄影师每天提交数百张图片和视频。你需要有人标记这些图片,以便如果你希望访问在车祸中拍摄到的名人图像,这些图像库可以搜索。

目标检测允许您自动标记这些图像,以便它们可以高效地存储、组织和检索。目标检测算法的一个关键特性是它们必须全面,并且应该能够检测大量对象。此外,此类算法还检测对象的边缘,并且应该能够返回对象的边界框。Amazon Rekognition 有效地执行这两项任务。

您可以通过 AWS 控制台访问 Amazon Rekognition 仪表板。只需在搜索栏中搜索 Rekognition,您就可以访问 Amazon Rekognition 的演示。演示向您展示了工具的工作方式,但如果您想分析多个图像,则需要使用 API。

一旦您进入演示屏幕,选择对象和场景检测以访问一个演示,您可以在其中选择单个图像并检测对象中的图像。

为了演示的目的,我使用了芝加哥河上的渡轮截图:

图片

如前一个截图所示,目标检测工具返回了图像中对象的排名列表以及检测的置信度。正如我们之前提到的,由于该工具使用类别层次结构,它可能会在顶部检测到相似类别。例如,它能够检测图像中的船。然而,它还以相同的置信度分数返回了车辆和交通类别。我们还可以看到,演示显示了它检测到的图像中每个对象的边界框。

然而,使用此工具进行目标检测可能很繁琐,因为它一次只能处理一张图像。因此,我们还可以使用 API 来访问 Amazon Rekognition 工具。您需要将图像上传到 S3 存储桶中的一个文件夹,以便在它们上执行目标检测。

以下 Python 代码可以用于对图像执行相同的操作:

import boto3
import json

client = boto3.client('rekognition')
response = client.detect_labels( 
                Image={
                    'S3Object': 
                            {
                                 'Bucket': 'masteringmlsagemaker',
                                 'Name': 'ImageRecognition/chicago_boats.JPG'
                            }
                    },
                MaxLabels=5, 
                MinConfidence=90 
)

print(json.dumps(response, sort_keys=True, indent=4))

图像必须在 S3 存储桶中,并且您必须指定存储桶名称和图像名称作为请求函数的参数。响应很长,所以我们只显示了响应 JSON 中第一个预测的格式:

{
  "LabelModelVersion": "2.0",
  "Labels": [
    {
      "Confidence": 99.86528778076172,
      "Instances": [
        {
          "BoundingBox": {
            "Height": 0.29408860206604004,
            "Left": 0.5391838550567627,
            "Top": 0.6836633682250977,
            "Width": 0.25161588191986084
          },
          "Confidence": 99.86528778076172
        },
        {
          "BoundingBox": {
            "Height": 0.11046414822340012,
            "Left": 0.23703880608081818,
            "Top": 0.6440696120262146,
            "Width": 0.07676628232002258
          },
          "Confidence": 99.5784912109375
        },
        {
          "BoundingBox": {
            "Height": 0.040305182337760925,
            "Left": 0.5480409860610962,
            "Top": 0.5758911967277527,
            "Width": 0.04315359890460968
          },
          "Confidence": 77.51519012451172
        }
      ],
      "Name": "Boat",
      "Parents": [
        {
          "Name": "Vehicle"
        },
        {
          "Name": "Transportation"
        }
      ]
    },
...
  ]
}

如您从响应中观察到的,我们在图像中发现了三个Boat对象的实例。响应提供了图像中找到的每个对象的边界框。此外,您还可以观察到最右侧的船很小,因此检测它的置信度远低于图像中的其他两艘船。响应还返回了对象在层次结构中的父级。因此,如果您有数百张图像需要分类,您可以将它们全部添加到 S3 存储桶中,并使用此代码遍历它们并检测这些对象的标签。由于有像 Amazon Rekognition 这样的工具,数据科学家现在可以访问世界级的深度学习模型,并将它们应用于他们正在构建的工具中。然而,这种对象检测算法仅适用于有限数量的对象。例如,我们尝试在这个工具中使用癌症 X 光图像的算法,但它无法返回任何结果。如果您正在开发一个非常专业的产品,试图检测肿瘤的医学图像或太空望远镜的图像,您将需要根据大量标记的图像训练自己的模型。

实现面部分析

Amazon Rekognition 还提供了一种强大的工具,可以对图像执行面部分析。它可以根据观察图像预测有趣的属性,如年龄和性别。它还可以从该模型检测到微笑或该人是否戴眼镜等特征。这样的模型将通过分析大量标记的面部图像,并训练一个图像识别模型来识别这些特征来训练。我们在第七章实现深度学习算法中研究的 CNN 模型非常适合此类应用,因为它可以使用局部感受野方法从图像中自动生成特征图,并检测包含这些面部特征的框。

面部分析演示可以像对象检测演示一样访问。为了测试模型,我们选择了莱昂纳多·达·芬奇的蒙娜丽莎画像。关于这幅画的一个长期谜团是画中的女士是否在微笑。

在下面的屏幕截图中,我们可以看到面部分析演示如何从图像中提供面部特征:

图片

面部分析模型确实预测图像中存在面部,并围绕它创建了一个正确的框。它正确地预测了图像是女性,并为该人预测了年龄范围。它预测图像中的人没有在微笑。它还正确地预测了该人没有戴眼镜。

您也可以通过 API 调用访问相同的信息。

使用以下 Python 代码,您可以执行与前面演示中相同的面部分析任务:

import boto3
import json

client = boto3.client('rekognition')
response = client.detect_faces(
    Image={
        'S3Object': {
            'Bucket': 'masteringmlsagemaker',
            'Name': 'ImageRecognition/monalisa.jpg'
        }
    },
    Attributes=['ALL']
)

print(json.dumps(response, sort_keys=True, indent=4))

你必须将你的图片存储在 S3 存储桶中,并向 API 调用提供存储桶和图片名称。你还可以指定需要返回的属性,或者指定All以获取所有属性。

此调用的响应以 JSON 格式呈现,如下所示:

{
    "FaceDetails": [
        {
            "BoundingBox": {
                "Width": 0.22473210096359253,
                "Height": 0.21790461242198944,
                "Left": 0.35767847299575806,
                "Top": 0.13709242641925812
            },
            "AgeRange": {
                "Low": 26,
                "High": 43
            },
            "Smile": {
                "Value": false,
                "Confidence": 96.82086944580078
            },
            "Gender": {
                "Value": "Female",
                "Confidence": 96.50946044921875
            },
          "Emotions": [
                {
                    "Type": "CALM",
                    "Confidence": 34.63209533691406
                },
                {
                    "Type": "SAD",
                    "Confidence": 40.639801025390625
                }
            ],
            "Landmarks": [
                {
                    "Type": "eyeLeft",
                    "X": 0.39933907985687256,
                    "Y": 0.23376932740211487
                },
                {
                    "Type": "eyeRight",
                    "X": 0.49918869137763977,
                    "Y": 0.23316724598407745
                },
            "Confidence": 99.99974060058594
        }
    ]
}

我们已编辑此响应以保持简洁。然而,你可以观察到,你可以看到关于年龄性别微笑的信息,正如我们在演示中所见。然而,它还识别出脸上的情绪,如悲伤和平静。它还定位了脸上的地标,如眼睛、鼻子和嘴唇。

这些工具在当前智能手机中使用,微笑可以触发拍照。它们在餐厅的消费调查中使用,以衡量餐厅中人们的统计数据以及他们是否对服务满意。

其他 Rekognition 服务

Amazon Rekognition 还提供其他图像识别服务。你可以使用本章中的示例来访问这些服务。我们将在此列出一些服务及其应用。

图片审查

我们可以使用 Rekognition 来监控图片并检查内容是否具有暗示性或不安全。此类技术用于调节实时视频服务,如 Twitch 或 Facebook Live,其中人工智能AI)可以自动检测不安全内容。由于 YouTube 或 Instagram 等服务每天上传的数据量巨大,使用此类 AI 技术可以帮助降低平台监管的成本。

以下截图显示了图片审查工具如何检测图片中的暗示性主题并自动标记它们:

图片

明星识别

识别还可以用于自动检测图片或视频中的名人。这可以通过从标记的图片和视频中学习的图像识别模型来完成。深度学习算法可以自动提取面部特征,然后进行比较以预测可能是哪位名人。例如,Amazon Prime 等服务上的许多电影和电视节目可以使用此技术显示屏幕上的演员名字。手动标记这些场景中的演员名字可能是一项非常繁琐的任务;然而,深度学习算法可以自动完成这项工作。

在以下示例中,Amazon Rekognition 检测到一张杰夫·贝索斯的图片并将其正确标记:

图片

面部比较

明星识别技术可以进一步扩展,用于面部比较和检测相似的面部。例如,您的 Facebook 账户会自动将您上传的图片中的面部与您的朋友匹配,并自动标记图片。他们使用这样的图像识别算法来训练每个面部的模型,并在您上传的图片上运行这些模型以检测您的朋友是否在图片中。Amazon Rekognition 还提供了一种名为 面部比较 的功能,该功能比较两张图片中的面部,并检测是否有人在两张图片中都出现。

在以下截图中,我们可以观察到面部比较算法可以自动匹配两张图片中的面部并检测哪些面部彼此相似:

图片

Amazon Rekognition 还提供另一种可以检测图片中文本的工具。此模型与我们第八章实现 AWS 上的 TensorFlow 深度学习中构建的类似,我们的模型能够检测数字。此工具在读取现实世界中的文本也非常有用。例如,Google Translate 这样的应用可以分析相机图像并将它们翻译成您的母语。自动驾驶汽车也可以使用这项技术来读取路标并做出相应反应。

以下截图显示了 Amazon Rekognition 如何检测图像中的文本:

图片

该识别在此图片上工作不准确,但能够框选并重新创建此图片中的文本。

在本节中,我们没有提供这些服务的代码示例。API 调用与我们在本节前两个工具中讨论的内容类似。我们鼓励您尝试这些服务的 API 调用并测试它们的工作方式。

摘要

Amazon Rekognition 允许数据科学家通过 API 调用访问高质量的图像识别算法。使用深度学习最大的障碍之一是生成大量数据集和运行昂贵的基于 GPU 的集群来训练模型。AWS Rekognition 使用户能够更容易地访问这些功能,而无需具备训练此类模型所需的专业知识。应用开发者可以专注于构建功能,而无需花费大量时间在深度学习任务上。在本章中,我们研究了 Amazon Rekognition 中可用的各种工具,还学习了如何进行 API 调用和读取响应 JSON。此外,我们还研究了这些工具可能有用的一些应用场景。

在下一章中,我们将演示如何使用名为 Amazon Lex 的服务构建自动聊天机器人。

练习

  1. 使用 Python 创建一个应用程序,你可以传递一张团体照片,并检测当时房间的情绪。提供基于面部分析工具的代码检测细节,以及你是如何总结结果以在照片中找到情绪的。

  2. 创建一个工具,用于识别电影剪辑中的演员,并给出演员出现在屏幕上的时间。

第十二章:使用 AWS Lex 构建对话界面

机器学习最受欢迎的应用之一是聊天机器人;它们可以像人类一样与你交谈,并理解你的指令。这些聊天机器人使用自然语言处理NLP)来解析指令,并根据你的问题返回查询或答案。亚马逊提供了一种名为Lex(它是Alexa的简称)的服务,你可以用它构建复杂的聊天机器人,这些机器人可以执行各种任务。

在本章中,我们将介绍以下主题:

  • 介绍 Amazon Lex

  • 使用 Amazon Lex 构建自定义聊天机器人

介绍 Amazon Lex

Amazon Lex (aws.amazon.com/lex/) 提供的服务可用于创建对话机器人。对话机器人使用各种机器学习技术,如语音识别NLP深度学习。由于近年来这些领域的进步,对话机器人已成为我们日常生活中的常客。数百万人使用亚马逊 Alexa、谷歌助手、Siri 或 Cortana 作为对话设备来完成各种任务。这些设备可以执行简单的任务,例如告诉你天气、为你叫一辆 Uber、订购披萨,以及控制你的照明。许多企业也提供聊天机器人以提供客户支持。例如,互联网服务提供商 Verizon FIOS 提供了一种聊天机器人,可以根据与你的对话执行诸如指向正确的故障排除文档或根据你的聊天重置路由器等任务。许多公司也使用此类对话机器人进行自动电话呼叫(机器人电话),在这种情况下,很难判断另一端的通话者是否不是真人。

从零开始构建这样的对话机器人并不容易。正如我们在第十章 Working with AWS Comprehend 中学习的那样,自然语言并不遵循严格的语法结构,我们有多种方式来表达相同的意思。因此,对话机器人需要能够从自然语言查询中解析相关数据,并给出最可能的答案。例如,亚马逊 Echo 可以理解不同格式的查询,并发现哪些是最相关的信息可以展示给用户。首先,这样的设备需要理解语音并将其转换为机器可以理解的文本。其次,它们需要触发能够回答该问题的正确技能,并将用户输入呈现给该技能。一旦技能生成答案,它必须使用文本到语音转换器将其转换回语音。所有这些步骤都需要专门的、高质量的深度学习模型来执行这些任务。例如,亚马逊使用深度学习模型来确定其文本到语音转换器中单词之间的停顿。

虽然构建这样的对话机器人可能听起来是一项艰巨的任务,但 Amazon 还提供了您可以使用他们的模型来生成此类任务的服务。这项服务称为 Amazon Lex,您可以通过 AWS 控制台访问它。

使用 Amazon Lex 构建 custom chatbot

在本节中,我们将使用 Amazon Lex 构建一个简单的自定义对话机器人。要访问 Amazon Lex 控制台,只需进入 AWS 控制台并搜索此服务。一旦到达控制台,您将有一个选项来创建一个新的机器人。您可以构建可以处理特定任务的独立机器人。在本例中,我们提供了以下步骤来创建一个用户可以要求在指定时间从特定餐厅订购食物的机器人:

  1. 要开始,请点击控制台上的“创建机器人”选项。您将能够访问以下截图:

图片

您必须在屏幕上指定机器人名称以及测试机器人时想要选择的语音。您还可以指定会话超时时间,这样离开订单未完成并离开机器的人就不会有其他人继续他们的聊天会话的风险。在本例中,我们创建了一个自定义机器人。然而,您也可以访问示例机器人来测试服务并了解这些机器人是如何创建的。

  1. 当您点击创建按钮时,您将被带到下一个屏幕,您需要输入有关您的机器人如何工作的信息。首先,您必须指定您的特定机器人在聊天屏幕中是如何被触发的。在我们的案例中,用户有多种方式可以让聊天窗口知道他们饿了,因此您应该添加应该触发您的机器人的查询示例。这些查询在 Amazon Lex 中被称为utterances。我们添加了以下将触发我们的机器人的 utterances:

图片

  1. Amazon Lex 将使用机器学习来扩展 utterances 列表,这样如果用户提出“你能订购一些食物”这样的问题,我们的机器人仍然会被触发,因为 utterance 与我们指定的相似。

  2. 一旦我们指定了将触发我们的机器人,我们必须指定机器人启动时会发生什么。您可以使用 AWS 上的 Lambda 函数执行特定任务,或者使用控制台来设计聊天。由于设计 Lambda 函数不在本书的范围内,我们将使用控制台来询问用户他们想订购什么。以下屏幕选项显示了我们可以添加的预期用户信息:

图片

  1. 我们定义了三个变量,我们希望我们的聊天机器人能够获取这些变量的输入。例如,我们想知道他们想要从哪家餐厅订购,他们想要订购什么,以及他们希望食物准备好的时间。Amazon Lex 提供了预构建的槽位(变量类型),您在获取输入时可以选择。例如,AMAZON.Food 槽位类型将尝试确保变量的值是食物类型,而 AMZON.Time 变量类型将确保添加的时间是有效的时间。

  2. 当我们的机器人有了所有必需变量的信息后,您必须指定机器人将如何响应。在我们的例子中,为了保持简单,我们只需告诉用户我们已经订购了食物(请注意,此代码实际上并没有订购食物)。如果您正在构建一个真正的订购食物的应用程序,您还可以调用一个 lambda 函数,该函数可以运行与变量名称相关的自定义代码。以下截图显示了如何添加有关机器人响应的信息,以及确认屏幕:

图片

  1. 如果用户确认,您可以使用以下 Fulfillment 选项向用户发送感谢信息:

图片

  1. 当您填写完表格后,您可以使用屏幕上的构建选项来构建您的机器人。如果您在屏幕上犯了任何错误,构建会提示您修复它们。最后,一旦您成功构建了机器人,您可以通过选择右侧的测试聊天机器人选项来测试它。以下屏幕显示了我们的聊天机器人是如何工作的。如您所见,我们能够与我们的聊天机器人进行聊天,并(假装)从它那里订购食物,如下面的截图所示:

图片

通过在真实机器学习模型和用户之间添加一层抽象层,Amazon Lex 使得创建聊天机器人对每个人都非常容易。您可以专注于构建最适合您需求的机器人,而无需担心背后的算法。由于 Amazon Lex 是一项服务,AWS 会根据您对其机器学习模型的调用次数向您收费。

此外,您可以通过每个机器人的操作下拉菜单中的“导出”选项轻松地将 Amazon Lex 模型导出到 Alexa 技能套件。因此,通过使用 Amazon Lex,您可以在几分钟内设计聊天机器人并将其发布供 Alexa 使用。Amazon Lex 还提供了 API,您可以使用这些 API 来构建机器人,以便您可以使用代码更新或编辑您的语句或槽位。请参阅 boto3 API 文档(boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lex-models.html)以了解如何使用 API。对 API 的调用使用与我们第十章“与 AWS Comprehend 一起工作”和第十一章“使用 AWS Rekognition”中提供的示例类似的代码。

摘要

Amazon Lex 使得构建对话机器人对每个人来说都更容易、更易于访问。对话机器人使用许多机器学习模型来为用户提供快速回答。Amazon Lex 提供了一个图形界面,您可以在其中指定您的机器人应该响应哪些语句,应该收集哪些信息槽位,以及机器人应该向用户提出哪些确认问题。此类工具可以直接在仪表板上进行测试,正如我们在上一节中所展示的那样。

作为数据科学家,需要构建能够让客户惊叹的应用程序,使用 Amazon Comprehend、Rekognition 和 Lex 等工具快速构建这些原型是一个好方法。然而,当大规模使用时,这些服务可能会变得昂贵。在这种情况下,我们总是致力于使用 Apache Spark 或 SageMaker 等框架构建我们自己的模型。

在下一章中,我们将学习如何设置新的 AWS 集群,并探讨如何根据您的任务选择正确的集群的细微差别。

练习

  1. 创建一个聊天机器人,根据用户提供的信息提供航班状态。

  2. 创建一个能够回答各种与天气相关的问题的聊天机器人。

第五部分:通过 AWS 优化和部署模型

在掌握 AWS 上机器学习各种工具的使用后,读者必须掌握的一个重要步骤是优化这些模型并将它们部署到生产环境中。在本部分书中,我们讨论了使用 AWS 工具训练的机器学习模型如何进行优化并准备好在生产环境中部署。

本节包含以下章节:

  • 第十三章,在 AWS 上创建集群

  • 第十四章,在 Spark 和 SageMaker 中优化模型

  • 第十五章,调整集群以适应机器学习

  • 第十六章,在 AWS 上构建模型的部署

第十三章:在 AWS 上创建集群

机器学习中的一个关键问题是如何在多台机器上扩展和并行化学习。无论你是训练深度学习模型,这些模型对硬件的使用非常重,还是仅仅为了创建预测而启动机器,选择合适的硬件配置对于成本考虑和运行时性能都是至关重要的。

在本章中,我们将涵盖以下主题:

  • 选择您的实例类型

  • 分布式深度学习

选择您的实例类型

在第四章“使用基于树的算法预测用户行为”和其他章节中,我们不得不启动 EMR 集群和 SageMaker 实例(服务器)来进行学习和模型服务。在本节中,我们讨论了不同实例类型的特性。在本章中,你可以找到 AWS 提供的所有支持的实例类型,请参阅aws.amazon.com/ec2/instance-types/

根据手头的任务,我们应该使用不同的实例类型。例如,我们可能需要一个带有 GPU 而不是 CPU 的实例类型来进行深度学习。当在 Apache Spark 上启动大型迭代提取、转换和加载ETL)(即数据转换作业)时,我们可能需要大量的内存。为了使用户更容易操作,AWS 已经将实例分类为针对不同用例的系列。此外,AWS 为每个系列不断提供新的硬件配置。这些被称为。通常,新一代提供了比上一代更好的性能。然而,旧一代通常仍然可用。反过来,每个系列在计算和内存能力方面都有不同大小的机器。

最常用的系列如下:

  • 计算优化型(C 系列)

  • 内存优化型(M 系列)

  • 加速计算型(P 系列)

  • 存储优化型(I 系列)

  • 通用型(R 系列)

每个优化目标都有其他系列,但在之前的列表中,我们列出了每个系列中最常用的一个。每个系列可能具有不同的配置。下表显示了 C 系列和 M 系列的一些配置。每个配置都有不同的价格。例如,在撰写本文时,AWS us-east-1 区域第五代 xlarge 和 C 系列机器的价格为每小时 0.085 美元。正如您所看到的,在给定的价格水平上,用户可以选择支付具有更多内存和较少计算能力的配置,或者相反。下表中的内存(GB)列显示的是千兆字节的值,而 vCPUs 是虚拟机中的处理能力单位,由 AWS 测量。表中的价格只是参考价格,对应于 2019 年 3 月 AWS 弗吉尼亚数据中心区域的价格。目前,AWS 按机器开启的每秒钟收费(即,尽管价格显示为每小时金额,但机器可以运行 120 秒,用户只需支付相应的小时价格的一部分):

模型 vCPU 内存(GB) On-demand 价格(us-east-1 区域)
c5.large 2 4 $0.085 per hour
c5.xlarge 4 8 $0.17 per hour
c5.2xlarge 8 16 $0.34 per hour
c5.4xlarge 16 32 $0.68 per hour
m5.large 2 8 $0.096 per hour
m5.xlarge 4 16 $0.192 per hour
m5.2xlarge 8 32 $0.384 per hour

给定配置的价格可能会因多种因素而变化,具体如下:

  • 机器的区域(数据中心)

  • 实例是否请求为 spot 或 on-demand

  • 使用预留定价

On-demand 与 spot 实例定价

On-demand 是请求云中机器最灵活的方式。on-demand 实例的价格是固定的,一旦启动机器,就保证其持续运行(除非发生错误或 AWS 正在实验容量问题,这极为罕见)。另一方面,spot 定价基于拍卖。AWS 有一系列过剩容量机器,通常以低于 on-demand 的价格拍卖。为了获得这样的机器,在启动时,用户需要指定他或她愿意为这样的实例支付多少。如果当前市场价格低于出价价值,机器将成功配置。一旦市场价格超过出价,机器就可以从用户那里收回。因此,如果您使用 spot 定价,您需要知道机器可以在任何时候关闭。话虽如此,根据我们的经验,spot 定价对于大规模(数千台机器)生产工作负载可以可靠地成功。重要的是要适当地选择出价价格和机器配置,并准备好根据 spot 市场价格的变化不时地更改这些配置。

在以下链接中,您可以检查不同地区和可用区域(这些是区域内的独立隔离数据中心)中每种实例类型的市值:console.aws.amazon.com/ec2sp/v1/spot/home?region=us-east-1#

图片

上述图表显示了 2019 年 3 月至 2 月期间 c5.4xlarge 机器的市场价格。读者可能会注意到,区域 us-east-1d 的市场价格似乎比其他地区低。这意味着,只要可能,您可以在该区域以较低出价请求 spot 实例。

目前,SageMaker 不支持 spot 定价,只允许按需实例。此外,还有针对 SageMaker 支持的实例的不同价格表,您可以通过以下链接找到:aws.amazon.com/sagemaker/pricing/。SageMaker 可以用于不同的事情(笔记、训练作业、批量转换作业、端点等),因此有不同的价格。

对于 弹性映射减少EMR),它确实支持 spot 实例。然而,当通过 EMR 启动时,会额外增加原始实例类型成本的一小部分。

预留定价

如果您提前准确估计了您的计算需求,可以降低成本。在这种情况下,您可以预先支付 AWS 并获得按需实例的显著折扣。例如,如果您计划在一年内为 m5.xlarge 机器花费 1,000 美元,您可以选择预先支付 1,000 美元并获得 40% 的节省。您预付的越多,节省率就越高。

详细信息可以在以下链接中找到:aws.amazon.com/ec2/pricing/reserved-instances/pricing/   

亚马逊机器镜像(AMIs)

机器可以通过 弹性计算 服务(aws.amazon.com/ec2)直接在 EMR 或 SageMaker 外启动。当你想在 AWS 云上部署自己的应用程序或想自定义配置实例上可用的软件包时,这非常有用。通过 EC2 启动实例时,你可以选择一个 AMI,机器将带有为你的应用程序所需的所有库和软件包。你可以从一个正在运行的实例创建自己的 AMI,以便稍后重用或通过 Docker 规范。然而,AWS 提供了几个预配置的 AMI,这些 AMI 对于深度学习非常有用。我们强烈建议您通过此链接查看可用的 AMI:aws.amazon.com/machine-learning/amis/。这些 AMI 包括最常见的机器学习软件包(如 TensorFlow、Anaconda 和 scikit-learn),以确保不同库版本之间的兼容性(通常是一个棘手的问题)。这些 深度学习 AMI 通常被称为 DLAMIs

深度学习硬件

AWS 中的大多数实例类型都是基于 CPU 的。CPU 实例通常适用于执行各种顺序任务。然而,加速计算实例类型(例如,P 或 G 系列)是基于 图形处理单元GPU)。这些最初在游戏机上流行的实例类型,最终被证明非常适合深度学习。GPU 的特点是拥有比 CPU 更多的核心,但处理能力较低。因此,GPU 能够快速并行处理简单的指令。

尤其是 GPU 允许进行非常快速和并行的矩阵乘法。回想一下 第七章,实现深度学习算法,深度学习涉及将权重与不同层输入的信号相乘,就像向量点积一样。实际上,矩阵乘法涉及同时进行多个列和行的点积。矩阵乘法通常是深度学习中的主要瓶颈,而 GPU 极擅长执行此类操作,因为有机会并行进行大量计算。

在以下表格中,我们可以看到用于深度学习的典型机器配置及其相关特性。当涉及到分配深度学习工作负载时,GPU 数量和网络性能尤其重要,我们将在以下章节中讨论:

模型 GPU vCPU Mem (GiB) GPU Mem (GiB) 网络性能
p3.2xlarge 1 8 61 16 最高 10 吉比特
p3.8xlarge 4 32 244 64 10 吉比特
p3.16xlarge 8 64 488 128 25 吉比特

弹性推理加速

2018 年,AWS 宣布了一个新功能,允许我们通过网络以 GPU 实例的一小部分成本将通过基于 GPU 的加速设备附加的常规实例组合起来。详细信息可以在 docs.aws.amazon.com/sagemaker/latest/dg/ei.htm 找到。

分布式深度学习

让我们接下来探索 分布式深度学习 概念。

模型与数据并行化

当训练大量数据或网络结构巨大时,我们通常需要在不同的机器/线程之间分发训练,以便可以并行执行学习。这种并行化可能发生在具有多个 GPU 的单台机器内,或者通过网络在不同机器之间同步。分发深度学习工作负载的两种主要策略是数据并行化和模型并行化。

在数据并行化中,我们使用相同的权重(即相同的模型)并行运行多个小批量。这意味着在一系列运行中同步不同小批量的权重。合并不同并行运行权重的策略之一是将每个并行小批量产生的权重进行平均。使用允许以分布式方式合并梯度而不需要中央合并器的算法(如 AllReduce)来平均每个机器或线程的梯度是一种有效的方法。其他替代方案包括托管一个参数服务器,该服务器充当同步权重的中央位置。

模型并行化,另一方面,涉及不同的线程或机器在并行处理相同的 mini-batch 的同时,分发实际的处理。正在运行的算法需要能够在不同的线程中分发工作。这种方法通常在具有多个 GPU 且共享高速总线的机器上运行良好,因为模型并行化通常只需要在每个前向传递后同步每个层的输出。然而,这种同步可能涉及的数据量可能比数据并行化中的权重同步多或少,这取决于网络的结构。

分布式 TensorFlow

TensorFlow 本地支持在具有多个 GPU 的单台机器上使用 AllReduce 进行数据并行化。TensorFlow 中通过 TensorFlow 分发学习的算法是一个活跃的开发领域。

例如,我们可以启动一个具有多个 GPU 的笔记本实例:

图片

在这个例子中,我们有一个四 GPU 机器。让我们看看我们如何修改代码来适应我们在 第八章 中考虑的回归估计器,在 AWS 上使用 TensorFlow 实现深度学习。回想一下,我们使用了 LinearRegressor 来解决我们的房屋价值估计问题。为了在 GPU 之间启用分布式学习,我们需要定义一个分布策略。

最简单的是MirroredStrategy,它使用 AllReduce 技术。这种策略被实例化并作为输入提交给回归器,正如我们在下面的代码块中所示:

distribution = tf.contrib.distribute.MirroredStrategy(num_gpus=4)
config = tf.estimator.RunConfig(train_distribute=distribution)

tf_regressor = tf.estimator.LinearRegressor(
  config=config,
  optimizer=tf.train.GradientDescentOptimizer(learning_rate=0.0000001),
  feature_columns=[tf.feature_column.numeric_column('inputs', 
                                  shape=(11,))],
)

目前,分布策略支持接受学习率作为输入的GradientDescentOptimizer。此外,与我们在第八章,在 AWS 上使用 TensorFlow 实现深度学习中所做的方法相比,提供输入函数的方式需要稍作改变。在分布式处理中,输入函数需要返回我们通过pandasas_matrix()函数获得的张量创建的tf.Dataset

def training_input_fn():
  return tf.data.Dataset.from_tensor_slices(
        ({'inputs': training_df[training_features].as_matrix()},             
         training_df[label].as_matrix())).repeat(50).batch(1)

训练方式与我们在第八章,在 AWS 上使用 TensorFlow 实现深度学习中做的一样:

tf_regressor.train(input_fn=training_input_fn)

train_distributed_tensorflow.ipynb笔记本中,你可以看到完整的示例。在这个特定的玩具示例中,分布式学习并不合理。然而,它应该为读者提供一个参考,因为目前关于如何在多 CPU 环境中成功进行训练的文档和示例并不多。

通过 Apache Spark 进行分布式学习

在前面的章节中,我们展示了如何通过 Spark ML 库使用 Apache Spark 进行分布式机器学习。然而,如果你想要将 Apache Spark 与 TensorFlow 等深度学习库结合使用,则可以获得显著的好处。

数据并行化

在这个方案中,相同的迷你批次在 Spark 执行器中并行运行(类似于映射转换),权重被平均(类似于归约操作)。例如,SparkFlow (github.com/lifeomic/sparkflow)这样的工具允许我们定义一个简单的 TensorFlow 模型(例如我们在第八章,在 AWS 上使用 TensorFlow 实现深度学习)中开发的模型)并通过让 Spark 驱动器充当参数服务器来执行并行训练。通过这个库,我们可以使用作为 TensorFlow 图智能包装器的管道抽象(估计器和转换器)。同样,BigDL (bigdl-project.github.io)允许我们使用allreduce 随机梯度下降SGD)实现来分布深度学习训练。

模型并行化

在撰写本章时,没有本机库允许我们通过 Apache Spark 使用 TensorFlow 进行模型并行化。然而,Apache Spark 确实提供了一个多层感知器分类器MLPC)(spark.apache.org/docs/latest/ml-classification-regression.html#multilayer-perceptron-classifier)的实现,该实现通过 Apache Spark 实现模型并行化。与 TensorFlow 等库的强大功能相比,这种实现相对简单。例如,网络结构和激活函数是固定的。你只能定义层数和少数其他参数。尽管如此,由于你的数据管道已经在 Spark 中,这是一个开始分布式深度学习的良好方式。

分布式超参数调整

通过拥有一个 Spark 集群,可以在不同的机器上训练同一神经网络的变体。这些变体可能具有不同的超参数,甚至稍微不同的网络结构。例如,你可能想要切换特定层的激活函数。如果我们事先预定义所有这些神经网络的组合,Spark 可以通过简单的map()转换来执行。每个并行训练作业都可以返回生成的模型以及损失指标。例如,sparkdl库(github.com/databricks/spark-deep-learning)提供了执行此类任务的良好工具(特别是如果你正在处理图像)。我们将在第十五章调整集群以进行机器学习中更详细地介绍超参数调整。

分布式大规模预测

一旦我们有了序列化的模型,就可以通过将模型发送到不同的执行器并将它应用于 Spark 分布式数据来并行地进行预测。例如,sparkdl库实现了 Keras 转换器,它可以在给定 Keras 模型(如我们在第八章在 AWS 上使用 TensorFlow 实现深度学习中开发的模型)的情况下进行分布式预测。

SageMaker 中的并行化

在上一节中确定的大多数用例也可以仅通过使用SageMaker轻松解决。使用 SageMaker,我们可以启动多个实例,执行不同模型的并行训练变体。SageMaker 的许多内置算法都是设计用来执行模型并行化的,这就是为什么我们通常指定用于训练的机器数量(和类型)。此外,它还提供了高级参数调整功能,我们将在第十五章“调整集群以适应机器学习”中探讨。最后,分布式预测是通过批量转换作业完成的,例如我们在第四章“使用基于树的预测用户行为”中展示的。

摘要

在本章中,我们介绍了关于如何选择训练集群机器类型的基本考虑因素。这些考虑因素包括在成本、内存大小、计算能力和供应限制之间进行权衡。至于深度学习,我们提供了一个如何在 SageMaker 笔记本上运行分布式 TensorFlow 的具体示例,以及一些如何在 EMR 上通过 Apache Spark 进一步分布式你的深度学习管道的指南。在下一章“在 Spark 和 SageMaker 中优化模型”中,我们将深入探讨从模型准确性的角度调整我们的模型以实现最佳性能的问题。

第十四章:在 Spark 和 SageMaker 中优化模型

在 AWS 上训练的模型可以通过修改训练指令或超参数进行进一步优化。在本章中,我们将讨论读者可以使用来提高其算法性能的各种技术。

在本章中,我们将涵盖以下主题:

  • 模型优化的重要性

  • 自动超参数调整

  • Apache Spark 和 SageMaker 中的超参数调整

模型优化的重要性

很少有算法在第一次尝试就能产生优化的模型。这是因为算法可能需要数据科学家进行一些参数调整以提高其准确性或性能。例如,我们在第七章中提到的学习率,实现深度学习算法,对于深度神经网络需要手动调整。低学习率可能导致算法运行时间更长(如果我们是在云上运行,那么成本更高),而高学习率可能会错过最优的权重集。同样,具有更多层的树可能需要更多时间来训练,但可能创建一个具有更好预测能力的模型(尽管它也可能导致树过度拟合)。这些指导算法学习的参数被称为超参数,与模型参数(例如,网络的权重)不同,这些参数在整个训练过程中并没有被学习。一些超参数不仅用于优化或调整模型,还用于定义或约束问题。例如,簇的数量也被视为超参数,尽管这并不是真的关于优化,而是用于定义要解决的问题。

调整这些超参数以获得最佳性能并非易事,在许多情况下,它需要理解手头的数据以及底层算法的工作方式。那么,为什么不学习这些超参数呢?许多数据科学家使用调整这些超参数值的算法来查看它们是否会产生更准确的结果。这种方法的缺点是,我们可能会找到在测试数据集上最优的超参数,而我们可能会认为我们的模型具有更好的准确性,当我们只是过度拟合测试数据集时。因此,我们通常将数据集分为三个部分:训练数据集,用于训练模型;验证数据集,用于参数调整;测试数据集,仅在参数调整完成后用于评估模型的最终准确性。

自动超参数调整

调整超参数的最简单方法被称为网格搜索。我们为每个超参数定义我们想要尝试的不同值。例如,如果我们正在训练树,我们可能想要尝试深度为 5、10 和 15。同时,我们还想看看最佳杂质度量是否是信息增益或基尼系数。这总共创建了六种组合,需要测试以确定准确性。正如你可能预料到的,组合的数量将随着要考虑的超参数数量的指数增长。因此,其他技术被用来避免测试所有可能的组合。一种简单的方法是随机化要尝试的组合。一些组合可能会被遗漏,但一些变化将在没有归纳偏差的情况下被测试。

AWS SageMaker 提供了一个智能选择要测试的超参数的调优服务。在网格搜索和随机化中,每次训练运行都不会使用先前运行中获得的准确性信息。SageMaker 使用一种称为贝叶斯优化的技术,能够根据先前测试组合的准确性值选择下一组要测试的超参数组合。该算法背后的主要思想是在超参数空间上构建一个概率分布。每次我们获得给定组合的准确性时,概率分布都会调整以反映新的信息。成功的优化将利用已知组合的信息,这些组合产生了良好的准确性,以及对新组合的充分探索,这些新组合可能导致潜在改进。你会欣赏这是一个极其困难的问题,因为每次训练运行都很慢,可能也很昂贵。我们通常负担不起测试太多的组合。

Apache Spark 中的超参数调整

回想一下我们来自第三章的回归问题,预测房价的回归算法,其中我们构建了一个线性回归来估计房屋的价值。在那个阶段,我们为超参数使用了一些任意值。

在下面的代码块中,我们将展示 Apache Spark 如何测试elasticNetParamregParamsolver的 18 种不同的超参数组合:

from pyspark.ml.tuning import CrossValidator, ParamGridBuilder
from pyspark.ml import Pipeline

linear = LinearRegression(featuresCol="features", labelCol="medv")
param_grid = ParamGridBuilder() \
  .addGrid(linear.elasticNetParam, [0.01, 0.02, 0.05]) \
  .addGrid(linear.solver, ['normal', 'l-bfgs']) \
  .addGrid(linear.regParam, [0.4, 0.5, 0.6]).build()

pipeline = Pipeline(stages=[vector_assembler, linear])
crossval = CrossValidator(estimator=pipeline,
                        estimatorParamMaps=param_grid,
                        evaluator=evaluator,
                        numFolds=10)
optimized_model = crossval.fit(housing_df)

我们将像往常一样开始构建我们的分类器,不提供任何超参数。我们将回归器存储在linear变量中。接下来,我们通过定义一个参数网格来定义要测试的每个超参数的不同值。设置值的方法的函数引用被传递给ParamGridBuilder,它负责保持要测试的组合。

与往常一样,我们可以使用任何预处理阶段(在这种情况下,我们使用向量组装器)来定义我们的管道。CrossValidator接受管道、参数网格和评估器。回想一下,评估器被用来使用测试数据集获得一个特定的分数:

evaluator = RegressionEvaluator(labelCol="medv", predictionCol="prediction", metricName="r2")

在这种情况下,我们将使用与第三章,使用回归算法预测房价中相同的 R2 指标。CrossValidator在调用fit()时将运行所有组合,并找到实现最高 R2 值的超参数。

一旦完成,我们可以通过访问optimized_model.bestModel引用来检查底层最佳模型。通过它,我们可以展示在最佳模型中实际使用的超参数集:

[(k.name, v) for (k, v) in optimized_model.bestModel.stages[1].extractParamMap().items()]

上述语句的输出如下:

[('epsilon', 1.35),
('featuresCol', 'features'),
('predictionCol', 'prediction'),
('loss', 'squaredError'),
('elasticNetParam', 0.02),
('regParam', 0.6),
('maxIter', 100),
('labelCol', 'medv'),
('tol', 1e-06),
('standardization', True),
('aggregationDepth', 2),
('fitIntercept', True),
('solver', 'l-bfgs')]

然而,比实际使用的参数更有趣的是看到不同组合测试中的准确度变化。optimized_model.avgMetrics值将显示所有 18 个超参数组合的准确度值:

[0.60228046689935, 0.6022857524897973, ... 0.6034106428627964, 0.6034118340373834]

我们可以使用CrossValidator返回的optimized_model来使用最佳模型进行预测,因为它也是一个转换器:

_, test_df = housing_df.randomSplit([0.8, 0.2], seed=17)
evaluator.evaluate(optimized_model.transform(test_df))

在这种情况下,我们得到了一个 R2 值为 0.72,这比我们在第三章中,使用回归算法预测房价所得到的任意超参数集要好一些。

SageMaker 中的超参数调整

正如我们在上一节中提到的,自动超参数调整,SageMaker 有一个使用贝叶斯优化的智能参数调整库。在本节中,我们将展示如何进一步调整我们在第四章,使用基于树的预测用户行为中创建的模型。回想一下,在那个章节中,我们提出了一个二元分类问题,试图预测用户是否会点击广告。我们使用了xgboost模型,但在那个阶段我们还没有进行任何参数调整。

我们将首先创建 SageMaker 会话并选择xgboost:

import boto3
import sagemaker
from sagemaker import get_execution_role

sess = sagemaker.Session()
role = get_execution_role()
container = sagemaker.amazon.amazon_estimator.get_image_uri('us-east-1', "xgboost", "latest")

s3_validation_data = 's3://mastering-ml-aws/chapter4/test-vector-csv/'
s3_train_data = 's3://mastering-ml-aws/chapter4/training-vector-csv/'
s3_output_location = 's3://mastering-ml-aws/chapter14/output/'

接下来,我们定义估计器,就像我们在第四章使用基于树的预测用户行为中所做的那样:

sagemaker_model = sagemaker.estimator.Estimator(container,
                                                role,
                                                train_instance_count=1,
                                                train_instance_type='ml.c4.4xlarge',
                                                train_volume_size=30,
                                                train_max_run=360000,
                                                input_mode='File',
                                                output_path=s3_output_location,
                                                sagemaker_session=sess)

sagemaker_model.set_hyperparameters(objective='binary:logistic',
                                    max_depth=5,
                                    eta=0.2,
                                    gamma=4,
                                    min_child_weight=6,
                                    subsample=0.7,
                                    silent=0,
                                    num_round=50)

正如我们总是对 SageMaker 服务调用所做的那样,我们定义了训练和验证输入数据的位置和格式:

train_data = sagemaker.session.s3_input(s3_train_data, distribution='FullyReplicated',
                                        content_type='text/csv', s3_data_type='S3Prefix')

validation_data = sagemaker.session.s3_input(s3_validation_data, distribution='FullyReplicated',
                                             content_type='text/csv', s3_data_type='S3Prefix')

data_channels = {'train': train_data, 'validation': validation_data}

在定义了基础估计器和确定了输入数据后,我们现在可以构建一个训练作业,该作业将使用这个估计器,并运行一系列的训练作业,以改变超参数:

from sagemaker.tuner import HyperparameterTuner, ContinuousParameter,IntegerParameter

tree_tuner = HyperparameterTuner
(estimator=sagemaker_model, 
                              objective_metric_name='validation:auc',
max_jobs=10,
max_parallel_jobs=3,
hyperparameter_ranges={'lambda': 
ContinuousParameter(0, 1000),
                                                               'max_depth': IntegerParameter(3,7),
                                                       'eta':ContinuousParameter(0.1, 0.5)})

tree_tuner.fit(inputs=data_channels, logs=True)

SageMaker:创建名为xgboost-190407-1532的超参数调整作业

第一步是创建一个HyperparameterTuner实例,在其中我们设置以下内容:

  • 基础估计器,超参数将在其上变化。

  • 目标指标,它将被用来找到最佳的超参数组合。由于我们处理的是一个二元分类问题,在验证数据上使用曲线下面积指标是一个不错的选择。

  • 我们希望为每个超参数测试的不同范围。这些范围可以使用ContinuousParameter为连续变化的参数指定,或者使用IntegerParameterCategoricalParameter为离散参数指定。

  • 要运行的作业数量,以及并行运行的最大作业数量。这里在准确性和速度之间有一个权衡。您运行的并行作业越多,用于告知下一组尝试的超参数的数据就越少。这会导致范围搜索次优。然而,它将更快地完成调整。在这个例子中,我们只运行了 10 个作业。我们通常希望运行更多的作业以获得显著的改进。在这里,我们只展示一个低值,以便读者可以快速获得结果。

可以通过 AWS 控制台(console.aws.amazon.com/sagemaker/home?region=us-east-1#/hyper-tuning-jobs)或通过 Python SDK 中的方法来监控拟合过程,我们可以看到作业的状态。

一旦完成,AWS 控制台应该看起来像下面的截图;在其中,您可以查看已运行的不同的作业和获得的不同性能指标:

让我们使用 SDK 检查哪个训练作业产生了最佳性能。首先,要找到最佳作业的名称:

tree_tuner.best_training_job()

'xgboost-190407-1342-001-5c7e2a26'

使用会话对象中的方法,我们可以显示最优训练作业的超参数值:

sess.sagemaker_client.describe_training_job(TrainingJobName=tree_tuner.best_training_job())

前一个 describe 命令的输出如下:

{'TrainingJobName': 'xgboost-190407-1532-005-0e830ada',
 'TrainingJobArn': 'arn:aws:sagemaker:us-east-1:095585830284:training-job/xgboost-190407-1532-005-0e830ada',
 'TuningJobArn': 'arn:aws:sagemaker:us-east-1:095585830284:hyper-parameter-tuning-job/xgboost-190407-1532',
 'ModelArtifacts': {'S3ModelArtifacts': 's3://mastering-ml-aws/chapter14/output/xgboost-190407-1532-005-0e830ada/output/model.tar.gz'},
 'TrainingJobStatus': 'Completed',
 'SecondaryStatus': 'Completed',
 'HyperParameters': {'_tuning_objective_metric': 'validation:auc',
 'eta': '0.4630125855085939',
 'gamma': '4',
 'lambda': '29.566673825272677',
 'max_depth': '7',
 'min_child_weight': '6',
 'num_round': '50',
 'objective': 'binary:logistic',
 'silent': '0',
 'subsample': '0.7'},....}

使用describe_hyper_parameter_tuning_job()方法,我们还可以获取最优 AUC 指标的最终值:

sess.sagemaker_client.describe_hyper_parameter_tuning_job(HyperParameterTuningJobName='xgboost-190407-1532')

以下输出是前一个命令的结果:

{'HyperParameterTuningJobName': 'xgboost-190407-1532',
 'HyperParameterTuningJobArn': 'arn:aws:sagemaker:us-east-1:095585830284:hyper-parameter-tuning-job/xgboost-190407-1532',
 'HyperParameterTuningJobConfig': {'Strategy': 'Bayesian',
 'HyperParameterTuningJobObjective': {'Type': 'Maximize',
 'MetricName': 'validation:auc'},
 'ResourceLimits': {'MaxNumberOfTrainingJobs': 10,
 'MaxParallelTrainingJobs': 3},
 ....
 'FinalHyperParameterTuningJobObjectiveMetric': {'MetricName': 'validation:auc',
 'Value': 0.6545940041542053},
 '
 ...}

您应该探索完整的 API 和 Python SDK,以获取有关自动调整的完整功能集和选项。请查看:github.com/aws/sagemaker-python-sdk 我们希望这个介绍能帮助您开始了解如何微调模型。

摘要

在本章中,我们介绍了通过超参数优化进行模型调整的重要性。我们提供了在 Apache Spark 中进行网格搜索的示例,以及如何使用 SageMaker 的高级参数调整。

在下一章中,我们将专注于优化硬件和集群设置,这是我们训练和应用模型的基础。模型优化和硬件优化对于成功且成本效益高的 AI 流程都至关重要。

练习

  1. 关于寻找最佳超参数的方法,比较网格搜索、随机搜索和贝叶斯优化在超参数调整中的应用的优缺点。

  2. 为什么我们在进行超参数调整时通常需要三个数据分割?

  3. 您认为哪个指标最适合我们的xgboost示例:validation:auc还是training:auc

第十五章:为机器学习调整集群

许多数据科学家和机器学习实践者在尝试在大数据上运行 ML 数据管道时都会遇到规模问题。在本章中,我们将主要关注弹性 MapReduceEMR),这是一个运行非常大的机器学习作业的非常强大的工具。配置 EMR 有许多方法,并不是每个设置都适用于每个场景。在本章中,我们将概述 EMR 的主要配置以及每种配置如何针对不同的目标工作。此外,我们将介绍 AWS Glue 作为我们的大数据管道结果编目工具。

在本章中,我们将涵盖以下主题:

  • EMR 架构简介

  • 为不同应用调整 EMR

  • 使用 Glue 管理数据管道

EMR 架构简介

在第四章 使用基于树的预测用户行为 中,我们介绍了 EMR,这是一个 AWS 服务,允许我们运行和扩展 Apache Spark、Hadoop、HBase、Presto、Hive 和其他大数据框架。这些大数据框架通常需要运行特定软件的机器集群,这些机器配置正确,以便机器能够相互通信。让我们看看 EMR 中最常用的产品。

Apache Hadoop

许多应用程序,例如 Spark 和 HBase,都需要 Hadoop。Hadoop 的基本安装包含两个主要服务:

  • Hadoop 分布式文件系统HDFS):这是一个允许我们在多个服务器上存储大量数据(例如,无法存储在单个机器上的文件)的服务。NameNode 服务器负责索引哪个文件的哪些块存储在哪个服务器上。每个文件的块在集群中复制,这样如果一台机器出现故障,我们不会丢失任何信息。DataNode 服务器负责在每个机器上保持和提供数据。许多其他 EMR 服务,如 Apache HBase、Presto 和 Apache Spark,能够使用 HDFS 来读取和写入数据。当您使用长期运行的集群时,HDFS 运行良好。对于仅为了执行单个作业(例如训练作业)而启动的集群,您应考虑使用 S3 进行数据存储。

  • MapReduce:这个框架多年来一直是大数据处理的基础。通过允许用户指定两个函数(一个 map 函数和一个 reduce 函数),许多大数据工作负载得以实现。map 函数负责将数据块取出来并以一对一的方式进行转换(例如,获取每笔交易的价格)。reduce 函数接收 map 函数的输出并以某种方式聚合它(例如,找出每个地区的平均交易价格)。MapReduce 被设计成在存储 HDFS 文件块的同一台机器上执行处理,以避免在网络中传输大量数据。这种数据本地性原则被证明对于在通用硬件上运行大数据作业以及有限的网络速度下运行大数据作业非常有效。

EMR 允许您创建包含三种类型节点的集群:

  • 主节点:这是集群中唯一的节点,通常负责协调集群中其他节点的作业。

  • 核心节点:这类节点将托管 HDFS 块并运行 DataNode 服务器,因此在这些节点上运行的作业可以利用数据本地性。

  • 任务节点:这些节点不托管 HDFS 块,但可以运行任意作业任务。在这些节点上运行的作业将需要从其他机器上托管(例如,核心节点或 S3 服务器)的文件系统中读取数据。

Apache Spark

Apache Spark 是最受欢迎的大数据框架之一。它通过允许用户在数据之上指定额外的函数来扩展 MapReduce 的概念。它不仅可以执行 map 和 reduce 函数,还支持过滤、分组、连接、窗口函数以及许多其他操作。此外,正如我们在整本书中看到的那样,我们可以使用 SQL 操作来执行 ETL 和分析。Apache Spark 被设计用来在内存中缓存大量数据以加速需要多次遍历数据的算法。例如,需要多次迭代 梯度下降 的算法如果数据集在内存中缓存,可以运行得快得多。

Apache Spark 还附带了一些非常实用的库,用于流处理、图操作以及我们在本书中使用的机器学习库。我们鼓励你探索这些额外的库,因为它们质量极高且非常有用。Spark 的独特之处在于它无缝地整合了许多成熟的库,例如 TensorFlow 和 scikit-learn。你可以使用这两个工具构建出色的模型,但它们目前不允许我们像 Spark 那样通过在集群中并行化工作来读取和准备数据。换句话说,Apache Spark 提供了从数据摄入到模型生成的完整堆栈的包。有些人将 Spark 称为大数据的操作系统。通常,数据科学家和工程师使用 Spark 进行大规模的数据准备,然后使用其他工具,如 TensorFlow 和 SageMaker 来构建和部署专门的模型。在 第五章 中,我们看到了如何通过使用 SageMaker Spark 估计器来平滑地整合 Apache Spark 和 SageMaker。

Apache Hive

Apache Hive 最初作为一个从 SQL 到 MapReduce 作业的翻译器诞生。你可以指定 数据定义语言 (DDL) 和 数据操作语言 (DML) 语句,并像使用 Apache Hive 一样在标准数据库管理系统上工作。当 Hive 首次出现时,许多了解 SQL 的非技术用户能够进行大规模的分析,这是其受欢迎的原因之一。Hive(以及 Spark SQL)内部发生的事情是,SQL 语句被解析,并动态构建一系列 MapReduce 作业,在集群上运行以执行 SQL 语句描述的声明性操作。

Presto

Presto 是由 Facebook 开发的一个产品,它也把 SQL 转换为大数据工作负载,但专为交互式分析而定制。它非常快,并且特别针对当你有一个大型事实表和几个小维度表(如交易和其他连接表,如产品和客户)时进行了优化。AWS 提供了一个基于 Presto 的无服务器替代方案,称为 Athena,当你的数据在 S3 上时,它非常出色。Athena 查询的收费基于扫描的数据量。因此,它已成为大数据分析中非常受欢迎的工具。

Apache HBase

HBase 是一个类似于 Google Bigtable 的产品。从概念上讲,它可以被视为一个巨大的分布式键值存储。由于 AWS DynamoDB 等技术的出现,HBase 的受欢迎程度已经不再那么高,后者是无服务器的,根据我们的经验,更加可靠。然而,当你需要通过键访问数据时,它可能是一个成本效益高的存储数据的方式。例如,你可以使用 HBase 存储每个用户的自定义模型(假设你有数十亿用户来证明这一点)。

另一个资源协商者

Apache Hadoop 还开发了另一个资源协调器YARN),这是 EMR 调度和协调不同应用的基本工具。YARN 实际上是 EMR 背后的集群管理器,负责在不同的机器上启动必要的守护进程。当你通过 EMR 配置集群时,你可以指定你想要运行的不同应用。这类应用的例子包括 Spark、HBase 和 Presto。YARN 负责启动必要的进程。在 Spark 的情况下,YARN 将根据需要启动 Spark 执行器和驱动器。这些进程将必要的内存和 CPU 消耗报告给 YARN。这样,YARN 可以确保集群负载得到适当管理,不会过载。

调整 EMR 以适应不同的应用

在本节中,我们将考虑调整我们用于机器学习的集群所涉及到的方面。当你启动一个 EMR 集群时,你可以指定你想要运行的不同应用。

以下截图显示了 EMR 版本 5.23.0 中可用的应用:

在启动 EMR 集群后,以下是需要配置的最相关项目:

  • 应用: 例如 Spark 应用。

  • 硬件: 我们在第十章中介绍了这一点,在 AWS 上创建集群

  • 使用 Glue 数据目录: 我们将在本章的最后部分介绍,使用 Glue 管理数据管道)。

  • 软件配置: 这些是我们可以指定以配置特定应用属性的属性。在下一节,配置应用属性中,我们将展示如何通过特定属性来定制 Spark 的行为。

  • 引导操作: 这些是用户特定的脚本(通常位于 S3),在集群启动时会运行在每个节点上。引导操作在例如你希望在集群启动时在所有机器上安装特定软件包时非常有用。

  • 步骤: 这些是在应用启动后用户想要运行的不同作业。例如,如果我们想要启动一个运行 Spark 训练作业的集群,然后我们想要关闭集群,我们就会指定一个 Spark 作业步骤,并在最后一步完成后选择自动终止集群选项。这种用例在通过 AWS API 程序化启动集群时是相关的。计划或事件驱动的 AWS Lambda 函数可以使用boto3等库在事件发生或定期计划时程序化地启动集群。有关 AWS Lambda 的更多信息,请参阅docs.aws.amazon.com/lambda/

配置应用属性

在前面的屏幕截图中,您可能已经注意到有一个名为 软件设置 的空间,用于自定义不同应用程序的配置。有不同的配置类别,称为 分类,允许您通过更改所选属性集的值来覆盖不同应用程序的默认配置。

在以下代码块中,我们提供了一组非常有用的属性来配置 Spark,用于两个目的:最大化资源分配并启用 AWS Glue 元数据存储:

classification=spark,properties=[maximizeResourceAllocation=true]
classification=spark-defaults,properties=[spark.sql.catalogImplementation=hive]
classification=spark-hive-site,properties=[hive.metastore.connect.retries=50,hive.metastore.client.factory.class=com.amazonaws.glue.catalog.metastore.AWSGlueDataCatalogHiveClientFactory]

让我们看看每个这些配置的效果。

最大化资源分配

当您启用 maximizeResourceAllocation 时,EMR 和 Spark 将确定如何配置 Spark 以使用所有可用资源(例如,内存和 CPU)。另一种选择是手动配置属性,如执行器的数量、每个执行器的 Java 堆空间以及每个执行器的核心数(即线程数)。如果您选择手动进行此操作,您需要非常小心,不要超过集群的可用资源(并且也不要未充分利用可用硬件)。我们建议始终默认设置此设置。

AWS Glue 目录

AWS Glue 提供了一种称为 Hive 元数据存储的服务。此服务的目的是通过定义描述数据的表来跟踪我们数据湖中的所有数据。数据湖通常托管在 S3 或 HDFS 上。任何位于这些分布式文件系统上的数据,且具有表格格式,如 Parquet 或 CSV,都可以添加到元数据存储中。这不会复制或移动数据;它只是保持所有数据目录的一种方式。通过在集群配置中配置 hive.metastore.client.factory.class 属性,我们允许 Spark 使用 Glue 目录中注册的所有表。此外,Spark 还可以通过 Spark SQL 语句创建新表或修改目录。在下一节中,我们将展示 Glue 如何有用的具体示例。

使用 Glue 管理数据管道

数据科学家和数据工程师运行不同的作业来转换、提取和加载数据到系统,如 S3。例如,我们可能有一个每日作业处理文本数据,并存储一个包含我们第二章中看到的词袋表示法的表,即 使用朴素贝叶斯分类 Twitter 流。我们可能希望每天更新该表以指向最新的可用数据。上游过程可以仅依赖于表名来查找和处理数据的最新版本。如果我们没有正确地编目这些数据,将非常难以合并不同的数据源,甚至不知道数据在哪里,这就是 AWS Glue 元数据存储发挥作用的地方。Glue 中的表被分组到数据库中。然而,不同数据库中的表可以连接和引用。

使用 Glue 创建表

您可以通过访问console.aws.amazon.com/glue/home?region=us-east-1#catalog:tab=databases来访问 AWS 上的 Glue 控制台。

在控制台中,创建一个新的数据库,如下面的屏幕截图所示:

图片

一旦创建了数据库,您就可以切换到 Athena AWS 服务,并开始从 S3 中的数据创建表以运行查询分析。AWS Athena 控制台可以通过console.aws.amazon.com/athena/home访问。

让我们在 S3 中为我们在第三章,“使用回归算法预测房价”中工作的波士顿房价数据集创建一个表。

在下面的屏幕截图中,我们可以看到创建表的 SQL 语句将指定来自 S3 中 CSV 数据的表名、格式和字段:

图片

注意,位置指定了一个文件夹(而不是一个文件)。在我们的例子中,我们在s3://mastering-ml-aws/chapter3/linearmodels/train/training-housing.csv有一个单独的CSV文件夹。然而,我们可以在同一个文件夹中有许多 CSV 文件,并且所有这些都会链接到我们刚刚创建的house_prices表。一旦我们创建了表,由于数据在 S3 上,我们就可以开始如下查询我们的表:

图片

注意数据是如何正确分表的。这是因为我们告诉 Glue 了数据的正确格式和位置。现在我们可以通过 Athena 使用 Presto-as-a-service 通过 SQL 进行超快速分析。

我们刚刚执行了一个创建表的操作;然而,通常我们想要执行更改表命令来将表背后的数据切换到更近的版本。执行添加分区操作以增量地向表中添加数据(如新批次或日期)也是非常常见的。分区也有助于查询引擎更有效地过滤数据。

在 Spark 中访问 Glue 表

一旦创建的表在 Glue 中,它也将可在每个 EMR Spark 集群中可用(只要我们配置了前述章节中描述的 hive.metastore.client.factory.class,即 调整 EMR 以适应不同应用)。让我们启动一个启用了 JupyterHub 应用的 EMR 集群。JupyterHub 应用是 第二章,使用朴素贝叶斯分类 Twitter 流,到 第六章,分析访问模式以生成推荐 中使用的 EMR 笔记本功能的替代品。当您有一组数据科学家在重用同一集群并运行不同的笔记本时,请考虑使用 JupyterHub。您可以在 docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-jupyterhub.html 上了解更多关于 JupyterHub 的信息。

以下截图显示了启用 Glue 元数据存储和 JupyterHub 作为应用的我们创建的集群:

图片

如果您点击 JupyterHub 链接,它将带您到一个认证页面,如下所示:

图片

JupyterHub 的默认配置有一个默认用户账户,用户名为 jovyan,密码为 jupyter。如果需要,可以通过 EMR 配置自定义认证。

认证后,我们可以像使用 EMR 笔记本一样开始创建笔记本。在这种情况下,我们将创建一个 PySpark3 笔记本:

图片

现在,笔记本可以使用 SparkMagic 在 Python 和 SQL 中交错段落。让我们看看以下笔记本示例:

图片

第一段通过 Glue/Athena 通过 SparkMagic 的 %%sql 魔法在刚刚创建的表上运行 SQL(有关 SparkMagic 的更多信息,请参阅 github.com/jupyter-incubator/sparkmagic)。第二段通过一个简单的 SQL 语句从我们的表中选择两个字段来构建 Spark DataFrame。第三段在我们的 Spark DataFrame 上运行 Spark 作业(即 describe 命令)。您将欣赏到,一旦我们在 Glue 元数据存储中正确编目了数据,处理、集成和处理数据是多么容易。

摘要

在本章中,我们探讨了 EMR 的主要配置参数以及它们如何帮助我们运行许多大数据框架,如 Spark、Hive 和 Presto。我们还探讨了 AWS 服务 Athena 和 Glue,作为在数据湖中编目数据的方式,以便我们能够正确同步我们的数据管道。最后,我们展示了 Glue 如何在 EMR 中使用,以及 JupyterHub 与 SparkMagic 的无缝集成。

在下一章* 在 AWS 中构建的模型部署*中,我们将介绍如何在不同的环境中部署机器学习模型。

第十六章:在 AWS 中构建的模型部署

到目前为止,我们在 AWS 中构建了模型,并希望将它们部署到生产环境中。我们知道模型应该部署在不同的环境中。在某些情况下,这就像生成一个 CSV 文件,其中包含将被输入到某个系统中的操作一样简单。通常我们只需要部署一个能够进行预测的 Web 服务。然而,在某些特殊情况下,我们需要将这些模型部署到复杂、低延迟或边缘系统中。在本章中,我们将探讨将机器学习模型部署到生产环境的不同方法。

在本章中,我们将涵盖以下主题:

  • SageMaker 模型部署

  • Apache Spark 模型部署

SageMaker 模型部署

在第二章,使用朴素贝叶斯分类 Twitter 流,我们使用 SageMaker 部署了我们的第一个模型。在那个阶段,我们已经使用BlazingText训练了我们的分类器,并将其存储在一个名为bt_model的变量中。要部署模型,我们只需调用deploy方法,并指定要使用的机器数量和类型:

bt_model.deploy(initial_instance_count = 1,instance_type = 'ml.m4.xlarge')

SageMaker 可以在实例数量之间平衡对端点的请求,并根据服务负载自动扩展或缩减。详细信息请参阅docs.aws.amazon.com/sagemaker/latest/dg/endpoint-auto-scaling.html

一旦我们调用deploy方法,一个端点应该出现在 AWS SageMaker 控制台中的console.aws.amazon.com/sagemaker。以下截图显示了我们的 BlazingText 示例端点:

通过在控制台中点击端点,我们可以找到更多详细信息:

特别是,我们可以看到端点有一个特定的 URL,服务就在那里托管。如果我们尝试通过 HTTP 工具,如curl直接调用此 URL,我们会得到以下结果:

curl -X POST \
> https://runtime.sagemaker.us-east-1.amazonaws.com/endpoints/blazingtext-endpoint-2019-01-04-01/invocations \
> -H 'cache-control: no-cache' \
> -H 'content-type: application/json' \
> -H 'postman-token: 7hsjkse-f24f-221e-efc9-af4c654d677a' \
> -d '{"instances": ["This new deal will be the most modern, up-to-date, and balanced trade agreement in the history of our country, with the most advanced protections for workers ever developed"]}'

{"message":"Missing Authentication Token"}

这是因为向 SageMaker 端点发出的每个请求都必须正确签名以确保身份验证。只有具有调用 Amazon SageMaker InvokeEndpoint API 角色权限的用户才能允许调用 SageMaker 端点。为了让 SageMaker 背后的 HTTP 服务能够识别和验证调用者,HTTP 请求需要正确签名。有关签名请求的更多信息,请参阅docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html。如果我们想公开我们的模型端点,请求签名的替代方案是创建一个 AWS 中的 lambda 函数,并通过 API 网关公开它。有关如何做到这一点的更多信息,请参阅docs.aws.amazon.com/sagemaker/latest/dg/getting-started-client-app.html

幸运的是,如果我们从 AWS 实例内部调用端点,我们可以通过使用sagemaker库来避免手动签名请求。让我们回顾一下如何进行此类调用。

如同往常,我们首先导入必要的 Python 库:

import sagemaker
from sagemaker import get_execution_role

sess = sagemaker.Session()
role = get_execution_role()

接下来,如果我们知道端点的名称,我们可以创建一个RealTimePredictor实例来进行实时预测:

from sagemaker.predictor import json_serializer, RealTimePredictor

predictor = RealTimePredictor(endpoint='blazingtext-endpoint-2019-01-04-01', serializer=json_serializer)

在这个例子中,我们使用的是json_serializer,这是一种方便且易于阅读的格式。要调用端点,我们只需调用predict()方法:

predictor.predict({"instances": ["This new deal will be the most modern, up-to-date, and balanced trade agreement in the history of our country, with the most advanced protections for workers ever developed"]})

这里是输出:

b'[{"prob": [0.5000401735305786], "label": ["__label__1"]}]'

你可以回到第二章,使用朴素贝叶斯分类 Twitter 流,来解释这个输出,但这里的重要点是RealTimePredictor实例代表我们完成了所有适当的身份验证、请求签名和端点调用。

除了端点的 URL 和基本信息外,AWS 控制台还显示了端点配置:

图片

通过配置,我们可以跟踪从这个端点起源的模型和训练作业。让我们点击链接来检查起源模型。然后我们得到以下屏幕:

图片

在模型描述中,我们可以找到诸如模型的 S3 位置等详细信息。这种模型序列化针对每种类型的模型都是特定的。在第四章使用基于树的预测用户行为中,我们看到了这种模型的格式方便地采用了xgboost pickle 序列化兼容格式。

你可能也注意到了,这个模型关联了一个图像。SageMaker 在 Amazon 弹性容器注册库ECR)中创建了一个托管此模型的机器的镜像。通常这些是底层的 Docker 镜像。

以下链接是一个关于部署内部工作原理以及 SageMaker 中容器化工作方式的优秀资源:sagemaker-workshop.com/custom/containers.html

Apache Spark 模型部署

Apache Spark 没有像 SageMaker 那样提供直接将模型作为端点暴露的现成方法。然而,有简单的方法可以使用 Spark ML 包的序列化和反序列化功能在标准网络服务上加载 Spark 模型。在本节中,我们将展示如何部署我们在第三章中创建的模型,即使用回归算法预测房屋价值,通过一个简单的端点提供预测。为此,我们将保存一个训练好的模型到磁盘,以便我们可以将该模型发送到通过端点提供模型的机器。

我们首先开始训练我们的模型。在第三章中,使用回归算法预测房屋价值,我们将房屋数据加载到一个 dataframe 中:

housing_df = sql.read.csv(SRC_PATH + 'train.csv', 
                          header=True, inferSchema=True)

为了简化这个例子,我们将使用一组减少的特征来构建一个作为端点公开的模型。在所有特征中,我们将只选择三个训练特征(crimznindus):

reduced_housing_df = housing_df.select(['crim', 'zn', 'indus', 'medv'])

你可能还记得medv是实际房屋价值(这是我们试图预测的值)。现在我们有了我们的 dataframe,我们可以创建一个pipeline,就像我们之前做的那样:

from pyspark.ml import Pipeline
from pyspark.ml.regression import LinearRegression
from pyspark.ml.feature import VectorAssembler

training_features = ['crim', 'zn', 'indus']
vector_assembler = VectorAssembler(inputCols=training_features,           
               outputCol="features")
linear = LinearRegression(featuresCol="features", labelCol="medv")
pipeline = Pipeline(stages=[vector_assembler, linear])
model = pipeline.fit(reduced_housing_df)

使用模型实例,我们可以通过调用save()方法将其保存到磁盘:

model.save("file:///tmp/linear-model")

这种序列化模型表示可以发送到我们想要提供预测的位置(例如,一个网络服务器)。在这种情况下,我们可以通过调用PipelineModel.load()静态方法来重新加载模型,如下所示:

from pyspark.ml import PipelineModel
loaded_model = PipelineModel.load('/tmp/linear-model')

让我们使用这个模型来获取我们减少的数据集的前几行的预测:

loaded_model.transform(reduced_housing_df.limit(3)).show()

前述命令的输出如下:

+-------+----+-----+----+-------------------+------------------+
| crim  | zn |indus|medv| features          | prediction       |
+-------+----+-----+----+-------------------+------------------+
|0.00632|18.0| 2.31|24.0|[0.00632,18.0,2.31]|27.714445239256854|
|0.02731| 0.0| 7.07|21.6| [0.02731,0.0,7.07]|24.859566163416336|
|0.03237| 0.0| 2.18|33.4| [0.03237,0.0,2.18]| 26.74953947801712|
+-------+----+-----+----+-------------------+------------------+

看看pipeline模型是如何从原始 CSV 文件开始,应用管道中的所有转换步骤,最终完成预测。当然,从我们的训练数据集中获取预测并不那么有趣。从现实的角度来看,在提供预测的端点上,我们希望接收我们三个特征的所有可能值并获得预测。在撰写本文时,Apache Spark 只能根据 dataframe 获取预测。因此,每次我们想要为几个值获取预测时,我们需要构建一个 dataframe,即使我们只需要找到单行数据的预测。

假设我们想要找到以下特征组合的预测:crim=0.00632zn=18.0indus=2.31。第一步是定义我们特征的架构,因为 Spark 期望 dataframe 的格式与训练时使用的格式完全相同。

我们定义了以下模式:

from pyspark.sql.types import *

schema = StructType([StructField('crim', DoubleType(), True),
                    StructField('zn', DoubleType(), True),
                    StructField('indus', DoubleType(), True)])

在前面的模式定义中,我们放置了每个字段的名称和类型。有了这个模式,我们可以构建一个包含我们感兴趣的特征值的单行 dataframe:

from pyspark.sql import Row

predict_df = 
sql.createDataFrame([Row
(crim=0.00632, zn=18.0,
indus=2.31)],
schema=schema)

这就是 dataframe 的样式:

+-------+----+-----+
| crim  | zn |indus|
+-------+----+-----+
|0.00632|18.0| 2.31|
+-------+----+-----+

使用这个简短的 dataframe 和加载的模型,我们可以为我们任意特征获得预测:

loaded_model.transform(predict_df).show()

以下是前一个命令的输出:

+-------+----+-----+-------------------+------------------+
| crim  | zn |indus| features          |        prediction|
+-------+----+-----+-------------------+------------------+
|0.00632|18.0| 2.31|[0.00632,18.0,2.31]|27.714445239256854|
+-------+----+-----+-------------------+------------------+

因此,考虑到前面的想法,我们如何构建一个能够提供这个模型的服务端点?最简单的方法是使用允许我们轻松在任何选择的机器上暴露端点的包,例如 Flask。有关 Flask 的详细信息,请参阅flask.pocoo.org。要运行一个 flask 网络服务,我们只需编写一个 Python 文件,该文件知道如何响应不同的端点请求。在我们的案例中,我们将只创建一个端点,根据我们三个特征的值提供预测。我们将实现一个简单的GET端点,其中三个特征将作为 URL 参数传递。

在本地主机上运行时调用服务的命令如下:

curl 'http://127.0.0.1:5000/predict?crim=0.00632&zn=18.0&indus=2.31'

这是服务的输出:

27.71

要在机器上启动 flask 服务,执行以下三个步骤:

  1. 创建一个 Python 文件,指定如何响应端点。我们将把这个文件命名为deploy_flask.py

  2. FLASK_APP环境变量设置为指向我们刚刚创建的 Python 文件。

  3. 运行flask run命令。

deploy_flask.py中,我们将关于如何加载模型和为预测构建 dataframe 的前面想法整合在一起:

from flask import Flask
from flask import request
from pyspark.ml import PipelineModel
from pyspark.sql import Row
from pyspark.sql.types import *
from pyspark.sql import SQLContext
from pyspark.context import SparkContext

sc = SparkContext('local', 'test')
sql = SQLContext(sc)
app = Flask(__name__)
loaded_model = PipelineModel.load('/tmp/linear-model')

schema = StructType([StructField('crim', DoubleType(), True),
                    StructField('zn', DoubleType(), True),
                    StructField('indus', DoubleType(), True)])

@app.route('/predict', methods=['GET'])
def predict():
   crim = float(request.args.get('crim'))
   zn = float(request.args.get('zn'))
   indus = float(request.args.get('indus'))
   predict_df = sql.createDataFrame([Row(crim=crim, zn=zn, indus=indus)],schema=schema)
   prediction = loaded_model.transform(predict_df).collect()[0].prediction
   return str(prediction)

deploy_flask.py文件中,唯一的新部分是 flask 应用的初始化和predict方法的定义,其中我们提取了作为 URL 参数授予的三个特征。接下来,我们设置提到的环境变量并运行服务:

export FLASK_APP=deploy_flask.py
flask run

在日志中,你可以看到服务和 Spark 的初始化过程,以及调用服务的操作:

* Serving Flask app "deploy_flask.py"
* Environment: production
  WARNING: Do not use the development server in a production environment.
  Use a production WSGI server instead.
* Debug mode: off
Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties
Setting default log level to "WARN".
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [13/Apr/2019 19:13:03] "GET /predict?crim=0.00632&zn=18.0&indus=2.31 HTTP/1.1" 200 -

如 flask 日志所述,如果你在考虑严肃的生产负载,考虑在 WSGI 服务器后面运行 flask。更多关于这方面的信息可以在 flask 文档中找到。

SageMaker 也能够托管任何任意模型。为此,我们需要创建一个响应两个端点的 Docker 镜像:/ping/invocations。就这么简单。在我们的案例中,/invocations端点将使用加载的 Spark 模型来响应预测。一旦 Docker 镜像创建完成,我们需要将其上传到 AWS ECR。一旦它被加载到 ECR 上,我们只需提供 ECR 镜像标识符就可以创建一个 SageMaker 模型。

在 AWS 控制台(或通过 API)中,选择创建模型:

图片

一旦你提供了基本模型详情,输入你自定义推理端点的 ECR 位置:

图片

与任何 SageMaker 模型一样,您可以使用常规方法将其部署到端点。在本章中,我们不会介绍 Docker 镜像创建的过程,但您可以在我们的 GitHub 仓库中找到笔记本,网址为 github.com/mg-um/mastering-ml-on-aws,在 第十六章,在 AWS 中构建的模型部署,其中解释了如何进行部署。

即使您的生产环境不在 AWS 内,SageMaker 和 EMR 中的 Spark 也可以非常有用,因为模型可以在 AWS 离线训练并发送到不同的环境。此外,AWS 创建的模型工件通常可以离线获取和使用(例如 xgboost 模型)。如果您需要将 Spark ML 模型移植到无法实例化本地 Spark 会话的环境或需要一个非常低延迟的预测器,请考虑使用以下工具:github.com/TrueCar/mleap

摘要

在本章中,我们探讨了如何通过 SageMaker 部署模型,并介绍了端点的定义和调用方法。通过使用 Spark 的模型序列化和反序列化,我们展示了模型如何被发送到其他环境,例如 flask 中的自定义 Web 服务实现。最后,我们概述了如何通过在 AWS ECR 中注册自定义 Docker 镜像,将您的 Spark 模型(或任何其他任意模型)通过 SageMaker 提供服务。

练习

  1. 为什么当您尝试直接访问服务时,SageMaker 端点会响应一个缺少身份验证令牌的消息?

  2. 列出两种解决上述问题的替代方案。

  3. 提供两种将基于 Apache Spark 构建的模型部署到端点的方法。

  4. 以我们的 flask 示例为基础,构建一个 Docker 镜像,该镜像提供 /invocations/ping 端点,然后通过 SageMaker 部署一个模型。

附录:AWS 入门

由于我们将重点关注 AWS 上的机器学习,因此您在尚未创建账户的情况下,通过创建账户开始使用 AWS 非常重要。请访问portal.aws.amazon.com/billing/signup#/start。您需要提供一些信用卡信息,但只有在您有效使用不同服务后才会被收费。考虑许多服务都有免费层,您可以免费开始使用。一旦您注册,下一步就是在平台上创建一个用户,您将使用该用户进行程序性访问。

导航到console.aws.amazon.com/iam/home并创建用户:

图片

一旦您创建了用户,请授予一些权限(在我们的例子中,我们将授予完全访问权限):

图片

您可以选择设置标签以更好地跟踪成本,如果您有多个用户的话,但在这本书中我们不会重点讨论这一点。一旦您创建了用户,您就可以导航到该用户并创建密钥:

一旦您生成了这些密钥,您可以将它们存储在您的机器上的 ~/.aws/credentials 中,如docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html中所述。通过将凭证存储在该文件中,您在机器上运行的代码将知道如何与 AWS 进行身份验证。

posted @ 2025-09-04 14:12  绝不原创的飞龙  阅读(2)  评论(0)    收藏  举报