亚马逊-Sagemaker-学习指南第二版-全-

亚马逊 Sagemaker 学习指南第二版(全)

原文:annas-archive.org/md5/9ea32aa91e5ec8d38bb57552873cedb5

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Amazon SageMaker 使你能够在不管理任何基础设施的情况下,快速构建、训练和部署大规模的机器学习模型。它帮助你专注于当前的机器学习问题,并通过消除每个 ML 过程步骤中通常涉及的繁重工作来部署高质量的模型。第二版将帮助数据科学家和机器学习开发人员探索新功能,如 SageMaker Data Wrangler、Pipelines、Clarify、Feature Store 等等。

你将从学习如何使用 SageMaker 的各种功能作为一个单一工具集来解决 ML 挑战开始,逐步涵盖 AutoML、内置算法和框架等功能,以及编写你自己的代码和算法来构建 ML 模型。本书随后将向你展示如何将 Amazon SageMaker 与流行的深度学习库(如 TensorFlow 和 PyTorch)集成,以扩展现有模型的功能。你将看到如何通过自动化工作流程,以最小的努力和更低的成本更快地进入生产阶段。最后,你将探索 SageMaker Debugger 和 SageMaker Model Monitor,以检测训练和生产中的质量问题。

本书结束时,你将能够在整个机器学习工作流程中使用 Amazon SageMaker,从实验、训练、监控到扩展、部署和自动化。

本书适用对象

本书适合软件工程师、机器学习开发人员、数据科学家以及那些刚接触 Amazon SageMaker 的 AWS 用户,帮助他们在无需担心基础设施的情况下构建高质量的机器学习模型。为了更有效地理解本书中涉及的概念,读者需要具备一定的 AWS 基础知识。对机器学习概念和 Python 编程语言的扎实理解也将大有裨益。

本书内容

第一章介绍 Amazon SageMaker,提供了 Amazon SageMaker 的概述,介绍了它的功能以及它如何帮助解决今天机器学习项目中面临的许多痛点。

第二章处理数据准备技术,讨论了数据准备选项。虽然这不是本书的核心内容,但数据准备是机器学习中的一个关键话题,值得在高层次上进行探讨。

第三章使用 Amazon SageMaker AutoPilot 进行 AutoML,展示了如何通过 Amazon SageMaker AutoPilot 自动构建、训练和优化机器学习模型。

第四章训练机器学习模型,展示了如何使用 Amazon SageMaker 中内置的统计机器学习算法集合来构建和训练模型。

第五章训练计算机视觉模型,展示了如何使用 Amazon SageMaker 中内置的计算机视觉算法集合来构建和训练模型。

第六章训练自然语言处理模型,展示了如何使用 Amazon SageMaker 中内置的自然语言处理算法集合来构建和训练模型。

第七章使用内置框架扩展机器学习服务,展示了如何利用 Amazon SageMaker 中内置的开源框架集合来构建和训练机器学习模型。

第八章使用你的算法和代码,展示了如何在 Amazon SageMaker 上使用自己的代码(例如 R 或自定义 Python)构建和训练机器学习模型。

第九章扩展训练任务,展示了如何将训练任务分发到多个托管实例,无论是使用内置算法还是内置框架。

第十章高级训练技巧,展示了如何在 Amazon SageMaker 中利用高级训练技巧。

第十一章部署机器学习模型,展示了如何以多种配置部署机器学习模型。

第十二章自动化机器学习工作流,展示了如何在 Amazon SageMaker 上自动化机器学习模型的部署。

第十三章优化成本和性能,展示了如何从基础设施和成本两个角度来优化模型部署。

要充分利用本书内容

你需要一个可用的 AWS 账户来运行所有内容。

如果你使用的是本书的数字版,建议你自己输入代码,或者通过 GitHub 仓库访问代码(链接将在下一节提供)。这样做有助于避免由于复制粘贴代码而产生的潜在错误。

下载示例代码文件

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

你可以按照以下步骤下载代码文件:

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

  2. 选择支持标签。

  3. 点击代码下载

  4. 搜索框中输入书名,并按照屏幕上的指示操作。

文件下载完成后,请确保使用以下最新版本解压或提取文件夹:

  • WinRAR/7-Zip for Windows

  • Zipeg/iZip/UnRarX for Mac

  • 适用于 Linux 的 7-Zip/PeaZip

本书的代码包也托管在 GitHub 上,地址为 github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。如果代码有更新,它将同步更新到现有的 GitHub 仓库中。

我们还提供了来自我们丰富图书和视频目录的其他代码包,网址为 github.com/PacktPublishing/。快来看看吧!

下载彩色图片

我们还提供了一个包含本书中使用的截图/图表的彩色图片 PDF 文件。你可以在这里下载:static.packt-cdn.com/downloads/9781801817950_ColorImages.pdf

使用的约定

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

文中的代码:指示文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账号。这里是一个例子:“你可以使用 describe-spot-price-history API 来编程收集这些信息。”

一段代码的格式如下:

od = sagemaker.estimator.Estimator(     container,     role,     train_instance_count=2,                                      train_instance_type='ml.p3.2xlarge',                                      train_use_spot_instances=True,     train_max_run=3600,                     # 1 hours      train_max_wait=7200,                    # 2 hour      output_path=s3_output)

当我们希望特别提醒你注意代码块中的某一部分时,相关的行或项目会以粗体显示:

[<sagemaker.model_monitor.model_monitoring.MonitoringExecution at 0x7fdd1d55a6d8>,<sagemaker.model_monitor.model_monitoring.MonitoringExecution at 0x7fdd1d581630>,<sagemaker.model_monitor.model_monitoring.MonitoringExecution at 0x7fdce4b1c860>]

粗体:表示一个新术语、一个重要的词汇,或屏幕上显示的词语。例如,菜单或对话框中的词汇会像这样出现在文本中。这里是一个例子:“我们可以在 SageMaker 控制台的 Processing jobs 部分找到更多关于监控任务的信息。”

提示或重要说明

显示如图所示。

联系我们

我们始终欢迎读者的反馈。

customercare@packtpub.com

勘误:尽管我们已尽力确保内容的准确性,但错误难免。如果你发现本书中有任何错误,我们将非常感激你能向我们报告。请访问 www.packtpub.com/support/errata,选择你的书籍,点击“勘误提交表格”链接,并输入相关细节。

copyright@packt.com 并附有相关材料的链接。

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

分享你的想法

阅读完 Learn Amazon Sagemaker, Second Edition 后,我们希望听到你的想法!请点击此处直接前往该书的 Amazon 评价页面,分享你的反馈。

你的评论对我们以及技术社区非常重要,将帮助我们确保提供优质内容。

第一部分:介绍 Amazon SageMaker

本节的目标是向你介绍关键概念,帮助你下载支持数据,并向你展示示例场景和使用案例。

本节包括以下章节:

  • 第一章介绍 Amazon SageMaker

  • 第二章处理数据准备技巧

第一章:介绍亚马逊 SageMaker

机器学习ML)从业者在项目过程中使用大量工具:开源库、深度学习框架等。此外,他们还经常需要编写自己的工具来进行自动化和编排。管理这些工具及其底层基础设施既耗时又容易出错。

这是亚马逊 SageMaker 旨在解决的一个问题(aws.amazon.com/sagemaker/)。亚马逊 SageMaker 是一个完全托管的服务,帮助你快速构建和部署机器学习模型。无论你是刚开始接触机器学习,还是已经是经验丰富的从业者,你都能找到 SageMaker 的功能来提高工作流的灵活性和模型的性能。你将能够 100%专注于当前的机器学习问题,而无需花费任何时间来安装、管理和扩展机器学习工具和基础设施。

在本章中,我们将学习 SageMaker 的主要功能,它们如何帮助解决机器学习从业者面临的痛点,以及如何设置 SageMaker。本章将涵盖以下主题:

  • 探索亚马逊 SageMaker 的功能

  • 在本地机器上设置亚马逊 SageMaker

  • 设置亚马逊 SageMaker Studio

  • 使用亚马逊 SageMaker JumpStart 部署一键解决方案和模型

技术要求

你需要一个 AWS 账户来运行本章中的示例。如果你还没有账户,请在浏览器中访问aws.amazon.com/getting-started/,了解 AWS 及其核心概念,并创建一个 AWS 账户。你还应该熟悉 AWS Free Tier(aws.amazon.com/free/),它让你在一定的使用限制内免费使用许多 AWS 服务。

你需要为你的账户安装并配置 AWS CLI(aws.amazon.com/cli/)。

你需要一个可用的 Python 3.x 环境。安装 Anaconda 发行版(www.anaconda.com/)不是强制性的,但强烈推荐安装,因为它包含了我们需要的许多项目(Jupyter、pandasnumpy等)。

本书中的代码示例可以在 GitHub 上找到:github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装一个 Git 客户端才能访问它们(git-scm.com/)。

探索亚马逊 SageMaker 的功能

Amazon SageMaker 于 2017 年 AWS re:Invent 大会推出。自那时以来,添加了许多新功能:你可以在 aws.amazon.com/about-aws/whats-new/machine-learning 上查看完整的(且不断增长的)功能列表。

在本节中,你将了解 Amazon SageMaker 的主要功能及其目的。别担心,我们会在后续章节深入探讨每一个功能。我们还会讨论实现这些功能的 SageMaker 应用程序 编程 接口APIs)和 软件 开发 工具包SDKs)。

Amazon SageMaker 的主要功能

Amazon SageMaker 的核心功能是能够在完全托管的基础设施上,按需准备、构建、训练、优化和部署模型。这让你可以专注于学习和解决手头的机器学习问题,而不是花费时间和资源在构建和管理基础设施上。简而言之,你可以更快地从构建到训练再到部署。接下来,我们将深入探讨每个步骤,并突出相关的 SageMaker 功能。

准备工作

Amazon SageMaker 包含强大的工具来标注和准备数据集:

  • Amazon SageMaker Ground Truth:在任何规模上标注数据集。内置了适用于流行用例的工作流(如图像检测、实体提取等),并且你可以实现自己的工作流。标注任务可以分配给属于私人、第三方或公共劳动力的工作人员。

  • Amazon SageMaker Processing:使用你用 scikit-learn 或 Spark 编写的代码,运行批处理作业进行数据处理(以及其他任务,如模型评估)。

  • Amazon SageMaker Data Wrangler:通过图形界面,将数百种内建的转换(或你自己的转换)应用到表格数据集,并通过一键操作将其导出到 Jupyter 笔记本中。

  • Amazon SageMaker 特征存储:将你的工程特征存储在 Amazon S3 中,用于离线构建数据集,或在线使用它们进行预测。

  • Amazon SageMaker Clarify:通过多种统计指标,分析数据集和模型中潜在的偏差,并解释模型如何进行预测。

构建

Amazon SageMaker 提供了两种开发环境:

  • Notebook 实例:完全托管的 Amazon EC2 实例,预装了最流行的工具和库:Jupyter、Anaconda 等。

  • Amazon SageMaker Studio:一个端到端的集成开发环境,专为机器学习项目设计,提供直观的图形界面,支持许多 SageMaker 功能。Studio 现在是运行笔记本的首选方式,我们建议你使用它,而非笔记本实例。

在实验算法时,你可以选择以下选项:

  • 提供 17 种内建算法,用于机器学习和深度学习,已经实现并优化以便在 AWS 上高效运行。无需编写机器学习代码!

  • 内置的开源框架集合(TensorFlowPyTorchApache MXNetscikit-learn 等),您只需带上自己的代码。

  • 在您自己的容器中运行您自己的代码:自定义 Python、R、C++、Java 等。

  • 来自 AWS Marketplace 的算法和预训练模型,适用于机器学习(aws.amazon.com/marketplace/solutions/machine-learning)。

  • 机器学习解决方案和先进的模型可通过 Amazon SageMaker JumpStart 一键获得。

此外,Amazon SageMaker Autopilot 利用自动机器学习(AutoML)自动构建、训练和优化模型,无需编写一行机器学习代码。

训练

如前所述,Amazon SageMaker 负责配置和管理您的训练基础设施。您将不再浪费时间管理服务器,专注于机器学习。同时,SageMaker 提供了如下一些先进功能:

  • 托管存储,可以选择 Amazon S3、Amazon EFS 或 Amazon FSx for Lustre,具体取决于您的性能需求。

  • 托管竞价训练,使用 Amazon EC2 Spot 实例进行训练,从而节省高达 80% 的成本。

  • 分布式训练 自动将大规模训练作业分配到一组托管实例,采用数据并行和模型并行等先进技术。

  • Pipe mode 将超大数据集从 Amazon S3 流式传输到训练实例,省去了复制数据的需求。

  • 自动模型调优 运行超参数优化,更快速地交付高精度模型。

  • Amazon SageMaker Experiments 轻松跟踪、组织和比较所有的 SageMaker 作业。

  • Amazon SageMaker Debugger 在训练过程中捕获内部模型状态,检查其以观察模型的学习方式,检测会影响准确性的非理想条件,并分析训练作业的性能。

部署

与训练一样,Amazon SageMaker 会处理所有部署基础设施,并提供一系列附加功能:

  • 实时终端 创建一个 HTTPS API,从您的模型中提供预测。正如您所期望的那样,自动扩展是可用的。

  • Batch transform 使用模型批量预测数据。

  • Amazon Elastic Inference 为基于 CPU 的终端提供分数级 GPU 加速,以找到最佳的成本/性能比,用于您的预测基础设施。

  • Amazon SageMaker Model Monitor 捕获发送到终端的数据,并与基准数据进行比较,识别并报警数据质量问题(缺失特征、数据漂移等)。

  • Amazon SageMaker Neo 为特定硬件架构(包括嵌入式平台)编译模型,并使用轻量级运行时部署优化版本。

  • Amazon SageMaker Edge Manager 帮助您在边缘设备上部署和管理模型。

  • 最后但同样重要的是,Amazon SageMaker Pipelines 允许你构建端到端的自动化流水线,以运行和管理数据准备、训练和部署工作负载。

Amazon SageMaker API

就像所有其他 AWS 服务一样,Amazon SageMaker 由 API 驱动,这些 API 被实现于 AWS 支持的语言 SDK 中 (aws.amazon.com/tools/)。此外,还提供了一个专门的 Python SDK,即 SageMaker SDK。让我们来看看这两者,并讨论它们各自的优点。

AWS 语言 SDK

语言 SDK 实现了所有 AWS 服务的特定 API:S3、EC2 等。当然,它们也包含了 SageMaker API,相关文档可以在这里查看:docs.aws.amazon.com/sagemaker/latest/dg/api-and-sdk-reference.htma机器学习。

在数据科学和机器学习领域,Python 是最受欢迎的语言,因此让我们来看一下在 boto3 中可用的 SageMaker API,它是 AWS 为 Python 语言提供的 SDK (boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sagemaker.htma机器学习)。这些 API 相当底层且冗长:例如,create_training_job() 有许多看起来不太直观的 JSON 参数。你可以在下一个截图中看到其中一些。你可能会觉得这对于日常的机器学习实验来说并不太吸引人……我完全同意!

图 1.1 – boto3 中 create_training_job() API 的(部分)视图

图 1.1 – boto3 中 create_training_job() API 的(部分)视图

事实上,这些服务级别的 API 并不适用于在笔记本中进行实验。它们的目的是自动化,通过定制脚本或基础设施即代码工具,如 AWS CloudFormation (aws.amazon.com/cloudformation) 和 Terraform (terraform.io)。你的 DevOps 团队将使用这些工具来管理生产环境,因为他们需要对每个可能的参数拥有完全控制权。

那么,应该使用什么来进行实验呢?你应该使用 Amazon SageMaker SDK。

Amazon SageMaker SDK

Amazon SageMaker SDK (github.com/aws/sagemaker-python-sdk) 是一个专为 Amazon SageMaker 提供的 Python SDK。你可以在 sagemaker.readthedocs.io/en/stable/ 找到它的文档。

注意

本书中的代码示例已经尽最大努力与最新的 SageMaker SDK(截至撰写时为 v2.58.0)进行过检查。

在这里,抽象层次要高得多:SDK 包含了用于模型、估算器、预测器等的对象。我们无疑已经回到了机器学习的领域。

例如,这个 SDK 使得启动训练作业(一行代码)和部署模型(一行代码)变得非常简单和方便。基础设施问题被抽象化,我们可以专注于机器学习。这里有一个例子。现在不用担心细节:

# Configure the training job
my_estimator = TensorFlow(
    entry_point='my_script.py',
    role=my_sagemaker_role,
    train_instance_type='machine learning.p3.2xlarge',
    instance_count=1,
    framework_version='2.1.0')
# Train the model
my_estimator.fit('s3://my_bucket/my_training_data/')
# Deploy the model to an HTTPS endpoint
my_predictor = my_estimator.deploy(
    initial_instance_count=1, 
    instance_type='machine learning.c5.2xlarge')

现在我们对 Amazon SageMaker 有了一些了解,让我们看看如何设置它。

在本地机器上设置 Amazon SageMaker

一个常见的误解是你不能在 AWS 云之外使用 SageMaker。显然,它是一个基于云的服务,其最吸引人的功能需要云基础设施来运行。然而,许多开发人员喜欢按照自己的方式设置开发环境,SageMaker 让他们可以做到:在本节中,您将学习如何在本地机器或本地服务器上安装 SageMaker SDK。在后面的章节中,您将学习如何在本地训练和部署模型。

最好的做法是隔离 Python 环境以避免依赖地狱。让我们看看如何使用两个流行项目virtualenvvirtualenv.pypa.io)和 Anaconda(www.anaconda.com/)来实现这一点。

使用 virtualenv 安装 SageMaker SDK

如果你以前从未使用过virtualenv,请在继续之前阅读本教程:packaging.python.org/guides/installing-using-pip-and-virtual-environments/

  1. 首先,让我们创建一个名为sagemaker的新环境并激活它:

    $ mkdir workdir
    $ cd workdir
    $ python3 -m venv sagemaker
    $ source sagemaker/bin/activate
    
  2. 现在,让我们安装boto3,SageMaker SDK 和pandas库(pandas.pydata.org/):

    $ pip3 install boto3 sagemaker pandas
    
  3. 现在,让我们快速检查我们是否可以将这些 SDK 导入 Python:

    $ python3
    Python 3.9.5 (default, May  4 2021, 03:29:30)
    >>> import boto3
    >>> import sagemaker
    >>> print(boto3.__version__)
    1.17.70
    >>> print(sagemaker.__version__)
    2.39.1
    >>> exit()
    

安装看起来很好。你自己的版本肯定会更新,这没问题。现在,让我们通过本地 Jupyter 服务器(jupyter.org/)运行一个快速测试。如果你的机器上没有安装 Jupyter,你可以在jupyter.org/install找到说明:

  1. 首先,让我们基于我们的虚拟环境创建一个 Jupyter 内核:

    $ pip3 install jupyter ipykernel
    $ python3 -m ipykernel install --user --name=sagemaker
    
  2. 然后,我们可以启动 Jupyter:

    $ jupyter notebook
    
  3. 创建一个新的笔记本,我们可以看到sagemaker内核是可用的,所以让我们在新建菜单中选择它,如下截图所示:图 1.2 – 创建一个新的笔记本

    图 1.2 – 创建一个新的笔记本

  4. 最后,我们可以通过导入它们并打印它们的版本来检查 SDK 是否可用,如下截图所示:

图 1.3 – 检查 SDK 版本

图 1.3 – 检查 SDK 版本

这样就完成了使用virtualenv的安装。不要忘记终止 Jupyter,并且取消激活你的virtualenv

$ deactivate

你也可以使用 Anaconda 安装 SDK。

使用 Anaconda 安装 SageMaker SDK

conda允许你创建和管理隔离的环境。如果你之前从未使用过conda,你应该做以下操作:

我们将通过以下步骤开始:

  1. 创建并激活一个名为conda-sagemaker的新conda环境:

    $ conda create -y -n conda-sagemaker
    $ conda activate conda-sagemaker
    
  2. 然后,我们安装pandasboto3和 SageMaker SDK。后者必须使用pip安装,因为它不是conda包:

    $ conda install -y boto3 pandas
    $ pip3 install sagemaker
    
  3. 现在,让我们将 Jupyter 及其依赖项添加到环境中,并创建一个新的内核:

    $ conda install -y jupyter ipykernel
    $ python3 -m ipykernel install --user --name conda-sagemaker
    
  4. 然后,我们可以启动 Jupyter:

    conda-sagemaker kernel is present in the New menu, as is visible in the following screenshot:![Figure 1.4 – Creating a new conda environment    ](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_01_004.jpg)Figure 1.4 – Creating a new conda environment
    
  5. 就像上一节中一样,我们可以使用这个内核创建一个笔记本,并检查 SDK 是否正确导入。

这完成了使用conda的安装。你是否选择使用conda而不是virtualenv,主要是个人偏好的问题。你完全可以使用其中任意一个来运行本书中的所有笔记本并构建你自己的项目。

关于 AWS 权限的说明

Amazon 身份与访问管理(IAM)使你能够安全地管理对 AWS 服务和资源的访问(aws.amazon.com/iam)。当然,这也适用于 Amazon SageMaker,你需要确保你的 AWS 用户有足够的权限来调用 SageMaker API。

IAM 权限

如果你完全不熟悉 IAM,请阅读以下文档:

docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html

你可以通过使用 AWS CLI 调用一个 SageMaker API(例如,list-endpoints)来快速测试。我这里使用的是eu-west-1区域,但你也可以使用离你最近的区域:

$ aws sagemaker list-endpoints --region eu-west-1
{
    "Endpoints": []
}

如果你收到权限不足的错误信息,你需要更新与你的 AWS 用户关联的 IAM 角色。

如果你拥有相关的 AWS 账户,你可以在 IAM 控制台中轻松地通过将AmazonSageMakerFullAccess托管策略添加到你的角色中来完成这项工作。请注意,这个策略权限非常宽松:对于开发账户来说没问题,但对于生产账户来说肯定不行。

如果你使用的账户没有管理员权限(例如公司提供的账户),请联系你的 IT 管理员为你的 AWS 用户添加 SageMaker 权限。

有关 SageMaker 权限的更多信息,请参阅文档:docs.aws.amazon.com/sagemaker/latest/dg/security-iam.html

设置 Amazon SageMaker Studio

实验是机器学习过程中的关键部分。开发人员和数据科学家使用一系列开源工具和库进行数据探索、数据处理,当然还有评估候选算法。安装和维护这些工具需要花费大量时间,而这些时间可能更好地用来研究机器学习问题本身!

Amazon SageMaker Studio 为你提供从实验到生产所需的机器学习工具。其核心是基于 Jupyter 的集成开发环境,使你可以立即上手。

此外,SageMaker Studio 与其他 SageMaker 功能集成,例如 SageMaker 实验,用于跟踪和比较所有任务,SageMaker Autopilot 用于自动创建机器学习模型等等。很多操作只需几次点击就能完成,无需编写任何代码。

SageMaker Studio 还进一步简化了基础设施管理。你无需创建笔记本实例:SageMaker Studio 为你提供了现成的计算环境,随时可以运行你的笔记本。

注意

本节需要具备 Amazon S3、Amazon VPC 和 Amazon IAM 的基本知识。如果你对这些完全不熟悉,请阅读以下文档:

docs.aws.amazon.com/AmazonS3/latest/dev/Welcome.htma机器学习

docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.htma机器学习

docs.aws.amazon.com/IAM/latest/UserGuide/introduction.htma机器学习

现在也许是时候查看(并收藏)SageMaker 定价页面:aws.amazon.com/sagemaker/pricing/

入职 Amazon SageMaker Studio

你可以通过以下三种方式之一访问 SageMaker Studio:

  • 使用快速入门流程:这是个人账户最简单的选择,我们将在接下来的段落中详细介绍。

  • 使用 AWS 单点登录(SSO):如果你的公司已经设置了 SSO 应用程序,这可能是最好的选择。你可以通过docs.aws.amazon.com/sagemaker/latest/dg/onboard-sso-users.htma了解更多关于 SSO 入职的信息。请联系你的 IT 管理员获取详细信息。

  • 使用 Amazon IAM:如果你的公司不使用 SSO,这可能是最好的选择。你可以通过docs.aws.amazon.com/sagemaker/latest/dg/onboard-iam.htma了解更多关于 SSO 入职的信息。再次提醒,请联系你的 IT 管理员了解更多细节。

使用快速入门流程进行入职

快速入门流程包含几个步骤:

  1. 首先,在 Amazon SageMaker Studio 可用的某个地区打开 AWS 控制台,例如,us-east-2.console.aws.amazon.com/sagemaker/

  2. 如下图所示,左侧的垂直面板中有一个链接到SageMaker Studio图 1.5 – 打开 SageMaker Studio

    图 1.5 – 打开 SageMaker Studio

  3. 点击这个链接会打开入门界面,你可以在下一个截图中看到它的第一部分:图 1.6 – 运行快速入门

    图 1.6 – 运行快速入门

  4. 选择快速入门。然后,输入我们希望用于登录 SageMaker Studio 的用户名,并按照前面截图中的说明创建一个新的 IAM 角色。接下来会打开以下界面:图 1.7 – 创建 IAM 角色

    图 1.7 – 创建 IAM 角色

    我们需要做的唯一决策是是否允许我们的笔记本实例访问特定的 Amazon S3 存储桶。选择任何 S3 存储桶并点击创建角色。这是开发和测试中最灵活的设置,但在生产环境中我们会希望应用更严格的设置。当然,我们可以稍后在 IAM 控制台中编辑这个角色,或者创建一个新的角色。

  5. 一旦点击创建角色,我们就会回到之前的界面。请确保该账户启用了项目模板和 JumpStart。(这应该是默认设置)。

  6. 我们只需要点击提交来启动入门程序。根据你的账户设置,可能会出现一个额外的界面,要求你选择一个 VPC 和子网。我建议选择你默认 VPC 中的任何子网。

  7. 几分钟后,SageMaker Studio 开始服务,如下图所示。我们如果需要可以添加额外的用户,但现在只需点击打开 Studio图 1.8 – 启动 SageMaker Studio

    图 1.8 – 启动 SageMaker Studio

    如果这需要再多等几分钟也不要担心,因为 SageMaker Studio 需要完成第一次运行环境的设置。如下图所示,一旦我们打开 SageMaker Studio,我们会看到熟悉的 JupyterLab 界面:

    注意

    SageMaker Studio 是一个不断发展的平台。当你阅读这篇文章时,某些界面可能已经更新。此外,你可能会注意到不同地区之间存在一些小差异,因为某些功能或实例类型在某些地区可能不可用。

    图 1.9 – SageMaker Studio 欢迎界面

    图 1.9 – SageMaker Studio 欢迎界面

  8. 我们可以立即创建我们的第一个笔记本。在启动器标签页的笔记本和计算资源部分,选择数据科学,然后点击笔记本Python 3

  9. 这会打开一个笔记本,如下图所示。我们首先检查 SDK 是否已准备好。由于这是我们第一次启动 数据科学 内核,我们需要等待几分钟。图 1.10 – 检查 SDK 版本

    图 1.10 – 检查 SDK 版本

  10. 如下图所示,我们可以轻松列出当前在 Studio 实例中运行的资源:一个 机器学习.t3.medium 实例、支持笔记本内核的数据科学镜像,以及笔记本本身:图 1.11 – 查看 Studio 资源

    图 1.11 – 查看 Studio 资源

  11. 为了避免不必要的费用,我们应该在完成工作后关闭这些资源。例如,我们可以关闭实例和它上面运行的所有资源,正如下面的截图所示。现在不要关闭,我们需要这个实例来运行接下来的示例!!图 1.12 – 关闭实例

    图 1.12 – 关闭实例

  12. 机器学习.t3.medium 是 Studio 使用的默认实例类型。你可以通过点击笔记本顶部的 2 vCPU + 4 GiB 切换到其他实例类型。这样可以选择新的实例类型并在 Studio 中启动它。几分钟后,实例就启动了,你的笔记本代码也会自动迁移。别忘了关闭之前的实例,正如前面所解释的那样。

  13. 当我们完成与 SageMaker Studio 的工作时,所要做的就是关闭浏览器标签。如果我们想要继续工作,只需返回到 SageMaker 控制台并点击 打开 Studio

  14. 如果我们想关闭 Studio 实例本身,只需在 文件 菜单中选择 关闭。所有文件会被保留,直到我们在 SageMaker 控制台中完全删除 Studio。

现在我们已经完成了设置,我相信你迫不及待地想开始进行机器学习了。让我们开始部署一些模型吧!

使用 Amazon SageMaker JumpStart 部署一键解决方案和模型

如果你是机器学习的新手,可能会发现很难开始实际项目。你已经运行了所有的玩具示例,也读了几篇关于计算机视觉或自然语言处理模型的博客。那么接下来该怎么办?如何将这些复杂的模型应用到你自己的数据上,解决你自己的业务问题?

即使你是经验丰富的从业者,构建端到端的机器学习解决方案也不是一项简单的任务。训练和部署模型只是其中的一部分:数据准备、自动化等怎么办?

Amazon SageMaker JumpStart 是专门为帮助每个人更快速地开始机器学习项目而构建的。只需点击一次,你就可以部署以下内容:

  • 16 个端到端的解决方案,涵盖实际业务问题,如金融交易中的欺诈检测、信用决策解释、预测性维护等

  • 超过 180 个预先训练的 TensorFlow 和 PyTorch 模型,涵盖各种计算机视觉和自然语言处理任务

  • 额外的学习资源,如示例笔记本、博客文章和视频教程

到了部署解决方案的时候了。

部署解决方案

让我们开始:

  1. 从左侧图标栏开始,我们打开 JumpStart。以下截图显示了启动屏幕:图 1.13 – 在 JumpStart 中查看解决方案

    图 1.13 – 在 JumpStart 中查看解决方案

  2. 选择金融交易中的欺诈检测。如以下截图所示,这是一个使用图数据和图神经网络基于交互预测欺诈活动的精彩示例:图 1.14 – 查看解决方案详情

    图 1.14 – 查看解决方案详情

  3. 阅读完解决方案详情后,我们只需点击启动按钮。这将运行一个 AWS CloudFormation 模板,负责构建解决方案所需的所有 AWS 资源。

    CloudFormation

    如果你对 CloudFormation 感兴趣,你可以参考这个介绍:docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.htma机器学习。

  4. 几分钟后,解决方案准备就绪,如以下截图所示。我们点击打开笔记本来打开第一个笔记本。图 1.15 – 打开解决方案

    图 1.15 – 打开解决方案

  5. 如以下截图所示,我们可以在左侧面板浏览解决方案文件:笔记本、训练代码等:图 1.16 – 查看解决方案文件

    图 1.16 – 查看解决方案文件

  6. 从此时起,你可以开始运行并调整笔记本。如果你还不熟悉 SageMaker SDK,也无需担心细节。

  7. 完成后,请返回解决方案页面并点击删除所有资源,以清理并避免不必要的费用,如以下截图所示:

图 1.17 – 删除解决方案

图 1.17 – 删除解决方案

如你所见,JumpStart 解决方案是探索如何使用机器学习解决业务问题的绝佳方式,并开始思考如何在自己的业务环境中实现相同的目标。

现在,让我们看看如何部署预训练模型。

部署模型

JumpStart 包括超过 180 个预先训练的 TensorFlow 和 PyTorch 模型,涵盖各种计算机视觉和自然语言处理任务。让我们来看一下计算机视觉模型:

  1. 从 JumpStart 主屏幕开始,我们打开视觉模型,如以下截图所示:图 1.18 – 查看计算机视觉模型

    图 1.18 – 查看计算机视觉模型

  2. 假设我们有兴趣尝试基于 单次检测器SSD)架构的物体检测模型。我们点击 PyTorch Hub 上的 SSD 模型(从左数第四个)。

  3. 这将打开模型详细信息页面,告诉我们模型的来源、训练使用的数据集以及它可以预测的标签。我们还可以选择部署模型的实例类型。保持默认设置后,我们点击 部署 将模型部署到实时端点,如以下截图所示:图 1.19 – 部署 JumpStart 模型

    图 1.19 – 部署 JumpStart 模型

  4. 几分钟后,模型已经部署。如以下截图所示,我们可以在左侧面板看到端点状态,点击 打开 Notebook 即可进行测试。图 1.20 – 打开 JumpStart Notebook

    图 1.20 – 打开 JumpStart Notebook

  5. 通过点击 Notebook 单元格,我们下载了一张测试图像,并预测其中包含的对象。以下截图显示了边界框、类别和概率:图 1.21 – 在图片中检测物体

    图 1.21 – 在图片中检测物体

  6. 完成后,请确保删除端点以避免不必要的费用:只需在 图 1.20 显示的端点详细信息屏幕上点击 删除

JumpStart 不仅让你轻松尝试最先进的模型,还提供了可以直接用于自己项目的代码:加载图像进行预测、使用端点进行预测、绘制结果等等。

尽管预训练模型非常有用,但我们通常需要在自己的数据集上对其进行微调。让我们看看如何使用 JumpStart 来实现这一点。

微调模型

这次我们使用一个图像分类模型:

注意

关于微调文本模型的警告:像 BERT 这样的复杂模型微调可能需要很长时间,甚至在单个 GPU 上每个 epoch 需要几个小时。除了长时间的等待,成本也不会忽视,因此除非你有一个实际的商业项目,否则我建议避免这些例子。

  1. 我们选择了 Resnet 18 模型(如 图 1.18 中第二个从左的模型)。

  2. 在模型详细信息页面,我们可以看到该模型可以在默认的数据集上进行微调,该数据集用于测试(一个包含五种花卉类别的 TensorFlow 数据集),或者在我们存储在 S3 中的数据集上进行微调。向下滚动,我们了解了数据集应具有的格式。

  3. 如下图所示,我们选择了默认数据集。我们还保持了部署配置和训练参数不变。然后,我们点击 训练 启动微调任务。图 1.22 – 微调模型

    图 1.22 – 微调模型

  4. 仅需几分钟,微调就完成了(这就是我选择这个例子的原因!)。我们可以看到微调模型存储在 S3 中的输出路径。让我们记下这个路径,稍后我们会用到它。图 1.23 – 查看微调结果

    图 1.23 – 查看微调结果

  5. 然后,我们像之前的例子一样点击 Deploy。模型部署完成后,我们打开示范笔记本,展示如何使用初始的预训练模型进行预测。

  6. 这个笔记本使用了模型预训练时所用的原始数据集中的图像。没问题,让我们来适应它!即使我们还不熟悉 SageMaker SDK,笔记本也足够简单,我们可以理解发生了什么,并添加几个单元格,用我们的微调模型预测一张花卉图片。

  7. 首先,我们添加一个单元格,从 S3 复制微调后的模型工件,并提取 JumpStart 添加的类别列表和类别索引:

    %%sh
    aws s3 cp s3://sagemaker-REGION_NAME-123456789012/smjs-d-pt-ic-resnet18-20210511-142657/output/model.tar.gz .
    tar xfz model.tar.gz
    cat class_label_to_prediction_index.json
    {"daisy": 0, "dandelion": 1, "roses": 2, "sunflowers": 3, "tulips": 4}
    
  8. 如预期所示,微调后的模型可以预测五个类别。让我们添加一个单元格,从 Wikipedia 下载一张向日葵的图片:

    %%sh
    wget https://upload.wikimedia.org/wikipedia/commons/a/a9/A_sunflower.jpg
    
  9. 现在,我们加载这张图片并调用端点:

    import boto3
    endpoint_name = 'jumpstart-ftd-pt-ic-resnet18'
    client = boto3.client('runtime.sagemaker')
    with open('A_sunflower.jpg', 'rb') as file:
        image = file.read()
    response = client.invoke_endpoint(
        EndpointName=endpoint_name, 
        ContentType='application/x-image',
        Body=image)
    
  10. 最后,我们打印出预测结果。最高的概率是类别 #3,概率为 60.67%,确认我们的图片包含一朵向日葵!

    import json
    model_predictions = json.loads(response['Body'].read())
    print(model_predictions)
    [0.30362239480018616, 0.06462913751602173, 0.007234351709485054, 0.6067869663238525, 0.017727158963680267]
    
  11. 测试完成后,请务必删除端点,以避免不必要的费用。

这个例子展示了如何通过 SageMaker JumpStart 在你自己的数据集上微调预训练模型,并用它们预测你自己的数据。这是一个很好的方式,能够尝试不同的模型并找出最适合解决你所面临的特定问题的模型。

这是第一章的结束,已经相当充实了,不是吗?现在是时候回顾我们学到的内容了。

总结

在本章中,你了解了 Amazon SageMaker 的主要功能,以及它们如何帮助解决机器学习中的痛点。通过为你提供托管的基础设施和预安装的工具,SageMaker 使你能够专注于机器学习问题本身。因此,你可以更快地从模型实验过渡到生产部署。

接着,你学习了如何在本地机器和 Amazon SageMaker Studio 中设置 Amazon SageMaker。后者是一个托管的机器学习 IDE,许多其他 SageMaker 功能只需几次点击即可访问。

最后,你了解了 Amazon SageMaker JumpStart,这是一个包含机器学习解决方案和最先进模型的集合,你可以一键部署并在几分钟内开始测试。

在下一章中,我们将看到如何使用 Amazon SageMaker 和其他 AWS 服务来准备你的数据集以进行训练。

第二章:处理数据准备技术

数据是任何机器学习项目的起点,将数据转化为可以用于训练模型的数据集需要大量工作。这项工作通常涉及标注数据集、运行定制的脚本进行预处理,并保存处理后的版本以备后用。如你所料,手动完成所有这些工作,或构建工具来自动化这些过程,对机器学习团队来说并不是一项令人兴奋的任务。

本章将介绍帮助你构建和处理数据的 AWS 服务。我们首先会讲解 Amazon SageMaker Ground Truth,这是 Amazon SageMaker 的一项功能,帮助你快速构建准确的训练数据集。接着,我们会介绍 Amazon SageMaker Data Wrangler,一种交互式转换数据的新方法。然后,我们会讨论 Amazon SageMaker Processing,另一项功能,帮助你运行数据处理工作负载,例如特征工程、数据验证、模型评估和模型解释。最后,我们会简要介绍其他可能有助于数据分析的 AWS 服务:Amazon Elastic Map ReduceAWS GlueAmazon Athena

本章包括以下主题:

  • 使用 Amazon SageMaker Ground Truth 标注数据

  • 使用 Amazon SageMaker Data Wrangler 转换数据

  • 使用 Amazon SageMaker Processing 运行批处理作业

技术要求

你需要一个 AWS 账户来运行本章中包含的示例。如果你还没有账户,请访问 aws.amazon.com/getting-started/ 创建一个。你还应该熟悉 AWS 免费套餐,它允许你在特定的使用限制内免费使用许多 AWS 服务。

你需要为你的账户安装并配置 AWS 命令行界面CLI)(aws.amazon.com/cli/)。

你需要一个正常工作的 Python 3.x 环境。安装 Anaconda 发行版(www.anaconda.com/)不是强制要求,但强烈推荐,因为它包含我们需要的许多项目(Jupyter、pandasnumpy 等)。

本书中的代码示例可以在 GitHub 上找到,地址是 github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装 Git 客户端才能访问这些示例(git-scm.com/)。

使用 Amazon SageMaker Ground Truth 标注数据

Amazon SageMaker 于 2018 年底增加的 Amazon SageMaker Ground Truth 帮助你快速构建准确的训练数据集。机器学习从业者可以将标注工作分发给公共和私人劳动力的人工标注员。得益于内置工作流程和图形界面,标注员可以立即开始高效工作,处理常见的图像、视频和文本任务。此外,Ground Truth 还支持自动标注技术,这是一种训练机器学习模型使其能够在没有额外人工干预的情况下进行数据标注的技术。

在本节中,你将学习如何使用 Ground Truth 标注图像和文本。

使用劳动力

使用 Ground Truth 的第一步是创建一个劳动力,这是负责标注数据样本的工作组。

让我们进入 SageMaker 控制台:在左侧的垂直菜单中,点击Ground Truth,然后点击标注劳动力。有三种类型的劳动力可供选择:Amazon Mechanical Turk供应商私人。我们来讨论一下它们是什么,以及何时使用它们。

Amazon Mechanical Turk

Amazon Mechanical Turkwww.mturk.com/)使得将大型批处理任务拆分为小的工作单元变得更加容易,这些单元可以由分布式劳动力处理。

使用 Mechanical Turk,你可以招募来自全球的成千上万的工人。这是当你需要标注极大数据集时的一个很好的选择。例如,想象一个自动驾驶数据集,由 1,000 小时的视频组成:每一帧都需要处理,以识别其他车辆、行人、交通标志等。如果你想对每一帧进行标注,你将需要处理 1,000 小时 x 3,600 秒 x 每秒 24 帧 = 8640 万张图片!显然,你需要扩展你的标注劳动力才能完成这项工作,而 Mechanical Turk 可以让你做到这一点。

供应商劳动力

尽管 Mechanical Turk 具有良好的可扩展性,但有时你需要更多控制,特别是当数据共享对象和标注质量需要更多关注时,尤其是在需要额外领域知识的情况下。

为此,AWS 已经审核了一些数据标注公司,这些公司已经将 Ground Truth 集成到他们的工作流程中。你可以在AWS Marketplaceaws.amazon.com/marketplace/)中找到这些公司的列表,路径为机器学习 | 数据标注服务 | Amazon SageMaker Ground Truth 服务

私人劳动力

有时,数据不能由第三方处理。也许它过于敏感,或者可能需要只有你公司员工才拥有的专业知识。在这种情况下,你可以创建一个由明确身份的个人组成的私人劳动力来访问并标注数据。

创建私人劳动力

创建私人劳动力是最快且最简单的选项。让我们来看看如何操作:

  1. 从 SageMaker 控制台中的Labeling workforces入口开始,我们选择Private标签,如下图所示。然后,点击Create private team图 2.1 – 创建私有工作团队

    图 2.1 – 创建私有工作团队

  2. 我们为团队命名,然后需要决定是通过电子邮件邀请工作人员,还是导入属于现有Amazon Cognito组的用户。

    Amazon Cognito (aws.amazon.com/cognito/) 是一种托管服务,可以让您在任何规模下构建和管理用户目录。Cognito 支持社交身份提供者(Google、Facebook 和 Amazon),以及企业身份提供者(Microsoft Active Directory、SAML)。

    在企业环境中,这非常有意义,但为了简化流程,我们可以改用电子邮件。在这里,我将使用一些示例电子邮件地址:请确保使用您自己的电子邮件地址,否则您将无法加入团队!

  3. 然后,我们需要输入组织名称,更重要的是提供一个工作人员可以用来提问和反馈标注工作的联系方式电子邮件。这些交流非常重要,以便微调标注说明,找出有问题的数据样本等。

  4. 可选地,我们可以使用Amazon Simple Notification Service (aws.amazon.com/sns/) 设置通知,以告知工作人员他们有任务需要完成。

  5. 屏幕应该像下图所示。然后,我们点击Create private team图 2.2 – 设置私有工作团队

    图 2.2 – 设置私有工作团队

  6. 几秒钟后,团队已建立。邀请已发送给工作人员,要求他们通过登录特定的 URL 加入工作团队。邀请邮件如下图所示:图 2.3 – 邮件邀请

    图 2.3 – 邮件邀请

  7. 点击链接会打开一个登录窗口。一旦我们登录并定义了新密码,就会进入一个新页面,显示可用的工作,如下图所示。由于我们尚未定义任何工作,因此它显然是空的:

图 2.4 – 工作控制台

图 2.4 – 工作控制台

让我们让工作人员忙起来,创建一个图像标注工作。

上传数据进行标注

正如您所期望的,Amazon SageMaker Ground Truth 使用 Amazon S3 来存储数据集:

  1. 使用 AWS CLI,我们创建一个托管在与我们运行 SageMaker 相同区域的 S3 桶。桶名称是全球唯一的,所以请确保在创建桶时选择一个独特的名称。使用以下代码(也可以选择其他 AWS 区域):

    $ aws s3 mb s3://sagemaker-book --region eu-west-1
    
  2. 然后,我们将位于chapter2文件夹中的猫咪图片从我们的 GitHub 仓库复制过来,如下所示:

    $ aws s3 cp --recursive cat/ s3://sagemaker-book/chapter2/cat/
    

现在我们有一些等待标注的数据,让我们创建一个标注工作。

创建标签工作

正如你所预期的,我们需要定义数据的位置、标注任务的类型,以及我们的标注指令:

  1. 在 SageMaker 控制台的左侧垂直菜单中,我们点击Ground Truth,然后点击标注工作,接着点击创建标注工作按钮。

  2. 首先,我们给工作命名,比如'my-cat-job'。然后,我们定义数据在 S3 中的位置。Ground Truth 需要一个清单文件:清单文件是一个JSON 文件,用于过滤哪些对象需要标注,哪些不需要。工作完成后,一个名为增强清单的新文件将包含标注信息,我们将能够使用这个文件来为训练工作提供数据。

  3. 然后,我们定义输入数据的位置和类型,正如下面的截图所示:图 2.5 – 配置输入数据

    图 2.5 – 配置输入数据

  4. 如下图所示,我们选择了第一章中为 SageMaker 创建的 IAM 角色(你的名字会有所不同),然后点击完成数据设置按钮以验证这一部分:图 2.6 – 验证输入数据

    图 2.6 – 验证输入数据

    点击查看更多详情,你可以了解后台发生了什么。SageMaker Ground Truth 会抓取你在 S3 中的数据,并创建一个名为清单文件的 JSON 文件。如果你感兴趣,可以从 S3 下载它。这个文件指向你在 S3 中的对象(图片、文本文件等)。

  5. 可选地,我们可以选择使用完整的清单、随机样本或基于SQL查询的过滤子集。我们还可以提供一个Amazon KMS 密钥来加密作业输出。这里我们保持默认设置。

  6. 任务类型部分会询问我们希望执行什么类型的任务。请花一分钟时间浏览可用的不同任务类别(文本、图像、视频、点云和自定义)。如下一张截图所示,让我们选择图像任务类别和语义分割任务,然后点击下一步图 2.7 – 选择任务类型

    图 2.7 – 选择任务类型

  7. 在下一个屏幕中,如下图所示,我们首先选择我们的私人工作人员团队:图 2.8 – 选择团队类型

    图 2.8 – 选择团队类型

  8. 如果我们有很多样本(比如说,数万个或更多),我们应该考虑启用自动化数据标注,因为此功能能够减少标注任务的持续时间和成本。事实上,当工作者开始标注数据样本时,SageMaker Ground Truth 会使用这些样本训练一个机器学习模型。它会将这些样本用作监督学习问题的数据集。在足够多的工作者标注数据的支持下,这个模型很快就能与人类的准确性匹敌,甚至超过人类的标注准确性,届时它将取代工作者并标注剩余的数据集。如果你想了解更多关于此功能的信息,请阅读文档 docs.aws.amazon.com/sagemaker/latest/dg/sms-automated-labeling.html

  9. 配置训练任务的最后一步是输入工作者的指示。这是一个重要步骤,特别是当你的任务分配给第三方工作者时。我们的指示越清晰,标注的质量就越高。在这里,我们将解释任务的内容,并为工作者输入一个“猫”标签。现实情况下,你应该添加详细的说明,提供良好和不良示例的样本图像,解释你的期望等等。以下截图展示了我们的指示内容:图 2.9 – 设置指示

    ](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_02_009.jpg)

    图 2.9 – 设置指示

  10. 完成指示后,我们点击创建来启动标注任务。几分钟后,任务准备好分配给工作者了。

标注图像

登录到工作者 URL,我们可以从以下截图显示的屏幕看到,我们还有任务要做:

图 2.10 – 工作者控制台

](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_02_010.jpg)

图 2.10 – 工作者控制台

我们将使用以下步骤:

  1. 点击开始工作会打开一个新窗口,接下来图中所示。它显示了说明以及第一个需要处理的图像:图 2.11 – 标注图像

    ](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_02_011.jpg)

    图 2.11 – 标注图像

  2. 使用工具栏中的图形工具,尤其是自动分割工具,我们可以非常快速地制作高质量的标注。请花几分钟时间进行练习,很快你就能做到同样的事情。

  3. 一旦我们完成了三个图像的标注,任务就完成了,我们可以在 SageMaker 控制台的标注任务下查看标注的图像。你的屏幕应该像下面的截图一样:

图 2.12 – 标注图像

](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_02_012.jpg)

图 2.12 – 标注图像

更重要的是,我们可以在 S3 输出位置找到标注信息。

特别是,output/my-cat-job/manifests/output/output.manifest 包含每个数据样本的标注信息,例如图像中存在的类别,以及分割掩码的链接。

第五章**,训练计算机视觉模型中,我们将看到如何将这些信息直接输入到 Amazon SageMaker 中内置的计算机视觉算法。当然,我们也可以解析这些信息,并根据我们用于训练计算机视觉模型的框架进行转换。

如您所见,SageMaker Ground Truth 使得标注图像数据集变得非常简单。您只需要将数据上传到 S3 并创建一个工作团队。Ground Truth 将自动分配任务,并将结果存储在 S3 中。

我们刚刚看到如何标注图像,那么文本任务呢?嗯,它们同样容易设置和运行。让我们通过一个示例来了解。

标注文本

这是一个标注命名实体识别文本的快速示例。数据集由我博客文章中的一些文本片段组成,我们希望标注所有 AWS 服务名称。这些内容可以在我们的 GitHub 仓库中找到。

我们将通过以下步骤开始标注文本:

  1. 首先,让我们使用以下代码将文本片段上传到 S3:

    $ aws s3 cp --recursive ner/ s3://sagemaker-book/chapter2/ner/
    
  2. 就像在前面的示例中,我们配置了一个文本标注任务,设置了输入数据并选择了一个 IAM 角色,截图如下所示:图 2.13 – 创建文本标注任务

    图 2.13 – 创建文本标注任务

  3. 然后,我们选择文本作为类别,选择命名实体识别作为任务。

  4. 在下一个屏幕上,截图如下所示,我们只需再次选择我们的私有团队,添加标签并输入指令:图 2.14 – 设置指令

    图 2.14 – 设置指令

  5. 一旦任务准备好,我们登录到工作控制台并开始标注。您可以在以下截图中看到一个标注示例:图 2.15 – 标注文本

    图 2.15 – 标注文本

  6. 我们很快就完成了任务,并且可以在我们的 S3 存储桶中找到标注信息。对于每个样本,我们可以看到起始偏移量、结束偏移量以及每个标注实体的标签。

Amazon SageMaker Ground Truth 使得大规模标注数据集变得非常简单。它有许多不错的功能,包括任务链和自定义工作流,强烈建议您访问 docs.aws.amazon.com/sagemaker/latest/dg/sms.html 进一步了解。

现在我们知道如何标注数据集了,接下来让我们看看如何使用 Amazon SageMaker Data Wrangler 轻松地交互式转换数据。

使用 Amazon SageMaker Data Wrangler 转换数据

收集和标注数据样本只是准备数据集的第一步。实际上,您很可能需要预处理数据集,以便进行如下操作:

  • 将其转换为您所使用的机器学习算法所期望的输入格式。

  • 对数值特征进行缩放或标准化。

  • 工程更高级的特征,例如,一热编码(One-Hot Encoding)。

  • 清理并标记文本,以用于自然语言处理应用程序

在机器学习项目的早期阶段,通常不容易明确需要哪些数据转换,或者哪些转换最为高效。因此,实践者通常需要尝试不同的组合,采用多种方式转换数据,训练模型,并评估结果。

在本节中,我们将学习Amazon SageMaker Data Wrangler,这是一个集成在 SageMaker Studio 中的图形界面,使得数据转换变得非常简单,并且能够将结果导出到多种 Jupyter 笔记本中。

在 SageMaker Data Wrangler 中加载数据集

首先,我们需要一个数据集。我们将使用由 S. Moro、P. Cortez 和 P. Rita 在《A Data-Driven Approach to Predict the Success of Bank Telemarketing》中发布的直接营销数据集,Decision Support Systems,Elsevier,62:22-31,2014 年 6 月。

该数据集描述了一个二分类问题:客户是否会接受营销优惠,接受或不接受?它包含了超过 41,000 个客户样本,标签存储在y列中。

我们将按照以下步骤开始使用:

  1. 使用 AWS 命令行,我们将下载数据集,解压并将其复制到我们所在区域的默认 SageMaker 桶中(它应该已自动创建)。你可以在本地机器或 Jupyter 终端中运行此操作:

    $ aws s3 cp s3://sagemaker-sample-data-us-west-2.s3-us-west-2.amazonaws.com/autopilot/direct_marketing/bank-additional.zip .
    $ unzip bank-additional.zip
    $ aws s3 cp bank-additional/bank-additional-full.csv s3://sagemaker-ap-northeast-2-123456789012/direct-marketing/
    
  2. 在 SageMaker Studio 中,我们通过文件 | 新建 | Data Wrangler 流来创建新的 Data Wrangler 流。以下截图展示了 Data Wrangler 图像正在加载:图 2.16 – 加载 Data Wrangler

    图 2.16 – 加载 Data Wrangler

  3. 一旦 Data Wrangler 准备就绪,导入屏幕将打开。我们还可以在左侧窗格中看到 Data Wrangler 图像,如下一个截图所示:图 2.17 – 打开 Data Wrangler

    图 2.17 – 打开 Data Wrangler

  4. 我们可以从 S3、Athena 或 Redshift 导入数据(点击添加数据源)。在这里,我们点击 S3。

  5. 如下截图所示,我们可以轻松定位到我们刚刚上传的数据集。点击它即可:图 2.18 – 定位数据集

    图 2.18 – 定位数据集

  6. 这将打开数据集的预览,如下一个截图所示:图 2.19 – 预览数据集

    图 2.19 – 预览数据集

  7. 让我们点击导入,这将打开准备视图,如下一个截图所示:图 2.20 – 预览数据集

    图 2.20 – 预览数据集

  8. 点击+图标,我们可以添加更多的数据源,将它们与我们的数据集连接或拼接。如果 Data Wrangler 错误地检测了数据类型,我们也可以编辑所有列的数据类型。否则,我们可以选择添加分析,以可视化我们数据集的属性。这将打开分析视图,如下图所示:图 2.21 – 可视化数据集

    图 2.21 – 可视化数据集

  9. 下一张截图显示了一个关于持续时间与年龄的散点图。看起来是不是很简单?你可以通过选择不同的列进行实验,点击预览查看结果,然后点击保存来创建分析并保存以供后续使用。图 2.22 – 构建散点图

    图 2.22 – 构建散点图

  10. 除了直方图和散点图,我们还可以构建表格摘要偏差分析目标泄漏报告。我们来构建后者,查看是否某些列泄漏到预测中,或者根本没有任何帮助。你可以在下图中看到该报告:图 2.23 – 构建目标泄漏报告

    图 2.23 – 构建目标泄漏报告

  11. 该报告告诉我们没有列发生泄漏(所有分数都低于 1)。一些列在预测目标时也没有什么用(某些分数为 0.5 或更低):我们可能需要在数据处理过程中丢弃这些列。

我们还可以尝试快速模型报告,该报告使用在 SageMaker Studio 中实现的随机森林算法训练模型。不幸的是,弹出一个错误消息,抱怨列名不符合要求。确实,一些列名中包含点号,而 Spark 不允许这种格式。没问题,我们可以在数据处理过程中轻松修复这个问题,然后稍后再构建报告。

实际上,让我们继续使用数据处理工具 Data Wrangler 来转换数据。

在 SageMaker Data Wrangler 中转换数据集

Data Wrangler 包含数百种内置转换,我们也可以添加自己的转换。

  1. 从下图所示的准备视图开始,我们点击+图标以添加转换。图 2.24 – 添加转换

    图 2.24 – 添加转换

  2. 这将打开转换列表,如下图所示。花点时间来浏览它们。

  3. 我们从删除标记为无用的列开始,这些列包括maritalday of weekmonthhousingcons.conf.idxnr.employedcons.price.idx。我们点击marital列。你的屏幕应该如下所示:图 2.25 – 删除列

    图 2.25 – 删除列

  4. 我们可以预览结果并将转换添加到管道中。我们将对其他需要删除的列重复相同的操作。

  5. 现在,让我们删除列名中的这些恼人的点号,用下划线替换它们。最简单的做法是使用df图 2.26 – 应用自定义转换

    图 2.26 – 应用自定义转换

  6. 返回到分析视图,点击步骤,我们可以看到已经应用的转换列表,如下图所示。我们也可以通过点击每个转换右侧的图标来删除它。图 2.27 – 查看管道

    图 2.27 – 查看管道

  7. 点击 y 标签,如下一个截图所示。该分类模型的 F1 分数为 0.881,最重要的特征是 durationeuribor3mpdays。通过应用更多的转换并再次构建快速模型,我们可以迭代地衡量我们的特征工程步骤的积极影响(或缺乏影响)。图 2.28 – 构建快速模型

    图 2.28 – 构建快速模型

  8. 回到 jobeducation。我们决定对它们进行编码,以帮助算法理解不同的值是问题的不同维度。从 job 开始,我们应用 job 列,然后该列会自动删除。图 2.29 – 对某列进行独热编码

    图 2.29 – 对某列进行独热编码

  9. job_admin. 列名中包含一个点!我们可以通过 education 列来移除它…并移除列名中的点。我们本可以应用处理数字转换来缩放和标准化数值列,但现在先停在这里。随时可以继续探索和实验!

  10. 最后一点:Data Wrangler 工作流存储在 .flow 文件中,可以在 Jupyter 文件视图中查看。这些是 JSON 文件,您可以(并且应该)将其存储在 Git 仓库中,以便以后重用并与其他团队成员共享。

现在我们的管道已准备好,让我们看看如何将其导出为 Python 代码。只需要单击一次,我们就不需要写任何代码。

导出 SageMaker Data Wrangler 管道

Data Wrangler 使导出管道变得容易,提供四种方式:

  • 纯 Python 代码,您可以直接将其包含到您的机器学习项目中。

  • 运行 SageMaker 处理任务的 Jupyter notebook,它将把管道应用于您的数据集并将结果保存到 S3 中。该 notebook 还包括用于训练模型的可选代码。

  • 一个将处理后数据集存储在 SageMaker Feature Store 中的 Jupyter notebook。

  • 一个创建 SageMaker Pipelines 工作流的 Jupyter notebook,包含处理数据集和在其上训练模型的步骤。

好的,我们开始吧:

  1. 导出视图开始,我们点击“步骤”并选择我们想要导出的步骤。在这里,我选择了所有步骤,如下一个截图所示:图 2.30 – 选择要导出的步骤

    图 2.30 – 选择要导出的步骤

  2. 然后,我们只需点击导出步骤并选择四个选项中的一个。在这里,我选择了保存到 S3,以便运行 SageMaker 处理任务。

  3. 这将打开一个新的 notebook。我们将在下一节讨论 SageMaker Processing,但现在让我们继续运行任务。一旦“任务状态与 S3 输出位置”单元格完成,我们的数据集就可以在 S3 中使用,如下一个截图所示:图 2.31 – 在 S3 中定位处理后的数据集

    图 2.31 – 在 S3 中定位处理后的数据集

  4. 下载并打开存储在此位置的 CSV 文件,我们看到它包含了处理过的数据集,如下一张截图所示。在典型的机器学习工作流中,我们随后将直接使用这些数据来训练模型。

Figure 2.32 – Viewing the processed dataset

Figure 2.32 – Viewing the processed dataset

正如您所见,SageMaker 数据处理非常简单(甚至有趣),可以轻松应用转换到您的数据集。完成后,您可以立即将它们导出为 Python 代码,无需编写任何代码。

在接下来的部分中,我们将学习关于 Amazon SageMaker 处理(Processing)的内容,这是一个执行批处理数据处理和其他机器学习任务的绝佳方式。

使用 Amazon SageMaker 处理运行批处理作业

如前节所述,数据集通常需要大量工作才能准备好进行训练。一旦训练完成,您可能还希望运行额外的作业来后处理预测数据并评估您的模型在不同数据集上的表现。

一旦实验阶段完成,最好开始自动化所有这些作业,这样您就可以随需求轻松运行它们。

探索 Amazon SageMaker 处理 API

Amazon SageMaker 处理 API 是 SageMaker SDK 的一部分,我们在Chapter 1**,Introducing Amazon SageMaker中安装了该 SDK。

SageMaker 处理作业运行在 Docker 容器内部:

日志可在/aws/sagemaker/ProcessingJobs日志组中找到。

让我们首先看看如何使用 scikit-learn 和 SageMaker 处理准备数据集进行训练。

使用 scikit-learn 处理数据集

这里是高级流程:

  • 将您的未加工数据集上传到 Amazon S3。

  • 使用 scikit-learn 编写脚本以加载数据集,处理它,并保存处理过的特征和标签。

  • 使用 SageMaker 处理在托管基础设施上运行此脚本。

将数据集上传到 Amazon S3

我们将重用前一节介绍的直接营销数据集,并应用我们自己的转换。

  1. 创建一个新的 Jupyter 笔记本,让我们首先下载并提取数据集:

    %%sh
    apt-get -y install unzip
    wget -N https://sagemaker-sample-data-us-west-2.s3-us-west-2.amazonaws.com/autopilot/direct_marketing/bank-additional.zip
    unzip -o bank-additional.zip
    
  2. 然后,我们用pandas加载它:

    import pandas as pd
    data = pd.read_csv('./bank-additional/bank-additional-full.csv')
    print(data.shape)
    (41188, 21)
    
  3. 现在,让我们显示前五行:

    data[:5] 
    

    This prints out the table visible in the following figure:

    Figure 2.33 – Viewing the dataset

    Figure 2.33 – Viewing the dataset

    向右滚动,我们可以看到一个名为y的列,存储了标签。

  4. 现在,让我们将数据集上传到 Amazon S3. 我们将使用 SageMaker 在我们运行的区域自动创建的默认存储桶。我们只需添加前缀以保持事务井然有序:

    import sagemaker
    prefix = 'sagemaker/DEMO-smprocessing/input'
    input_data = sagemaker.Session().upload_data(path='./bank-additional/bank-additional-full.csv', key_prefix=prefix)
    

使用 scikit-learn 编写处理脚本

由于 SageMaker Processing 负责所有基础设施工作,我们可以专注于脚本本身。SageMaker Processing 还会自动将输入数据集从 S3 复制到容器内,并将处理后的数据集从容器复制到 S3:

配置作业时,我们会提供容器路径。以下是我们将使用的路径:

  • 输入数据集:/opt/ml/processing/input

  • 处理后的训练集:/opt/ml/processing/train

  • 处理后的测试集:/opt/ml/processing/test

在我们的 Jupyter 环境中,让我们开始编写一个新的 Python 文件,名为preprocessing.py。正如你所期待的,这个脚本将加载数据集,执行基本的特征工程,并保存处理后的数据集:

  1. 首先,我们使用argparse库(docs.python.org/3/library/argparse.html)读取我们的单个命令行参数:训练集和测试集的比例。实际值将通过 SageMaker Processing SDK 传递给脚本:

    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('--train-test-split-ratio', 
                        type=float, default=0.3)
    args, _ = parser.parse_known_args()
    print('Received arguments {}'.format(args))
    split_ratio = args.train_test_split_ratio
    
  2. 我们使用pandas加载输入数据集。在启动时,SageMaker Processing 会自动将其从 S3 复制到容器内的用户定义位置/opt/ml/processing/input

    import os
    import pandas as pd
    input_data_path = os.path.join('/opt/ml/processing/input', 'bank-additional-full.csv')
    df = pd.read_csv(input_data_path) 
    
  3. 然后,我们删除任何缺失值的行,以及重复的行:

    df.dropna(inplace=True)
    df.drop_duplicates(inplace=True)
    
  4. 然后,我们统计负样本和正样本,并显示类别比例。这将告诉我们数据集的不平衡程度:

    one_class = df[df['y']=='yes']
    one_class_count = one_class.shape[0]
    zero_class = df[df['y']=='no']
    zero_class_count = zero_class.shape[0]
    zero_to_one_ratio = zero_class_count/one_class_count
    print("Ratio: %.2f" % zero_to_one_ratio)
    
  5. 查看数据集,我们可以看到有一列名为pdays,表示客户最后一次联系的时间。有些行的值为 999,这看起来相当可疑:实际上,这个值是一个占位符,表示该客户从未被联系过。为了帮助模型理解这一假设,我们将添加一个新列,明确指出这一点:

    import numpy as np
    df['no_previous_contact'] = 
       np.where(df['pdays'] == 999, 1, 0)
    
  6. 在职位列中,我们可以看到三种类别(studentretiredunemployed),它们可能应该合并在一起,表示这些客户没有全职工作。我们来添加另一列:

    df['not_working'] = np.where(np.in1d(df['job'], ['student', 'retired', 'unemployed']), 1, 0)
    
  7. 现在,让我们将数据集拆分为训练集和测试集。Scikit-learn 提供了一个方便的 API,我们根据传递给脚本的命令行参数来设置拆分比例:

    from sklearn.model_selection import train_test_split
    X_train, X_test, y_train, y_test = train_test_split(
            df.drop('y', axis=1),
            df['y'],
            test_size=split_ratio, random_state=0) 
    
  8. 下一步是对数值特征进行缩放,并对分类特征进行独热编码。我们将使用StandardScaler来处理数值特征,使用OneHotEncoder来处理分类特征:

    from sklearn.compose import make_column_transformer
    from sklearn.preprocessing import StandardScaler,OneHotEncoder
    preprocess = make_column_transformer(
      (StandardScaler(), ['age', 'duration', 'campaign', 'pdays', 'previous']),
      (OneHotEncoder(sparse=False), ['job', 'marital', 'education', 'default', 'housing', 'loan','contact', 'month', 'day_of_week', 'poutcome'])
    )
    
  9. 然后,我们处理训练集和测试集:

    train_features = preprocess.fit_transform(X_train)
    test_features = preprocess.transform(X_test)
    
  10. 最后,我们保存处理后的数据集,将特征和标签分开。它们被保存到容器内用户定义的位置,SageMaker Processing 会在作业结束前自动将文件复制到 S3:

    train_features_output_path = os.path.join('/opt/ml/processing/train', 'train_features.csv')
    train_labels_output_path = os.path.join('/opt/ml/processing/train', 'train_labels.csv')
    test_features_output_path = os.path.join('/opt/ml/processing/test', 'test_features.csv')
    test_labels_output_path = os.path.join('/opt/ml/processing/test', 'test_labels.csv')
    pd.DataFrame(train_features).to_csv(train_features_output_path, header=False, index=False)
    pd.DataFrame(test_features).to_csv(test_features_output_path, header=False, index=False)
    y_train.to_csv(train_labels_output_path, header=False, index=False)
    y_test.to_csv(test_labels_output_path, header=False, index=False)
    

就这样。如你所见,这段代码是标准的 scikit-learn,因此将自己的脚本适配到 SageMaker Processing 并不困难。现在,让我们看看如何实际运行这个过程。

运行处理脚本

回到我们的 Jupyter 笔记本,我们使用 SageMaker SDK 中的SKLearnProcessor对象来配置处理作业:

  1. 首先,我们定义要使用的 scikit-learn 版本和我们的基础设施需求。在这里,我们选择一个 ml.m5.xlarge 实例,这是一个多功能的良好选择:

    from sagemaker.sklearn.processing import SKLearnProcessor
    sklearn_processor = SKLearnProcessor(
        framework_version='0.23-1',
        role=sagemaker.get_execution_role(),
        instance_type='ml.m5.xlarge',
        instance_count=1)
    
  2. 然后,我们只需启动作业,传递脚本的名称、S3 中数据集的输入路径、SageMaker Processing 环境中用户定义的数据集路径以及命令行参数:

    from sagemaker.processing import ProcessingInput, ProcessingOutput
    sklearn_processor.run(
        code='preprocessing.py',
        inputs=[ProcessingInput(
            source=input_data,   # Our data in S3                   
            destination='/opt/ml/processing/input')
        ],               
        outputs=[
            ProcessingOutput(
                source='/opt/ml/processing/train',                             
                output_name='train_data'),                                   
            ProcessingOutput(
                source='/opt/ml/processing/test',
                output_name='test_data'                                                 
                )
        ],
        arguments=['--train-test-split-ratio', '0.2']
    )
    

    当作业开始时,SageMaker 会自动配置一个托管的 ml.m5.xlarge 实例,将适当的容器拉取到该实例,并在容器内运行我们的脚本。一旦作业完成,实例会被终止,我们只需为实际使用的时间付费。完全无需管理基础设施,也不会因为没有理由而让空闲实例长时间运行。

  3. 几分钟后,作业完成,我们可以看到脚本的输出如下:

    Received arguments Namespace(train_test_split_ratio=0.2)
    Reading input data from /opt/ml/processing/input/bank-additional-full.csv
    Positive samples: 4639
    Negative samples: 36537
    Ratio: 7.88
    Splitting data into train and test sets with ratio 0.2
    Running preprocessing and feature engineering transformations
    Train data shape after preprocessing: (32940, 58)
    Test data shape after preprocessing: (8236, 58)
    Saving training features to /opt/ml/processing/train/train_features.csv
    Saving test features to /opt/ml/processing/test/test_features.csv
    Saving training labels to /opt/ml/processing/train/train_labels.csv
    Saving test labels to /opt/ml/processing/test/test_labels.csv
    

    以下截图显示了 CloudWatch 中的相同日志:

    图 2.34 – 在 CloudWatch Logs 中查看日志

    图 2.34 – 在 CloudWatch Logs 中查看日志

  4. 最后,我们可以描述作业并查看处理后的数据集位置:

    preprocessing_job_description = 
       sklearn_processor.jobs[-1].describe()
    output_config = preprocessing_job_description['ProcessingOutputConfig']
    for output in output_config['Outputs']:
        print(output['S3Output']['S3Uri'])
    

    这将产生以下输出:

    s3://sagemaker-eu-west-1-123456789012/sagemaker-scikit-learn-2020-04-22-10-09-43-146/output/train_data
    s3://sagemaker-eu-west-1-123456789012/sagemaker-scikit-learn-2020-04-22-10-09-43-146/output/test_data
    

    在终端中,我们可以使用 AWS CLI 获取位于前述路径的处理过的训练集,并查看第一个样本和标签:

    $ aws s3 cp s3://sagemaker-eu-west-1-123456789012/sagemaker-scikit-learn-2020-04-22-09-45-05-711/output/train_data/train_features.csv .
    $ aws s3 cp s3://sagemaker-eu-west-1-123456789012/sagemaker-scikit-learn-2020-04-22-09-45-05-711/output/train_data/train_labels.csv .
    $ head -1 train_features.csv
    0.09604515376959515,-0.6572847857673993,-0.20595554104907898,0.19603112301129622,-0.35090125695736246,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0
    $ head -1 train_labels.csv
    no
    

现在数据集已经用我们的代码处理完成,我们可以用它来训练一个机器学习模型。在实际应用中,我们还会自动化这些步骤,而不是手动在笔记本中运行。

重要提示

最后一个要点:在这里,我们的作业将输出数据写入 S3。SageMaker Processing 还支持直接写入现有的 SageMaker Feature Store 中的 Feature Group(我们将在本书后面介绍)。API 详细信息可以在 sagemaker.readthedocs.io/en/stable/api/training/processing.html#sagemaker.processing.ProcessingOutput 中查看。

使用你自己的代码处理数据集

在前面的示例中,我们使用了一个内置容器来运行我们的 scikit-learn 代码。SageMaker Processing 还支持使用自定义容器。你可以在 docs.aws.amazon.com/sagemaker/latest/dg/build-your-own-processing-container.html 找到一个示例。

如你所见,SageMaker Processing 使得运行数据处理作业变得非常简单。你可以专注于编写和运行脚本,而无需担心基础设施的配置和管理。

总结

在本章中,你学习了 Amazon SageMaker Ground Truth 如何帮助你通过图像和文本标注工作流构建高度准确的训练数据集。我们将在 第五章 中看到如何使用通过 Ground Truth 标注的图像数据集来训练计算机视觉模型。

然后,你了解了 Amazon SageMaker 处理功能,这是一个帮助你在托管基础设施上运行自定义数据处理工作负载的能力:特征工程、数据验证、模型评估等等。

最后,我们讨论了另外三个 AWS 服务(Amazon EMR、AWS Glue 和 Amazon Athena),以及它们如何融入到你的分析和机器学习工作流中。

在下一章,我们将开始使用 Amazon SageMaker 内置的机器学习模型来训练模型。

第二部分:构建和训练模型

在本节中,你将了解如何使用 Amazon SageMaker 构建和训练机器学习模型。本部分涵盖了 AutoML、内置算法、内置框架以及自定义代码。通过基于 SageMaker SDK 的笔记本,它将解释如何读取训练数据、如何设置训练任务、如何定义训练参数,以及如何在完全托管的基础设施上进行训练。

本节包括以下章节:

  • 第三章使用 Amazon SageMaker AutoPilot 进行 AutoML

  • 第四章训练机器学习模型

  • 第五章训练计算机视觉模型

  • 第六章训练自然语言处理模型

  • 第七章使用内置框架扩展机器学习服务

  • 第八章使用你的算法和代码

第三章:使用亚马逊 SageMaker Autopilot 进行 AutoML

在上一章中,您已经了解了亚马逊 SageMaker 如何帮助您构建和准备数据集。在典型的机器学习项目中,下一步将是开始尝试不同的算法,以找到初步适合的模型,并了解您可以从模型中预期的预测能力。

无论你是使用传统机器学习还是深度学习,在选择算法时有三个选项:

  • 写自己的代码,或者自定义现有代码。只有在你具备强大的统计和计算机科学技能,并且相当确定你能比调优后的现成算法做得更好,而且有足够的时间来完成项目时,这才有意义。让我们面对现实吧,这些条件很少能够满足。

  • 使用内置算法实现您喜欢的库中的一个,如线性回归XGBoost。对于深度学习问题,这包括在TensorFlowPyTorch等中可用的预训练模型。此选项省去了编写机器学习代码的麻烦,而是让您专注于特征工程和模型优化。

  • 使用AutoML,这是一种新兴技术,可以自动构建、训练和优化机器学习模型。

在本章中,您将了解亚马逊 SageMaker Autopilot,这是亚马逊 SageMaker 的 AutoML 功能之一,具有内置的模型可解释性。我们将看到如何在亚马逊 SageMaker Studio 中使用它,而无需编写一行代码,并且如何使用亚马逊 SageMaker SDK:

  • 发现亚马逊 SageMaker Autopilot

  • 在 SageMaker Studio 中使用亚马逊 SageMaker Autopilot

  • 使用亚马逊 SageMaker Autopilot 和 SageMaker SDK

  • 深入探讨亚马逊 SageMaker Autopilot

技术要求

您需要一个 AWS 帐户来运行本章中包含的示例。如果您尚未拥有,请访问aws.amazon.com/getting-started/创建一个。您还应该熟悉 AWS Free Tier(aws.amazon.com/free/),它允许您在一定使用限制内免费使用许多 AWS 服务。

您将需要为您的帐户安装和配置 AWS 命令行界面CLI)(aws.amazon.com/cli/)。

您将需要一个运行 Python 3.x 的环境。虽然安装 Anaconda 发行版(www.anaconda.com/)不是强制的,但强烈建议这样做,因为它包含了我们需要的许多项目(Jupyter、pandasnumpy等)。

本书中包含的代码示例可在 GitHub 上访问(github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition)。您需要安装 Git 客户端才能访问这些示例(git-scm.com/)。

发现 Amazon SageMaker Autopilot

Amazon SageMaker Autopilot 于 2019 年末加入 Amazon SageMaker,是一个 AutoML 功能,能够为你处理所有机器学习步骤。你只需要将列数据集上传到 Amazon S3 存储桶,并定义你希望模型学习的列(目标属性)。然后,你只需启动一个 Autopilot 作业,无论是通过在 SageMaker Studio GUI 中点击几下,还是使用 SageMaker SDK 编写几行代码。

SageMaker Autopilot 的简便性并不意味着缺乏透明度和控制权。你可以看到模型是如何构建的,并且可以不断实验以优化结果。从这个角度来看,SageMaker Autopilot 应该能吸引新手和经验丰富的从业者。

在这一部分中,你将了解 SageMaker Autopilot 作业的不同步骤,以及它们如何帮助提供高质量的模型:

  • 数据分析

  • 特征工程

  • 模型调优

让我们从了解 SageMaker Autopilot 如何分析数据开始。

数据分析

这一步骤负责理解我们要解决的机器学习问题类型。SageMaker Autopilot 当前支持 线性回归二元分类多类别分类

注意

一个常见的问题是:“构建这样的模型需要多少数据?”这是一个出人意料的难题。答案—如果有的话—取决于许多因素,比如特征的数量和质量。作为一个基本的经验法则,一些从业者建议样本数量是特征数量的 10 到 100 倍。无论如何,我建议你至少收集几百个样本(如果你在构建分类模型的话,应该按类别收集样本)。如果有更多特征,收集上千或上万个样本会更好。对于统计机器学习来说,通常不需要几百万个样本,因此从现有数据开始,分析结果,进行迭代,再决定是否需要收集更多数据!

通过分析目标属性的分布,SageMaker Autopilot 可以轻松判断哪一种是正确的。例如,如果目标属性只有两个值(比如“是”和“否”),那么你很可能是在构建一个二元分类模型。

然后,SageMaker Autopilot 会计算数据集和各个列的统计信息:唯一值的数量、均值、中位数等等。机器学习从业者通常会这样做,以便初步了解数据,看到这一过程自动化也是很好的。此外,SageMaker Autopilot 还会生成一个 Jupyter 笔记本,数据探索笔记本,以用户友好的方式呈现这些统计信息。

一旦 SageMaker Autopilot 分析了数据集,它会构建 候选管道,用于训练候选模型。管道是以下内容的组合:

  • 一个数据处理任务,负责特征工程。正如你所猜测的,这个任务运行在Amazon SageMaker Processing上,正如我们在第二章《数据准备技巧》中所研究的那样。

  • 一个训练任务,运行在处理过的数据集上。算法包括 SageMaker 中内置的 Linear Learner、XGBoost 和多层感知机。

接下来,让我们看看 Autopilot 如何在特征工程中发挥作用。

特征工程

这一步负责根据数据分析过程中定义的管道来预处理输入数据集。

候选管道在另一个自动生成的笔记本中进行了完整文档记录——候选生成笔记本。这个笔记本不仅仅是描述性的:你实际上可以运行它的单元格,手动重现 SageMaker Autopilot 执行的步骤。这种透明度和控制级别非常重要,因为它可以让你准确理解模型是如何构建的。因此,你可以验证它是否按预期执行,并且能够向你的利益相关者解释它。此外,如果你有兴趣,你还可以使用这个笔记本作为进一步优化和调整的起点。

最后,让我们看看 Autopilot 中的模型调优。

模型调优

这一步负责根据数据分析过程中定义的管道来训练和调优模型。对于每个管道,SageMaker Autopilot 将启动一个自动模型调优任务(我们将在后续章节中详细介绍这个主题)。简而言之,每个调优任务将使用超参数优化在处理过的数据集上训练大量逐渐提高精度的模型。像往常一样,所有这些都发生在受管的基础设施上。

一旦模型调优完成,您可以在 Amazon SageMaker Studio 中查看模型信息和指标,构建可视化图表等。您也可以使用Amazon SageMaker Experiments SDK 以编程方式执行相同的操作。

最后,您可以像使用任何其他 SageMaker 模型一样部署所选模型,使用 SageMaker Studio GUI 或 SageMaker SDK。

现在我们理解了 Autopilot 任务的不同步骤,让我们在 SageMaker Studio 中运行一个任务。

在 SageMaker Studio 中使用 Amazon SageMaker Autopilot

我们将仅使用 SageMaker Studio 构建一个模型。我们不会写一行机器学习代码,所以准备好进行零代码 AI。

在本节中,您将学习如何完成以下操作:

  • 在 SageMaker Studio 中启动一个 SageMaker Autopilot 任务。

  • 监控任务的不同步骤。

  • 可视化模型并比较它们的属性。

启动任务

首先,我们需要一个数据集。我们将重新使用在第二章《数据准备技巧》中使用的直接营销数据集。这个数据集描述了一个二分类问题:客户是否会接受营销优惠,是或不是?它包含了超过 41,000 个标记的客户样本。让我们深入了解:

  1. 让我们打开 SageMaker Studio。使用数据科学内核创建一个新的 Python 3 笔记本,如下图所示:图 3.1 – 创建一个笔记本

    图 3.1 – 创建一个笔记本

  2. 现在,让我们按以下步骤下载并提取数据集:

    %%sh
    apt-get -y install unzip
    wget -N https://sagemaker-sample-data-us-west-2.s3-us-west-2.amazonaws.com/autopilot/direct_marketing/bank-additional.zip
    unzip -o bank-additional.zip
    
  3. 第二章《数据处理技巧》中,我们通过 Amazon SageMaker Processing 运行了一个特征工程脚本。但在这里,我们不做这样的操作:我们只是将数据集原封不动地上传到 S3,上传到 SageMaker 创建的默认桶中:

    import sagemaker
    prefix = 'sagemaker/DEMO-autopilot/input'
    sess   = sagemaker.Session()
    uri = sess.upload_data(path=”./bank-additional/bank-additional-full.csv”, key_prefix=prefix)
    print(uri)
    

    数据集将可在 S3 中的以下位置获取:

    s3://sagemaker-us-east-2-123456789012/sagemaker/DEMO-autopilot/input/bank-additional-full.csv
    
  4. 现在,我们点击左侧垂直图标栏中的组件与注册表图标,正如下图所示。这将打开实验标签页,我们点击创建自动驾驶实验按钮以创建一个新的自动驾驶任务。图 3.2 – 查看实验

    图 3.2 – 查看实验

  5. 下一个屏幕是我们配置任务的地方。让我们输入my-first-autopilot-job作为实验名称。

  6. 我们使用第 3 步返回的路径设置输入数据集的位置。如下图所示,我们可以浏览 S3 桶,或者直接输入 S3 位置:图 3.3 – 定义输入位置

    图 3.3 – 定义输入位置

  7. 下一步是定义目标属性的名称,如下图所示。存储“是”或“否”标签的列称为“y”。图 3.4 – 定义目标属性

    图 3.4 – 定义目标属性

  8. 如下图所示,我们设置任务输出文件的位置,任务生成的工件将被复制到该位置。我在此使用 s3://sagemaker-us-east-2-123456789012/sagemaker/DEMO-autopilot/output/,当然,你应该用你自己的区域和账户编号来更新此位置。图 3.5 – 定义输出位置

    图 3.5 – 定义输出位置

  9. 我们设置所需的任务类型,如下图所示。在这里,我们选择自动,让 SageMaker Autopilot 自动确定问题类型。或者,我们可以选择二元分类,并选择我们的指标:准确率AUCF1(默认设置)。图 3.6 – 设置问题类型

    图 3.6 – 设置问题类型

  10. 最后,我们决定是运行完整任务,还是仅生成笔记本。如果选择前者,则如下一图所示。如果选择后者,那么当我们希望手动训练并调整参数时,它将是一个不错的选择。我们还决定暂时不自动部署最佳模型。图 3.7 – 运行完整实验

    图 3.7 – 运行完整实验

  11. 可选地,在高级设置部分,我们可以更改 IAM 角色,设置作业工件的加密密钥,定义我们希望启动作业实例的 VPC 等。这里我们保持默认值。

  12. 任务设置已完成:这一切只需要一个屏幕。然后,我们点击创建实验,任务就开始了!

监控任务

一旦任务启动,它将经过我们已经讨论过的三个步骤,预计大约需要 5 小时完成。新实验会列在实验标签中,我们可以右键点击描述 AutoML 任务以查看其当前状态。这会打开如下屏幕,我们可以看到任务的进度:

  1. 如预期的那样,任务开始时会分析数据,如下面的截图所示:图 3.8 – 查看任务进度

    图 3.8 – 查看任务进度

  2. 大约 10 分钟后,数据分析完成,任务进入特征工程阶段,此时输入数据集将按照候选管道中定义的步骤进行转换。如下面的截图所示,我们还可以看到右上角新增了两个按钮,分别指向候选生成数据探索笔记本:别担心,我们将在本章后面详细讲解这两者。图 3.9 – 查看任务进度

    图 3.9 – 查看任务进度

  3. 一旦特征工程完成,接下来的工作就是模型调优,在这一阶段,候选模型会被训练和调整。如下面的截图所示,第一个训练任务很快出现在试验标签中。 "试验"是 SageMaker 用来表示一组相关任务的名称,例如处理任务、批量转换任务和训练任务。我们可以看到目标,也就是任务试图优化的指标(在本例中是 F1 得分)。我们可以根据该指标对任务进行排序,当前最好的调优任务会被高亮显示并标注星标。图 3.10 – 查看调优任务

    图 3.10 – 查看调优任务

  4. 一旦 AutoPilot 任务完成,你的屏幕应该会类似于以下截图。在这里,顶部模型达到了 0.8031 的 F1 得分。图 3.11 – 查看结果

    图 3.11 – 查看结果

  5. 如果我们选择最佳任务并右键点击打开模型详细信息,我们可以看到一个模型可解释性图,显示了最重要的特征,如下截图所示。该图基于由 AutoPilot 自动计算的全局SHapley 加法解释SHAP)值 (github.com/slundberg/shap)。图 3.12 – 查看最重要特征

    图 3.12 – 查看最重要特征

  6. Artifacts(工件)标签中,我们还可以看到构建模型时涉及的训练工件和参数的列表:输入数据、训练和验证分割、转换后的数据集、特征工程代码、算法(在我的案例中是 XGBoost)等。

此时,我们可以直接部署最佳作业,但我们将使用 SageMaker Studio 内置的可视化工具来比较前 10 个作业。

比较作业

默认情况下,单个 SageMaker Autopilot 作业会训练 250 个作业。随着时间的推移,你可能会有成千上万个作业,并且你可能希望比较它们的属性。让我们看看如何操作:

  1. 在左侧的 Experiments 标签页中,我们定位到我们的作业,右键点击在试验组件列表中打开,如下图所示:图 3.13 – 打开试验列表

    图 3.13 – 打开试验列表

  2. 这将打开试验组件列表,如下图所示。

    我们点击右侧的齿轮图标打开 表格属性 面板,然后取消勾选除 Experiment name(实验名称)、Trial component name(试验组件名称)和 ObjectiveMetric(目标指标)以外的所有选项。在主面板中,我们通过点击箭头将作业按目标指标降序排序。按住Shift键并点击前 10 个作业进行选择,如下图所示:

    图 3.14 – 比较作业

    图 3.14 – 比较作业

  3. 然后,我们点击添加图表按钮。这将打开一个新的视图,见下图所示。点击图表框底部以打开右侧的图表属性面板。图 3.15 – 创建图表

    图 3.15 – 创建图表

    由于我们的训练作业非常短(大约一分钟),因此没有足够的数据生成时间序列图表,所以我们选择汇总统计。我们将构建一个散点图,将 eta 和 lambda 超参数进行对比,如下图所示。我们还将数据点按我们的试验名称进行着色。

    图 3.16 – 创建图表

    图 3.16 – 创建图表

  4. 放大下图,我们可以快速地可视化我们的作业及其相应的参数。我们还可以构建其他图表,展示某些超参数对准确率的影响。这将帮助我们筛选出一些模型进行进一步的测试。也许我们最终会考虑将多个模型用于集成预测。

图 3.17 – 绘制超参数图

图 3.17 – 绘制超参数图

下一步是部署模型并开始测试。

部署和调用模型

SageMaker Studio 使得部署模型变得非常简单。让我们看看如何操作:

  1. 返回到实验标签页,我们右键点击实验名称并选择描述 AutoML 作业。这会打开训练作业列表。确保它们按目标值降序排列,我们选择最好的那个(它会用星标高亮显示),如下图所示,然后点击部署模型按钮:图 3.18 – 部署模型

    图 3.18 – 部署模型

  2. my-first-autopilot-endpoint下,保持其他设置不变,点击ml.m5.xlarge实例:图 3.19 – 部署模型

    图 3.19 – 部署模型

  3. 进入左侧垂直面板中的端点部分,我们可以看到正在创建的端点。如下面的截图所示,最初它会处于创建中状态。几分钟后,它会变为在服务中图 3.20 – 创建端点

    图 3.20 – 创建端点

  4. 转到 Jupyter 笔记本(我们可以重复使用写来下载数据集的那个),我们定义端点的名称和要预测的样本。这里,我使用的是数据集的第一行:

    ep_name = 'my-first-autopilot-endpoint'
    sample = '56,housemaid,married,basic.4y,no,no,no,telephone,may,mon,261,1,999,0,nonexistent,1.1,93.994,-36.4,4.857,5191.0'
    
  5. 我们为 SageMaker 运行时创建一个boto3客户端。这个运行时包含一个 API,invoke_endpoint (boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sagemaker-runtime.html)。这使得它非常高效,适合嵌入到只需要调用模型的客户端应用程序中:

    import boto3
    sm_rt = boto3.Session().client('runtime.sagemaker')
    
  6. 我们将样本发送到端点,同时传递输入和输出内容类型:

    response = sm_rt.invoke_endpoint(EndpointName=ep_name,
                                  ContentType='text/csv',
                                  Accept='text/csv',
                                  Body=sample)
    
  7. 我们解码预测结果并打印出来——该客户不太可能接受该提议:

    response = response['Body'].read().decode(”utf-8”)
    print(response)
    

    这个样本被预测为“否”:

    no
    
  8. 当我们完成测试端点时,我们应该删除它以避免不必要的费用。我们可以通过boto3中的delete_endpoint API 来实现这一点(boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sagemaker.html#SageMaker.Client.delete_endpoint):

    sm = boto3.Session().client('sagemaker')
    sm.delete_endpoint(EndpointName=ep_name)
    

恭喜!你已经成功地在 Amazon SageMaker 上构建、训练并部署了你的第一个机器学习模型。是不是很简单?我们写的唯一代码是下载数据集并用模型进行预测。

使用SageMaker Studio是快速尝试新数据集的好方法,也让技术要求较低的用户可以独立构建模型。高级用户还可以将自己的自定义镜像添加到 SageMaker Studio,更多详细信息请参考docs.aws.amazon.com/sagemaker/latest/dg/studio-byoi.html

现在,让我们看看如何通过SageMaker SDK以编程方式使用 SageMaker Autopilot。

使用 SageMaker Autopilot SDK

Amazon SageMaker SDK 包括一个简单的 API 用于 SageMaker Autopilot。你可以在 sagemaker.readthedocs.io/en/stable/automl.html 查阅文档。

在本节中,你将学习如何使用此 API 在与上一节相同的数据集上训练模型。

启动任务

SageMaker SDK 使启动 Autopilot 任务变得异常简单——只需将数据上传到 S3,然后调用一个 API!让我们来看看如何操作:

  1. 首先,我们导入 SageMaker SDK:

    import sagemaker
    sess = sagemaker.Session()
    
  2. 接着,我们下载数据集:

    %%sh
    wget -N https://sagemaker-sample-data-us-west-2.s3-us-west-2.amazonaws.com/autopilot/direct_marketing/bank-additional.zip
    unzip -o bank-additional.zip
    
  3. 接下来,我们将数据集上传到 S3:

    bucket = sess.default_bucket()                     
    prefix = 'sagemaker/DEMO-automl-dm'
    s3_input_data = sess.upload_data(path=”./bank-additional/bank-additional-full.csv”, key_prefix=prefix+'input')
    
  4. 接着,我们配置 AutoML 任务,这只需要一行代码。我们定义了目标属性(记住,这一列的名称是“y”),以及训练产物的存储位置。可选地,我们还可以设置任务的最大运行时间、每个任务的最大运行时间,或者减少将要调优的候选模型数量。请注意,限制任务持续时间过多可能会影响其准确性。对于开发目的而言,这不是问题,因此我们将任务时长限制为一小时,或者 250 个调优任务(以先到为准):

    from sagemaker.automl.automl import AutoML
    auto_ml_job = AutoML(
        role = sagemaker.get_execution_role(),                                          
        sagemaker_session = sess,                             
        target_attribute_name = 'y',                             
        output_path = 
            's3://{}/{}/output'.format(bucket,prefix),
        max_runtime_per_training_job_in_seconds = 600,
        max_candidates = 250,
        total_job_runtime_in_seconds = 3600
    )
    
  5. 接下来,我们启动 Autopilot 任务,传递给它训练集的位置。我们关闭日志(谁想阅读数百个调优日志呢?),并将调用设置为非阻塞,因为我们希望在接下来的单元格中查询任务状态:

    auto_ml_job.fit(inputs=s3_input_data, logs=False, wait=False)
    

任务会立刻开始。现在让我们看看如何监控它的状态。

监控任务

在任务运行时,我们可以使用describe_auto_ml_job() API 来监控其进度:

  1. 例如,以下代码将每 60 秒检查一次任务的状态,直到数据分析步骤完成:

    from time import sleep
    job = auto_ml_job.describe_auto_ml_job()
    job_status = job['AutoMLJobStatus']
    job_sec_status = job['AutoMLJobSecondaryStatus']
    if job_status not in ('Stopped', 'Failed'):
        while job_status in ('InProgress') and job_sec_status in ('AnalyzingData'):
            sleep(60)
            job = auto_ml_job.describe_auto_ml_job()
            job_status = job['AutoMLJobStatus']
            job_sec_status =   
                 job['AutoMLJobSecondaryStatus']
            print (job_status, job_sec_status)
    
  2. 一旦数据分析完成,两个自动生成的 notebook 就会可用。我们可以使用相同的 API 查找它们的位置:

    job = auto_ml_job.describe_auto_ml_job()
    job_candidate_notebook = job['AutoMLJobArtifacts']['CandidateDefinitionNotebookLocation']
    job_data_notebook = job['AutoMLJobArtifacts']['DataExplorationNotebookLocation']
    print(job_candidate_notebook)
    print(job_data_notebook)
    

    这会打印出两个 notebook 的 S3 路径:

    s3://sagemaker-us-east-2-123456789012/sagemaker/DEMO-automl-dm/output/automl-2020-04-24-14-21-16-938/sagemaker-automl-candidates/pr-1-a99cb56acb5945d695c0e74afe8ffe3ddaebafa94f394655ac973432d1/notebooks/SageMakerAutopilotCandidateDefinitionNotebook.ipynb
    s3://sagemaker-us-east-2-123456789012/sagemaker/DEMO-automl-dm/output/automl-2020-04-24-14-21-16-938/sagemaker-automl-candidates/pr-1-a99cb56acb5945d695c0e74afe8ffe3ddaebafa94f394655ac973432d1/notebooks/SageMakerAutopilotDataExplorationNotebook.ipynb
    
  3. 使用 AWS CLI,我们可以将两个 notebook 下载到本地。我们稍后将在本章中查看它们:

    %%sh -s $job_candidate_notebook $job_data_notebook
    aws s3 cp $1 .
    aws s3 cp $2 .
    
  4. 当特征工程运行时,我们可以使用与之前相同的代码片段等待完成,循环判断 job_sec_status 是否等于 FeatureEngineering

  5. 一旦模型调优完成,我们可以非常轻松地找到最佳候选模型:

    job_best_candidate = auto_ml_job.best_candidate()
    print(job_best_candidate['CandidateName'])
    print(job_best_candidate['FinalAutoMLJobObjectiveMetric'])
    

    这会输出最佳调优任务的名称及其验证准确度:

    tuning-job-1-57d7f377bfe54b40b1-030-c4f27053
    {'MetricName': 'validation:accuracy', 'Value': 0.9197599935531616}
    

然后,我们可以使用 SageMaker SDK 部署并测试模型。我们已经涵盖了很多内容,所以将这部分留到后续章节,我们会重新回顾这个例子。

清理

SageMaker Autopilot 创建了许多底层产物,例如数据集拆分、预处理脚本、预处理数据集和模型。如果你想完全清理,下面的代码片段可以做到这一点。当然,你也可以使用 AWS CLI:

import boto3
job_outputs_prefix = '{}/output/{}'.format(prefix, job['AutoMLJobName'])
s3_bucket = boto3.resource('s3').Bucket(bucket)
s3_bucket.objects.filter(Prefix=job_outputs_prefix).delete()

既然我们已经知道如何使用 SageMaker Studio GUI 和 SageMaker SDK 训练模型,让我们深入了解其背后的原理。工程师们喜欢理解事物是如何真正工作的,对吧?

深入了解 SageMaker Autopilot

在本节中,我们将详细了解 SageMaker Autopilot 如何处理数据并训练模型。如果现在感觉太高级,可以跳过这些内容。等你对该服务有了更多经验后,可以随时回头再看。

首先,让我们来看一下 SageMaker Autopilot 生成的工件。

作业工件

列出我们的 S3 存储桶可以确认存在许多不同的工件:

$ aws s3 ls s3://sagemaker-us-east-2-123456789012/sagemaker/DEMO-autopilot/output/my-first-autopilot-job/

我们可以看到许多新的前缀。让我们来弄明白它们分别代表什么:

PRE data-processor-models/
PRE documentation/
PRE preprocessed-data/
PRE sagemaker-automl-candidates/
PRE transformed-data/
PRE tuning/
PRE validations/
  • preprocessed-data/tuning_data 前缀包含从输入数据集生成的训练和验证拆分。每个拆分进一步被分解成小的 CSV 块。

  • sagemaker-automl-candidates 前缀包含 10 个数据预处理脚本(dpp[0-9].py),每个管道一个。它还包含训练它们的代码(trainer.py),以及使用这 10 个生成模型中的每一个处理输入数据集的代码(sagemaker_serve.py)。最后但同样重要的是,它包含自动生成的笔记本。

  • data-processor-models 前缀包含通过 dpp 脚本训练的 10 个数据处理模型。

  • transformed-data 前缀包含训练和验证拆分的 10 个处理版本。

  • tuning 前缀包含在模型调优步骤中训练的实际模型。

  • documentation 前缀包含可解释性报告。

以下图表总结了这些工件之间的关系:

图 3.21 – 总结 Autopilot 过程

图 3.21 – 总结 Autopilot 过程

在接下来的几节中,我们将看一下两个自动生成的笔记本,它们是 SageMaker Autopilot 中最重要的功能之一。

数据探索笔记本

一旦数据分析步骤完成,此笔记本可以在 Amazon S3 上使用。

第一部分,如下图所示,简单地显示了数据集的一个样本:

图 3.22 – 查看数据集统计信息

图 3.22 – 查看数据集统计信息

如下截图所示,第二部分重点分析列:缺失值的百分比、唯一值的计数和描述性统计数据。例如,pdays 字段的最大值和中位数均为 999,这看起来很可疑。如上一章所述,999 确实是一个占位符值,意味着客户之前从未被联系过。

图 3.23 – 查看数据集统计信息

图 3.23 – 查看数据集统计信息

如你所见,这个笔记本为我们省去了计算这些统计数据的麻烦,我们可以利用它们快速检查数据集是否符合预期。

现在,让我们来看一下第二个笔记本。正如你将看到的,它非常具有洞察力!

候选生成笔记本

本笔记本包含了 10 个候选管道的定义,以及它们的训练方式。这是一个可运行的笔记本,高级实践者可以利用它重现 AutoML 过程,并不断优化实验。请注意,这完全是可选的!直接部署最佳模型并开始测试也是完全可以的。

话虽如此,让我们手动运行其中一个管道:

  1. 我们打开笔记本并通过点击右上角的导入笔记本链接保存一个可读写的副本。

  2. 然后,我们在SageMaker 设置部分运行单元格,以导入所有所需的工件和参数。

  3. 移动到候选管道部分,我们创建一个运行器对象,它将为选定的候选管道启动作业:

    from sagemaker_automl import AutoMLInteractiveRunner, AutoMLLocalCandidate
    automl_interactive_runner = AutoMLInteractiveRunner(AUTOML_LOCAL_RUN_CONFIG)
    
  4. 接着,我们添加第一个管道(dpp0)。笔记本告诉我们:“此数据转换策略首先使用RobustImputer转换'数值'特征(将缺失值转换为 nan),然后使用ThresholdOneHotEncoder转换'类别'特征。它将所有生成的特征合并并应用RobustStandardScaler。转换后的数据将用于调整 XGBoost 模型。”我们只需运行以下单元格来添加它:

    automl_interactive_runner.select_candidate(
        {”data_transformer”: {
            ”name”: ”dpp0”,
            …
        }
    )
    

    如果你对RobustImputerThresholdOneHotEncoder的实现感到好奇,超链接将引导你到sagemaker_sklearn_extension模块中的相关源文件(https://github.com/aws/sagemaker-scikit-learn-extension/)。

    这样,你可以准确理解数据是如何处理的。由于这些对象是基于 scikit-learn 对象构建的,它们应该会很快变得非常熟悉。例如,我们可以看到RobustImputer是建立在sklearn.impute.SimpleImputer之上的,并增加了额外的功能。同样,ThresholdOneHotEncodersklearn.preprocessing.OneHotEncoder的扩展。

  5. 浏览其他管道时,我们可以看到不同的处理策略和算法。你应该能看到在一些管道中使用的线性学习者算法。它是 SageMaker 中的内置算法之一,我们将在下一章进行讲解。你还应该能看到基于神经网络的mlp算法。

  6. 向下滚动,我们进入了dpp0.py脚本,并看到该模型将使用 XGBoost 算法进行训练。

  7. 点击dpp0超链接会打开脚本。正如预期的那样,我们看到它构建了一个 scikit-learn 变换器管道(不要与由预处理和训练作业组成的 SageMaker 管道混淆)。数值特征的缺失值被填补,而类别特征则被进行独热编码。接下来,所有特征都被缩放,标签也被编码:

    numeric_processors = Pipeline(
      steps=[('robustimputer',
             RobustImputer(strategy='constant',fill_values=nan))]
    )
    categorical_processors = Pipeline(
      steps=[('thresholdonehotencoder', 
             ThresholdOneHotEncoder(threshold=301))]
    )
    column_transformer = ColumnTransformer(
      transformers=[
        ('numeric_processing', numeric_processors, numeric),
        ('categorical_processing', categorical_processors,   
         categorical)]
    )
    return Pipeline(steps=[
      ('column_transformer', column_transformer),   
      ('robuststandardscaler', RobustStandardScaler())]
    )
    
  8. 回到笔记本中,我们在运行数据转换步骤部分启动此脚本:

    automl_interactive_runner.fit_data_transformers(parallel_jobs=7)
    
  9. 这将创建两个顺序执行的 SageMaker 作业,其工件将存储在为笔记本运行创建的新前缀中:

    dpp0 transformers on the input dataset.The second job processes the input dataset with the resulting model. For the record, this job uses the SageMaker **Batch Transform** feature, which will be covered in a later chapter.
    
  10. 返回到 SageMaker Studio,让我们进一步了解这两个作业。从my-first-a-notebook-run-24-13-17-22-dpp0-train-24-13-38-38-aws-training-jobmy-first-a-notebook-run-24-13-17-22-dpp0-transform-24-13-38-38-aws-transform-job开始。

  11. 双击作业名称会打开试验详情窗口,如下截图所示。它告诉我们关于作业的所有信息:参数、工件的位置等等:

图 3.25 – 描述一个试验

图 3.25 – 描述一个试验

一旦数据处理完成,笔记本将继续进行自动模型调优模型部署。我们还没有讨论这些主题,所以现在先停在这里。等你对这些内容熟悉后,建议你继续阅读笔记本的其余部分。

总结

正如你所看到的,Amazon SageMaker Autopilot 使得初学者和高级用户都能轻松构建、训练和优化机器学习模型。

在本章中,你了解了 Autopilot 作业的不同步骤,以及它们从机器学习角度的含义。你还学会了如何使用 SageMaker Studio 图形用户界面和 SageMaker SDK 构建一个分类模型,几乎不需要编写代码。然后,我们深入探讨了自动生成的笔记本,这些笔记本让你对建模过程拥有完全的控制权和透明度。特别是,你学会了如何手动运行候选生成笔记本,以重播所有涉及的步骤。

在下一章中,你将学习如何使用 Amazon SageMaker 中的内置算法,训练模型以解决各种机器学习问题。

第四章:训练机器学习模型

在上一章中,你学习了 Amazon SageMaker Autopilot 如何让你轻松构建、训练和自动优化模型,而无需编写一行机器学习代码。

对于 SageMaker Autopilot 不支持的问题类型,下一最佳选择是使用 SageMaker 中已实现的算法之一,并在你的数据集上进行训练。这些算法被称为 内置算法,它们涵盖了许多典型的机器学习问题,从分类到时间序列到异常检测。

在本章中,你将学习适用于监督学习和无监督学习的内置算法,了解你可以用它们解决什么类型的问题,以及如何使用 SageMaker SDK 来使用它们:

  • 发现 Amazon SageMaker 中的内置算法

  • 使用内置算法训练和部署模型

  • 使用 SageMaker SDK 与内置算法

  • 使用更多内置算法

技术要求

你需要一个 AWS 账户才能运行本章中的示例。如果你还没有账户,请访问 aws.amazon.com/getting-started/ 创建一个。你还应该熟悉 AWS 免费套餐 (aws.amazon.com/free/),它让你在某些使用限制内免费使用许多 AWS 服务。

你需要为你的账户安装并配置 AWS 命令行界面(CLI) (aws.amazon.com/cli/)。

你需要一个工作中的 Python 3.x 环境。安装 Anaconda 发行版 (www.anaconda.com/) 不是强制性的,但强烈推荐,因为它包含了我们将需要的许多项目(Jupyter,pandasnumpy 等)。

本书中包含的代码示例可在 GitHub 上找到:github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装 Git 客户端才能访问它们 (git-scm.com/)。

发现 Amazon SageMaker 中的内置算法

内置算法是由亚马逊实现的机器学习算法,在某些情况下是由亚马逊发明的 (docs.aws.amazon.com/sagemaker/latest/dg/algos.html)。它们让你在无需编写一行机器学习代码的情况下,快速训练和部署自己的模型。实际上,由于训练和预测代码是现成的,你无需担心实现它,能够专注于眼前的机器学习问题。像往常一样,SageMaker 完全管理基础设施,节省你更多的时间。

在本节中,您将了解传统机器学习问题的内置算法。计算机视觉和自然语言处理相关的算法将在接下来的两章中介绍。

有监督学习

有监督学习专注于需要标记数据集的问题,如回归或分类:

  • 线性学习器构建线性模型来解决回归问题以及分类问题(包括二分类和多分类)。

  • 因式分解机构建线性模型来解决回归问题以及分类问题(包括二分类和多分类)。因式分解机是线性模型的推广,特别适用于高维稀疏数据集,如推荐系统中的用户-物品交互矩阵。

  • K 最近邻KNN)构建非参数模型用于回归和分类问题。

  • XGBoost构建回归、分类和排序问题的模型。XGBoost 可能是当前使用最广泛的机器学习算法,SageMaker 使用的是github.com/dmlc/xgboost提供的开源实现。

  • DeepAR构建多变量时间序列的预测模型。DeepAR 是基于循环神经网络的亚马逊发明算法,您可以通过arxiv.org/abs/1704.04110了解更多信息。

  • Object2Vec从通用高维对象中学习低维嵌入。Object2Vec 是由亚马逊发明的算法。

  • BlazingText构建文本分类模型。这个算法由亚马逊发明,您可以通过dl.acm.org/doi/10.1145/3146347.3146354了解更多信息。

无监督学习

无监督学习不需要标记数据集,涉及的问题包括聚类或异常检测:

  • K 均值构建聚类模型。SageMaker 使用了一个修改版的网页规模 K 均值聚类算法(www.eecs.tufts.edu/~dsculley/papers/fastkmeans.pdf)。

  • 主成分分析PCA)构建降维模型。

  • 随机切割森林构建异常检测模型。

  • IP 洞察构建模型以识别 IPv4 地址的使用模式。这对于监控、网络安全等领域非常有用。

  • BlazingText计算词向量,这对于自然语言处理任务非常有用。

本章剩余部分将详细介绍其中一些算法。

关于可扩展性

在我们深入了解如何使用这些算法进行模型训练和部署之前,您可能会想知道为什么要使用它们,而不是使用像scikit-learnR等著名库中的算法。

首先,这些算法已经由亚马逊团队实现并调优,亚马逊团队可不是机器学习的新人!大量的工作已经投入到确保这些算法在 AWS 基础设施上尽可能快速运行,无论你使用什么类型的实例。此外,许多算法支持分布式训练,让你可以将模型训练分布到一个完全托管的实例集群中。

正因如此,基准测试表明,这些算法通常比竞争对手的实现要好 10 倍。在许多情况下,它们也更具成本效益。你可以在以下网址了解更多信息:

当然,这些算法也受益于 SageMaker 中的所有功能,正如你在本书的结尾部分会发现的那样。

使用内置算法训练和部署模型

Amazon SageMaker 让你以多种不同配置训练和部署模型。虽然它鼓励最佳实践,但它是一个模块化的服务,允许你按照自己的方式操作。

在本节中,我们将首先看看一个典型的端到端工作流程,展示如何从数据上传到模型部署,使用 SageMaker。然后,我们将讨论替代工作流程,以及如何根据需要挑选所需的功能。最后,我们将深入了解,从基础设施的角度看,当我们训练和部署时会发生什么。

理解端到端工作流程

让我们看看一个典型的 SageMaker 工作流程。你会在我们的示例中反复看到它,也会在 GitHub 上提供的 AWS 笔记本中看到它(github.com/awslabs/amazon-sagemaker-examples/):

  1. protobuf (developers.google.com/protocol-buffers)。

  2. 配置训练任务:在这里,你可以选择想要训练的算法,设置超参数,并定义训练任务的基础设施要求。

  3. 启动训练任务:在这里,我们传入你在 S3 中的数据集位置。训练发生在托管的基础设施上,系统会根据你的需求自动创建和配置。一旦训练完成,模型工件会被保存在 S3 中。训练基础设施会自动终止,你只需为实际使用的部分付费。

  4. 部署模型:你可以将模型部署到 实时 HTTPS 端点 进行实时预测,或部署到 批量转换 中。同样,你只需要定义基础设施要求。

  5. 预测数据:可以调用实时端点或批量转换器。正如你所期望的,这里的基础设施也已托管。对于生产环境,你还需要监控数据和预测的质量。

  6. 清理!:这涉及关闭端点,以避免不必要的费用。

理解这个工作流程对于提高 Amazon SageMaker 的生产力至关重要。幸运的是,SageMaker SDK 提供了与这些步骤高度匹配的简单 API,因此你不必担心该使用哪个 API,或者何时使用它。

在我们开始查看 SDK 之前,让我们考虑一些在你的业务和技术环境中可能有意义的替代工作流。

使用替代工作流

Amazon SageMaker 是一个模块化的服务,允许你按自己的方式工作。让我们首先考虑一个工作流,其中你会在 SageMaker 上进行训练,并在你自己的服务器上进行部署,无论原因是什么。

导出模型

步骤 1-3 与前面的示例相同,然后你可以做如下操作:

  1. 从 S3 下载训练工件,它以 model.tar.gz 文件的形式存在。

  2. 提取存储在工件中的模型。

  3. 在你自己的服务器上,使用适当的机器学习库加载模型:

现在,让我们看看如何将现有模型导入并部署到 SageMaker 上。

导入模型

步骤同样简单:

  1. 将你的模型打包成模型工件(model.tar.gz)。

  2. 将工件上传到 S3 桶。

  3. 将工件注册为 SageMaker 模型。

  4. 部署模型并进行预测。

这只是一个快速的概览。我们将在 第十一章部署机器学习模型 中为这两个工作流运行完整的示例。

使用完全托管的基础设施

所有 SageMaker 作业都在托管基础设施上运行。让我们看看背后的工作原理,看看训练和部署模型时发生了什么。

将算法打包到 Docker 容器中

所有 SageMaker 算法必须打包在 Docker 容器中。别担心,使用 SageMaker 并不需要你对 Docker 有太多了解。如果你不熟悉 Docker,建议你阅读这个教程,以了解关键概念和工具:docs.docker.com/get-started/。多了解一点总是有好处的!

正如您所预期的,内置算法已经打包好,并且容器随时可用于训练和部署。它们托管在Amazon Elastic Container RegistryECR)中,这是 AWS 的 Docker 注册服务 (aws.amazon.com/ecr/)。由于 ECR 是一个基于区域的服务,您会在每个 SageMaker 可用的区域找到一系列容器。

您可以在 docs.aws.amazon.com/sagemaker/latest/dg/sagemaker-algo-docker-registry-paths.html 找到内置算法容器的列表。例如,在 eu-west-1 区域,Linear Learner 算法的容器名称是 438346466558.dkr.ecr.eu-west-1.amazonaws.com/linear-learner:latest。这些容器只能拉取到 SageMaker 管理的实例中,因此您无法在本地计算机上运行它们。

现在,让我们来看一下底层基础设施。

创建训练基础设施

当您启动训练作业时,SageMaker 会根据您的需求(实例类型和实例数量)启动基础设施。

一旦训练实例投入使用,它会从 ECR 拉取适当的训练容器。超参数会应用到算法上,算法也会接收到数据集的位置。默认情况下,算法会从 S3 复制完整的数据集并开始训练。如果配置了分布式训练,SageMaker 会自动将数据集批次分发到集群中的不同实例。

训练完成后,模型会被打包成模型工件并保存在 S3 中。然后,训练基础设施会自动关闭。日志可以在Amazon CloudWatch Logs中查看。最后但同样重要的是,您只会按实际的训练时间收费。

创建预测基础设施

当您启动部署作业时,SageMaker 会根据您的需求再次创建基础设施。

现在让我们暂时专注于实时终端节点,而不关注批量转换。

一旦终端节点实例投入使用,它会从 ECR 拉取适当的预测容器,并从 S3 加载您的模型。然后,HTTPS 端点会被配置好,并在几分钟内准备好进行预测。

如果您配置了多个实例的终端节点,负载均衡和高可用性将自动设置。如果您配置了自动扩展,也会应用此设置。

正如您所预期的,终端节点会保持开启状态,直到显式删除它,无论是在 AWS 控制台中,还是通过 SageMaker API 调用。在此期间,您将为终端节点付费,因此请确保删除不再需要的终端节点!

现在我们了解了大致情况,让我们开始了解 SageMaker SDK,看看如何使用它来训练和部署模型。

使用内置算法的 SageMaker SDK

熟悉 SageMaker SDK 对于充分利用 SageMaker 至关重要。您可以在sagemaker.readthedocs.io找到其文档。

通过一个简单的示例入手是最好的开始方式。在本节中,我们将使用 Linear Learner 算法在波士顿房价数据集上训练回归模型(www.kaggle.com/c/boston-housing)。我们将非常慢地进行,每一步都不遗漏。再次强调,这些概念非常重要,请花时间理解每一个步骤。

提醒

我建议您跟随并运行伴随 GitHub 仓库中的代码。我们已尽一切努力检查文中所有代码示例。然而,对于电子版的用户,复制粘贴可能会导致不可预测的结果:格式问题、奇怪的引号等。

数据准备

内置算法要求数据集必须符合特定格式,例如CSVprotobuflibsvm。支持的格式可以在算法文档中找到。例如,Linear Learner 支持 CSV 和 RecordIO 包装的 protobuf(docs.aws.amazon.com/sagemaker/latest/dg/linear-learner.html#ll-input_output)。

我们的输入数据集已经以 CSV 格式存储在仓库中,因此我们将使用它。数据集准备将极为简单,我们将手动运行它:

  1. 使用pandas,我们用 pandas 加载 CSV 数据集:

    import pandas as pd
    dataset = pd.read_csv('housing.csv')
    
  2. 然后,我们打印数据集的形状:

    print(dataset.shape)
    

    它包含 506 个样本和 13 列:

    (506, 13)
    
  3. 现在,我们显示数据集的前 5 行:

    dataset[:5]
    

    这将输出以下图中可见的表格。对于每个房子,我们可以看到 12 个特征,以及一个目标属性(medv),该属性是房子的中位数价格(以千美元为单位):

    图 4.1 – 查看数据集

    图 4.1 – 查看数据集

  4. 阅读算法文档(docs.aws.amazon.com/sagemaker/latest/dg/cdf-training.html),我们看到Amazon SageMaker 要求 CSV 文件没有头部记录,且目标变量必须位于第一列。因此,我们将medv列移动到数据框的最前面:

    dataset = pd.concat([dataset['medv'],
                         dataset.drop(['medv'], axis=1)], 
                         axis=1)
    
  5. 一些scikit-learn的魔法帮助我们将数据框分为两部分——90%用于训练,10%用于验证:

    from sklearn.model_selection import train_test_split
    training_dataset, validation_dataset =  
        train_test_split(dataset, test_size=0.1)
    
  6. 我们将这两个拆分保存为单独的 CSV 文件,不包含索引或头部:

    training_dataset.to_csv('training_dataset.csv', 
                            index=False, header=False)
    validation_dataset.to_csv('validation_dataset.csv', 
                              index=False, header=False)
    
  7. 现在,我们需要将这两个文件上传到 S3。我们可以使用任何桶,在这里我们将使用 SageMaker 在我们运行的区域自动创建的默认桶。我们可以通过sagemaker.Session.default_bucket() API 查找其名称:

    import sagemaker
    sess = sagemaker.Session()
    bucket = sess.default_bucket()
    
  8. 最后,我们使用 sagemaker.Session.upload_data() API 将两个 CSV 文件上传到默认存储桶。这里,训练和验证数据集每个由一个文件组成,但如果需要,我们也可以上传多个文件。因此,我们必须将数据集上传到不同的 S3 前缀下,以避免文件混淆:

    prefix = 'boston-housing'
    training_data_path = sess.upload_data(
        path='training_dataset.csv', 
        key_prefix=prefix + '/input/training')
    validation_data_path = sess.upload_data(
        path='validation_dataset.csv', 
        key_prefix=prefix + '/input/validation')
    print(training_data_path)
    print(validation_data_path)
    

    这两个 S3 路径如下所示。自然,默认存储桶名称中的账户号码会有所不同:

    s3://sagemaker-eu-west-1-123456789012/boston-housing/input/training/training_dataset.csv
    s3://sagemaker-eu-west-1-123456789012/boston-housing/input/validation/validation_dataset.csv
    

现在数据已经准备好在 S3 中,我们可以配置训练任务。

配置训练任务

Estimator 对象(sagemaker.estimator.Estimator)是模型训练的基石。它让你选择适当的算法,定义你的训练基础设施要求等。

SageMaker SDK 还包括特定算法的估算器,例如 sagemaker.LinearLearnersagemaker.PCA。我通常认为它们不如通用估算器灵活(例如不支持 CSV 格式),并且不推荐使用它们。使用 Estimator 对象还可以让你在不同示例之间重用代码,正如我们在接下来的章节中所看到的:

  1. 在本章之前,我们学习了 SageMaker 算法是打包在 Docker 容器中的。通过使用 boto3image_uris.retrieve() API,我们可以轻松找到我们运行的区域中 Linear Learner 算法的名称:

    from sagemaker import get_execution_role
    from sagemaker.image_uris import retrieve
    region = sess.boto_session.region_name
    container = retrieve('linear-learner', region)
    
  2. 现在我们知道了容器的名称,我们可以使用 Estimator 对象来配置我们的训练任务。除了容器名称外,我们还需要传递 SageMaker 实例将使用的 IAM 角色、用于训练的实例类型和实例数量,以及模型的输出位置。Estimator 会自动生成一个训练任务,我们还可以通过 base_job_name 参数设置自己的前缀:

    from sagemaker.estimator import Estimator
    ll_estimator = Estimator(
        container,
        role=sagemaker.get_execution_role(),
        instance_count=1,
        instance_type='ml.m5.large',
        output_path='s3://{}/{}/output'.format(bucket, 
                                               prefix))
    

    SageMaker 支持多种不同类型的实例,不同的 AWS 区域之间有所差异。你可以在 docs.aws.amazon.com/sagemaker/latest/dg/instance-types-az.html 查看完整列表。

    我们应该选择哪个呢?查看 Linear Learner 文档 (docs.aws.amazon.com/sagemaker/latest/dg/linear-learner.html#ll-instances),我们看到 你可以在单机或多机 CPU 和 GPU 实例上训练 Linear Learner 算法。在这里,我们处理的是一个小数据集,因此让我们选择我们所在区域中最小的训练实例:ml.m5.large

    查看定价页面 (aws.amazon.com/sagemaker/pricing/),我们看到这个实例在 eu-west-1 区域的费用是每小时 $0.128(我为此任务使用的区域)。

  3. 接下来,我们需要设置 predictor_type。它定义了 Linear Learner 训练的目标问题类型(回归、二分类或多分类)。

    更深入地查看,我们发现mini_batch_size的默认值是 1000:对于我们 506 个样本的数据集,这个值并不适合,因此我们将其设置为 32。我们还发现normalize_data参数默认设置为 true,这使得我们不需要自己进行数据标准化:

    ll_estimator.set_hyperparameters(
        predictor_type='regressor', 
        mini_batch_size=32)
    
  4. 现在,让我们定义数据通道:通道是传递给 SageMaker 估算器的命名数据源。所有内置算法至少需要一个训练通道,许多算法还接受用于验证和测试的额外通道。在这里,我们有两个通道,它们都提供 CSV 格式的数据。TrainingInput() API 让我们定义数据的位置、格式、是否压缩等:

    from sagemaker import TrainingInput
    training_data_channel = TrainingInput(
        s3_data=training_data_path, 
        content_type='text/csv')
    validation_data_channel = TrainingInput(
        s3_data=validation_data_path,  
        content_type='text/csv')
    

    默认情况下,通道提供的数据会完全复制到每个训练实例,这对于小数据集来说没问题。在第十章《高级训练技术》中,我们将研究其他方法。

一切准备就绪,接下来我们启动训练任务。

启动训练任务

只需要一行代码:

  1. 我们只需将包含两个通道的 Python 字典传递给fit() API:

    ll_estimator.fit(
        {'train': training_data_channel, 
         'validation': validation_data_channel})
    

    训练任务立即开始:

    Starting - Starting the training job.
    
  2. 一旦任务启动,它就会出现在SageMaker 组件和注册表 | 实验和试验面板中。在那里,你可以看到所有任务的元数据:数据集的位置、超参数等。

  3. 训练日志在笔记本中可见,同时也存储在 Amazon CloudWatch Logs 中,路径为/aws/sagemaker/TrainingJobs。这里是前几行,显示了基础设施的配置过程,如前面在使用完全托管的基础设施部分所解释的:

    Starting - Starting the training job...
    Starting - Launching requested ML instances......
    Starting - Preparing the instances for training...
    Downloading - Downloading input data...
    Training - Training image download completed.
    
  4. 在训练日志的末尾,我们看到有关均方误差MSE)和损失指标的信息:

    #quality_metric: host=algo-1, validation mse <loss>=13.7226685169
    #quality_metric: host=algo-1, validation absolute_loss <loss>=2.86944983987
    
  5. 训练完成后,模型会自动复制到 S3,SageMaker 会告诉我们训练任务花费的时间:

    ml.m5.large instance is $0.128 per hour. As we trained for 49 seconds, this job cost us (49/3600)*0.128= $0.00174 – less than a fifth of a penny. Any time spent setting up infrastructure ourselves would have certainly cost more!
    
  6. 查看 S3 存储桶中的输出位置,我们看到了模型工件:

    model.tar.gz.
    

我们将在第十一章,《部署机器学习模型》中,了解该工件内部的内容以及如何在 SageMaker 外部部署模型。现在,我们将模型部署到实时端点。

部署模型

这是我最喜欢的 SageMaker 部分;我们只需要一行代码就能将模型部署到HTTPS 端点

  1. 最好的做法是创建可识别且唯一的端点名称。我们也可以在部署时让 SageMaker 为我们创建一个端点名称:

    from time import strftime, gmtime
    timestamp = strftime('%d-%H-%M-%S', gmtime())
    endpoint_name = 'linear-learner-demo-'+timestamp
    print(endpoint_name)
    

    这里,端点名称是linear-learner-demo-29-08-37-25

  2. 我们使用deploy() API 部署模型。由于这是一个测试端点,我们使用可用的最小端点实例ml.t2.medium。在欧盟西部 1 区,这将仅花费我们每小时 0.07 美元:

    ll_predictor = ll_estimator.deploy(
        endpoint_name=endpoint_name,
        initial_instance_count=1,
        instance_type='ml.t2.medium')
    

    当端点创建完成后,我们可以在SageMaker 组件和注册表 | 端点面板中看到它:

  3. 几分钟后,端点开始服务。我们可以使用 predict() API 发送一个 CSV 样本进行预测。我们使用内置函数设置序列化:

    ll_predictor.serializer =   
        sagemaker.serializers.CSVSerializer()
    ll_predictor.deserializer =
        sagemaker.deserializers.CSVDeserializer()
    test_sample = '0.00632,18.00,2.310,0,0.5380,6.5750,65.20,4.0900,1,296.0,15.30,4.98'
    response = ll_predictor.predict(test_sample)
    print(response)
    

    预测输出告诉我们,这栋房子的预计价格为 $30,173:

     [['30.17342185974121']]
    

    我们还可以一次预测多个样本:

    test_samples = [
    '0.00632,18.00,2.310,0,0.5380,6.5750,65.20,4.0900,1,296.0,15.30,4.98',
    '0.02731,0.00,7.070,0,0.4690,6.4210,78.90,4.9671,2,242.0,17.80,9.14']
    response = ll_predictor.predict(test_samples)
    print(response)
    

    现在预测输出如下:

     [['30.413358688354492'],['24.884408950805664']]
    

当我们完成端点的工作时,我们不应忘记删除它以避免不必要的费用

清理工作

删除端点就像调用 delete_endpoint() API 一样简单:

ll_predictor.delete_endpoint()

再次重申,本节涵盖的主题非常重要,请确保您完全熟悉它们,因为我们将在本书的其余部分经常使用它们。请花些时间阅读服务和 SDK 文档:

现在让我们探索其他内置算法。您将看到工作流程和代码非常相似!

使用更多内置算法:

在本章的其余部分,我们将使用内置算法运行更多示例,包括监督和无监督模式。这将帮助您非常熟悉 SageMaker SDK,并学习如何解决实际的机器学习问题。以下列表显示了其中一些算法:

  • 使用 XGBoost 进行分类:

  • 使用因子分解机进行推荐:

  • 使用 PCA 进行降维:

  • 使用 Random Cut Forest 进行异常检测:

使用 XGBoost 进行回归:

让我们使用 XGBoost 算法在波士顿房屋数据集上训练模型(github.com/dmlc/xgboost)。正如我们将在 第七章 中看到的,使用内置框架扩展机器学习服务,SageMaker 也支持 XGBoost 脚本:

  1. 我们重复了前面示例中的数据集准备步骤。

  2. 我们找到 XGBoost 容器的名称。由于支持多个版本,我们选择最新版本(写作时为 1.3.1):

    from sagemaker import image_uris
    region = sess.boto_session.region_name     
    container = image_uris.retrieve('xgboost', region, 
                                    version='latest')
    
  3. 我们配置 Estimator 函数。代码与使用 LinearLearner 时完全相同:

    xgb_estimator = Estimator(
       container,
       role=sagemaker.get_execution_role(),
       instance_count=1,
       instance_type='ml.m5.large',
       output_path='s3://{}/{}/output'.format(bucket, 
                                              prefix))
    
  4. 查看超参数(docs.aws.amazon.com/sagemaker/latest/dg/xgboost_hyperparameters.html),我们看到唯一需要的是 num_round。因为不明确应设置哪个值,我们将选择一个较大的值,并且还会定义 early_stopping_rounds 参数以避免过拟合。当然,我们需要为回归问题设置目标:

    xgb_estimator.set_hyperparameters(
        objective='reg:linear',
        num_round=200,
        early_stopping_rounds=10)
    
  5. 我们定义训练输入,就像前面的示例一样:

    from sagemaker import TrainingInput
    training_data_channel = TrainingInput(
        s3_data=training_data_path, 
        content_type='text/csv')
    validation_data_channel = TrainingInput(
        s3_data=validation_data_path,  
        content_type='text/csv')
    
  6. 然后我们启动训练作业:

    xgb_estimator.fit(
        {'train': training_data_channel, 
         'validation': validation_data_channel})
    
  7. 任务只运行了 22 轮,这意味着触发了早停。从训练日志中看到,第 12 轮实际上是最佳的,均方根误差RMSE)为 2.43126:

    [12]#011train-rmse:1.25702#011validation-rmse:2.43126
    <output removed>
    [22]#011train-rmse:0.722193#011validation-rmse:2.43355
    
  8. 部署仍然只需要一行代码:

    from time import strftime, gmtime
    timestamp = strftime('%d-%H-%M-%S', gmtime())
    endpoint_name = 'xgb-demo'+'-'+timestamp
    xgb_predictor = xgb_estimator.deploy(
        endpoint_name=endpoint_name,
        initial_instance_count=1,
        instance_type='ml.t2.medium')
    
  9. 一旦模型部署完成,我们再次使用predict() API 发送一个 CSV 样本:

    test_sample = '0.00632,18.00,2.310,0,0.5380,6.5750,65.20,4.0900,1,296.0,15.30,4.98'
    xgb_predictor.serializer =
        sagemaker.serializers.CSVSerializer()
    xgb_predictor.deserializer =
        sagemaker.deserializers.CSVDeserializer()
    response = xgb_predictor.predict(test_sample)
    print(response)
    

    结果告诉我们,这套房子应该值 23,754 美元:

    [['23.73023223876953']]
    
  10. 最后,当我们完成任务时,删除端点:

    xgb_predictor.delete_endpoint()
    

如你所见,SageMaker 的工作流程非常简单,使我们能够快速地尝试不同的算法,而无需重写所有代码。

让我们继续讲解因式分解机算法。在这个过程中,我们将了解高效的 RecordIO 封装 protobuf 格式。

使用因式分解机进行推荐

因式分解机是线性模型的广义化(www.csie.ntu.edu.tw/~b97053/paper/Rendle2010FM.pdf)。它非常适合高维稀疏数据集,如推荐系统中的用户-物品交互矩阵。

在这个示例中,我们将基于MovieLens数据集训练一个推荐模型(grouplens.org/datasets/movielens/)。

该数据集有多个版本。为了减少训练时间,我们将使用 100k 版本。它包含 943 个用户对 1,682 部电影的 100,000 条评分(评分值从 1 到 5)。数据集已经划分为训练集和验证集。

正如你现在所知道的,使用 SageMaker 进行训练和部署非常简单。大部分代码将与之前的两个示例完全相同,这很棒!这样我们可以专注于理解和准备数据。

理解稀疏数据集

想象构建一个矩阵来存储这个数据集。它会有 943 行(每行代表一个用户)和 1,682 列(每列代表一部电影)。单元格中存储评分。下图展示了一个基本的示例:

图 4.2 – 稀疏矩阵

图 4.2 – 稀疏矩阵

因此,矩阵将有 943*1,682=1,586,126 个单元格。然而,由于只有 100,000 条评分,93.69%的单元格将是空的。以这种方式存储数据集将非常低效,浪费 RAM、存储和网络带宽来存储和传输大量零值!

事实上,情况更糟,因为算法希望输入数据集看起来像下图所示:

图 4.3 – 稀疏矩阵

图 4.3 – 稀疏矩阵

为什么我们需要以这种方式存储数据?答案很简单:因式分解机是一个监督学习算法,所以我们需要用带标签的样本来训练它。

从前面的图示可以看到,每一行代表一个电影评分。左边的矩阵存储它的独热编码特征(用户和电影),而右边的向量存储它的标签。例如,最后一行告诉我们,用户 4 给电影 5 打了“5”分。

这个矩阵的大小是 100,000 行和 2,625 列(943 部电影加 1,682 部电影)。总共有 262,500,000 个单元格,其中只有 0.076% 被填充(200,000 / 262,500,000)。如果我们为每个单元格使用一个 32 位的值,我们需要接近一 GB 的内存来存储这个矩阵。这非常低效,但仍然可以管理。

为了好玩,让我们以 MovieLens 的最大版本为例,它有 2500 万条评分,62,000 部电影和 162,000 名用户。这个矩阵将有 2500 万行和 224,000 列,总共 5,600,000,000,000 个单元格。是的,这是 5.6 万亿个单元格,尽管它们有 99.999% 是空的,但我们仍然需要超过 20 TB 的内存来存储它们。哎呀。如果这还不够糟糕,想想推荐模型,它们有数百万的用户和产品:这些数字令人咋舌!

我们不会使用普通的矩阵,而是使用一个 SciPy 提供的对象,名为 lil_matrixdocs.scipy.org/doc/scipy/reference/generated/scipy.sparse.lil_matrix.html)。这将帮助我们去除所有这些讨厌的零。

理解 protobuf 和 RecordIO

那么,我们如何将这个稀疏矩阵传递给 SageMaker 算法呢?正如你所预期的,我们将序列化这个对象并将其存储在 S3 中。但我们不会使用 Python 的序列化方法。相反,我们将使用 protobufdevelopers.google.com/protocol-buffers/),这是一种流行且高效的序列化机制。

此外,我们将把 protobuf 编码的数据存储在一种名为 RecordIO 的记录格式中(mxnet.apache.org/api/faq/recordio/)。我们的数据集将作为一系列记录存储在单个文件中。这有以下几个好处:

  • 单个文件更容易移动:谁想处理成千上万的单独文件,这些文件可能会丢失或损坏呢?

  • 顺序文件读取速度更快,这使得训练过程更加高效。

  • 一系列记录便于分割用于分布式训练。

如果你不熟悉 protobuf 和 RecordIO,不用担心。SageMaker SDK 包含隐藏其复杂性的实用函数。

在 MovieLens 上构建因式分解机模型

我们将按照以下步骤开始构建模型:

  1. 在 Jupyter notebook 中,我们首先下载并提取 MovieLens 数据集:

    %%sh
    wget http://files.grouplens.org/datasets/movielens/ml-100k.zip
    unzip ml-100k.zip
    
  2. 由于数据集是按用户 ID 排序的,我们进行了一次洗牌作为预防措施。然后,我们查看前几行:

    %cd ml-100k
    !shuf ua.base -o ua.base.shuffled
    !head -5 ua.base.shuffled
    

    我们看到了四列:用户 ID、电影 ID、评分和时间戳(我们将在模型中忽略时间戳):

    378  43  3  880056609
    919  558 5  875372988
    90   285 5  891383687
    249  245 2  879571999
    416  64  5  893212929
    
  3. 我们定义了大小常数:

    num_users = 943
    num_movies = 1682
    num_features = num_users+num_movies
    num_ratings_train = 90570
    num_ratings_test = 9430
    
  4. 现在,让我们编写一个函数,将数据集加载到稀疏矩阵中。根据之前的解释,我们逐行遍历数据集。在 X 矩阵中,我们将相应的用户和电影列设置为 1。我们还将评分存储在 Y 向量中:

    import csv
    import numpy as np
    from scipy.sparse import lil_matrix
    def loadDataset(filename, lines, columns):
        X = lil_matrix((lines, columns)).astype('float32')
        Y = []
        line=0
        with open(filename,'r') as f:
            samples=csv.reader(f,delimiter='\t')
            for userId,movieId,rating,timestamp in samples:
                X[line,int(userId)-1] = 1
                X[line,int(num_users)+int(movieId)-1] = 1
                Y.append(int(rating))
                line=line+1       
        Y=np.array(Y).astype('float32')
        return X,Y
    
  5. 然后,我们处理训练集和测试集:

    X_train, Y_train = loadDataset('ua.base.shuffled', 
                                   num_ratings_train,  
                                   num_features)
    X_test, Y_test = loadDataset('ua.test',
                                 num_ratings_test, 
                                 num_features)
    
  6. 我们检查形状是否符合预期:

    print(X_train.shape)
    print(Y_train.shape)
    print(X_test.shape)
    print(Y_test.shape)
    

    这将显示数据集的形状:

    (90570, 2625)
    (90570,)
    (9430, 2625)
    (9430,)
    
  7. 现在,让我们编写一个函数,将数据集转换为 RecordIO 封装的 protobuf 格式,并将其上传到 S3 桶中。我们首先使用 io.BytesIO() 创建一个内存中的二进制流。然后,我们使用至关重要的 write_spmatrix_to_sparse_tensor() 函数,将样本矩阵和标签向量以 protobuf 格式写入该缓冲区。最后,我们使用 boto3 将缓冲区上传到 S3:

    import io, boto3
    import sagemaker.amazon.common as smac
    def writeDatasetToProtobuf(X, Y, bucket, prefix, key):
        buf = io.BytesIO()
        smac.write_spmatrix_to_sparse_tensor(buf, X, Y)
        buf.seek(0)
        obj = '{}/{}'.format(prefix, key)  
    
        boto3.resource('s3').Bucket(bucket).Object(obj).
        upload_fileobj(buf)
        return 's3://{}/{}'.format(bucket,obj)
    

    如果我们的数据存储在 numpy 数组中,而不是 lilmatrix,我们将使用 write_numpy_to_dense_tensor() 函数。它的效果是一样的。

  8. 我们将此函数应用于两个数据集,并存储它们的 S3 路径:

    import sagemaker
    sess   = sagemaker.Session()
    bucket = sess.default_bucket()
    prefix = 'fm-movielens'
    train_key      = 'train.protobuf'
    train_prefix   = '{}/{}'.format(prefix, 'train')
    test_key       = 'test.protobuf'
    test_prefix    = '{}/{}'.format(prefix, 'test')
    output_prefix  = 's3://{}/{}/output'.format(bucket, 
                                                prefix)
    train_data = writeDatasetToProtobuf(X_train, Y_train, 
                 bucket, train_prefix, train_key)    
    test_data  = writeDatasetToProtobuf(X_test, Y_test, 
                 bucket, test_prefix, test_key)    
    
  9. 在终端查看 S3 桶时,我们看到训练数据集仅占用 5.5 MB。稀疏矩阵、protobuf 和 RecordIO 的结合取得了效果:

    $ aws s3 ls s3://sagemaker-eu-west-1-123456789012/fm-movielens/train/train.protobuf
    5796480 train.protobuf
    
  10. 接下来的过程是 SageMaker 的常规操作。我们找到分解机容器的名称,配置 Estimator 函数,并设置超参数:

    from sagemaker.image_uris import retrieve
    region = sess.boto_session.region_name    
    container=retrieve('factorization-machines', region)
    fm=sagemaker.estimator.Estimator(
        container,
        role=sagemaker.get_execution_role(),
        instance_count=1,
        instance_type='ml.c5.xlarge',
        output_path=output_prefix)
    fm.set_hyperparameters(
        feature_dim=num_features,
        predictor_type='regressor',
        num_factors=64,
        epochs=10)
    

    查阅文档(docs.aws.amazon.com/sagemaker/latest/dg/fact-machines-hyperparameters.html),我们看到所需的超参数为 feature_dimpredictor_typenum_factorsepochs 的默认设置为 1,这感觉有点低,因此我们将其设置为 10

  11. 然后,我们启动训练作业。你有没有注意到我们没有配置训练输入?我们只是传递了两个 protobuf 文件的位置。由于 protobuf 是分解机(以及其他内置算法)的默认格式,我们可以省略这一步:

    fm.fit({'train': train_data, 'test': test_data})
    
  12. 作业完成后,我们将模型部署到实时端点:

    endpoint_name = 'fm-movielens-100k'
    fm_predictor = fm.deploy(
        endpoint_name=endpoint_name,
        instance_type='ml.t2.medium', 
        initial_instance_count=1)
    
  13. 我们现在将以 JSON 格式(https://docs.aws.amazon.com/sagemaker/latest/dg/fact-machines.html#fm-inputoutput)将样本发送到端点。为此,我们编写一个自定义序列化器,将输入数据转换为 JSON。由于我们将内容类型设置为 'application/json',默认的 JSON 反序列化器将自动使用:

    import json
    from sagemaker.deserializers import JSONDeserializer
    from sagemaker.serializers import JSONSerializer
    class FMSerializer(JSONSerializer):
        def serialize(self, data):
            js = {'instances': []}
            for row in data:
                js['instances'].append({'features':   
                                row.tolist()})
            return json.dumps(js)
    fm_predictor.serializer = FMSerializer()
    fm_predictor.deserializer = JSONDeserializer()
    
  14. 我们发送测试集的前三个样本进行预测:

    result = fm_predictor.predict(X_test[:3].toarray())
    print(result)
    

    预测结果如下:

    {'predictions': [{'score': 3.3772034645080566}, {'score': 3.4299235343933105}, {'score': 3.6053106784820557}]}
    
  15. 使用这个模型,我们可以填充推荐矩阵中的所有空白单元。对于每个用户,我们只需预测所有电影的评分,并存储,例如,排名前 50 的电影。这些信息将存储在后台,前端应用程序中将显示相应的元数据(如标题、类型等)。

  16. 最后,我们删除端点:

    fm_predictor.delete_endpoint()
    

到目前为止,我们仅使用了监督学习算法。在接下来的部分,我们将转向使用主成分分析进行无监督学习。

使用主成分分析

在因子分解机示例中建立的protobuf数据集。它的 2,625 列非常适合降维!我们将采用 PCA,步骤如下:

  1. 从处理过的数据集开始,我们为 PCA 配置Estimator。现在,你应该(几乎)可以闭着眼睛做到这一点:

    from sagemaker.image_uris import retrieve
    region = sess.boto_session.region_name   
    container = retrieve('pca', region) 
    pca = sagemaker.estimator.Estimator(
        container=container,
        role=sagemaker.get_execution_role(),
        instance_count=1,                               
        instance_type='ml.c5.xlarge',
        output_path=output_prefix)
    
  2. 然后,我们设置超参数。必需的是初始特征数、计算主成分的数量以及批处理大小:

    pca.set_hyperparameters(feature_dim=num_features,
                            num_components=64,
                            mini_batch_size=1024)
    
  3. 我们训练并部署模型:

    pca.fit({'train': train_data, 'test': test_data})
    pca_predictor = pca.deploy(
        endpoint_name='pca-movielens-100k',
        instance_type='ml.t2.medium',
        initial_instance_count=1)
    
  4. 然后,我们预测第一个测试样本,使用与前一个示例相同的序列化代码:

    import json
    from sagemaker.deserializers import JSONDeserializer
    from sagemaker.serializers import JSONSerializer
    class PCASerializer(JSONSerializer):
        def serialize(self, data):
            js = {'instances': []}
            for row in data:
                js['instances'].append({'features': 
                                row.tolist()})
            return json.dumps(js)
    pca_predictor.serializer = PCASerializer()
    pca_predictor.deserializer = JSONDeserializer()
    result = pca_predictor.predict(X_test[0].toarray())
    print(result)
    

    这将打印出测试样本的 64 个主要成分。在实际生活中,我们通常会使用这个模型处理数据集,保存结果,并用它们来训练回归模型:

    {'projections': [{'projection': [-0.008711372502148151, 0.0019895541481673717, 0.002355781616643071, 0.012406938709318638, -0.0069608548656105995, -0.009556426666676998, <output removed>]}]} 
    

当你完成时别忘了删除端点。接着,让我们运行另一个无监督学习的例子来结束这一章节!

使用随机森林检测异常

随机森林 (RCF) 是一种用于异常检测的无监督学习算法(proceedings.mlr.press/v48/guha16.pdf)。我们将把它应用于家庭电力消耗数据集的一个子集(archive.ics.uci.edu/ml/),该数据集可在本书的 GitHub 仓库中获取。数据在接近一年的时间内按小时聚合(大约不到 8,000 个值):

  1. 在 Jupyter 笔记本中,我们使用pandas加载数据集,并显示前几行:

    import pandas as pd
    df = pd.read_csv('item-demand-time.csv', dtype = object, names=['timestamp','value','client'])
    df.head(3)
    

    如下截图所示,数据集有三列 - 每小时时间戳、功耗值(以千瓦时计算)、客户 ID:

    图 4.4 – 查看列

    图 4.4 – 查看列

  2. 使用matplotlib,我们绘制数据集以快速了解其外观:

    import matplotlib
    import matplotlib.pyplot as plt
    df.value=pd.to_numeric(df.value)
    df_plot=df.pivot(index='timestamp',columns='item',
                     values='value')
    df_plot.plot(figsize=(40,10))
    

    下图显示了绘图结果。我们看到三个时间序列对应于三个不同的客户:

    图 4.5 – 查看数据集

    图 4.5 – 查看数据集

  3. 这个数据集存在两个问题。首先,它包含多个时间序列:RCF 只能在单个序列上训练模型。其次,RCF 需要pandas - 我们只保留了"client_12"时间序列,将其值乘以 100,并转换为整数类型:

    df = df[df['item']=='client_12']
    df = df.drop(['item', 'timestamp'], axis=1)
    df.value *= 100
    df.value = df.value.astype('int32')
    df.head()
    

    下图显示了转换后数据集的前几行:

    图 4.6 – 第一行的值

    图 4.6 – 第一行的值

  4. 我们再次绘制它以检查它的预期外观。请注意在第 2000 步后的大幅下降,这在下图的框中得到了突出显示。这显然是一个异常,希望我们的模型能够捕捉到它:图 4.7 – 查看单个时间序列

    图 4.7 – 查看单一时间序列

  5. 与前面的示例一样,我们将数据集保存到 CSV 文件中,并上传到 S3:

    import sagemaker
    sess = sagemaker.Session()
    bucket = sess.default_bucket()
    prefix = 'electricity'
    df.to_csv('electricity.csv', index=False, 
              header=False)
    training_data_path = sess.upload_data(
                           path='electricity.csv', 
                           key_prefix=prefix + 
                                      '/input/training')
    
  6. 然后,我们定义 label_size=1)。即使训练通道从未包含标签,我们仍然需要告诉 RCF。

    其次,唯一的 ShardedByS3Key。该策略将数据集跨训练集群中的不同实例进行拆分,而不是发送完整副本。我们不会在这里运行分布式训练,但仍然需要设置该策略:

    training_data_channel = 
        sagemaker.TrainingInput(
            s3_data=training_data_path,                                                            
            content_type='text/csv;label_size=0',                                         
            distribution='ShardedByS3Key')
    rcf_data = {'train': training_data_channel}
    
  7. 其余部分照常:训练并部署!我们再次重用前面示例中的代码,几乎没有改变:

    from sagemaker.estimator import Estimator
    from sagemaker.image_uris import retrieve
    region = sess.boto_session.region_name
    container = retrieve('randomcutforest', region)
    rcf_estimator = Estimator(container,
        role= sagemaker.get_execution_role(),
        instance_count=1,
        instance_type='ml.m5.large',
        output_path='s3://{}/{}/output'.format(bucket, 
                                               prefix))
    rcf_estimator.set_hyperparameters(feature_dim=1)
    rcf_estimator.fit(rcf_data)
    endpoint_name = 'rcf-demo'
    rcf_predictor = rcf_estimator.deploy(
        endpoint_name=endpoint_name,
        initial_instance_count=1,
        instance_type='ml.t2.medium')
    
  8. 几分钟后,模型已被部署。我们将输入的时间序列转换为 Python 列表,然后发送到端点进行预测。我们分别使用 CSV 和 JSON 进行序列化和反序列化:

    rcf_predictor.serializer =
        sagemaker.serializers.CSVSerializer()
    rcf_predictor.deserializer =
        sagemaker.deserializers.JSONDeserializer()
    values = df['value'].astype('str').tolist()
    response = rcf_predictor.predict(values)
    print(response)
    

    响应包含时间序列中每个值的异常分数。它看起来是这样的:

    {'scores': [{'score': 1.0868037776}, {'score': 1.5307718138}, {'score': 1.4208102841} …
    
  9. 然后,我们将此响应转换为 Python 列表,并计算其均值和标准差:

    from statistics import mean,stdev
    scores = []
    for s in response['scores']:
        scores.append(s['score'])
    score_mean = mean(scores)
    score_std = stdev(scores)
    
  10. 我们绘制时间序列的一个子集以及相应的分数。让我们集中关注 "[2000:2500]" 区间,因为这是我们看到大幅下降的地方。我们还绘制了代表均值加三倍标准差(得分分布的 99.7%)的线——任何大大超过该线的得分都可能是异常:

    df[2000:2500].plot(figsize=(40,10))
    plt.figure(figsize=(40,10))
    plt.plot(scores[2000:2500])
    plt.autoscale(tight=True)
    plt.axhline(y=score_mean+3*score_std, color='red')
    plt.show()
    

    下降在以下图表中显而易见:

    图 4.8 – 聚焦于异常

    图 4.8 – 聚焦于异常

    如下图所示,其得分非常高!毫无疑问,这个值是一个异常:

    图 4.9 – 查看异常分数

    图 4.9 – 查看异常分数

    探索时间序列的其他区间,我们肯定能找到更多。谁说机器学习不有趣?

  11. 最后,我们删除端点:

    rcf_predictor.delete_endpoint()
    

经过五个完整示例后,你现在应该对内置算法、SageMaker 工作流以及 SDK 有了熟悉。为了更深入地掌握这些主题,我建议你尝试自己的数据集,并运行 github.com/awslabs/amazon-sagemaker-examples/tree/master/introduction_to_amazon_algorithms 中的其他示例。

总结

如你所见,内置算法是快速训练和部署模型的好方法,无需编写任何机器学习代码。

在本章中,你了解了 SageMaker 工作流,并学习了如何通过 SageMaker SDK 中的一些 API 实现它,而无需担心基础设施。

你已经学习了如何处理 CSV 格式和 RecordIO 封装的 protobuf 格式数据,后者是大规模训练庞大数据集时推荐的格式。你还学会了如何使用重要的算法来构建监督学习和无监督学习模型:线性学习器(Linear Learner)、XGBoost、因式分解机(Factorization Machines)、主成分分析(PCA)和随机切割森林(Random Cut Forest)。

在下一章中,你将学习如何使用额外的内置算法来构建计算机视觉模型。

第五章:训练计算机视觉模型

在上一章中,你学习了如何使用 SageMaker 内置的算法来解决传统机器学习问题,包括分类、回归和异常检测。我们看到这些算法在处理表格数据(如 CSV 文件)时效果良好。然而,它们不太适合图像数据集,通常在 CV计算机视觉)任务上表现非常差。

近年来,计算机视觉(CV)已经席卷全球,几乎每个月都有新的突破,能够从图像和视频中提取模式。本章中,你将学习三种专为计算机视觉任务设计的内置算法。我们将讨论你可以用它们解决的各种问题,并详细解释如何准备图像数据集,因为这一关键主题经常被莫名其妙地忽视。当然,我们还会训练并部署模型。

本章内容包括以下主题:

  • 发现 Amazon SageMaker 中的计算机视觉内置算法

  • 准备图像数据集

  • 使用内置的计算机视觉算法:图像分类目标检测语义分割

技术要求

你需要一个 AWS 账户来运行本章中的示例。如果你还没有账户,请访问 aws.amazon.com/getting-started/ 创建一个。你还应当熟悉 AWS 免费套餐(aws.amazon.com/free/),它允许你在一定的使用限制内免费使用许多 AWS 服务。

你需要为你的账户安装并配置 AWS 命令行界面CLI)(aws.amazon.com/cli/)。

你需要一个可用的 Python 3.x 环境。安装 Anaconda 发行版(www.anaconda.com/)不是强制性的,但强烈建议安装,因为它包含了我们将需要的许多项目(Jupyter、pandasnumpy 等)。

本书中的代码示例可以在 GitHub 上找到:github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装一个 Git 客户端来访问这些示例(git-scm.com/)。

发现 Amazon SageMaker 中的计算机视觉内置算法

SageMaker 包括三种基于深度学习网络的计算机视觉算法。在本节中,你将了解这些算法,它们能帮助你解决什么样的问题,以及它们的训练场景:

  • 图像分类将一个或多个标签分配给图像。

  • 目标检测是在图像中检测并分类物体。

  • 语义分割将图像的每一个像素分配到一个特定的类别。

发现图像分类算法

从输入图像开始,图像分类算法为训练数据集中每个类别预测一个概率。该算法基于ResNet卷积神经网络(arxiv.org/abs/1512.03385)。ResNet于 2015 年发布,并在同年获得 ILSVRC 分类任务的冠军(www.image-net.org/challenges/LSVRC/)。从那时起,它成为了一个流行且多用途的图像分类选择。

可以设置许多超参数,包括网络的深度,范围从 18 层到 200 层。通常,网络层数越多,学习效果越好,但训练时间也会增加。

请注意,图像分类算法支持单标签多标签分类。在本章中,我们将重点讨论单标签分类。处理多个标签非常类似,您可以在github.com/awslabs/amazon-sagemaker-examples/blob/master/introduction_to_amazon_algorithms/imageclassification_mscoco_multi_label/找到一个完整的示例。

发现物体检测算法

从输入图像开始,物体检测算法预测图像中每个物体的类别和位置。当然,该算法只能检测训练数据集中存在的物体类别。每个物体的位置由一组四个坐标定义,称为边界框

该算法基于单次多框检测器SSD)架构(arxiv.org/abs/1512.02325)。对于分类,您可以选择两个基础网络:VGG-16arxiv.org/abs/1409.1556)或ResNet-50

以下输出展示了一个物体检测示例(来源:www.dressagechien.net/wp-content/uploads/2017/11/chien-et-velo.jpg):

图 5.1 – 测试图像

图 5.1 – 测试图像

发现语义分割算法

从输入图像开始,语义分割算法预测图像中每个像素的类别。这比图像分类(仅考虑整幅图像)或物体检测(仅关注图像的特定部分)要困难得多。通过使用预测中包含的概率,可以构建分割掩码,覆盖图像中的特定物体。

三种神经网络可以用于分割:

编码器网络是ResNet,有 50 层或 101 层。

以下输出显示了对前一张图像进行分割后的结果。我们可以看到分割掩膜,每个类别都分配了一个独特的颜色;背景为黑色,等等:

图 5.2 – 分割后的测试图像

图 5.2 – 分割后的测试图像

现在让我们看看如何在我们自己的数据上训练这些算法。

使用计算机视觉(CV)算法进行训练

这三种算法都是基于监督学习的,因此我们的起点将是一个带标签的数据集。当然,这些标签的性质对于每个算法会有所不同:

  • 图像分类的类别标签

  • 目标检测的边界框和类别标签

  • 语义分割的分割掩膜和类别标签

对图像数据集进行标注是一项繁重的工作。如果你需要构建自己的数据集,Amazon SageMaker Ground Truth绝对能帮助你,我们在第二章中研究了它,处理数据准备技术。在本章稍后部分,我们将展示如何使用标注过的图像数据集。

在打包数据集时,强烈建议使用RecordIO文件(mxnet.apache.org/api/faq/recordio)。将图像打包成少量的记录结构化文件,可以使数据集的移动和分割变得更容易,便于分布式训练。话虽如此,如果你愿意,也可以在单独的图像文件上进行训练。

一旦你的数据集准备好并存储在 S3 上,你需要决定是从头开始训练,还是从一个预训练的网络开始。

如果你有大量数据,而且确信从这些数据中构建特定模型是有价值的,那么从头开始训练是没问题的。然而,这将耗费大量时间,可能需要数百次的迭代,并且超参数选择在获得好结果方面至关重要。

使用预训练网络通常是更好的选择,即使你有大量数据。得益于迁移学习,你可以从一个在大量图像(假设是数百万张)上训练的模型开始,然后根据你的数据和类别进行微调。这样训练的时间会大大缩短,并且你会更快地获得更高准确率的模型。

鉴于模型的复杂性和数据集的大小,使用 CPU 实例进行训练显然不可行。我们将为所有示例使用 GPU 实例。

最后但同样重要的是,所有这三种算法都是基于Apache MXNet的。这使得你可以将它们的模型导出到 SageMaker 之外,并在任何地方进行部署。

在接下来的章节中,我们将深入探讨图像数据集,以及如何为训练准备它们。

准备图像数据集

图像数据集的输入格式比表格数据集更为复杂,我们需要确保格式完全正确。SageMaker 中的计算机视觉算法支持三种输入格式:

  • 图像文件

  • RecordIO文件

  • SageMaker Ground Truth构建的增强清单

在本节中,你将学习如何准备这些不同格式的数据集。据我所知,这个话题很少被如此详细地讨论。准备好学习很多内容吧!

处理图像文件

这是最简单的格式,所有三种算法都支持它。让我们看看如何将其与图像分类算法一起使用。

将图像分类数据集转换为图像格式

图像格式的数据集必须存储在 S3 中。图像文件不需要按任何特定方式排序,你可以将它们全部存储在同一个存储桶中。

图像描述存储在列表文件中,列表文件是一个文本文件,每行代表一张图像。对于图像分类,文件中有三列:图像的唯一标识符、类标签以及路径。以下是一个示例:

1023  5  prefix/image2753.jpg
38    6  another_prefix/image72.jpg
983   2  yet_another_prefix/image863.jpg

第一行告诉我们,image2753.jpg属于第 5 类,且已分配 ID 1023。

每个通道都需要一个列表文件,因此你需要为训练数据集、验证数据集等创建一个。你可以编写定制的代码来生成它们,或者使用im2rec中的一个简单程序,该程序在 Python 和 C++中都有。我们将使用 Python 版本。

让我们使用Kaggle上的 Dogs vs. Cats 数据集(www.kaggle.com/c/dogs-vs-cats)。该数据集为 812 MB。不出所料,包含两类:狗和猫。它已经分为训练集和测试集(分别为 25,000 张和 12,500 张图片)。具体操作如下:

  1. 我们创建一个kaggle命令行工具(github.com/Kaggle/kaggle-api)。

  2. 在我们的本地机器上,我们下载并提取训练数据集(你可以忽略测试集,它只用于竞赛):

    $ kaggle competitions download -c dogs-vs-cats
    $ sudo yum -y install zip unzip
    $ unzip dogs-vs-cats.zip
    $ unzip train.zip
    
  3. 狗和猫的图像混合在同一个文件夹中。我们为每个类别创建一个子文件夹,并将相应的图像移动到这些子文件夹中:

    $ cd train
    $ mkdir dog cat
    $ find . -name 'dog.*' -exec mv {} dog \;
    $ find . -name 'cat.*' -exec mv {} cat \;
    
  4. 我们需要验证图像,因此让我们将 1,250 张随机狗图像和 1,250 张随机猫图像移动到指定目录。我这里使用bash脚本,但你可以根据需要使用任何工具:

    $ mkdir -p val/dog val/cat
    $ ls dog | sort -R | tail -1250 | while read file;
    do mv dog/$file val/dog; done
    $  ls cat | sort -R | tail -1250 | while read file;
    do mv cat/$file val/cat; done
    
  5. 我们将剩余的 22,500 张图片移动到训练文件夹:

    $ mkdir train
    $ mv dog cat train
    
  6. 现在我们的数据集如下所示:

    $ du -h
    33M     ./val/dog
    28M     ./val/cat
    60M     ./val
    289M    ./train/dog
    248M    ./train/cat
    537M    ./train
    597M    .
    
  7. 我们从 GitHub 下载im2rec工具(github.com/apache/incubator-mxnet/blob/master/tools/im2rec.py)。它需要依赖项,我们需要安装这些依赖(你可能需要根据自己的环境和 Linux 版本调整命令):

    $ wget https://raw.githubusercontent.com/apache/incubator-mxnet/master/tools/im2rec.py
    $ sudo yum -y install python-devel python-pip opencv opencv-devel opencv-python
    $ pip3 install mxnet opencv-python
    
  8. 我们运行im2rec以生成两个列表文件,一个用于训练数据,另一个用于验证数据:

    dogscats-train.lst and dogscats-val.lst files. Their three columns are a unique image identifier, the class label (0 for cats, 1 for dogs), and the image path, as follows:
    
    

    3197 0.000000 cat/cat.1625.jpg

    15084 1.000000 dog/dog.12322.jpg

    1479 0.000000 cat/cat.11328.jpg

    5262 0.000000 cat/cat.3484.jpg

    20714 1.000000 dog/dog.6140.jpg

    
    
  9. 我们将列表文件移动到特定目录。这是必要的,因为它们将作为两个新通道train_lstvalidation_lst传递给Estimator

    $ mkdir train_lst val_lst
    $ mv dogscats-train.lst train_lst
    $ mv dogscats-val.lst val_lst
    
  10. 数据集现在看起来是这样的:

    $ du -h
    33M     ./val/dog
    28M     ./val/cat
    60M     ./val
    700K    ./train_lst
    80K     ./val_lst
    289M    ./train/dog
    248M    ./train/cat
    537M    ./train
    597M    .
    
  11. 最后,我们将这个文件夹同步到 SageMaker 默认存储桶中以备后用。请确保只同步这四个文件夹,其他的不要同步:

    $ aws s3 sync . 
      s3://sagemaker-eu-west-1-123456789012/dogscats-images/input/
    

现在,让我们继续在目标检测算法中使用图像格式。

将检测数据集转换为图像格式

一般原则是相同的。我们需要构建一个文件树,表示四个通道:trainvalidationtrain_annotationvalidation_annotation

主要的区别在于标签信息的存储方式。我们需要构建 JSON 文件,而不是列表文件。

这是一个虚构图像的示例,来自一个目标检测数据集。对于图像中的每个物体,我们定义其边界框的左上角坐标、其高度和宽度。我们还定义了类标识符,它指向一个类别数组,该数组还存储类名称:

{
   "file": " my-prefix/my-picture.jpg",
   "image_size": [{"width": 512,"height": 512,"depth": 3}],
   "annotations": [
      {
       "class_id": 1, 
       "left": 67, "top": 121, "width": 61, "height": 128
      },
      {
       "class_id": 5, 
       "left": 324, "top": 134, "width": 112, "height": 267
      }
   ],
   "categories": [
      { "class_id": 1, "name": "pizza" },
      { "class_id": 5, "name": "beer" }
   ]
}

我们需要对数据集中的每张图片执行此操作,为训练集构建一个 JSON 文件,为验证集构建一个 JSON 文件。

最后,让我们看看如何在语义分割算法中使用图像格式。

将分割数据集转换为图像格式

图像格式是图像分割算法唯一支持的格式。

这次,我们需要构建一个文件树,表示四个通道:trainvalidationtrain_annotationvalidation_annotation。前两个通道包含源图像,最后两个通道包含分割掩膜图像。

文件命名在将图像与其掩膜匹配时至关重要:源图像和掩膜图像必须在各自的通道中具有相同的名称。以下是一个示例:

├── train
│   ├── image001.png
│   ├── image007.png
│   └── image042.png
├── train_annotation
│   ├── image001.png
│   ├── image007.png
│   └── image042.png 
├── validation
│   ├── image059.png
│   ├── image062.png
│   └── image078.png
└── validation_annotation
│   ├── image059.png
│   ├── image062.png
│   └── image078.png

你可以在下图中看到示例图像。左侧的源图像应放入train文件夹,右侧的掩膜图像应放入train_annotation文件夹。它们必须具有完全相同的名称,以便算法可以匹配它们:

图 5.3 – Pascal VOC 数据集中的示例图像

图 5.3 – Pascal VOC 数据集中的示例图像

这种格式的一个巧妙特点是它如何将类标识符与掩膜颜色匹配。掩膜图像是具有 256 色调色板的 PNG 文件。数据集中的每个类都分配了调色板中的一个特定条目。这些颜色就是你在该类物体的掩膜中看到的颜色。

如果你的标签工具或现有数据集不支持这种 PNG 特性,你可以添加自己的颜色映射文件。详情请参考 AWS 文档:docs.aws.amazon.com/sagemaker/latest/dg/semantic-segmentation.html

现在,让我们准备 Pascal VOC 数据集。这个数据集常用于对目标检测和语义分割模型进行基准测试:

  1. 我们首先下载并提取数据集的 2012 版本。我建议使用 AWS 托管的实例来加速网络传输:

    $ wget https://data.deepai.org/PascalVOC2012.zip
    $ unzip PascalVOC2012.zip 
    
  2. 我们创建一个工作目录,在这里构建四个通道:

    $ mkdir input
    $ cd input
    $ mkdir train validation train_annotation validation_annotation
    
  3. 使用数据集中定义的训练文件列表,我们将相应的图像复制到 train 文件夹。我在这里使用的是 bash 脚本,当然你也可以使用你喜欢的工具:

    $ for file in 'cat ../ImageSets/Segmentation/train.txt | xargs'; do cp ../JPEGImages/$file".jpg" train; done
    
  4. 然后我们对验证图像、训练掩码和验证掩码做相同的处理:

    $ for file in 'cat ../ImageSets/Segmentation/val.txt | xargs'; do cp ../JPEGImages/$file".jpg" validation; done
    $ for file in 'cat ../ImageSets/Segmentation/train.txt | xargs'; do cp ../SegmentationClass/$file".png" train_annotation; done
    $ for file in 'cat ../ImageSets/Segmentation/val.txt | xargs'; do cp ../SegmentationClass/$file".png" validation_annotation; done
    
  5. 我们检查两个训练通道和两个验证通道中的图像数量是否一致:

    $ for dir in train train_annotation validation validation_annotation; do find $dir -type f | wc -l; done
    

    我们看到了 1,464 个训练文件和掩码,以及 1,449 个验证文件和掩码。我们准备好了:

    1464
    1464
    1449
    1449
    
  6. 最后一步是将文件树同步到 S3 以备后用。再次提醒,请确保只同步四个文件夹:

    $ aws s3 sync . s3://sagemaker-eu-west-1-123456789012/pascalvoc-segmentation/input/
    

我们知道如何准备图像格式的分类、检测和分割数据集。这是一个关键步骤,你必须确保一切都准确无误。

不过,我敢肯定你会觉得本节中的步骤有些麻烦,我也是!现在想象一下,面对数百万张图像做同样的事情。这听起来可不太刺激,对吧?

我们需要一种更简便的方法来准备图像数据集。让我们看看如何使用 RecordIO 文件简化数据集准备。

使用 RecordIO 文件

RecordIO 文件更容易搬动。对于算法来说,读取一个大的顺序文件比读取很多存储在随机磁盘位置的小文件更高效。

将图像分类数据集转换为 RecordIO

让我们将 Dogs vs. Cats 数据集转换为 RecordIO:

  1. 从新提取的数据集副本开始,我们将图像移到相应的类别文件夹中:

    $ cd train
    $ mkdir dog cat
    $ find . -name 'dog.*' -exec mv {} dog \;
    $ find . -name 'cat.*' -exec mv {} cat \;
    
  2. 我们运行 im2rec 来为训练数据集(90%)和验证数据集(10%)生成列表文件。我们无需自己分割数据集!

    $ python3 im2rec.py --list --recursive --train-ratio 0.9 dogscats .
    
  3. 我们再次运行 im2rec 来生成 RecordIO 文件:

    .rec) containing the packed images, and two index files (.idx) containing the offsets of these images inside the record files:
    
    

    $ ls dogscats*

    dogscats_train.idx dogscats_train.lst dogscats_train.rec

    dogscats_val.idx dogscats_val.lst dogscats_val.rec

    
    
  4. 我们将 RecordIO 文件存储在 S3 上,因为我们稍后会用到它们:

    $ aws s3 cp dogscats_train.rec s3://sagemaker-eu-west-1-123456789012/dogscats/input/train/
    $ aws s3 cp dogscats_val.rec s3://sagemaker-eu-west-1-123456789012/dogscats/input/validation/
    

这要简单得多,不是吗?im2rec 还提供了额外的选项来调整图像大小等等。它还可以将数据集分割成几个块,这是 管道模式分布式训练 中一个有用的技术。我们将在第九章,“扩展你的训练任务”中学习这些内容。

现在,让我们继续使用 RecordIO 文件进行目标检测。

将目标检测数据集转换为 RecordIO

过程非常相似。一个主要的区别是列表文件的格式。我们不仅要处理类标签,还需要存储边界框。

让我们看看这对 Pascal VOC 数据集意味着什么。以下图像取自该数据集:

图 5.4 – 来自 Pascal VOC 数据集的示例图像

图 5.4 – 来自 Pascal VOC 数据集的示例图像

它包含三个椅子。标注信息存储在一个单独的 XML 文件中,文件内容略有简化,如下所示:

<annotation>
        <folder>VOC2007</folder>
        <filename>003988.jpg</filename>
        . . .
        <object>
                <name>chair</name>
                <pose>Unspecified</pose>
                <truncated>1</truncated>
                <difficult>0</difficult>
                <bndbox>
                    <xmin>1</xmin>
                    <ymin>222</ymin>
                    <xmax>117</xmax>
                    <ymax>336</ymax>
                </bndbox>
        </object>
        <object>
                <name>chair</name>
                <pose>Unspecified</pose>
                <truncated>1</truncated>
                <difficult>1</difficult>
                <bndbox>
                    <xmin>429</xmin>
                    <ymin>216</ymin>
                    <xmax>448</xmax>
                    <ymax>297</ymax>
                </bndbox>
        </object>
        <object>
                <name>chair</name>
                <pose>Unspecified</pose>
                <truncated>0</truncated>
                <difficult>1</difficult>
                <bndbox>
                    <xmin>281</xmin>
                    <ymin>149</ymin>
                    <xmax>317</xmax>
                    <ymax>211</ymax>
                </bndbox>
        </object>
</annotation>

将此转换为列表文件条目的格式应如下所示:

9404 2 6  8.0000  0.0022  0.6607  0.2612  1.0000  0.0000 8.0000  0.9576  0.6429  1.0000  0.8839  1.0000 8.0000  0.6272  0.4435  0.7076  0.6280  1.0000 VOC2007/JPEGImages/003988.jpg 

让我们解码每一列:

  • 9404 是唯一的图像标识符。

  • 2 是包含头信息的列数,包括这一列。

  • 6 是标注信息的列数。这六列分别是类别标识符、四个边界框坐标,以及一个标志,告诉我们该对象是否难以看到(我们不会使用它)。

  • 以下是第一个对象的信息:

    a) 8 是类别标识符。这里,8 代表 chair 类别。

    b) 0.0022 0.6607 0.2612 1.00000 表示该对象不困难的相对坐标。

  • 对于第二个对象,我们有如下信息:

    a) 8 是类别标识符。

    b) 0.9576 0.6429 1.0000 0.8839 是第二个对象的坐标。

    c) 1 表示该对象是困难的。

  • 第三个对象具有以下信息:

    a) 8 是类别标识符。

    b) 0.6272 0.4435 0.7076 0.628 是第三个对象的坐标。

    c) 1 表示该对象是困难的。

  • VOC2007/JPEGImages/003988.jpg 是图片的路径。

那么,我们如何将成千上万的 XML 文件转换为几个列表文件呢?除非你喜欢写解析器,否则这并不是一项非常有趣的任务。

幸运的是,我们的工作已经简化了。Apache MXNet 包含一个 Python 脚本 prepare_dataset.py,可以处理这项任务。让我们看看它是如何工作的:

  1. 对于接下来的步骤,我建议使用至少有 10 GB 存储的 Amazon Linux 2 EC2 实例。以下是设置步骤:

    $ sudo yum -y install git python3-devel python3-pip opencv opencv-devel opencv-python
    $ pip3 install mxnet opencv-python --user
    $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ec2-user/.local/lib/python3.7/site-packages/mxnet/
    $ sudo ldconfig
    
  2. 使用 wget 下载 2007 和 2012 版本的 Pascal VOC 数据集,并将它们提取到同一个目录中:

    $ mkdir pascalvoc
    $ cd pascalvoc
    $ wget https://data.deepai.org/PascalVOC2012.zip
    $ wget https://data.deepai.org/PASCALVOC2007.zip
    $ unzip PascalVOC2012.zip
    $ unzip PASCALVOC2007.zip 
    $ mv VOC2012 VOCtrainval_06-Nov-2007/VOCdevkit
    
  3. 克隆 Apache MXNet 仓库 (github.com/apache/incubator-mxnet/):

    $ git clone --single-branch --branch v1.4.x https://github.com/apache/incubator-mxnet
    
  4. 运行 prepare_dataset.py 脚本来构建我们的训练数据集,将 2007 和 2012 版本的训练集和验证集合并:

    $ cd VOCtrainval_06-Nov-2007
    $ python3 ../incubator-mxnet/example/ssd/tools/prepare_dataset.py --dataset pascal --year 2007,2012 --set trainval --root VOCdevkit --target VOCdevkit/train.lst
    $ mv VOCdevkit/train.* ..
    
  5. 让我们遵循类似的步骤来生成验证数据集,使用 2007 版本的测试集:

    $ cd ../VOCtest_06-Nov-2007
    $ python3 ../incubator-mxnet/example/ssd/tools/prepare_dataset.py --dataset pascal --year 2007 --set test --root VOCdevkit --target VOCdevkit/val.lst
    $ mv VOCdevkit/val.* ..
    $ cd ..
    
  6. 在顶级目录中,我们看到脚本生成的文件。可以随意查看文件列表,它们应该与之前展示的格式一致:

    train.idx  train.lst  train.rec  
    val.idx  val.lst  val.rec  
    
  7. 让我们将 RecordIO 文件存储到 S3,因为我们稍后会使用它们:

    $ aws s3 cp train.rec s3://sagemaker-eu-west-1-123456789012/pascalvoc/input/train/
    $ aws s3 cp val.rec s3://sagemaker-eu-west-1-123456789012/pascalvoc/input/validation/
    

prepare_dataset.py 脚本确实简化了很多事情。它还支持 COCO 数据集 (cocodataset.org),并且工作流程非常相似。

那么,如何转换其他公共数据集呢?嗯,结果可能因情况而异。你可以在 gluon-cv.mxnet.io/build/examples_datasets/index.html 上找到更多示例。

RecordIO 绝对是一个进步。然而,在处理自定义数据集时,很可能你需要编写自己的列表文件生成器。这虽然不是一件大事,但也是额外的工作。

使用Amazon SageMaker Ground Truth标注的数据集解决了这些问题。让我们看看它是如何工作的!

与 SageMaker Ground Truth 文件合作

第二章《数据准备技巧处理》中,您了解了 SageMaker Ground Truth 工作流及其结果,一个增强的清单文件。该文件采用JSON Lines格式:每个 JSON 对象描述了一个特定的标注。

这是我们在第二章《数据准备技巧处理》中运行的语义分割任务的一个示例(对于其他任务类型,流程相同)。我们看到原始图像和分割掩模的路径,以及颜色映射信息,告诉我们如何将掩模颜色与类别匹配:

{"source-ref":"s3://julien-sagemaker-book/chapter2/cat/cat1.jpg",
"my-cat-job-ref":"s3://julien-sagemaker-book/chapter2/cat/output/my-cat-job/annotations/consolidated-annotation/output/0_2020-04-21T13:48:00.091190.png",
"my-cat-job-ref-metadata":{
  "internal-color-map":{
   "0":{"class-name":"BACKGROUND","hex-color": "#ffffff", 
        "confidence": 0.8054600000000001}, 
   "1":{"class-name":"cat","hex-color": "#2ca02c", 
        "confidence":0.8054600000000001}
}, 
"type":"groundtruth/semantic-segmentation",
"human-annotated":"yes",
"creation-date":"2020-04-21T13:48:00.562419",
"job-name":"labeling-job/my-cat-job"}}

以下是前面 JSON 文档中引用的图像:

图 5.5 – 原始图像和分割图像

图 5.5 – 原始图像和分割图像

这正是我们训练模型所需要的。事实上,我们可以将增强的清单直接传递给 SageMaker 的Estimator,完全不需要任何数据处理。

要使用指向 S3 中标注图像的增强清单,我们只需传递其位置和 JSON 属性的名称(在前面的示例中已突出显示):

training_data_channel = sagemaker.s3_input(
    s3_data=augmented_manifest_file_path, 
    s3_data_type='AugmentedManifestFile',
    attribute_names=['source-ref', 'my-job-cat-ref'])

就这样!这比我们之前看到的任何内容都要简单。

您可以在github.com/awslabs/amazon-sagemaker-examples/tree/master/ground_truth_labeling_jobs找到更多使用 SageMaker Ground Truth 的示例。

现在我们已经知道如何为训练准备图像数据集,让我们开始使用计算机视觉(CV)算法。

使用内置的计算机视觉(CV)算法

在本节中,我们将使用公共图像数据集,通过所有三种算法训练并部署模型。我们将涵盖从头开始训练和迁移学习两种方法。

训练图像分类模型

在这个第一个示例中,我们将使用图像分类算法来构建一个模型,分类我们在前一节准备的狗与猫数据集。我们将首先使用图像格式进行训练,然后使用 RecordIO 格式。

使用图像格式进行训练

我们将通过以下步骤开始训练:

  1. 在 Jupyter 笔记本中,我们定义了适当的数据路径:

    import sagemaker
    sess = sagemaker.Session()
    bucket = sess.default_bucket()
    prefix = 'dogscats-images'
    s3_train_path = 
      's3://{}/{}/input/train/'.format(bucket, prefix)
    s3_val_path = 
      's3://{}/{}/input/val/'.format(bucket, prefix)
    s3_train_lst_path = 
      's3://{}/{}/input/train_lst/'.format(bucket, prefix)
    s3_val_lst_path = 
      's3://{}/{}/input/val_lst/'.format(bucket, prefix)
    s3_output = 's3://{}/{}/output/'.format(bucket, prefix)
    
  2. 我们为图像分类算法配置Estimator

    from sagemaker.image_uris import retrieve
    region_name = sess.boto_session.boto_region_name
    container = retrieve('image-classification', region)
    ic = sagemaker.estimator.Estimator(container,
                  sagemaker.get_execution_role(),
                  instance_count=1,
                  instance_type='ml.p3.2xlarge', 
                  output_path=s3_output)
    

    我们使用名为ml.p3.2xlarge的 GPU 实例,这个实例对这个数据集提供了足够的计算力(在 eu-west-1 区域的价格为每小时$4.131)。

    超参数怎么样(docs.aws.amazon.com/sagemaker/latest/dg/IC-Hyperparameter.html)?我们设置类别数(2)和训练样本数(22,500)。由于我们使用图像格式,我们需要显式调整图像大小,将最小尺寸设置为 224 像素。由于数据充足,我们决定从头开始训练。为了保持训练时间短,我们选择一个 18 层的ResNet模型,只训练 10 个 epochs:

    ic.set_hyperparameters(num_layers=18,
                           use_pretrained_model=0,
                           num_classes=2,
                           num_training_samples=22500,
                           resize=224,
                           mini_batch_size=128,
                           epochs=10)
    
  3. 我们定义四个通道,设置它们的内容类型为application/x-image

    from sagemaker import TrainingInput
    train_data = TrainingInput (
        s3_train_path,                              
        content_type='application/x-image')                                       
    val_data = TrainingInput (
        s3_val_path,                                                                  
        content_type='application/x-image')
    train_lst_data = TrainingInput (
        s3_train_lst_path,                                  
        content_type='application/x-image')
    val_lst_data = TrainingInput (
        s3_val_lst_path,                                    
        content_type='application/x-image')                                      
    s3_channels = {'train': train_data, 
                   'validation': val_data,
                   'train_lst': train_lst_data, 
                   'validation_lst': val_lst_data}
    
  4. 我们按以下方式启动训练作业:

    ic.fit(inputs=s3_channels)
    

    在训练日志中,我们看到数据下载大约需要 3 分钟。令人惊讶的是,我们还看到算法在训练之前构建 RecordIO 文件。这一步骤大约持续 1 分钟:

    Searching for .lst files in /opt/ml/input/data/train_lst.
    Creating record files for dogscats-train.lst
    Done creating record files...
    Searching for .lst files in /opt/ml/input/data/validation_lst.
    Creating record files for dogscats-val.lst
    Done creating record files...
    
  5. 训练开始时,我们看到一个 epoch 大约需要 22 秒:

    Epoch[0] Time cost=22.337
    Epoch[0] Validation-accuracy=0.605859
    
  6. 该作业总共持续了 506 秒(约 8 分钟),花费了我们(506/3600)*$4.131=$0.58。它达到了91.2%的验证准确率(希望您看到类似的结果)。考虑到我们甚至还没有调整超参数,这是相当不错的结果。

  7. 然后,我们在小型 CPU 实例上部署模型如下:

    ic_predictor = ic.deploy(initial_instance_count=1,
                             instance_type='ml.t2.medium')
    
  8. 我们下载以下测试图像,并以application/x-image格式发送进行预测。图 5.6 – 测试图片

    import boto3, json
    import numpy as np
    with open('test.jpg', 'rb') as f:
        payload = f.read()
        payload = bytearray(payload)
    runtime = boto3.Session().client(
        service_name='runtime.sagemaker')
    response = runtime.invoke_endpoint(
        EndpointName=ic_predictor.endpoint_name,                                  
        ContentType='application/x-image',
        Body=payload)
    result = response['Body'].read()
    result = json.loads(result)
    index = np.argmax(result)
    print(result[index], index)
    

    打印出概率和类别后,我们的模型指示这是一只狗,置信度为 99.997%,并且图像属于类别 1:

    0.9999721050262451 1
    
  9. 完成后,我们按以下方式删除端点:

    ic_predictor.delete_endpoint()
    

现在让我们使用 RecordIO 格式的数据集运行相同的训练作业。

训练 RecordIO 格式

唯一的区别在于我们如何定义输入通道。这次我们只需要两个通道,以便提供我们上传到 S3 的 RecordIO 文件。因此,内容类型设置为application/x-recordio

from sagemaker import TrainingInput
prefix = 'dogscats'
s3_train_path=
  's3://{}/{}/input/train/'.format(bucket, prefix)
s3_val_path=
  's3://{}/{}/input/validation/'.format(bucket, prefix)
train_data = TrainingInput(
    s3_train_path,
    content_type='application/x-recordio')
validation_data = TrainingInput(
    s3_val_path,                                        
    content_type='application/x-recordio')

再次训练时,我们看到数据下载需要 1 分钟,文件生成步骤已消失。虽然从单次运行中很难得出任何结论,但使用 RecordIO 数据集通常会节省您时间和金钱,即使在单个实例上进行训练。

狗与猫数据集每类样本超过 10,000 个,足以从头开始训练。现在,让我们尝试一个数据集,情况不同。

微调图像分类模型

请考虑Caltech-256数据集,这是一个受欢迎的公共数据集,包含 256 类共 15,240 张图像,还有一个混乱的类别(www.vision.caltech.edu/Image_Datasets/Caltech256/)。浏览图像类别,我们发现所有类别都只有少量样本。例如,“鸭子”类仅有 60 张图像:用这么少的数据,无论多么复杂的深度学习算法都很难提取出鸭子的独特视觉特征。

在这种情况下,从头开始训练根本不可行。相反,我们将使用一种叫做迁移学习的技术,在一个已经在非常大且多样化的图像数据集上训练过的网络基础上开始训练。ImageNetwww.image-net.org/)可能是最流行的预训练选择,包含 1,000 个类别和数百万张图像。

预训练的网络已经学会了如何从复杂图像中提取特征。假设我们数据集中的图像与预训练数据集中的图像足够相似,我们的模型应该能够继承这些知识。只需在我们的数据集上训练几个周期,我们应该能够微调预训练模型,以适应我们的数据和类别。

让我们看看如何通过 SageMaker 轻松实现这一点。实际上,我们将重用前一个示例中的代码,只做最小的修改。我们开始吧:

  1. 我们下载 Caltech-256 数据集的 RecordIO 格式版本(如果你愿意,可以下载图像格式,并转换成 RecordIO 格式:实践出真知!):

    %%sh
    wget http://data.mxnet.io/data/caltech-256/caltech-256-60-train.rec
    wget http://data.mxnet.io/data/caltech-256/caltech-256-60-val.rec
    
  2. 我们将数据集上传到 S3:

    import sagemaker
    session = sagemaker.Session()
    bucket = session.default_bucket()
    prefix = 'caltech256/'
    s3_train_path = session.upload_data(
        path='caltech-256-60-train.rec',
        bucket=bucket, key_prefix=prefix+'input/train')
    s3_val_path = session.upload_data(
        path='caltech-256-60-val.rec',
        bucket=bucket, key_prefix=prefix+'input/validation')
    
  3. 我们配置Estimator函数来进行图像分类算法。代码与前一个示例中的步骤 2完全相同。

  4. 我们使用use_pretrained_network来 1. 预训练网络的最终全连接层将根据我们数据集中的类别数量进行调整,并将其权重赋予随机值。

    我们设置正确的类别数量(256+1)和训练样本数,如下所示:

    ic.set_hyperparameters(num_layers=50,
                           use_pretrained_model=1,
                           num_classes=257,
                           num_training_samples=15240,
                           learning_rate=0.001,
                           epochs=5)
    

    由于我们在进行微调,所以我们只训练 5 个周期,并使用较小的学习率 0.001。

  5. 我们配置通道,并启动训练任务。代码与前一个示例中的步骤 4完全相同。

  6. 在经过 5 个周期和 272 秒后,我们在训练日志中看到以下指标:

    Epoch[4] Validation-accuracy=0.8119
    

    这对于仅仅几分钟的训练来说已经相当不错。即使数据量充足,从头开始也需要更长的时间才能获得这样的结果。

  7. 为了部署和测试模型,我们将重用前一个示例中的步骤 7-9

如你所见,迁移学习是一种非常强大的技术。即使数据量很少,它也能提供优秀的结果。你还会训练更少的周期,从而节省时间和资金。

现在,让我们进入下一个算法——物体检测

训练一个物体检测模型

在这个例子中,我们将使用物体检测算法,在前一部分准备好的 Pascal VOC 数据集上构建一个模型:

  1. 我们从定义数据路径开始:

    import sagemaker
    sess = sagemaker.Session()
    bucket = sess.default_bucket()
    prefix = 'pascalvoc'
    s3_train_data = 's3://{}/{}/input/train'.format(bucket, prefix)
    s3_validation_data = 's3://{}/{}/input/validation'.format(bucket, prefix)
    s3_output_location = 's3://{}/{}/output'.format(bucket, prefix)
    
  2. 我们选择物体检测算法:

    from sagemaker.image_uris import retrieve
    region = sess.boto_region_name
    container = retrieve('object-detection', region)
    
  3. 我们配置Estimator

    od = sagemaker.estimator.Estimator(
             container,
             sagemaker.get_execution_role(),
             instance_count=1,
             instance_type='ml.p3.2xlarge',
             output_path=s3_output_location)
    
  4. 我们设置所需的超参数。我们选择一个预训练的 ResNet-50 网络作为基础网络。我们设置类别数量和训练样本数。我们决定训练 30 个周期,应该足够开始看到结果:

    od.set_hyperparameters(base_network='resnet-50',
                           use_pretrained_model=1,
                           num_classes=20,
                           num_training_samples=16551,
                           epochs=30)
    
  5. 然后我们配置两个通道,并启动训练任务:

    from sagemaker.session import TrainingInput
    train_data = TrainingInput (
          s3_train_data,
          content_type='application/x-recordio')
    validation_data = TrainingInput (
          s3_validation_data, 
          content_type='application/x-recordio')
    data_channels = {'train': train_data, 
                     'validation': validation_data}
    od.fit(inputs=data_channels)
    

    SageMaker 组件与注册表 | 实验与试验中选择我们的任务,我们可以看到接近实时的指标和图表。下一张图显示了验证集的平均精度指标(mAP),这是目标检测模型的一个关键指标。

    图 5.7 – 验证集 mAP

    图 5.7 – 验证集 mAP

    请浏览其他标签页(指标参数工件等)。它们包含了关于特定任务的所有信息。请注意右上角的停止训练任务按钮,您可以随时使用它来终止任务。

  6. 训练持续了 1 小时 40 分钟。这个模型相当庞大!我们得到了平均精度指标mAP)为 0.5151。生产环境中使用需要更多的训练,但我们现在应该可以测试模型了。

  7. 由于模型的复杂性,我们将其部署到一个更大的 CPU 实例上:

    od_predictor = od.deploy(
        initial_instance_count = 1, 
        instance_type= 'ml.c5.2xlarge')
    
  8. 我们从维基百科下载一张测试图像,并用我们的模型进行预测:

    import boto3,json
    with open('test.jpg', 'rb') as image:
        payload = image.read()
        payload = bytearray(payload)
    runtime = boto3.Session().client(
        service_name='runtime.sagemaker')
    response = runtime.invoke_endpoint(
        EndpointName=od_predictor.endpoint_name,                                  
        ContentType='image/jpeg',
        Body=payload)
    response = response['Body'].read()
    response = json.loads(response)
    
  9. 响应包含一个预测列表。每个单独的预测包含一个类别标识符、置信度得分以及边界框的相对坐标。以下是响应中的前几个预测:

    {'prediction': 
    [[14.0, 0.7515302300453186, 0.39770469069480896, 0.37605002522468567, 0.5998836755752563, 1.0], 
    [14.0, 0.6490200161933899, 0.8020403385162354, 0.2027685046195984, 0.9918708801269531, 0.8575668931007385]
    

    利用这些信息,我们可以在源图像上绘制边界框。为了简洁起见,我不会在此处包含代码,但您可以在本书的 GitHub 仓库中找到。以下输出展示了结果:

    图 5.8 – 测试图像

    图 5.8 – 测试图像

  10. 当我们完成后,我们删除端点,如下所示:

    od_predictor.delete_endpoint()
    

这就结束了我们的目标检测探索。我们还有一个算法要介绍:语义分割

训练语义分割模型

在这个示例中,我们将使用语义分割算法,在之前章节中准备的 Pascal VOC 数据集上构建模型:

  1. 如常,我们定义数据路径,如下所示:

    import sagemaker
    sess = sagemaker.Session()
    bucket = sess.default_bucket()  
    prefix = 'pascalvoc-segmentation'
    s3_train_data = 's3://{}/{}/input/train'.format(bucket, prefix)
    s3_validation_data = 's3://{}/{}/input/validation'.format(bucket, prefix)
    s3_train_annotation_data = 's3://{}/{}/input/train_annotation'.format(bucket, prefix)
    s3_validation_annotation_data = 's3://{}/{}/input/validation_annotation'.format(bucket, prefix)
    s3_output_location = 
    's3://{}/{}/output'.format(bucket, prefix)
    
  2. 我们选择语义分割算法,并配置Estimator函数:

    from sagemaker.image_uris import retrieve
    container = retrieve('semantic-segmentation', region)
    seg = sagemaker.estimator.Estimator(
              container,
              sagemaker.get_execution_role(),                                     
              instance_count = 1,
              instance_type = 'ml.p3.2xlarge',
              output_path = s3_output_location)
    
  3. 我们定义了所需的超参数。我们选择了一个预训练的 ResNet-50 网络作为基础网络,并选择一个预训练的FCN进行检测。我们设置了类别数和训练样本数。再次,我们选择了 30 个训练周期,这应该足以开始看到结果:

    seg.set_hyperparameters(backbone='resnet-50',
                            algorithm='fcn',
                            use_pretrained_model=True,
                            num_classes=21,
                            num_training_samples=1464,
                            epochs=30)
    
  4. 我们配置四个通道,设置源图像的内容类型为image/jpeg,掩膜图像的内容类型为image/png。然后,我们启动训练任务:

    from sagemaker import TrainingInput
    train_data = TrainingInput(
        s3_train_data, 
        content_type='image/jpeg')
    validation_data = TrainingInput(
        s3_validation_data,
        content_type='image/jpeg')
    train_annotation = TrainingInput(
        s3_train_annotation_data,
        content_type='image/png')
    validation_annotation = TrainingInput(
        s3_validation_annotation_data,  
        content_type='image/png')
    data_channels = {
      'train': train_data,
      'validation': validation_data,
      'train_annotation': train_annotation,           
      'validation_annotation':validation_annotation
    }
    seg.fit(inputs=data_channels)
    
  5. 训练大约持续了 32 分钟。我们得到了平均交并比指标mIOU)为 0.4874,如下图所示:图 5.9 – 验证集 mIOU

    图 5.9 – 验证集 mIOU

  6. 我们将模型部署到一个 CPU 实例上:

    seg_predictor = seg.deploy(
        initial_instance_count=1, 
        instance_type='ml.c5.2xlarge')
    
  7. 一旦端点开始服务,我们获取一张测试图像,并将其作为字节数组发送进行预测,附上正确的内容类型:

    !wget -O test.jpg https://bit.ly/3yhXB9l 
    filename = 'test.jpg'
    with open(filename, 'rb') as f:
        payload = f.read()
        payload = bytearray(payload)
    runtime = boto3.Session().client(
        service_name='runtime.sagemaker')
    response = runtime.invoke_endpoint(
        EndpointName=od_predictor.endpoint_name,
        ContentType='image/jpeg',
        Body=payload)
    response = response['Body'].read()
    response = json.loads(response)
    
  8. 使用Python 图像库PIL),我们处理响应掩膜并显示它:

    import PIL
    from PIL import Image
    import numpy as np
    import io
    num_classes = 21
    mask = np.array(Image.open(io.BytesIO(response)))
    plt.imshow(mask, vmin=0, vmax=num_classes-1, cmap='gray_r')
    plt.show()
    

    以下图像展示了源图像和预测的掩码。这个结果很有前景,随着更多训练,它会得到改进:

    图 5.10 – 测试图像和分割后的测试图像

    图 5.10 – 测试图像和分割后的测试图像

  9. 再次使用application/x-protobuf接收类型进行预测时,我们会收到源图像中所有像素的类别概率。响应是一个 protobuf 缓冲区,我们将其保存到一个二进制文件中:

    response = runtime.invoke_endpoint(
        EndpointName=seg_predictor.endpoint_name,
        ContentType='image/jpeg',
        Accept='application/x-protobuf',
        Body=payload)
    result = response['Body'].read()
    seg_predictor.accept = 'application/x-protobuf'
    response = seg_predictor.predict(img)
    results_file = 'results.rec'
    with open(results_file, 'wb') as f:
        f.write(response)
    
  10. 该缓冲区包含两个张量:一个是概率张量的形状,另一个是实际的概率值。我们使用values张量加载它们,values张量描述的是一个 289x337 大小的图像,其中每个像素都被分配了 21 个概率值,分别对应 Pascal VOC 的每个类别。您可以验证 28933721=2,045,253。

  11. 知道这些之后,我们现在可以重新塑形values张量,获取(0,0)像素的 21 个概率,并打印出概率最高的类别标识符:

    mask = np.reshape(np.array(values), shape)
    pixel_probs = mask[0,:,0,0]
    print(pixel_probs)
    print(np.argmax(pixel_probs))
    

    这是输出结果:

    [9.68291104e-01 3.72813665e-04 8.14868137e-04 1.22414716e-03
     4.57380433e-04 9.95167647e-04 4.29908326e-03 7.52388616e-04
     1.46311778e-03 2.03254796e-03 9.60668200e-04 1.51833100e-03
     9.39570891e-04 1.49350625e-03 1.51627266e-03 3.63648031e-03
     2.17934581e-03 7.69103528e-04 3.05095245e-03 2.10589729e-03
     1.12741732e-03]
    0
    

    最高概率位于索引 0:像素(0,0)的预测类别为类别 0,即背景类别。

  12. 完成后,我们可以按如下方式删除端点:

    seg_predictor.delete_endpoint()
    

总结

如您所见,这三种算法使得训练计算机视觉(CV)模型变得简单。即使使用默认的超参数,我们也能很快得到不错的结果。不过,我们开始感觉到需要扩展训练工作。别担心,一旦相关内容在后续章节中讲解完毕,我们会再次回顾一些 CV 示例,并大幅扩展它们!

本章您学习了图像分类、目标检测和语义分割算法。您还学习了如何准备图像、RecordIO 和 SageMaker Ground Truth 格式的数据集。标注和准备数据是一个非常关键的步骤,需要大量的工作,我们在这一部分进行了详细的讲解。最后,您学习了如何使用 SageMaker SDK 来训练和部署这三种算法的模型,并了解如何解读结果。

在下一章中,您将学习如何使用内置算法进行自然语言处理。

第六章:训练自然语言处理模型

在前一章中,您学习了如何使用 SageMaker 的内置算法进行 计算机视觉CV)任务,解决包括图像分类、物体检测和语义分割等问题。

自然语言处理NLP)是机器学习中另一个非常有前景的领域。事实上,NLP 算法在建模语言和从非结构化文本中提取上下文方面已经证明非常有效。凭借这一点,像搜索和翻译应用程序以及聊天机器人等应用程序如今已经非常普遍。

在本章中,您将学习专门为 NLP 任务设计的内置算法,我们将讨论您可以用它们解决哪些类型的问题。与前一章一样,我们还将详细讲解如何准备实际数据集,如亚马逊客户评论。当然,我们还将训练和部署模型。我们将在以下主题中覆盖所有内容:

  • 发现亚马逊 SageMaker 中的 NLP 内置算法

  • 准备自然语言数据集

  • 使用内置的 NLP 算法

技术要求

您需要一个 Amazon Web ServicesAWS)账户才能运行本章中的示例。如果您还没有账户,请访问 aws.amazon.com/getting-started/ 创建一个。您还应该了解 AWS 免费套餐(aws.amazon.com/free/),它允许您在一定的使用限制内免费使用许多 AWS 服务。

您需要为您的账户安装并配置 AWS 命令行界面CLI)工具(aws.amazon.com/cli/)。

您需要一个可用的 Python 3.x 环境。虽然安装 Anaconda 发行版(www.anaconda.com/)不是必需的,但强烈推荐安装,因为它包括我们将需要的许多项目(Jupyter、pandasnumpy 等)。

本书中的代码示例可以在 GitHub 上找到,链接为 github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。您需要安装 Git 客户端才能访问这些代码(git-scm.com/)。

发现亚马逊 SageMaker 中的 NLP 内置算法

SageMaker 包含四种 NLP 算法,支持 有监督学习SL)和 无监督学习UL)场景。在本节中,您将了解这些算法,它们能解决哪些问题,以及它们的训练场景。让我们先来看一下我们将讨论的算法概述:

  • BlazingText 构建文本分类模型(SL)或计算词向量(UL)。BlazingText 是亚马逊发明的算法。

  • LDA 构建 UL 模型,将一组文本文档分组为多个主题。这种技术称为 主题建模

  • NTM 是另一个基于神经网络的主题建模算法,它能让你更深入地了解主题是如何构建的。

  • Sequence to Sequenceseq2seq)构建深度学习DL)模型,从一系列输入标记预测一系列输出标记。

发现 BlazingText 算法

BlazingText 算法是由亚马逊发明的。你可以在dl.acm.org/doi/10.1145/3146347.3146354阅读更多关于它的内容。BlazingText 是FastText的演进,FastText 是 Facebook 开发的一个高效文本分类和表示学习库 (fasttext.cc)。

它让你能够训练文本分类模型,并计算词向量。词向量也叫做嵌入,是许多自然语言处理(NLP)任务的基石,比如寻找词语相似度、词语类比等。Word2Vec 是计算这些向量的领先算法之一 (arxiv.org/abs/1301.3781),而 BlazingText 正是实现了这一算法。

BlazingText 的主要改进是能够在图形处理单元GPU)实例上进行训练,而 FastText 只支持中央处理单元CPU)实例。

速度提升是显著的,这也是它名字的来源:“blazing”比“fast”更快!如果你对基准测试感兴趣,一定会喜欢这篇博客文章:aws.amazon.com/blogs/machine-learning/amazon-sagemaker-blazingtext-parallelizing-word2vec-on-multiple-cpus-or-gpus/

最后,BlazingText 与 FastText 完全兼容。你可以非常轻松地导出并测试模型,正如你稍后在本章中将看到的那样。

发现 LDA 算法

这个 UL 算法使用一种生成技术,叫做主题建模,来识别大规模文本文档集合中的主题。它首次应用于机器学习是在 2003 年 (jmlr.csail.mit.edu/papers/v3/blei03a.html)。

请注意,LDA 并不是一个分类算法。你给它传递的是要构建的主题数量,而不是你期望的主题列表。用福雷斯特·甘普的话说:“主题建模就像一盒巧克力,你永远不知道自己会得到什么。

LDA 假设文档集合中的每个文本都是由几个潜在(即“隐藏”)主题生成的。一个主题通过词语概率分布来表示。对于文档集合中的每个词,这个分布给出该词在由该主题生成的文档中出现的概率。例如,在“金融”主题中,分布会为“收入”、“季度”或“收益”等词汇给出较高的概率,而“弩炮”或“鸭嘴兽”等词汇则会有较低的概率(我想应该是这样)。

主题分布不是独立考虑的。它们通过Dirichlet 分布表示,这是单变量分布的多变量推广(en.wikipedia.org/wiki/Dirichlet_distribution)。这个数学对象赋予了算法它的名称。

给定词汇表中的词语数量和潜在主题的数量,LDA 算法的目的是建立一个尽可能接近理想 Dirichlet 分布的模型。换句话说,它会尝试将词语分组,使得分布尽可能良好,并与指定的主题数量匹配。

训练数据需要仔细准备。每个文档需要转换为词袋BoW)表示:每个词语被一对整数替换,表示一个唯一的词语标识符ID)和文档中该词的出现次数。最终的数据集可以保存为逗号分隔值CSV)格式或RecordIO 包装的 protobuf格式,这是一种我们在第四章《训练机器学习模型》中已经学习过的技术。

一旦模型训练完成,我们可以对任何文档进行评分,并为每个主题打分。预期是,包含相似词语的文档应该具有相似的得分,从而使我们能够识别它们的主要主题。

探索 NTM 算法

NTM 是另一种主题建模算法。你可以在arxiv.org/abs/1511.06038上阅读更多关于它的信息。以下博客文章也总结了论文的关键要素:aws.amazon.com/blogs/machine-learning/amazon-sagemaker-neural-topic-model-now-supports-auxiliary-vocabulary-channel-new-topic-evaluation-metrics-and-training-subsampling/

与 LDA 一样,文档需要转换为 BoW 表示,并且数据集可以保存为 CSV 格式或 RecordIO 包装的 protobuf 格式。

对于训练,NTM 采用一种完全不同的方法,基于神经网络——更准确地说,是基于编码器架构(en.wikipedia.org/wiki/Autoencoder)。真正的深度学习方式是,编码器在文档的小批量上进行训练。它通过反向传播和优化调整网络参数,试图学习它们的潜在特征。

与 LDA 不同,NTM 可以告诉我们每个主题中最具影响力的词语。它还为每个主题提供了两个度量指标,词嵌入主题一致性WETC)和主题独特性TU)。这些在这里有更详细的描述:

  • WETC 告诉我们主题词之间的语义相似度。该值介于 0 和 1 之间,值越高表示越好。它是通过预训练的 全球向量 (GloVe) 模型中的对应词向量的 余弦相似度 (en.wikipedia.org/wiki/Cosine_similarity) 计算得到的(这是另一个类似于 Word2Vec 的算法)。

  • TU 告诉我们该主题的独特性——也就是说,其词汇是否在其他主题中出现过。该值介于 0 和 1 之间,得分越高,主题越独特。

一旦模型训练完成,我们就可以对文档进行评分,并为每个主题获得评分。

发现 seq2sea 算法

seq2seq 算法基于 长短期记忆 (LSTM) 神经网络 (arxiv.org/abs/1409.3215)。顾名思义,seq2seq 可以被训练成将一系列标记映射到另一系列标记。它的主要应用是机器翻译,训练大规模的双语语料库,例如 统计机器翻译研讨会 (WMT) 数据集 (www.statmt.org/wmt20/)。

除了在 SageMaker 中提供的实现外,AWS 还将 seq2seq 算法打包成了一个开源项目,AWS Sockeye (github.com/awslabs/sockeye),该项目还包括数据集准备工具。

本章不会介绍 seq2seq。详细讲解会占用太多篇幅,而且没有必要重复 Sockeye 文档中已经包含的内容。

你可以在 github.com/awslabs/amazon-sagemaker-examples/tree/master/introduction_to_amazon_algorithms/seq2seq_translation_en-de 找到一个 seq2seq 示例。不幸的是,它使用了低级别的 boto3 应用程序编程接口 (API),我们将在 第十二章自动化机器学习工作流 中讲解。尽管如此,它仍然是一本有价值的读物,而且你不会遇到太多麻烦就能搞明白。

使用 NLP 算法进行训练

就像 CV 算法一样,训练是相对简单的,尤其是在使用 SageMaker 软件开发工具包 (SDK) 时。到目前为止,你应该已经熟悉了工作流程和 API,并且我们将在本章中继续使用它们。

为 NLP 算法准备数据是另一回事。首先,现实生活中的数据集通常非常庞大。在本章中,我们将处理数百万个样本和数亿个单词。当然,这些数据需要清洗、处理,并转换为算法所期望的格式。

在本章中,我们将使用以下技术:

  • 使用pandas库加载和清理数据(pandas.pydata.org

  • 使用自然语言工具包NLTK)库去除停用词并进行词形还原(www.nltk.org

  • 使用spaCy库进行分词(spacy.io/

  • 使用gensim库构建词汇表并生成 BoW 表示(radimrehurek.com/gensim/

  • 使用Amazon SageMaker Processing运行数据处理任务,我们在第二章中学习过,处理数据准备技术

当然——这不是一本 NLP 书籍,我们不会深入数据处理。但这将是一次非常有趣的学习机会,希望能了解一些流行的开源 NLP 工具。

准备自然语言数据集

在上一章的 CV 算法中,数据准备侧重于数据集所需的技术格式(图像格式、RecordIO增强清单)。图像本身没有被处理。

对于 NLP 算法,情况完全不同。文本需要经过大量处理、转换,并以正确的格式保存。在大多数学习资源中,这些步骤通常会被简化或忽略。数据已经“自动”准备好进行训练,这让读者感到沮丧,有时甚至不知从何处开始准备自己的数据集。

这里没有这种情况!在本节中,您将学习如何准备不同格式的 NLP 数据集。再一次,准备好学习大量内容吧!

让我们从为 BlazingText 准备数据开始。

使用 BlazingText 准备分类数据

BlazingText 期望标签输入数据与 FastText 相同的格式,详见此处:

  • 一个纯文本文件,每行一个样本。

  • 每行包含两个字段,如下所示:

    a) 以__label__LABELNAME__形式的标签

    b) 文本本身,形成以空格分隔的词元(单词和标点符号)

让我们开始工作,准备一个用于情感分析(正面、中立或负面)的客户评论数据集。我们将使用Amazon Customer Reviews数据集,可以在s3.amazonaws.com/amazon-reviews-pds/readme.html找到。那应该是充足的现实数据。

在开始之前,请确保您有足够的存储空间。在这里,我使用的是具有 10 千兆字节GB)存储的笔记本实例。我还选择了一个 C5 实例类型,以更快地运行处理步骤。我们将按以下方式进行:

  1. 让我们通过运行以下代码来下载相机评论:

    %%sh
    aws s3 cp s3://amazon-reviews-pds/tsv/amazon_reviews_us_Camera_v1_00.tsv.gz /tmp
    
  2. 我们使用pandas加载数据,忽略任何会引发错误的行,并删除任何包含缺失值的行。以下代码演示了这一过程:

    data = pd.read_csv(
        '/tmp/amazon_reviews_us_Camera_v1_00.tsv.gz', 
        sep='\t', compression='gzip', 
        error_bad_lines=False, dtype='str')
    data.dropna(inplace=True)
    
  3. 我们打印数据的形状和列名,如下所示:

    print(data.shape)
    print(data.columns)
    

    这将给我们以下输出:

    (1800755, 15)
    Index(['marketplace','customer_id','review_id','product_id','product_parent', 'product_title','product_category',  'star_rating','helpful_votes','total_votes','vine', 'verified_purchase','review_headline','review_body', 
    'review_date'], dtype='object')
    
  4. 180 万行!我们保留了 100,000 行,足够满足我们的需求。我们还删除了除 star_ratingreview_body 之外的所有列,以下代码片段展示了这一过程:

    data = data[:100000]
    data = data[['star_rating', 'review_body']]
    
  5. 基于星级评分,我们新增了一个名为 label 的列,标签格式已正确。你不得不佩服 pandas 让这一切变得如此简单。然后,我们删除了 star_rating 列,以下代码片段展示了这一过程:

    data['label'] = data.star_rating.map({
        '1': '__label__negative__',
        '2': '__label__negative__',
        '3': '__label__neutral__',
        '4': '__label__positive__',
        '5': '__label__positive__'})
    data = data.drop(['star_rating'], axis=1)
    
  6. BlazingText 期望每行的开头是标签,因此我们将 label 列移到前面,如下所示:

    data = data[['label', 'review_body']]
    
  7. 数据现在应该像这样:图 6.1 – 查看数据集

    图 6.1 – 查看数据集

  8. BlazingText 期望标记符号之间用空格分隔:每个单词和标点符号必须与下一个单词或符号用空格分隔开。让我们使用 nltk 库中的便捷 punkt 分词器。根据你使用的实例类型,这可能需要几分钟。下面是你需要的代码:

    !pip -q install nltk
    import nltk
    nltk.download('punkt')
    data['review_body'] = data['review_body'].apply(nltk.word_tokenize)
    
  9. 我们将标记符号连接成一个字符串,并将其转换为小写,如下所示:

    data['review_body'] = 
        data.apply(lambda row: "".join(row['review_body'])
            .lower(), axis=1)
    
  10. 数据现在应该像这样(请注意,所有的标记符号都已正确地用空格分隔):图 6.2 – 查看标记化后的数据集

    图 6.2 – 查看标记化后的数据集

  11. 最后,我们将数据集拆分为训练集(95%)和验证集(5%),并将这两个拆分保存为纯文本文件,以下代码片段展示了这一过程:

    from sklearn.model_selection import train_test_split
    training, validation = train_test_split(data, test_size=0.05)
    np.savetxt('/tmp/training.txt', training.values, fmt='%s')
    np.savetxt('/tmp/validation.txt', validation.values, fmt='%s')
    
  12. 打开其中一个文件,你应该会看到许多类似这样的行:

    __label__neutral__ really works for me , especially on the streets of europe . wished it was less expensive though . the rain cover at the base really works . the padding which comes in contact with your back though will suffocate & make your back sweaty .
    

数据准备其实并不算太糟糕,对吧?不过,标记化过程运行了一两分钟。现在,想象一下如果是在数百万个样本上运行这个过程会怎样。没错,你可以启动一个更大的环境来运行它,在 SageMaker Studio 上运行。不过,你还得为使用它支付更多费用,特别是当只有这一道步骤需要额外计算力时,这显然是浪费的。另外,想象一下如果你需要对许多其他数据集运行同样的脚本。你会想一次又一次地手动做这些事情,等待 20 分钟,每次还要希望你的笔记本不会崩溃吗?我想应该不会!

你已经知道这两个问题的答案了。那就是 Amazon SageMaker Processing,我们在 第二章《数据准备技术》中学过这个内容。你应该能够两全其美,在最小且最便宜的环境中进行实验,并在需要更多资源时运行按需作业。日复一日,你将节省成本,并更快完成任务。

让我们将这些处理代码移到 SageMaker Processing。

为使用 BlazingText 进行分类准备数据,第 2 版

我们在 第二章《数据准备技术》中已经详细讨论了这个内容,所以这次我会讲得更快一些。我们将按如下步骤进行:

  1. 我们将数据集上传到 简单存储服务 (S3),如下所示:

    import sagemaker
    session = sagemaker.Session()
    prefix = 'amazon-reviews-camera'
    input_data = session.upload_data(
        path='/tmp/amazon_reviews_us_Camera_v1_00.tsv.gz', 
        key_prefix=prefix)
    
  2. 我们通过运行以下代码来定义处理器:

    from sagemaker.sklearn.processing import SKLearnProcessor
    sklearn_processor = SKLearnProcessor(
       framework_version='0.23-1',
       role= sagemaker.get_execution_role(),
       instance_type='ml.c5.2xlarge',
       instance_count=1)
    
  3. 我们运行处理作业,传递处理脚本及其参数,如下所示:

    from sagemaker.processing import ProcessingInput, ProcessingOutput
    sklearn_processor.run(
        code='preprocessing.py',
        inputs=[
            ProcessingInput(
                source=input_data,
                destination='/opt/ml/processing/input')
        ],
        outputs=[
            ProcessingOutput(
                output_name='train_data',
                source='/opt/ml/processing/train'),
            ProcessingOutput(
                output_name='validation_data',
                source='/opt/ml/processing/validation')
        ],
        arguments=[
            '--filename', 'amazon_reviews_us_Camera_v1_00.tsv.gz',
            '--num-reviews', '100000',
            '--split-ratio', '0.05'
        ]
    )
    
  4. 简化的预处理脚本如下所示,完整版本可以在本书的 GitHub 仓库中找到。我们首先安装nltk包,如下所示:

    import argparse, os, subprocess, sys
    import pandas as pd
    import numpy as np
    from sklearn.model_selection import train_test_split
    def install(package):
        subprocess.call([sys.executable, "-m", "pip",  
                         "install", package]) 
    if __name__=='__main__':
        install('nltk')
        import nltk
    
  5. 我们读取命令行参数,如下所示:

        parser = argparse.ArgumentParser()
        parser.add_argument('--filename', type=str)
        parser.add_argument('--num-reviews', type=int)
        parser.add_argument('--split-ratio', type=float, 
                            default=0.1)
        args, _ = parser.parse_known_args()
        filename = args.filename
        num_reviews = args.num_reviews
        split_ratio = args.split_ratio
    
  6. 我们读取输入数据集并进行处理,如下所示:

        input_data_path = 
        os.path.join('/opt/ml/processing/input', filename)
        data = pd.read_csv(input_data_path, sep='\t', 
               compression='gzip', error_bad_lines=False,
               dtype='str')
        # Process data
        . . . 
    
  7. 最后,我们将数据分为训练集和验证集,并将其保存到两个文本文件中,如下所示:

        training, validation = train_test_split(
                               data, 
                               test_size=split_ratio)
        training_output_path = os.path.join('
        /opt/ml/processing/train', 'training.txt')    
        validation_output_path = os.path.join(
        '/opt/ml/processing/validation', 'validation.txt')
        np.savetxt(training_output_path, 
        training.values, fmt='%s')
        np.savetxt(validation_output_path, 
        validation.values, fmt='%s')
    

如你所见,将手动处理的代码转化为 SageMaker 处理作业并不难。你实际上还可以重用大部分代码,因为它涉及一些通用的主题,比如命令行参数、输入和输出。唯一的技巧是使用subprocess.call在处理容器内安装依赖项。

配备了这个脚本后,你现在可以按需大规模处理数据,而不必运行和管理长期的笔记本。

现在,让我们为另一个 BlazingText 场景——词向量——准备数据!

使用 BlazingText 准备词向量数据

BlazingText 让你能够轻松且大规模地计算词向量。它期望输入数据为以下格式:

  • 一个纯文本文件,每行一个样本。

  • 每个样本必须具有以空格分隔的标记(词语和标点符号)。

让我们处理与上一节相同的数据集,如下所示:

  1. 我们将需要spaCy库,因此让我们安装它并一起安装其英语语言模型,如下所示:

    %%sh
    pip -q install spacy
    python -m spacy download en_core_web_sm
    python -m spacy validate
    
  2. 我们使用pandas加载数据,忽略任何导致错误的行。我们还会删除任何包含缺失值的行。无论如何,我们的数据量应该足够多。以下是你需要的代码:

    data = pd.read_csv(
        '/tmp/amazon_reviews_us_Camera_v1_00.tsv.gz', 
        sep='\t', compression='gzip', 
        error_bad_lines=False, dtype='str')
    data.dropna(inplace=True)
    
  3. 我们保留 100,000 行,并且删除除了review_body之外的所有列,如下所示:

    data = data[:100000]
    data = data[['review_body']]
    

    我们编写一个函数来使用spaCy对评论进行分词,并将其应用到DataFrame中。这个步骤应该比前一个例子中使用nltk分词要明显更快,因为spaCy基于cythoncython.org)。以下是代码示例:

    import spacy
    spacy_nlp = spacy.load('en_core_web_sm')
    def tokenize(text):
        tokens = spacy_nlp.tokenizer(text)
        tokens = [ t.text for t in tokens ]
        return " ".join(tokens).lower()
    data['review_body'] = 
        data['review_body'].apply(tokenize)
    

    现在数据应该是这样的:

    图 6.3 – 查看分词后的数据集

    图 6.3 – 查看分词后的数据集

  4. 最后,我们将评论保存到一个纯文本文件中,如下所示:

    import numpy as np
    np.savetxt('/tmp/training.txt', data.values, fmt='%s')
    
  5. 打开这个文件,你应该看到每行一个分词后的评论,正如以下代码片段所示:

    Ok
    perfect , even sturdier than the original !
    

在这里,我们也应该使用 SageMaker 处理来运行这些步骤。你可以在这本书的 GitHub 仓库中找到相应的笔记本和预处理脚本。

现在,让我们为 LDA 和 NTM 算法准备数据。

为 LDA 和 NTM 准备主题建模数据

在这个示例中,我们将使用 百万新闻头条 数据集(doi.org/10.7910/DVN/SYBGZL),该数据集也可以在 GitHub 仓库中找到。顾名思义,它包含来自澳大利亚新闻源 ABC 的一百万条新闻头条。与产品评论不同,头条新闻通常是非常简短的句子。构建一个主题模型将是一个有趣的挑战!

分词数据

正如你所预料的,两个算法都需要一个已经分词的数据集,所以我们按以下步骤进行:

  1. 我们需要安装 nltkgensim 库,步骤如下:

    %%sh
    pip -q install nltk gensim
    
  2. 下载数据集后,我们使用 pandas 完整加载它,如下所示:

    num_lines = 1000000
    data = pd.read_csv('abcnews-date-text.csv.gz', 
                       compression='gzip', error_bad_lines=False, 
                       dtype='str', nrows=num_lines)
    
  3. 数据应该是这样的:图 6.4 – 查看已分词的数据集

    图 6.4 – 查看已分词的数据集

  4. 数据按日期排序,并且为了安全起见我们将其打乱。然后,我们运行以下代码删除 date 列:

    data = data.sample(frac=1)
    data = data.drop(['publish_date'], axis=1)
    
  5. 我们编写一个函数来清理和处理头条新闻。首先,我们去除所有标点符号和数字。使用 nltk,我们还会去除停用词——即那些非常常见且不添加任何上下文的单词,例如“this”,“any”等。为了减少词汇量并保持上下文,我们可以应用 词干提取https://en.wikipedia.org/wiki/Stemming)或 词形还原en.wikipedia.org/wiki/Lemmatisation),这两种是流行的自然语言处理技术。这里我们选择后者。根据你的实例类型,这个过程可能需要几分钟。以下是你需要的代码:

    import string
    import nltk
    from nltk.corpus import stopwords
    #from nltk.stem.snowball import SnowballStemmer
    from nltk.stem import WordNetLemmatizer 
    nltk.download('stopwords')
    stop_words = stopwords.words('english')
    #stemmer = SnowballStemmer("english")
    wnl = WordNetLemmatizer()
    def process_text(text):
        for p in string.punctuation:
            text = text.replace(p, '')
            text = ''.join([c for c in text if not 
                            c.isdigit()])
            text = text.lower().split()
            text = [w for w in text if not w in 
                    stop_words]
            #text = [stemmer.stem(w) for w in text]
            text = [wnl.lemmatize(w) for w in text]
            return text
    data['headline_text'] = 
        data['headline_text'].apply(process_text)
    
  6. 数据处理后应该是这样的:

图 6.5 – 查看已词形还原的数据集

图 6.5 – 查看已词形还原的数据集

现在评论已经分词,我们需要将其转换为 BoW 表示,将每个单词替换为一个唯一的整数 ID 和其频率计数。

将数据转换为 BoW 表示

我们将按照以下步骤将评论转换为 BoW 表示:

  1. gensim 库正好包含了我们需要的功能!我们使用以下代码构建一个字典,它包含了文档集合中的所有单词:

    from gensim import corpora
    dictionary = corpora.Dictionary(data['headline_text'])
    print(dictionary)
    

    字典看起来是这样的:

    Dictionary(83131 unique tokens: ['aba', 'broadcasting', 'community', 'decides', 'licence']...)
    

    这个数字看起来很高。如果维度太多,训练将会非常长,算法可能会很难拟合数据;例如,NTM 是基于神经网络架构的。输入层的大小将根据分词的数量来决定,因此我们需要保持维度合理。这样可以加速训练,并帮助编码器学习一个可管理的潜在特征数量。

  2. 我们可以回头再清理一下头条新闻。不过,我们使用 gensim 的一个函数来移除极端单词——即那些非常稀有或非常频繁的异常词。然后,冒着风险,我们决定将词汇限制为剩余的前 512 个单词。是的——这不到 1%。以下是执行此操作的代码:

    dictionary.filter_extremes(keep_n=512)
    
  3. 我们将词汇表写入一个文本文件。这不仅有助于我们检查哪些单词最重要,而且我们还将把这个文件作为额外的通道传递给 NTM 算法。训练模型时,你会明白为什么这很重要。执行此操作的代码示例如下:

    with open('vocab.txt', 'w') as f:
        for index in range(0,len(dictionary)):
            f.write(dictionary.get(index)+'\n')
    
  4. 我们使用字典为每个标题构建一个词袋(BoW)。它被存储在一个名为tokens的新列中。当我们完成后,会删除文本评论。代码示例如下:

    data['tokens'] = data.apply(lambda row: dictionary.doc2bow(row['headline_text']), axis=1)
    data = data.drop(['headline_text'], axis=1)
    
  5. 现在,数据应如下所示:

图 6.6 – 查看 BoW 数据集

图 6.6 – 查看 BoW 数据集

如您所见,每个单词已经被替换为其唯一的 ID 以及在评论中的频率。例如,最后一行告诉我们单词#11 出现一次,单词#12 出现一次,依此类推。

数据处理现已完成。最后一步是将其保存为适当的输入格式。

保存输入数据

NTM 和 LDA 期望数据以 CSV 格式或 RecordIO 封装的 protobuf 格式提供。正如在第四章中关于矩阵分解的例子所讨论的,训练机器学习模型,我们正在处理的数据非常稀疏。任何给定的评论只包含词汇表中的少数几个单词。由于 CSV 格式是稠密格式,我们最终会得到大量的零频率单词。这可不是什么好主意!

再次,我们将使用lil_matrix,一个SciPy库。它的行数与评论数相等,列数与字典中的单词数相等。我们按以下方式进行:

  1. 我们按如下方式创建稀疏矩阵:

    from scipy.sparse import lil_matrix
    num_lines = data.shape[0]
    num_columns = len(dictionary)
    token_matrix = lil_matrix((num_lines,num_columns))
                   .astype('float32')
    
  2. 我们编写一个函数,将标题添加到矩阵中。对于每个令牌,我们只需在相应的列中写下其频率,如下所示:

    def add_row_to_matrix(line, row):
        for token_id, token_count in row['tokens']:
            token_matrix[line, token_id] = token_count
        return
    
  3. 然后,我们遍历标题并将其添加到矩阵中。快速提示:我们不能使用行索引值,因为它们可能大于行数。代码示例如下:

    line = 0
    for _, row in data.iterrows():
        add_row_to_matrix(line, row)
        line+=1
    
  4. 最后的步骤是将这个矩阵写入protobuf格式的内存缓冲区,并上传到 S3 以供将来使用,如下所示:

    import io, boto3
    import sagemaker
    import sagemaker.amazon.common as smac
    buf = io.BytesIO()
    smac.write_spmatrix_to_sparse_tensor(buf, token_matrix, None)
    buf.seek(0)
    bucket = sagemaker.Session().default_bucket()
    prefix = 'headlines-lda-ntm'
    train_key = 'reviews.protobuf'
    obj = '{}/{}'.format(prefix, train_key))
    s3 = boto3.resource('s3')
    s3.Bucket(bucket).Object(obj).upload_fileobj(buf)
    s3_train_path = 's3://{}/{}'.format(bucket,obj)
    
  5. 构建(1000000,512)的矩阵需要几分钟。上传到 S3 后,我们可以看到它只有 42 兆字节MB)。确实是小矩阵。代码示例如下:

    $ aws s3 ls s3://sagemaker-eu-west-1-123456789012/amazon-reviews-ntm/training.protobuf
    43884300 training.protobuf
    

这就是 LDA 和 NTM 的数据准备工作。现在,让我们看看如何使用SageMaker Ground Truth准备的文本数据集。

使用 SageMaker Ground Truth 标记的数据集

如在第二章中讨论的,数据准备技术处理,SageMaker Ground Truth 支持文本分类任务。我们完全可以使用它的输出构建一个用于 FastText 或 BlazingText 的数据集。

首先,我快速运行了一个文本分类任务,应用了两种标签之一:“aws_service”如果句子提到 AWS 服务,和“no_aws_service”如果没有提到。

一旦任务完成,我可以从 S3 获取 增强后的清单。它是 JavaScript 对象表示法行JSON Lines)格式,下面是其中一个条目:

{"source":"With great power come great responsibility. The second you create AWS resources, you're responsible for them: security of course, but also cost and scaling. This makes monitoring and alerting all the more important, which is why we built services like Amazon CloudWatch, AWS Config and AWS Systems Manager.","my-text-classification-job":0,"my-text-classification-job-metadata":{"confidence":0.84,"job-name":"labeling-job/my-text-classification-job","class-name":"aws_service","human-annotated":"yes","creation-date":"2020-05-11T12:44:50.620065","type":"groundtruth/text-classification"}}

我们来写点 Python 代码,把它转成 BlazingText 格式怎么样?当然可以!来看看:

  1. 我们直接从 S3 加载增强后的清单,如下所示:

    import pandas as pd
    bucket = 'sagemaker-book'
    prefix = 'chapter2/classif/output/my-text-classification-job/manifests/output'
    manifest = 's3://{}/{}/output.manifest'.format(bucket, prefix)
    data = pd.read_json(manifest, lines=True)
    
  2. 数据看起来像这样:图 6.7 – 查看标注过的数据集

    ](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_06_7.jpg)

    图 6.7 – 查看标注过的数据集

  3. 标签隐藏在 my-text-classification-job-metadata 列中。我们将其提取到一个新列,如下所示:

    def get_label(metadata):
        return metadata['class-name']
    data['label'] = 
    data['my-text-classification-job-metadata'].apply(get_label)
    data = data[['label', 'source']]
    
  4. 数据现在看起来像下面的截图所示。从此以后,我们可以应用分词等操作。很简单,不是吗?

图 6.8 – 查看处理过的数据集

](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_06_8.jpg)

图 6.8 – 查看处理过的数据集

现在,让我们构建 NLP 模型!

使用内置的 NLP 算法

在本节中,我们将使用 BlazingText、LDA 和 NTM 训练并部署模型。当然,我们将使用前一节准备的数据集。

使用 BlazingText 进行文本分类

BlazingText 让构建文本分类模型变得极其简单,尤其是如果你没有 NLP 技能。让我们看看,以下是步骤:

  1. 我们将训练和验证数据集上传到 S3。或者,我们可以使用 SageMaker 处理作业返回的输出路径。代码如下所示:

    import sagemaker
    session = sagemaker.Session()
    bucket = session.default_bucket()
    prefix = 'amazon-reviews'
    s3_train_path = session.upload_data(path='/tmp/training.txt', bucket=bucket, key_prefix=prefix+'/input/train')
    s3_val_path = session.upload_data(
        path='/tmp/validation.txt', bucket=bucket,   
        key_prefix=prefix+'/input/validation')
    s3_output = 's3://{}/{}/output/'.format(bucket, 
        prefix)
    
  2. 我们为 BlazingText 配置 Estimator 函数,如下所示:

    from sagemaker.image_uris import retrieve
    region_name = session.boto_session.region_name
    container = retrieve('blazingtext', region)
    bt = sagemaker.estimator.Estimator(container, 
         sagemaker.get_execution_role(),
         instance_count=1,
         instance_type='ml.p3.2xlarge',
         output_path=s3_output)
    
  3. 我们设置一个超参数,告诉 BlazingText 以监督模式进行训练,如下所示:

    bt.set_hyperparameters(mode='supervised')
    
  4. 我们定义通道,设置内容类型为 text/plain,然后启动训练,如下所示:

    from sagemaker import TrainingInput
    train_data = TrainingInput(
        s3_train_path, content_type='text/plain')
    validation_data = TrainingInput(
        s3_val_path, content_type='text/plain')
    s3_channels = {'train': train_data, 
                   'validation': validation_data}
    bt.fit(inputs=s3_channels)
    
  5. 我们得到了 88.4% 的验证准确率,在没有调整任何超参数的情况下,这个结果相当不错。然后,我们将模型部署到一个小型 CPU 实例,如下所示:

    bt_predictor = bt.deploy(initial_instance_count=1, 
                             instance_type='ml.t2.medium')
    
  6. 一旦端点启动,我们将发送三个已分词的样本进行预测,并请求所有三个标签,如下所示:

    import json
    sentences = ['This is a bad camera it doesnt work at all , i want a refund  . ' , 'The camera works , the pictures are decent quality, nothing special to say about it . ' , 'Very happy to have bought this , exactly what I needed . ']
    payload = {"instances":sentences, 
               "configuration":{"k": 3}}
    bt_predictor.serializer =  
        sagemaker.serializers.JSONSerializer()
    response = bt_predictor.predict(json.dumps(payload))
    
  7. 打印响应后,我们可以看到三个样本已正确分类,如下图所示:

    [{'prob': [0.9758228063583374, 0.023583529517054558, 0.0006236258195713162], 'label': ['__label__negative__', '__label__neutral__', '__label__positive__']}, 
    {'prob': [0.5177792906761169, 0.2864232063293457, 0.19582746922969818], 'label': ['__label__neutral__', '__label__positive__', '__label__negative__']}, 
    {'prob': [0.9997835755348206, 0.000205090589588508, 4.133415131946094e-05], 'label': ['__label__positive__', '__label__neutral__', '__label__negative__']}]
    
  8. 像往常一样,一旦完成,我们通过运行以下代码删除端点:

    bt_predictor.delete_endpoint()
    

现在,让我们训练 BlazingText 来计算词向量。

使用 BlazingText 计算词向量

这段代码几乎与前一个示例相同,只有两个不同点。首先,只有一个通道,包含训练数据。其次,我们需要将 BlazingText 设置为 UL 模式。

BlazingText 支持在 Word2Vec 中实现的训练模式:skipgram连续的 BoWCBOW)。它还添加了一种第三种模式,batch_skipgram,用于更快的分布式训练。它还支持 子词嵌入,这是一种可以为拼写错误或不在词汇表中的词返回词向量的技术。

我们选择使用 skipgram 和子词嵌入。我们保持向量的维度不变(默认是 100)。以下是所需的代码:

bt.set_hyperparameters(mode='skipgram', subwords=True)

与其他算法不同,这里没有需要部署的内容。模型文件存储在 S3 中,并且可以用于下游 NLP 应用程序。

说到这里,BlazingText 与 FastText 兼容,那么如何将我们刚训练的模型加载到 FastText 中呢?

使用 BlazingText 模型与 FastText

首先,我们需要编译 FastText,这非常简单。你甚至可以在笔记本实例上进行编译,无需安装任何东西。下面是你需要的代码:

$ git clone https://github.com/facebookresearch/fastText.git
$ cd fastText
$ make

让我们首先尝试我们的分类模型。

使用 BlazingText 分类模型与 FastText

我们将通过以下步骤来尝试该模型:

  1. 我们从 S3 复制模型文件并进行解压,方法如下:

    $ aws s3 cp s3://sagemaker-eu-west-1-123456789012/amazon-reviews/output/JOB_NAME/output/model.tar.gz .
    $ tar xvfz model.tar.gz
    
  2. 我们使用 FastText 加载 model.bin,方法如下:

    $ ./fasttext predict model.bin -
    
  3. 我们预测样本并查看它们的 top 类别,方法如下:

    This is a bad camera it doesnt work at all , i want a refund  .
    __label__negative__
    The camera works , the pictures are decent quality, nothing
    special to say about it .
    __label__neutral__
    Very happy to have bought this , exactly what I needed
    __label__positive__
    

我们通过 Ctrl + C 退出。现在,让我们来探索我们的向量。

使用 BlazingText 词向量与 FastText

现在,我们将使用 FastText 和向量,方法如下:

  1. 我们从 S3 复制模型文件并解压,方法如下:

    $ aws s3 cp s3://sagemaker-eu-west-1-123456789012/amazon-reviews-word2vec/output/JOB_NAME/output/model.tar.gz .
    $ tar xvfz model.tar.gz
    
  2. 我们可以探索词语相似度。例如,让我们找出与“远摄”最相近的词汇。这可能有助于我们改进搜索查询的处理方式或推荐相似产品的方式。以下是你需要的代码:

    $ ./fasttext nn vectors.bin
    Query word? Telephoto
    telephotos 0.951023
    75-300mm 0.79659
    55-300mm 0.788019
    18-300mm 0.782396
    . . .
    
  3. 我们也可以寻找类比。例如,假设我们问我们的模型以下问题:尼康 D3300 相机的佳能等效型号是什么?代码如下所示:

    $ ./fasttext analogies vectors.bin
    Query triplet (A - B + C)? nikon d3300 canon
    xsi 0.748873
    700d 0.744358
    100d 0.735871
    

    根据我们的模型,你应该考虑 XSI 和 700D 相机!

如你所见,词向量非常强大,BlazingText 使得在任何规模下计算词向量都变得非常容易。现在,让我们继续进行主题建模,这是另一个令人着迷的主题。

使用 LDA 进行主题建模

在前面的部分,我们准备了百万条新闻标题,现在我们将使用它们进行主题建模,采用 LDA 方法,具体如下:

  1. 首先,我们通过运行以下代码定义有用的路径:

    import sagemaker
    session = sagemaker.Session()
    bucket = session.default_bucket()
    prefix = reviews-lda-ntm'
    train_key = 'reviews.protobuf'
    obj = '{}/{}'.format(prefix, train_key)
    s3_train_path = 's3://{}/{}'.format(bucket,obj)
    s3_output = 's3://{}/{}/output/'.format(bucket, prefix)
    
  2. 我们配置 Estimator 函数,如下所示:

    from sagemaker.image_uris import retrieve
    region_name = session.boto_session.region_name
    container = retrieve('lda', region)
    lda = sagemaker.estimator.Estimator(container, 
          role = sagemaker.get_execution_role(), 
          instance_count=1,                                
          instance_type='ml.c5.2xlarge', 
          output_path=s3_output)
    
  3. 我们设置超参数:希望构建的主题数量(10),问题的维度(词汇大小)以及我们正在训练的样本数量。可选地,我们可以设置一个名为 alpha0 的参数。根据文档:“较小的值更容易生成稀疏的主题混合,而较大的值(大于 1.0)则生成更加均匀的混合。”我们将其设置为 0.1,并希望算法能够构建出良好识别的主题。以下是所需的代码:

    lda.set_hyperparameters(num_topics=5,
       feature_dim=len(dictionary),
       mini_batch_size=num_lines,
       alpha0=0.1)
    
  4. 我们启动训练。由于 RecordIO 是该算法期望的默认格式,我们无需定义通道。代码如下所示:

    lda.fit(inputs={'train': s3_train_path})
    
  5. 一旦训练完成,我们将模型部署到一个小型 CPU 实例,方法如下:

    lda_predictor = lda.deploy(
        initial_instance_count=1,    
        instance_type='ml.t2.medium')
    
  6. 在发送样本进行预测之前,我们需要像处理训练集一样处理它们。我们编写一个函数来完成这个任务:构建稀疏矩阵,填充 BoW,并保存到内存中的 protobuf 缓冲区,方法如下:

    def process_samples(samples, dictionary):
        num_lines = len(samples)
        num_columns = len(dictionary)
        sample_matrix = lil_matrix((num_lines,  
                        num_columns)).astype('float32')
        for line in range(0, num_lines):
            s = samples[line]
            s = process_text(s)
            s = dictionary.doc2bow(s)
            for token_id, token_count in s:
                sample_matrix[line, token_id] = token_count
            line+=1
        buf = io.BytesIO()
        smac.write_spmatrix_to_sparse_tensor(
            buf,
            sample_matrix,
            None)
        buf.seek(0)
        return buf
    

    请注意,我们在这里需要字典。这就是为什么相应的 SageMaker 处理任务保存了它的 pickle 版本,我们可以稍后将其反序列化并使用它。

  7. 然后,我们定义一个包含五个标题的 Python 数组,命名为samples。这些是真实的标题,我从 ABC 新闻网站上复制的,网址是www.abc.net.au/news/。代码示例如下:

    samples = [ "Major tariffs expected to end Australian barley trade to China", "Satellite imagery sparks more speculation on North Korean leader Kim Jong-un", "Fifty trains out of service as fault forces Adelaide passengers to 'pack like sardines", "Germany's Bundesliga plans its return from lockdown as football world watches", "All AFL players to face COVID-19 testing before training resumes" ]
    
  8. 让我们处理并预测它们,如下所示:

    lda_predictor.serializer =  
        sagemaker.serializers.CSVSerializer()
    response = lda_predictor.predict(
               process_samples(samples, dictionary))
    print(response)
    
  9. 响应中包含每个评论的得分向量(为了简洁起见,已删除额外的小数)。每个向量反映了各个主题的混合,并为每个主题分配得分。所有得分总和为 1。代码示例如下:

    {'predictions': [
    {'topic_mixture': [0,0.22,0.54,0.23,0,0,0,0,0,0]}, 
    {'topic_mixture': [0.51,0.49,0,0,0,0,0,0,0,0]}, {'topic_mixture': [0.38,0,0.22,0,0.40,0,0,0,0,0]}, {'topic_mixture': [0.38,0.62,0,0,0,0,0,0,0,0]}, {'topic_mixture': [0,0.75,0,0,0,0,0,0.25,0,0]}]}
    
  10. 这并不容易读取。让我们打印出主要主题及其得分,如下所示:

    import numpy as np
    vecs = [r['topic_mixture'] for r in response['predictions']]
    for v in vecs:
        top_topic = np.argmax(v)
        print("topic %s, %2.2f"%(top_topic,v[top_topic]))
    

    这将打印出以下结果:

    topic 2, 0.54
    topic 0, 0.51
    topic 4, 0.40
    topic 1, 0.62
    topic 1, 0.75 
    
  11. 和往常一样,训练完成后我们会删除终端节点,如下所示:

    lda_predictor.delete_endpoint()
    

解释 LDA 结果并不容易,所以我们在这里要小心。不要有过度的幻想!

  • 我们看到每个标题都有一个明确的主题,这是好消息。显然,LDA 能够识别出稳固的主题,也许得益于较低的alpha0值。

  • 与无关的标题相比,主要的主题是不同的,这很有前景。

  • 最后的两个标题都是关于体育的,它们的主要主题是相同的,这是另一个好兆头。

  • 所有五条评论在主题 5、6、8 和 9 上得分为零。这可能意味着已经构建了其他主题,我们需要运行更多示例来发现它们。

这是一个成功的模型吗?可能是。我们能确定主题 0 是关于世界事务的,主题 1 是关于体育的,主题 2 也是关于体育的吗?直到我们预测了几千个评论并检查相关标题是否被分配到同一主题,才能得出结论。

正如本章开头提到的,LDA 并不是一种分类算法。它有自己的“思想”,可能会构建出完全出乎意料的主题。也许它会根据情感或城市名称将标题进行分组。一切取决于这些词语在文档集合中的分布。

如果我们能够看到哪些词语在某个主题中“更重要”,那该有多好呢?这无疑会帮助我们更好地理解这些主题。于是,NTM 应运而生!

使用 NTM 建模主题

这个例子与前一个非常相似。我们将仅强调其中的不同之处,完整的示例可以在书籍的 GitHub 仓库中找到。让我们开始吧,如下所示:

  1. 我们将词汇文件上传到 S3,像这样:

    s3_auxiliary_path =    
        session.upload_data(path='vocab.txt',   
        key_prefix=prefix + '/input/auxiliary')
    
  2. 我们选择 NTM 算法,如下所示:

    from sagemaker.image_uris import retrieve
    region_name = session.boto_session.region_name
    container = retrieve('ntm', region)
    
  3. 一旦我们配置了Estimator函数,就可以设置超参数,如下所示:

    ntm.set_hyperparameters(num_topics=10, 
                            feature_dim=len(dictionary),
                            optimizer='adam',
                            mini_batch_size=256,
                            num_patience_epochs=10)
    
  4. 我们启动训练,将词汇文件传递到auxiliary通道,如下所示:

    ntm.fit(inputs={'train': s3_training_path, 
                    'auxiliary': s3_auxiliary_path})
    

当训练完成后,我们可以在训练日志中看到大量信息。首先,我们看到 10 个主题的平均 WETC 和 TU 分数,如下所示:

(num_topics:10) [wetc 0.42, tu 0.86]

这些结果不错。主题的独特性很高,主题词之间的语义距离适中。

对于每个话题,我们看到它的 WETC 和 TU 分数,以及它的关键词——即在与该话题相关的文档中最有可能出现的词汇。

让我们详细看看每个话题,尝试为它们命名。

第 0 个话题相当明显,我认为。几乎所有的词汇都与犯罪相关,所以我们称它为crime。你可以在这里查看该话题:

[0.51, 0.84] stabbing charged guilty pleads murder fatal man assault bail jailed alleged shooting arrested teen girl accused boy car found crash

以下的第 1 个话题有些模糊。怎么样,叫它legal?请在这里查看它:

[0.36, 0.85] seeker asylum climate live front hears change export carbon tax court wind challenge told accused rule legal face stand boat

第 2 个话题是关于事故和火灾的。我们称它为disaster。你可以在这里查看该话题:

[0.39, 0.78] seeker crew hour asylum cause damage truck country firefighter blaze crash warning ta plane near highway accident one fire fatal

第 3 个话题显而易见:sports。TU 分数最高,表明体育文章使用的是非常特定的词汇,其他地方找不到,正如我们在这里看到的那样:

[0.54, 0.93] cup world v league one match win title final star live victory england day nrl miss beat team afl player

第 4 个话题是天气信息和自然资源的奇怪混合。它的 WETC 分数和 TU 分数都是最低的。我们称它为unknown1。请在这里查看它:

[0.35, 0.77] coast korea gold north east central pleads west south guilty queensland found qld rain beach cyclone northern nuclear crop mine

第 5 个话题似乎是关于世界事务的。我们称它为international。你可以在这里查看该话题:

[0.38, 0.88] iraq troop bomb trade korea nuclear kill soldier iraqi blast pm president china pakistan howard visit pacific u abc anti

第 6 个话题感觉像是地方新闻,因为它包含了澳大利亚地区的缩写:qld是昆士兰,ta是塔斯马尼亚,nsw是新南威尔士州,等等。我们称它为local。该话题如下:

[0.25, 0.88] news hour country rural national abc ta sport vic abuse sa nsw weather nt club qld award business

第 7 个话题显而易见:finance。它具有最高的 WETC 分数,表明从语义角度来看,它的词汇密切相关。话题的独特性也非常高,我们可能在医学或工程等领域特定话题中看到相同的情况。请查看该话题:

[0.62, 0.90] share dollar rise rate market fall profit price interest toll record export bank despite drop loss post high strong trade

第 8 个话题是关于政治的,带有一些犯罪元素。有些人会说,这实际上是同一回事。由于我们已经有了一个crime话题,我们将这个命名为politics。请查看该话题:

[0.41, 0.90] issue election vote league hunt interest poll parliament gun investigate opposition raid arrest police candidate victoria house northern crime rate

第 9 个话题是一个混合的例子。很难说它是关于农业还是失踪人员!我们称之为unknown2。你可以在这里看到该话题:

[0.37, 0.84] missing search crop body found wind rain continues speaks john drought farm farmer smith pacific crew river find mark tourist

综合来看,这是一个相当不错的模型:10 个话题中有 8 个非常清晰。

让我们定义我们的话题列表,并在部署模型后运行示例头条新闻,如下所示:

topics = ['crime','legal','disaster','sports','unknown1',
          'international','local','finance','politics', 
          'unknown2']
samples = [ "Major tariffs expected to end Australian barley trade to China", "US woman wanted over fatal crash asks for release after coronavirus halts extradition", "Fifty trains out of service as fault forces Adelaide passengers to 'pack like sardines", "Germany's Bundesliga plans its return from lockdown as football world watches", "All AFL players to face COVID-19 testing before training resumes" ]

我们使用以下函数来打印前 3 个话题及其分数:

import numpy as np
for r in response['predictions']:
    sorted_indexes = np.argsort(r['topic_weights']).tolist()
    sorted_indexes.reverse()
    top_topics = [topics[i] for i in sorted_indexes]
    top_weights = [r['topic_weights'][i] 
                   for i in sorted_indexes]
    pairs = list(zip(top_topics, top_weights))
    print(pairs[:3])

这是输出结果:

[('finance', 0.30),('international', 0.22),('sports', 0.09)]
[('unknown1', 0.19),('legal', 0.15),('politics', 0.14)]
[('crime', 0.32), ('legal', 0.18), ('international', 0.09)]
[('sports', 0.28),('unknown1', 0.09),('unknown2', 0.08)]
[('sports', 0.27),('disaster', 0.12),('crime', 0.11)]

头条 0、2、3 和 4 完全符合预期。这并不令人惊讶,因为这些话题的强度非常高。

头条 1 在我们称为legal的话题中得分很高。也许阿德莱德的乘客应该起诉火车公司?说真的,我们需要找到其他匹配的头条新闻,以便更好地了解该话题的真实含义。

如你所见,NTM 使得理解话题变得更加容易。我们可以通过处理词汇文件来改进模型,添加或移除特定词汇来影响话题,增加话题数目,调整alpha0等。我直觉上认为,我们应该在其中看到一个“天气”话题。请尝试一下,看看是否能让它出现。

如果你想运行另一个示例,你可以在这个笔记本中找到有趣的技巧:

github.com/awslabs/amazon-sagemaker-examples/blob/master/introduction_to_applying_machine_learning/ntm_20newsgroups_topic_modeling/ntm_20newsgroups_topic_model.ipynb

总结

自然语言处理(NLP)是一个非常激动人心的主题。它也是一个困难的主题,因为语言本身的复杂性,以及构建数据集所需的处理工作量。尽管如此,SageMaker 中的内置算法将帮助你轻松获得良好的结果。训练和部署模型是直接的过程,这为你提供了更多时间来探索、理解和准备数据。

在本章中,你了解了 BlazingText、LDA 和 NTM 算法。你还学习了如何使用流行的开源工具,如nltkspaCygensim,来处理数据集,并将其保存为适当的格式。最后,你学习了如何使用 SageMaker SDK 训练和部署这三种算法的模型,以及如何解读结果。这标志着我们对内置算法的探索的结束。

在下一章中,你将学习如何使用内置的机器学习框架,如scikit-learnTensorFlowPyTorchApache MXNet

第七章:使用内置框架扩展机器学习服务

在过去的三章中,你学习了如何使用内置算法来训练和部署模型,而无需编写一行机器学习代码。然而,这些算法并没有涵盖所有的机器学习问题。在许多情况下,你需要编写自己的代码。幸运的是,几个开源框架让这个过程相对容易。

在本章中,你将学习如何使用最流行的开源机器学习和深度学习框架来训练和部署模型。我们将涵盖以下主题:

  • 发现 Amazon SageMaker 中的内置框架

  • 在 Amazon SageMaker 上运行你的框架代码

  • 使用内置框架

让我们开始吧!

技术要求

你需要一个 AWS 账户来运行本章中包含的示例。如果你还没有账户,请访问 aws.amazon.com/getting-started/ 创建一个。你还应该了解 AWS 免费套餐(aws.amazon.com/free/),它允许你在一定的使用限制内免费使用许多 AWS 服务。

你需要为你的账户安装并配置 AWS 命令行界面(aws.amazon.com/cli/)。

你需要一个可用的 Python 3.x 环境。安装 Anaconda 发行版(www.anaconda.com/)不是必须的,但强烈建议安装,因为它包含了我们需要的许多项目(Jupyter、pandasnumpy 等)。

你需要一个可用的 Docker 安装。你可以在 docs.docker.com 找到安装说明和相关文档。

本书中包含的代码示例可以在 GitHub 上找到,网址是 github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装一个 Git 客户端才能访问它们(git-scm.com/)。

发现 Amazon SageMaker 中的内置框架

SageMaker 让你使用以下机器学习和深度学习框架来训练和部署模型:

  • Scikit-learn,无疑是最广泛使用的开源机器学习库。如果你是这个领域的新手,可以从这里开始: scikit-learn.org

  • XGBoost,一种非常流行且多功能的开源算法,适用于回归、分类和排序问题(xgboost.ai)。它也作为内置算法提供,如在第四章《训练机器学习模型》中所展示的那样。以框架模式使用它将为我们提供更多的灵活性。

  • TensorFlow,一个极受欢迎的深度学习开源库(www.tensorflow.org)。SageMaker 还支持受人喜爱的 Keras API(keras.io)。

  • PyTorch,另一个备受欢迎的深度学习开源库(pytorch.org)。特别是研究人员喜欢它的灵活性。

  • Apache MXNet,一个有趣的深度学习挑战者。它是用 C++ 原生实现的,通常比其竞争对手更快且更具可扩展性。其 Gluon API 提供了丰富的计算机视觉工具包(gluon-cv.mxnet.io)、自然语言处理NLP)(gluon-nlp.mxnet.io)和时间序列数据(gluon-ts.mxnet.io)。

  • Chainer,另一个值得关注的深度学习挑战者(chainer.org)。

  • Hugging Face,最流行的、用于自然语言处理的最前沿工具和模型集合(huggingface.co)。

  • 强化学习框架,如 Intel CoachRay RLlibVowpal Wabbit。由于这可能需要一本书的篇幅,我在这里不会讨论这个话题!

  • Spark,借助一个专用的 SDK,允许你直接从 Spark 应用程序中使用 PySparkScala 训练和部署模型(github.com/aws/sagemaker-spark)。

你可以在 github.com/awslabs/amazon-sagemaker-examples/tree/master/sagemaker-python-sdk 找到这些的许多示例。

在本章中,我们将重点关注最流行的框架:XGBoost、scikit-learn、TensorFlow、PyTorch 和 Spark。

最好的入门方式是先运行一个简单的示例。如你所见,工作流与内置算法是相同的。我们将在过程中强调一些差异,稍后将在本章深入讨论。

使用 XGBoost 运行第一个示例

在这个例子中,我们将使用 XGBoost 内置框架构建一个二分类模型。写这篇文章时,SageMaker 支持的最新版本是 1.3-1。

我们将使用基于 xgboost.XGBClassifier 对象和直接营销数据集的自定义训练脚本,这个数据集我们在 第三章使用 Amazon SageMaker Autopilot 进行自动化机器学习 中用过:

  1. 首先,我们下载并解压数据集:

    %%sh
    wget -N https://sagemaker-sample-data-us-west-2.s3-us-west-2.amazonaws.com/autopilot/direct_marketing/bank-additional.zip
    unzip -o bank-additional.zip
    
  2. 我们导入 SageMaker SDK,并为任务定义一个 S3 前缀:

    import sagemaker
    sess   = sagemaker.Session()
    bucket = sess.default_bucket()                     
    prefix = 'xgboost-direct-marketing'
    
  3. 我们加载数据集并进行非常基础的处理(因为这不是我们的重点)。简单地对分类特征进行独热编码,将标签移动到第一列(这是 XGBoost 的要求),打乱数据集,分割为训练集和验证集,并将结果保存在两个单独的 CSV 文件中:

    import pandas as pd
    import numpy as np
    from sklearn.model_selection import train_test_split
    data = pd.read_csv('./bank-additional/bank-additional-full.csv')
    data = pd.get_dummies(data)
    data = data.drop(['y_no'], axis=1)
    data = pd.concat([data['y_yes'], 
                     data.drop(['y_yes'], axis=1)], 
                     axis=1)
    data = data.sample(frac=1, random_state=123)
    train_data, val_data = train_test_split(
        data, test_size=0.05)
    train_data.to_csv(
        'training.csv', index=False, header=False)
    val_data.to_csv(
        'validation.csv', index=False, header=False)
    
  4. 我们将这两个文件上传到 S3:

    training = sess.upload_data(path='training.csv', 
               key_prefix=prefix + '/training')
    validation = sess.upload_data(path='validation.csv', 
                 key_prefix=prefix + "/validation")
    output   = 's3://{}/{}/output/'.format(bucket,prefix)
    
  5. 我们定义两个输入,数据格式为 CSV:

    from sagemaker import TrainingInput
    train_input = TrainingInput(
        training_path, content_type='text/csv')
    val_input = TrainingInput(
        validation_path, content_type='text/csv')
    
  6. 为训练任务定义一个估算器。当然,我们也可以使用通用的 Estimator 对象,并传递 XGBoost 容器在 XGBoost 估算器中的名称,这样就会自动选择正确的容器:

    from sagemaker.xgboost import XGBoost
    xgb_estimator = XGBoost(
        role= sagemaker.get_execution_role(),
        entry_point='xgb-dm.py',
        instance_count=1, 
        instance_type='ml.m5.large',
        framework_version='1.2-2',
        output_path=output,
        hyperparameters={
            'num_round': 100,
            'early_stopping_rounds': 10,
            'max-depth': 5,
            'eval-metric': 'auc'}
    )
    

    这里有几个熟悉的参数:角色、基础设施要求和输出路径。其他参数呢?entry_point 是我们训练脚本的路径(可以在本书的 GitHub 仓库中找到)。hyperparameters 会传递给训练脚本。我们还需要选择一个 framework_version 值;这是我们想要使用的 XGBoost 版本。

  7. 我们照常进行训练:

    xgb_estimator.fit({'train':training, 
                       'validation':validation})
    
  8. 我们也照常进行部署,创建一个唯一的端点名称:

    from time import strftime,gmtime
    xgb_endpoint_name = 
        prefix+strftime("%Y-%m-%d-%H-%M-%S", gmtime())
    xgb_predictor = xgb_estimator.deploy(
        endpoint_name=xgb_endpoint_name,
        initial_instance_count=1,
        instance_type='ml.t2.medium')
    

    然后,我们从验证集加载一些样本,并将它们以 CSV 格式发送进行预测。响应包含每个样本的 0 到 1 之间的分数:

    payload = val_data[:10].drop(['y_yes'], axis=1) 
    payload = payload.to_csv(header=False, 
              index=False).rstrip('\n')
    xgb_predictor.serializer =
        sagemaker.serializers.CSVSerializer()
    xgb_predictor.deserializer = 
        sagemaker.deserializers.CSVDeserializer()
    response = xgb_predictor.predict(payload)
    print(response)
    

    这将输出以下概率:

    [['0.07206538'], ['0.02661967'], ['0.16043524'], ['4.026455e-05'], ['0.0002120432'], ['0.52123886'], ['0.50755614'], ['0.00015006188'], ['3.1439096e-05'], ['9.7614546e-05']]
    
  9. 完成后,我们删除端点:

    xgb_predictor.delete_endpoint()
    

我们在这里使用了 XGBoost,但这个工作流程对于其他框架也是完全相同的。这种标准的训练和部署方式使得从内置算法切换到框架,或从一个框架切换到下一个框架变得非常简单。

我们需要重点关注的要点如下:

  • 框架容器:它们是什么?我们能看到它们是如何构建的吗?我们可以自定义它们吗?我们能用它们在本地机器上进行训练吗?

  • 训练:SageMaker 训练脚本与普通框架代码有何不同?它如何接收超参数?它应该如何读取输入数据?模型应该保存在哪里?

  • 部署:模型是如何部署的?脚本需要提供一些相关代码吗?预测的输入格式是什么?

  • entry_point 脚本?我们可以为训练和部署添加库吗?

所有这些问题现在都将得到解答!

使用框架容器

SageMaker 为每个内置框架提供训练和推理容器,并定期更新到最新版本。不同的容器也可供 CPU 和 GPU 实例使用。所有这些容器统称为深度学习容器 (aws.amazon.com/machine-learning/containers)。

正如我们在前面的例子中看到的,它们允许你使用自己的代码,而无需维护定制的容器。在大多数情况下,你不需要再进一步了解容器的细节,你可以高兴地忘记它们的存在。如果这个话题目前对你来说太高级,可以暂时跳过,继续阅读本地训练与部署部分。

如果你感到好奇或有定制需求,你会很高兴得知这些容器的代码是开源的:

首先,这可以帮助你理解这些容器是如何构建的,以及 SageMaker 是如何使用它们进行训练和预测的。你还可以执行以下操作:

  • 在本地机器上构建并运行它们进行本地实验。

  • 在你最喜欢的托管 Docker 服务上构建并运行它们,例如Amazon ECSAmazon EKSAmazon Fargateaws.amazon.com/containers)。

  • 自定义它们,推送到 Amazon ECR,并使用 SageMaker SDK 中的估算器。我们将在第八章中演示这一点,使用你的算法和代码

这些容器有一个很好的特性。你可以与 SageMaker SDK 一起使用它们,在本地机器上训练和部署模型。让我们看看这个是如何工作的。

本地训练和部署

本地模式是通过 SageMaker SDK 训练和部署模型,而无需启动 AWS 中的按需托管基础设施。你将使用本地机器代替。在此上下文中,“本地”指的是运行笔记本的机器:它可以是你的笔记本电脑、本地服务器,或者一个小型笔记本实例

注意

在写本文时,本地模式在 SageMaker Studio 中不可用。

这是一个快速实验和迭代小型数据集的极好方式。你无需等待实例启动,也无需为此支付费用!

让我们重新审视之前的 XGBoost 示例,重点讲解使用本地模式时所需的更改:

  1. 显式设置 IAM 角色的名称。get_execution_role()在本地机器上不起作用(在笔记本实例上有效):

    #role = sagemaker.get_execution_role()
    role = 'arn:aws:iam::0123456789012:role/Sagemaker-fullaccess'
    
  2. 从本地文件加载训练和验证数据集。将模型存储在本地/tmp目录中:

    training = 'file://training.csv'
    validation = 'file://validation.csv'
    output = 'file:///tmp'
    
  3. XGBoost估算器中,将instance_type设置为local。对于本地 GPU 训练,我们将使用local_gpu

  4. xgb_estimator.deploy()中,将instance_type设置为local

这就是使用与 AWS 大规模环境中相同的容器在本地机器上进行训练所需的一切。此容器会被拉取到本地机器,之后你将一直使用它。当你准备好大规模训练时,只需将locallocal_gpu实例类型替换为适当的 AWS 实例类型,就可以开始训练了。

故障排除

如果遇到奇怪的部署错误,可以尝试重启 Docker(sudo service docker restart)。我发现它在部署过程中经常被中断,尤其是在 Jupyter Notebooks 中工作时!

现在,让我们看看在这些容器中运行自己代码所需的条件。这个功能叫做脚本模式

使用脚本模式进行训练

由于您的训练代码运行在 SageMaker 容器内,它需要能够执行以下操作:

  • 接收传递给估算器的超参数。

  • 读取输入通道中可用的数据(训练数据、验证数据等)。

  • 将训练好的模型保存到正确的位置。

脚本模式是 SageMaker 使这一切成为可能的方式。该名称来自于您的代码在容器中被调用的方式。查看我们 XGBoost 作业的训练日志,我们可以看到:

Invoking script with the following command:
/miniconda3/bin/python3 -m xgb-dm --early-stopping-rounds 10 
--eval-metric auc --max-depth 5 

我们的代码像普通的 Python 脚本一样被调用(因此称为脚本模式)。我们可以看到,超参数作为命令行参数传递,这也回答了我们应该在脚本中使用什么来读取它们:argparse

这是我们脚本中相应的代码片段:

parser = argparse.ArgumentParser()
parser.add_argument('--max-depth', type=int, default=4)
parser.add_argument('--early-stopping-rounds', type=int, 
                    default=10)
parser.add_argument('--eval-metric', type=str, 
                    default='error')

那么输入数据和保存模型的位置呢?如果我们稍微仔细查看日志,就会看到:

SM_CHANNEL_TRAIN=/opt/ml/input/data/train
SM_CHANNEL_VALIDATION=/opt/ml/input/data/validation
SM_MODEL_DIR=/opt/ml/model

这三个环境变量定义了容器内的本地路径,指向训练数据、验证数据和保存模型的相应位置。这是否意味着我们必须手动将数据集和模型从 S3 复制到容器中并返回?不!SageMaker 会为我们自动处理这一切。这是容器中支持代码的一部分。

我们的脚本只需要读取这些变量。我建议再次使用 argparse,这样我们可以在 SageMaker 之外训练时将路径传递给脚本(稍后会详细介绍)。

这是我们脚本中相应的代码片段:

parser.add_argument('--model-dir', type=str, 
    default=os.environ['SM_MODEL_DIR'])
parser.add_argument('--training-dir', type=str, 
    default=os.environ['SM_CHANNEL_TRAIN'])
parser.add_argument('--validation', type=str, 
    default=os.environ['SM_CHANNEL_VALIDATION'])

通道名称

SM_CHANNEL_xxx 变量是根据传递给 fit() 的通道命名的。例如,如果您的算法需要一个名为 foobar 的通道,您需要在 fit() 中将其命名为 foobar,并在脚本中使用 SM_CHANNEL_FOOBAR。在您的容器中,该通道的数据会自动保存在 /opt/ml/input/data/foobar 目录下。

总结一下,为了在 SageMaker 上训练框架代码,我们只需要做以下几件事:

  1. 使用 argparse 读取作为命令行参数传递的超参数。您可能已经在代码中这样做了!

  2. 读取 SM_CHANNEL_xxx 环境变量并从中加载数据。

  3. 读取 SM_MODEL_DIR 环境变量并将训练好的模型保存到该位置。

现在,让我们讨论在脚本模式下部署训练好的模型。

理解模型部署

通常,您的脚本需要包括以下内容:

  • 一个加载模型的函数。

  • 一个在将输入数据传递给模型之前处理数据的函数。

  • 一个在返回预测结果给调用方之前处理预测结果的函数。

所需的实际工作量取决于您使用的框架和输入格式。让我们看看这对 TensorFlow、PyTorch、MXNet、XGBoost 和 scikit-learn 意味着什么。

使用 TensorFlow 部署

TensorFlow 推理容器依赖于TensorFlow Serving模型服务器进行模型部署(www.tensorflow.org/tfx/guide/serving)。因此,你的训练代码必须以这种格式保存模型。模型加载和预测功能会自动提供。

JSON 是预测的默认输入格式,并且由于自动序列化,它也适用于numpy数组。JSON Lines 和 CSV 也被支持。对于其他格式,你可以实现自己的预处理和后处理函数,input_handler()output_handler()。你可以在sagemaker.readthedocs.io/en/stable/using_tf.html#deploying-from-an-estimator找到更多信息。

你也可以深入了解 TensorFlow 推理容器,访问github.com/aws/deep-learning-containers/tree/master/tensorflow/inference

使用 PyTorch 进行部署

PyTorch 推理容器依赖于__call__()方法。如果没有,你应该在推理脚本中提供predict_fn()函数。

对于预测,numpy是默认的输入格式。JSON Lines 和 CSV 也被支持。对于其他格式,你可以实现自己的预处理和后处理函数。你可以在sagemaker.readthedocs.io/en/stable/frameworks/pytorch/using_pytorch.html#serve-a-pytorch-model找到更多信息。

你可以深入了解 PyTorch 推理容器,访问github.com/aws/deep-learning-containers/tree/master/pytorch/inference

使用 Apache MXNet 进行部署

Apache MXNet 推理容器依赖于多模型服务器MMS)进行模型部署(github.com/awslabs/multi-model-server)。它使用默认的 MXNet 模型格式。

基于Module API 的模型不需要模型加载函数。对于预测,它们支持 JSON、CSV 或numpy格式的数据。

Gluon 模型确实需要一个模型加载函数,因为参数需要显式初始化。数据可以通过 JSON 或numpy格式发送。

对于其他数据格式,你可以实现自己的预处理、预测和后处理函数。你可以在sagemaker.readthedocs.io/en/stable/using_mxnet.html找到更多信息。

你可以深入了解 MXNet 推理容器,访问github.com/aws/deep-learning-containers/tree/master/mxnet/inference/docker

使用 XGBoost 和 scikit-learn 进行部署

同样,XGBoost 和 scikit-learn 分别依赖于github.com/aws/sagemaker-xgboost-containergithub.com/aws/sagemaker-scikit-learn-container

你的脚本需要提供以下内容:

  • 一个model_fn()函数用于加载模型。与训练类似,加载模型的位置通过SM_MODEL_DIR环境变量传递。

  • 两个可选函数,用于反序列化和序列化预测数据,分别命名为input_fn()output_fn()。只有当你需要其他格式的输入数据(例如非 JSON、CSV 或numpy)时,才需要这些函数。

  • 一个可选的predict_fn()函数将反序列化的数据传递给模型并返回预测结果。仅当你需要在预测之前对数据进行预处理,或对预测结果进行后处理时才需要这个函数。

对于 XGBoost 和 scikit-learn,model_fn()函数非常简单且通用。以下是一些大多数情况下都能正常工作的示例:

# Scikit-learn
def model_fn(model_dir):
    clf = joblib.load(os.path.join(model_dir, 
                                   'model.joblib'))
    return clf
# XGBoost
def model_fn(model_dir):
    model = xgb.Booster()
    model.load_model(os.path.join(model_dir, 'xgb.model'))
    return model

SageMaker 还允许你导入和导出模型。你可以将现有模型上传到 S3 并直接在 SageMaker 上部署。同样,你也可以将训练好的模型从 S3 复制到其他地方进行部署。我们将在第十一章,《部署机器学习模型》中详细介绍这一点。

现在,让我们来讨论训练和部署所需的依赖关系。

管理依赖关系

在许多情况下,你需要向框架的容器中添加额外的源文件和库。让我们看看如何轻松做到这一点。

添加训练所需的源文件

默认情况下,所有估算器都会从当前目录加载入口脚本。如果你需要额外的源文件来进行训练,估算器允许你传递一个source_dir参数,指向存储额外文件的目录。请注意,入口脚本必须位于同一目录中。

在以下示例中,myscript.py和所有额外的源文件必须放在src目录中。SageMaker 将自动打包该目录并将其复制到训练容器中:

sk = SKLearn(entry_point='myscript.py',
             source_dir='src',
             . . .

添加训练所需的库

你可以使用不同的技术来添加训练所需的库。

对于可以通过pip安装的库,最简单的方法是将requirements.txt文件与入口脚本放在同一文件夹中。SageMaker 会自动在容器内安装这些库。

另外,你可以通过在训练脚本中执行pip install命令,使用pip直接安装库。我们在第六章,《训练自然语言处理模型》中使用了这个方法,处理了 LDA 和 NTM。这个方法在你不想或者不能修改启动训练作业的 SageMaker 代码时非常有用:

import subprocess, sys
def install(package):
    subprocess.call([sys.executable, "-m", 
                    "pip", "install", package])
if __name__=='__main__':
    install('gensim')
    import gensim
    . . . 

对于不能通过 pip 安装的库,你应该使用 dependencies 参数。这个参数在所有估算器中都可用,它允许你列出要添加到训练作业中的库。这些库需要在本地、虚拟环境或特定目录中存在。SageMaker 会将它们打包并复制到训练容器中。

在以下示例中,myscript.py 需要 mylib 库。我们将在 lib 本地目录中安装它:

$ mkdir lib
$ pip install mylib -t lib

然后,我们将其位置传递给估算器:

sk = SKLearn(entry_point='myscript.py',
             dependencies=['lib/mylib'],
             . . .

最后的技术是将库安装到 Dockerfile 中的容器里,重建镜像并将其推送到 Amazon ECR。如果在预测时也需要这些库(例如,用于预处理),这是最好的选择。

为部署添加库

如果你需要在预测时提供特定的库,可以使用一个 requirements.txt 文件,列出那些可以通过 pip 安装的库。

对于其他库,唯一的选择是自定义框架容器。你可以通过image_uri参数将其名称传递给估算器:

sk = SKLearn(entry_point='myscript.py', image_uri= '123456789012.dkr.ecr.eu-west-1.amazonaws.com/my-sklearn' 
. . .

我们在本节中涵盖了许多技术主题。现在,让我们来看一下大局。

将所有内容整合在一起

使用框架时的典型工作流程如下所示:

  1. 在你的代码中实现脚本模式;也就是说,读取必要的超参数、输入数据和输出位置。

  2. 如有需要,添加一个 model_fn() 函数来加载模型。

  3. 在本地测试你的训练代码,避免使用任何 SageMaker 容器。

  4. 配置适当的估算器(如XGBoostTensorFlow等)。

  5. 使用估算器在本地模式下训练,使用内置容器或你自定义的容器。

  6. 在本地模式下部署并测试你的模型。

  7. 切换到托管实例类型(例如,ml.m5.large)进行训练和部署。

这个逻辑进展每一步所需的工作很少。它最大程度地减少了摩擦、错误的风险和挫败感。它还优化了实例时间和成本——如果你的代码因为一个小错误立即崩溃,就不必等待并支付托管实例的费用。

现在,让我们开始运用这些知识。在接下来的部分中,我们将运行一个简单的 scikit-learn 示例。目的是确保我们理解刚刚讨论的工作流程。

在 Amazon SageMaker 上运行你的框架代码

我们将从一个简单的 scikit-learn 程序开始,该程序在波士顿住房数据集上训练并保存一个线性回归模型,数据集在第四章中使用过,训练机器学习模型

import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import joblib
data = pd.read_csv('housing.csv')
labels = data[['medv']]
samples = data.drop(['medv'], axis=1)
X_train, X_test, y_train, y_test = train_test_split(
samples, labels, test_size=0.1, random_state=123)
regr = LinearRegression(normalize=True)
regr.fit(X_train, y_train)
y_pred = regr.predict(X_test)
print('Mean squared error: %.2f' 
       % mean_squared_error(y_test, y_pred))
print('Coefficient of determination: %.2f' 
       % r2_score(y_test, y_pred))
joblib.dump(regr, 'model.joblib')

让我们更新它,使其可以在 SageMaker 上运行。

实现脚本模式

现在,我们将使用框架实现脚本模式,如下所示:

  1. 首先,读取命令行参数中的超参数:

    import argparse
    if __name__ == '__main__':
      parser = argparse.ArgumentParser()
      parser.add_argument('--normalize', type=bool, 
                          default=False)
      parser.add_argument('--test-size', type=float, 
                          default=0.1)
      parser.add_argument('--random-state', type=int, 
                          default=123)
      args, _ = parser.parse_known_args()
      normalize = args.normalize
      test_size = args.test_size
      random_state = args.random_state
      data = pd.read_csv('housing.csv')
      labels = data[['medv']]
      samples = data.drop(['medv'], axis=1)
      X_train, X_test, y_train, y_test = train_test_split(
        samples, labels,test_size=test_size, 
        random_state=random_state)
      . . . 
    
  2. 将输入和输出路径作为命令行参数读取。我们可以决定去除拆分代码,改为传递两个输入通道。我们还是坚持使用一个通道,也就是training

    import os
    if __name__ == '__main__':
      . . .    
      parser.add_argument('--model-dir', type=str, 
        default=os.environ['SM_MODEL_DIR'])
      parser.add_argument('--training', type=str, 
        default=os.environ['SM_CHANNEL_TRAINING'])
      . . .
      model_dir = args.model_dir
      training_dir = args.training
      . . . 
      filename = os.path.join(training_dir, 'housing.csv')
      data = pd.read_csv(filename)
      . . .
      model = os.path.join(model_dir, 'model.joblib')
      dump(regr, model)
    
  3. 由于我们使用的是 scikit-learn,我们需要添加model_fn()以便在部署时加载模型:

    def model_fn(model_dir):
      model = joblib.load(os.path.join(model_dir, 
                                       'model.joblib'))
      return model
    

到此为止,我们完成了。是时候测试了!

本地测试

首先,我们在本地机器上的 Python 3 环境中测试我们的脚本,不依赖任何 SageMaker 容器。我们只需要确保安装了pandas和 scikit-learn。

我们将环境变量设置为空值,因为我们将在命令行上传递路径:

$ source activate python3
$ export SM_CHANNEL_TRAINING=
$ export SM_MODEL_DIR=
$ python sklearn-boston-housing.py --normalize True –test-ration 0.1 --training . --model-dir .
Mean squared error: 41.82
Coefficient of determination: 0.63

很好。我们的代码在命令行参数下运行得很顺利。我们可以使用它进行本地开发和调试,直到我们准备好将其迁移到 SageMaker 本地模式。

使用本地模式

我们将按照以下步骤开始:

  1. 仍然在我们的本地机器上,我们配置一个SKLearn估算器以本地模式运行,根据我们使用的设置来设定角色。只使用本地路径:

    role = 'arn:aws:iam::0123456789012:role/Sagemaker-fullaccess'
    sk = SKLearn(entry_point='sklearn-boston-housing.py',
      role=role,
      framework_version='0.23-1',
      instance_count=1,
      instance_type='local',
      output_path=output_path,
      hyperparameters={'normalize': True, 
                       'test-size': 0.1})
    sk.fit({'training':training_path})
    
  2. 如预期的那样,我们可以在训练日志中看到如何调用我们的代码。当然,我们得到的是相同的结果:

    /miniconda3/bin/python -m sklearn-boston-housing --normalize True --test-size 0.1
    . . . 
    Mean squared error: 41.82
    Coefficient of determination: 0.63
    
  3. 我们在本地部署并发送一些 CSV 样本进行预测:

    sk_predictor = sk.deploy(initial_instance_count=1, 
                             instance_type='local')
    data = pd.read_csv('housing.csv')
    payload = data[:10].drop(['medv'], axis=1) 
    payload = payload.to_csv(header=False, index=False)
    sk_predictor.serializer = 
        sagemaker.serializers.CSVSerializer()
    sk_predictor.deserializer =
        sagemaker.deserializers.CSVDeserializer()
    response = sk_predictor.predict(payload)
    print(response)
    

    通过打印响应,我们将看到预测值:

    [['29.801388899699845'], ['24.990809475886074'], ['30.7379654455552'], ['28.786967125316544'], ['28.1421501991961'], ['25.301714533101716'], ['22.717977231840184'], ['19.302415613883348'], ['11.369520911229536'], ['18.785593532977657']]
    

    使用本地模式,我们可以快速迭代模型。我们仅受限于本地机器的计算和存储能力。当达到限制时,我们可以轻松迁移到托管基础设施。

使用托管基础设施

当需要进行大规模训练并在生产环境中部署时,我们只需确保输入数据存储在 S3 中,并将“本地”实例类型替换为实际的实例类型:

sess = sagemaker.Session()
bucket = sess.default_bucket()                     
prefix = 'sklearn-boston-housing'
training_path = sess.upload_data(path='housing.csv', 
           key_prefix=prefix + "/training")
output_path = 's3://{}/{}/output/'.format(bucket,prefix)
sk = SKLearn(. . ., instance_type='ml.m5.large')
sk.fit({'training':training_path})
. . .
sk_predictor = sk.deploy(initial_instance_count=1, 
                         instance_type='ml.t2.medium')

由于我们使用的是相同的容器,我们可以放心训练和部署会按预期工作。再次强调,我强烈建议您遵循以下逻辑流程:首先进行本地工作,然后是 SageMaker 本地模式,最后是 SageMaker 托管基础设施。这将帮助你集中精力处理需要做的事以及何时做。

在本章的其余部分,我们将运行更多示例。

使用内建框架

我们已经覆盖了 XGBoost 和 scikit-learn。现在,是时候看看如何使用深度学习框架了。让我们从 TensorFlow 和 Keras 开始。

使用 TensorFlow 和 Keras

在这个示例中,我们将使用 TensorFlow 2.4.1 来训练一个简单的卷积神经网络,数据集使用 Fashion-MNIST(github.com/zalandoresearch/fashion-mnist)。

我们的代码分成了两个源文件:一个是入口点脚本(fmnist.py),另一个是模型(model.py,基于 Keras 层)。为了简洁起见,我只讨论 SageMaker 的步骤。你可以在本书的 GitHub 仓库中找到完整代码:

  1. fmnist.py首先从命令行读取超参数:

    import tensorflow as tf
    import numpy as np
    import argparse, os
    from model import FMNISTModel
    parser = argparse.ArgumentParser()
    parser.add_argument('--epochs', type=int, default=10)
    parser.add_argument('--learning-rate', type=float,  
                        default=0.01)
    parser.add_argument('--batch-size', type=int, 
                        default=128)
    
  2. 接下来,我们读取环境变量,即训练集和验证集的输入路径、模型的输出路径以及实例上可用的 GPU 数量。这是我们第一次使用后者。它对于调整多 GPU 训练的批量大小非常有用,因为通常做法是将初始批量大小乘以 GPU 数量:

    parser.add_argument('--training', type=str, 
        default=os.environ['SM_CHANNEL_TRAINING'])
    parser.add_argument('--validation', type=str,
        default=os.environ['SM_CHANNEL_VALIDATION'])
    parser.add_argument('--model-dir', type=str, 
        default=os.environ['SM_MODEL_DIR'])
    parser.add_argument('--gpu-count', type=int, 
        default=os.environ['SM_NUM_GPUS'])
    
  3. 将参数存储在本地变量中。然后,加载数据集。每个通道为我们提供一个压缩的numpy数组,用于存储图像和标签:

    x_train = np.load(os.path.join(training_dir, 
              'training.npz'))['image']
    y_train = np.load(os.path.join(training_dir, 
              'training.npz'))['label']
    x_val = np.load(os.path.join(validation_dir, 
            'validation.npz'))['image']
    y_val = np.load(os.path.join(validation_dir, 
            'validation.npz'))['label']
    
  4. 然后,通过重新调整图像张量的形状、标准化像素值、进行独热编码图像标签,并创建将数据传输给模型的tf.data.Dataset对象,为训练准备数据。

  5. 创建模型、编译并拟合它。

  6. 训练完成后,将模型以 TensorFlow Serving 格式保存到适当的输出位置。此步骤非常重要,因为这是 SageMaker 用于 TensorFlow 模型的模型服务器:

    model.save(os.path.join(model_dir, '1'))
    

我们使用常规工作流程训练和部署模型:

  1. 在一个由 TensorFlow 2 内核支持的笔记本中,我们下载数据集并将其上传到 S3:

    import os
    import numpy as np
    import keras
    from keras.datasets import fashion_mnist
    (x_train, y_train), (x_val, y_val) =  
        fashion_mnist.load_data()
    os.makedirs("./data", exist_ok = True)
    np.savez('./data/training', image=x_train,       
             label=y_train)
    np.savez('./data/validation', image=x_val, 
             label=y_val)
    prefix = 'tf2-fashion-mnist'
    training_input_path = sess.upload_data(
        'data/training.npz', 
        key_prefix=prefix+'/training')
    validation_input_path = sess.upload_data(
        'data/validation.npz',   
        key_prefix=prefix+'/validation')
    
  2. 我们配置TensorFlow估算器。我们还设置source_dir参数,以便将模型文件也部署到容器中:

    from sagemaker.tensorflow import TensorFlow
    tf_estimator = TensorFlow(entry_point='fmnist.py',
        source_dir='.',
        role=sagemaker.get_execution_role(),
        instance_count=1,
        instance_type='ml.p3.2xlarge', 
        framework_version='2.4.1',
        py_version='py37',
        hyperparameters={'epochs': 10})
    
  3. 像往常一样训练和部署。我们将直接使用托管基础设施,但相同的代码也可以在本地模式下在你的本地机器上正常运行:

    from time import strftime,gmtime
    tf_estimator.fit(
        {'training': training_input_path, 
         'validation': validation_input_path})
    tf_endpoint_name = 'tf2-fmnist-'+strftime("%Y-%m-%d-%H-%M-%S", gmtime())
    tf_predictor = tf_estimator.deploy(
                   initial_instance_count=1,
                   instance_type='ml.m5.large',
                   endpoint_name=tf_endpoint_name)
    
  4. 验证准确率应为 91-92%。通过加载并显示一些验证数据集中的样本图像,我们可以预测它们的标签。numpy负载会自动序列化为 JSON,这是预测数据的默认格式:

    response = tf_predictor.predict(payload)
    prediction = np.array(reponse['predictions'])
    predicted_label = prediction.argmax(axis=1)
    print('Predicted labels are: 
        {}'.format(predicted_label))
    

    输出应如下所示:

    图 7.1 – 查看预测类别

    图 7.1 – 查看预测类别

  5. 完成后,我们删除端点:

    tf_predictor.delete_endpoint()
    

如你所见,脚本模式与内置容器的结合使得在 SageMaker 上运行 TensorFlow 变得非常简单。一旦进入常规流程,你会惊讶于将模型从笔记本电脑迁移到 AWS 的速度有多快。

现在,让我们来看一下 PyTorch。

使用 PyTorch

PyTorch 在计算机视觉、自然语言处理等领域非常流行。

在此示例中,我们将训练一个图神经网络GNN)。这种类型的网络在图结构数据上表现特别好,如社交网络、生命科学等。事实上,我们的 PyTorch 代码将使用深度图书馆DGL),这是一个开源库,可以更轻松地使用 TensorFlow、PyTorch 和 Apache MXNet 构建和训练 GNN(www.dgl.ai/)。DGL 已经安装在这些容器中,所以我们可以直接开始工作。

我们将使用 Zachary 空手道俱乐部数据集(konect.cc/networks/ucidata-zachary/)。以下是该图的内容:

图 7.2 – Zachary 空手道俱乐部数据集

图 7.2 – Zachary 空手道俱乐部数据集

节点 0 和 33 是教师,而其他节点是学生。边表示这些人之间的关系。故事是这样的,两位老师发生了争执,俱乐部需要被分成两部分。

训练任务的目的是找到“最佳”分割。这可以定义为一个半监督分类任务。第一位老师(节点 0)被分配为类别 0,而第二位老师(节点 33)被分配为类别 1。所有其他节点是未标记的,它们的类别将由图卷积网络计算。在最后一个周期结束时,我们将提取节点类别,并根据这些类别来分割俱乐部。

数据集被存储为一个包含边的 pickle 格式的 Python 列表。以下是前几条边:

[('0', '8'), ('1', '17'), ('24', '31'), . . .

SageMaker 的代码简洁明了。我们将数据集上传到 S3,创建一个PyTorch估算器,然后进行训练:

import sagemaker
from sagemaker.pytorch import PyTorch
sess = sagemaker.Session()
prefix = 'dgl-karate-club'
training_input_path = sess.upload_data('edge_list.pickle', 
key_prefix=prefix+'/training')
estimator = PyTorch(role=sagemaker.get_execution_role(),
    entry_point='karate_club_sagemaker.py',
    hyperparameters={'node_count': 34, 'epochs': 30},
    framework_version='1.5.0',
    py_version='py3',
    instance_count=1,
    instance_type='ml.m5.large')
estimator.fit({'training': training_input_path})

这一点几乎无需任何解释,对吧?

让我们来看一下简化的训练脚本,在这里我们再次使用了脚本模式。完整版本可在本书的 GitHub 仓库中找到:

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--epochs', type=int, default=30)
    parser.add_argument('--node_count', type=int)
    args, _    = parser.parse_known_args()
    epochs     = args.epochs
    node_count = args.node_count
    training_dir = os.environ['SM_CHANNEL_TRAINING']
    model_dir    = os.environ['SM_MODEL_DIR']
    with open(os.path.join(training_dir, 'edge_list.pickle'), 
    'rb') as f:
        edge_list = pickle.load(f)
    # Build the graph and the model
    . . .
    # Train the model
    . . .
    # Print predicted classes
    last_epoch = all_preds[epochs-1].detach().numpy()
    predicted_class = np.argmax(last_epoch, axis=-1)
    print(predicted_class)
    # Save the model
    torch.save(net.state_dict(), os.path.join(model_dir, 
    'karate_club.pt'))

预测了以下类别。节点 0 和 1 是类别 0,节点 2 是类别 1,依此类推:

[0 0 1 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1]

通过绘制它们,我们可以看到俱乐部已经被干净利落地分开了:

图 7.3 – 查看预测类别

图 7.3 – 查看预测类别

再次强调,SageMaker 的代码并不会妨碍你。工作流和 API 在各个框架之间保持一致,你可以专注于机器学习问题本身。现在,让我们做一个新的示例,使用 Hugging Face,同时还会看到如何通过内建的 PyTorch 容器部署一个 PyTorch 模型。

与 Hugging Face 合作

Hugging Face (huggingface.co) 已迅速成为最受欢迎的自然语言处理开源模型集合。在撰写本文时,他们托管了近 10,000 个最先进的模型(huggingface.co/models),并在超过 250 种语言的预训练数据集上进行了训练(huggingface.co/datasets)。

为了快速构建高质量的自然语言处理应用,Hugging Face 积极开发了三个开源库:

SageMaker 在 2021 年 3 月增加了对 Hugging Face 的支持,涵盖 TensorFlow 和 PyTorch。正如你所料,你可以使用HuggingFace估算器和内置容器。让我们运行一个示例,构建一个英文客户评论的情感分析模型。为此,我们将微调一个DistilBERT模型(arxiv.org/abs/1910.01108),该模型使用 PyTorch 实现,并在两个大型英语语料库(Wikipedia 和 BookCorpus 数据集)上进行了预训练。

准备数据集

在这个例子中,我们将使用一个名为generated_reviews_enth的 Hugging Face 数据集(huggingface.co/datasets/generated_reviews_enth)。它包含英文评论、其泰语翻译、一个标记指示翻译是否正确,以及星级评分:

{'correct': 0, 'review_star': 4, 'translation': {'en': "I had a hard time finding a case for my new LG Lucid 2 but finally found this one on amazon. The colors are really pretty and it works just as well as, if not better than the otterbox. Hopefully there will be more available by next Xmas season. Overall, very cute case. I love cheetah's. :)", 'th': 'ฉันมีปัญหาในการหาเคสสำหรับ LG Lucid 2 ใหม่ของฉันแต่ในที่สุดก็พบเคสนี้ใน Amazon สีสวยมากและใช้งานได้ดีเช่นเดียวกับถ้าไม่ดีกว่านากหวังว่าจะมีให้มากขึ้นในช่วงเทศกาลคริสต์มาสหน้าโดยรวมแล้วน่ารักมากๆฉันรักเสือชีตาห์ :)'}}

这是 DistilBERT 分词器所期望的格式:一个labels变量(0代表负面情绪,1代表正面情绪)和一个text变量,包含英文评论:

{'labels': 1,
 'text': "I had a hard time finding a case for my new LG Lucid 2 but finally found this one on amazon. The colors are really pretty and it works just as well as, if not better than the otterbox. Hopefully there will be more available by next Xmas season. Overall, very cute case. I love cheetah's. :)"}

让我们开始吧!我将向你展示每个步骤,你也可以在这本书的 GitHub 仓库中找到一个SageMaker 处理版本:

  1. 我们首先安装transformersdatasets库:

    !pip -q install "transformers>=4.4.2" "datasets[s3]==1.5.0" --upgrade
    
  2. 我们下载了数据集,该数据集已经分为训练集(141,369 个实例)和验证集(15,708 个实例)。所有数据均为 JSON 格式:

    from datasets import load_dataset
    train_dataset, valid_dataset = load_dataset('generated_reviews_enth', 
                 split=['train', 'validation'])
    
  3. 在每个评论中,我们创建一个名为labels的新变量。当review_star大于或等于 4 时,我们将其设置为1,否则设置为0

    def map_stars_to_sentiment(row):
        return {
            'labels': 1 if row['review_star'] >= 4 else 0
        }
    train_dataset = 
        train_dataset.map(map_stars_to_sentiment)
    valid_dataset = 
        valid_dataset.map(map_stars_to_sentiment)
    
  4. 这些评论是嵌套的 JSON 文档,这使得移除我们不需要的变量变得困难。让我们将两个数据集扁平化:

    train_dataset = train_dataset.flatten()
    valid_dataset = valid_dataset.flatten()
    
  5. 现在我们可以轻松丢弃不需要的变量。我们还将translation.en变量重命名为text

    train_dataset = train_dataset.remove_columns(
        ['correct', 'translation.th', 'review_star'])
    valid_dataset = valid_dataset.remove_columns(
        ['correct', 'translation.th', 'review_star'])
    train_dataset = train_dataset.rename_column(
        'translation.en', 'text')
    valid_dataset = valid_dataset.rename_column(
        'translation.en', 'text')
    

现在,训练和验证实例已经具有 DistilBERT 分词器所期望的格式。我们已经在第六章训练自然语言处理模型中讲解了分词。一个显著的区别是,我们使用的是一个在与模型相同的英语语料库上预训练的分词器:

  1. 我们下载我们预训练模型的分词器:

    from transformers import AutoTokenizer
    tokenizer = AutoTokenizer.from_pretrained(
        'distilbert-base-uncased')
    def tokenize(batch):
        return tokenizer(batch['text'], 
        padding='max_length', truncation=True)
    
  2. 我们对两个数据集进行分词。单词和标点符号会被相应的标记替换。如果需要,每个序列会被填充或截断,以适应模型的输入层(512 个标记):

    train_dataset = train_dataset.map(tokenize, 
        batched=True, batch_size=len(train_dataset))
    valid_dataset = valid_dataset.map(tokenize, 
        batched=True, batch_size=len(valid_dataset))
    
  3. 我们丢弃text变量,因为它不再需要:

    train_dataset = train_dataset.remove_columns(['text'])
    valid_dataset = valid_dataset.remove_columns(['text'])
    
  4. 打印出一个实例,我们可以看到注意力掩码(全为 1,意味着输入序列中的没有标记被掩盖)、输入 ID(标记序列)和标签:

    '{"attention_mask": [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,<zero padding>], "input_ids": [101,1045, 2018,1037,2524,2051,4531,1037,2553,2005,2026,2047,1048,2290,12776,3593,1016,2021,2633,2179,2023,2028,2006,9733,1012,1996,6087,2024,2428,3492,1998,2009,2573,2074,2004,2092,2004,1010,2065,2025,2488,2084,1996,22279,8758,1012,11504,2045,2097,2022,2062,2800,2011,2279,1060,9335,2161,1012,3452,1010,2200,10140,2553,1012,1045,2293,18178,12928,2232,1005,1055,1012,1024,1007,102,<zero padding>], "labels": 1}'
    

数据准备工作完成。接下来我们进入模型训练阶段。

微调 Hugging Face 模型

我们不打算从头开始训练:那样会花费太长时间,而且我们可能数据量也不足。相反,我们将对模型进行微调。从一个在大规模文本语料库上训练的模型开始,我们只会在我们自己的数据上再训练一个 epoch,以便模型能够学习到数据中的特定模式:

  1. 我们首先将两个数据集上传到 S3。datasets库提供了一个方便的 API 来实现这一点:

    import sagemaker
    from datasets.filesystems import S3FileSystem
    bucket = sagemaker.Session().default_bucket()
    s3_prefix = 'hugging-face/sentiment-analysis'
    train_input_path = 
        f's3://{bucket}/{s3_prefix}/training'
    valid_input_path =  
        f's3://{bucket}/{s3_prefix}/validation'
    s3 = S3FileSystem()
    train_dataset.save_to_disk(train_input_path, fs=s3)
    valid_dataset.save_to_disk(valid_input_path, fs=s3)
    
  2. 我们定义超参数并配置一个HuggingFace估算器。请注意,我们将仅对模型进行一个 epoch 的微调:

    hyperparameters={
        'epochs': 1,
        'train_batch_size': 32,
        'model_name':'distilbert-base-uncased'
    }
    from sagemaker.huggingface import HuggingFace
    huggingface_estimator = HuggingFace(
        role=sagemaker.get_execution_role(),
        entry_point='train.py',
        hyperparameters=hyperparameters,
        transformers_version='4.4.2',
        pytorch_version='1.6.0',
        py_version='py36',
        instance_type='ml.p3.2xlarge',
        instance_count=1
    )
    

    为了简洁起见,我不会讨论训练脚本(train.py),该脚本可以在本书的 GitHub 仓库中找到。它没有特别之处:我们使用Trainer Hugging Face API,并通过脚本模式与 SageMaker 进行接口。由于我们只训练一个 epoch,因此禁用了检查点保存(save_strategy='no')。这有助于缩短训练时间(不保存检查点)和部署时间(模型工件较小)。

  3. 还值得注意的是,你可以在 Hugging Face 网站上为你的估算器生成模板代码。如以下截图所示,你可以点击Amazon SageMaker,选择任务类型,然后复制并粘贴生成的代码:图 7.4 – 在 Hugging Face 网站上查看我们的模型

    ](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_07_4.jpg)

    图 7.4 – 在 Hugging Face 网站上查看我们的模型

  4. 我们像往常一样启动训练作业,持续了大约 42 分钟:

    huggingface_estimator.fit(
        {'train': train_input_path, 
         'valid': valid_input_path})
    

就像其他框架一样,我们可以调用deploy() API 来将模型部署到 SageMaker 端点。你可以在aws.amazon.com/blogs/machine-learning/announcing-managed-inference-for-hugging-face-models-in-amazon-sagemaker/找到一个示例。

相反,让我们看看如何使用内置的 PyTorch 容器和TorchServe部署我们的模型。实际上,这个部署示例可以推广到任何你希望通过 TorchServe 提供的 PyTorch 模型。

我发现我同事 Todd Escalona 写的这篇精彩博客文章在理解如何通过 TorchServe 提供 PyTorch 模型方面非常有帮助:aws.amazon.com/blogs/machine-learning/serving-pytorch-models-in-production-with-the-amazon-sagemaker-native-torchserve-integration/

部署一个 Hugging Face 模型

与之前的示例相比,唯一的区别是我们必须使用 S3 中的模型工件来创建一个PyTorchModel对象,并构建一个Predictor模型,我们可以在其上使用deploy()predict()

  1. 从模型工件开始,我们定义一个Predictor对象,然后用它创建一个PyTorchModel

    from sagemaker.pytorch import PyTorchModel
    from sagemaker.serializers import JSONSerializer
    from sagemaker.deserializers import JSONDeserializer
    model = PyTorchModel(
        model_data=huggingface_estimator.model_data,
        role=sagemaker.get_execution_role(), 
        entry_point='torchserve-predictor.py',
        framework_version='1.6.0',
        py_version='py36')
    
  2. 聚焦于推理脚本(torchserve-predictor.py),我们编写了一个模型加载函数,解决 Hugging Face 特有的 PyTorch 容器无法默认处理的情况:

    def model_fn(model_dir):
      config_path = '{}/config.json'.format(model_dir)
      model_path ='{}/pytorch_model.bin'.format(model_dir)
      config = AutoConfig.from_pretrained(config_path)
      model = DistilBertForSequenceClassification
              .from_pretrained(model_path, config=config)
      return model
    
  3. 我们还添加了一个返回文本标签的预测函数:

    tokenizer = AutoTokenizer.from_pretrained(
                'distilbert-base-uncased')
    CLASS_NAMES = ['negative', 'positive']
    def predict_fn(input_data, model):
        inputs = tokenizer(input_data['text'],  
                           return_tensors='pt')
        outputs = model(**inputs)
        logits = outputs.logits
        _, prediction = torch.max(logits, dim=1)
        return CLASS_NAMES[prediction]
    
  4. 推理脚本还包括基本的 input_fn()output_fn() 函数,用于检查数据是否为 JSON 格式。你可以在本书的 GitHub 仓库中找到相关代码。

  5. 回到我们的笔记本,我们像往常一样部署模型:

    predictor = model.deploy(
        initial_instance_count=1,
        instance_type='ml.m5.xlarge')
    
  6. 一旦端点启动,我们预测一个文本样本并打印结果:

    predictor.serializer = JSONSerializer()
    predictor.deserializer = JSONDeserializer()
    sample = {'text':'This camera is really amazing!}
    prediction = predictor.predict(test_data)
    print(prediction)
    ['positive']
    
  7. 最后,我们删除端点:

    predictor.delete_endpoint()
    

如你所见,使用 Hugging Face 模型非常简单。这也是一种具有成本效益的构建高质量 NLP 模型的方式,因为我们通常只需对模型进行非常少的训练周期(epoch)微调。

为了结束本章,让我们看看 SageMaker 和 Apache Spark 如何协同工作。

使用 Apache Spark

除了我们一直在使用的 Python SageMaker SDK,SageMaker 还包括 Spark 的 SDK(github.com/aws/sagemaker-spark)。这使你能够直接从运行在 Spark 集群上的 PySpark 或 Scala 应用程序中运行 SageMaker 作业。

结合 Spark 和 SageMaker

首先,你可以将提取-转换-加载ETL)步骤与机器学习步骤解耦。每个步骤通常有不同的基础设施需求(实例类型、实例数量、存储),这些需求需要在技术上和财务上都能满足。为 ETL 设置合适的 Spark 集群,并在 SageMaker 上使用按需基础设施进行训练和预测是一个强大的组合。

其次,尽管 Spark 的 MLlib 是一个令人惊叹的库,但你可能还需要其他工具,如不同语言的自定义算法或深度学习。

最后,将模型部署到 Spark 集群进行预测可能不是最佳选择。应考虑使用 SageMaker 端点,尤其是因为它们支持MLeap格式(combust.github.io/mleap-docs/)。

在以下示例中,我们将结合 SageMaker 和 Spark 构建一个垃圾邮件检测模型。数据将托管在 S3 中,垃圾邮件和非垃圾邮件(“ham”)各有一个文本文件。我们将使用在 Amazon EMR 集群上运行的 Spark 进行数据预处理。然后,我们将使用 SageMaker 中可用的 XGBoost 算法训练并部署模型。最后,我们将在 Spark 集群上进行预测。为了语言的多样性,这次我们使用 Scala 编写代码。

首先,我们需要构建一个 Spark 集群。

创建 Spark 集群

我们将如下方式创建集群:

  1. sagemaker-cluster 开始,再次点击下一步,然后点击创建集群。你可以在docs.aws.amazon.com/emr/找到更多详细信息。

  2. 在集群创建过程中,我们在左侧垂直菜单的Notebooks条目中定义我们的 Git 仓库。然后,我们点击Add repository图 7.6 – 添加 Git 仓库

    图 7.6 – 添加 Git 仓库

  3. 然后,我们创建一个连接到集群的 Jupyter 笔记本。从左侧垂直菜单中的Notebooks条目开始,如下图所示,我们为其命名,并选择我们刚刚创建的 EMR 集群和仓库。然后,我们点击Create notebook图 7.7 – 创建 Jupyter 笔记本

    图 7.7 – 创建 Jupyter 笔记本

  4. 一旦集群和笔记本准备好,我们可以点击Open in Jupyter,这将带我们进入熟悉的 Jupyter 界面。

一切准备就绪。让我们编写一个垃圾邮件分类器!

使用 Spark 和 SageMaker 构建垃圾邮件分类模型

在这个示例中,我们将利用 Spark 和 SageMaker 的结合优势,通过几行 Scala 代码来训练、部署和预测垃圾邮件分类模型:

  1. 首先,我们需要确保数据集已在 S3 中可用。在本地机器上,将这两个文件上传到默认的 SageMaker 桶(也可以使用其他桶):

    $ aws s3 cp ham s3://sagemaker-eu-west-1-123456789012
    $ aws s3 cp spam s3://sagemaker-eu-west-1-123456789012
    
  2. 返回到 Jupyter 笔记本,确保它正在运行 Spark 内核。然后,从 Spark MLlib 和 SageMaker SDK 中导入必要的对象。

  3. 从 S3 加载数据。将所有句子转换为小写字母。然后,移除所有标点符号和数字,并修剪掉任何空白字符:

    val spam = sc.textFile(
    "s3://sagemaker-eu-west-1-123456789012/spam")
    .map(l => l.toLowerCase())
    .map(l => l.replaceAll("[^ a-z]", ""))
    .map(l => l.trim())
    val ham = sc.textFile(
    "s3://sagemaker-eu-west-1-123456789012/ham")
    .map(l => l.toLowerCase())
    .map(l => l.replaceAll("[^ a-z]", ""))
    .map(l => l.trim())
    
  4. 然后,将消息拆分成单词,并将这些单词哈希到 200 个桶中。这个技术比我们在第六章,“训练自然语言处理模型”中使用的单词向量简单得多,但应该能奏效:

    val tf = new HashingTF(numFeatures = 200)
    val spamFeatures = spam.map(
                       m => tf.transform(m.split(" ")))
    val hamFeatures = ham.map(
                      m => tf.transform(m.split(" ")))
    

    例如,以下消息中,桶 15 中的单词出现了一次,桶 83 中的单词出现了一次,桶 96 中的单词出现了两次,桶 188 中的单词也出现了两次:

    Array((200,[15,83,96,188],[1.0,1.0,2.0,2.0]))
    
  5. 我们为垃圾邮件消息分配1标签,为正常邮件消息分配0标签:

    val positiveExamples = spamFeatures.map(
        features => LabeledPoint(1, features))
    val negativeExamples = hamFeatures.map(
        features => LabeledPoint(0, features))
    
  6. 合并消息并将其编码为LIBSVM格式,这是XGBoost支持的格式之一:

    val data = positiveExamples.union(negativeExamples)
    val data_libsvm =  
        MLUtils.convertVectorColumnsToML(data.toDF)
    

    现在样本看起来类似于这样:

    Array([1.0,(200,[2,41,99,146,172,181],[2.0,1.0,1.0,1.0,1.0])])
    
  7. 将数据分为训练集和验证集:

    val Array(trainingData, testData) = 
        data_libsvm.randomSplit(Array(0.8, 0.2))
    
  8. 配置 SageMaker SDK 中可用的 XGBoost 估算器。在这里,我们将一次性训练并部署:

    val roleArn = "arn:aws:iam:YOUR_SAGEMAKER_ROLE"
    val xgboost_estimator = new XGBoostSageMakerEstimator(
        trainingInstanceType="ml.m5.large",
        trainingInstanceCount=1,
        endpointInstanceType="ml.t2.medium",
        endpointInitialInstanceCount=1,
        sagemakerRole=IAMRole(roleArn))
    xgboost_estimator.setObjective("binary:logistic")
    xgboost_estimator.setNumRound(25)
    
  9. 启动一个训练任务和一个部署任务,在托管基础设施上,就像我们在第四章,“训练机器学习模型”中使用内置算法时那样。SageMaker SDK 会自动将 Spark DataFrame 传递给训练任务,因此我们无需做任何工作:

    val xgboost_model =   
        xgboost_estimator.fit(trainingData_libsvm)
    

    正如你所期待的,这些活动将在 SageMaker Studio 的Experiments部分中可见。

  10. 部署完成后,转换测试集并对模型进行评分。这会自动调用 SageMaker 端点。再次提醒,我们无需担心数据迁移:

    val transformedData = 
        xgboost_model.transform(testData_libsvm)
    val accuracy = 1.0*transformedData.filter(
        $"label"=== $"prediction")
        .count/transformedData.count()
    

    准确率应该在 97% 左右,表现得还不错!

  11. 完成后,删除作业创建的所有 SageMaker 资源。这将删除模型、端点以及端点配置(一个我们还没有讨论过的对象):

    val cleanup = new SageMakerResourceCleanup(
                      xgboost_model.sagemakerClient)
    cleanup.deleteResources(
        xgboost_model.getCreatedResources)
    
  12. 别忘了终止笔记本和 EMR 集群。你可以在 EMR 控制台轻松完成这一步。

这个示例演示了如何轻松地结合 Spark 和 SageMaker 各自的优势。另一种方法是构建包含 Spark 和 SageMaker 阶段的 MLlib 流水线。你可以在 github.com/awslabs/amazon-sagemaker-examples/tree/master/sagemaker-spark 找到相关示例。

摘要

开源框架,如 scikit-learn 和 TensorFlow,简化了机器学习和深度学习代码的编写。它们在开发者社区中非常受欢迎,原因显而易见。然而,管理训练和部署基础设施仍然需要大量努力和技能,而这些通常是数据科学家和机器学习工程师所不具备的。SageMaker 简化了整个过程。你可以迅速从实验阶段过渡到生产环境,无需担心基础设施问题。

在本章中,你了解了 SageMaker 中用于机器学习和深度学习的不同框架,以及如何自定义它们的容器。你还学习了如何使用脚本模式和本地模式进行快速迭代,直到你准备好在生产环境中部署。最后,你运行了几个示例,其中包括一个结合了 Apache Spark 和 SageMaker 的示例。

在下一章中,你将学习如何在 SageMaker 上使用你自己的自定义代码,而无需依赖内置容器。

第八章:使用你的算法和代码

在上一章中,你学习了如何使用内置框架(如 scikit-learnTensorFlow)进行模型的训练和部署。通过 脚本模式,这些框架使你能够轻松使用自己的代码,而无需管理任何训练或推理容器。

在某些情况下,你的业务或技术环境可能使得使用这些容器变得困难,甚至不可能使用。也许你需要完全控制容器的构建方式,也许你希望实现自己的预测逻辑,或者你正在使用 SageMaker 本身不原生支持的框架或语言。

在本章中,你将学习如何根据自己的需求定制训练和推理容器。你还将学习如何使用 SageMaker SDK 或命令行开源工具来训练和部署你自己的自定义代码。

本章将涵盖以下主题:

  • 理解 SageMaker 如何调用你的代码

  • 定制内置框架容器

  • 使用 SageMaker 训练工具包构建自定义训练容器

  • 使用 Python 和 R 构建完全自定义的训练和推理容器

  • 使用自定义 Python 代码在 MLflow 上进行训练和部署

  • 为 SageMaker 处理构建完全自定义容器

技术要求

你将需要一个 AWS 账户才能运行本章中的示例。如果你还没有 AWS 账户,请访问aws.amazon.com/getting-started/创建一个。你还应该熟悉 AWS 免费套餐(aws.amazon.com/free/),它允许你在一定的使用限制内免费使用许多 AWS 服务。

你需要为你的账户安装并配置 AWS 命令行界面CLI)(aws.amazon.com/cli/)。

你需要一个正常工作的 Python 3.x 环境。安装 Anaconda 发行版(www.anaconda.com/)不是强制性的,但强烈推荐,因为它包含了我们需要的许多项目(Jupyter、pandasnumpy 等)。

你将需要一个正常工作的 Docker 安装环境。你可以在docs.docker.com找到安装说明和文档。

本书中包含的代码示例可以在 GitHub 上找到:github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装 Git 客户端来访问这些示例(git-scm.com/)。

理解 SageMaker 如何调用你的代码

当我们使用内置算法和框架时,并没有太多关注 SageMaker 实际上是如何调用训练和部署代码的。毕竟,“内置”意味着:直接拿来需要的工具并开始工作。

当然,如果我们想使用自定义代码和容器,情况就不一样了。我们需要了解它们如何与 SageMaker 接口,以便我们能够准确地实现它们。

在本节中,我们将详细讨论这一接口。让我们从文件布局开始。

理解 SageMaker 容器内的文件布局

为了简化我们的工作,SageMaker 估算器会自动将超参数和输入数据复制到训练容器中。同样,它们会自动将训练好的模型(以及任何检查点)从容器复制到 S3。在部署时,它们会执行反向操作,将模型从 S3 复制到容器中。

如你所想,这要求遵循文件布局约定:

  • 超参数以 JSON 字典形式存储在/opt/ml/input/config/hyperparameters.json中。

  • 输入通道存储在/opt/ml/input/data/CHANNEL_NAME中。我们在前一章中看到,通道名称与传递给fit() API 的名称匹配。

  • 模型应保存在/opt/ml/model中,并从该位置加载。

因此,我们需要在自定义代码中使用这些路径。现在,让我们看看如何调用训练和部署代码。

理解自定义训练选项

第七章使用内置框架扩展机器学习服务中,我们研究了脚本模式以及 SageMaker 如何使用它来调用我们的训练脚本。此功能由框架容器中额外的 Python 代码启用,即 SageMaker 训练工具包(github.com/aws/sagemaker-training-toolkit)。

简而言之,训练工具包将入口脚本、其超参数和依赖项复制到容器内。它还将从输入通道中复制数据到容器中。然后,它会调用入口脚本。有好奇心的朋友可以阅读src/sagemaker_training/entry_point.py中的代码。

在自定义训练代码时,你有以下选项:

  • 自定义现有的框架容器,只添加你额外的依赖和代码。脚本模式和框架估算器将可用。

  • 基于 SageMaker 训练工具包构建自定义容器。脚本模式和通用的Estimator模块将可用,但你需要安装其他所有依赖。

  • 构建一个完全自定义的容器。如果你想从头开始,或者不想在容器中添加任何额外的代码,这是最合适的选择。你将使用通用的Estimator模块进行训练,并且脚本模式将不可用。你的训练代码将直接调用(稍后会详细说明)。

理解自定义部署选项

框架容器包括用于部署的额外 Python 代码。以下是最流行框架的仓库:

就像训练一样,你有三个选项:

  • 自定义现有的框架容器。模型将使用现有的推理逻辑提供服务。

  • 基于 SageMaker 推理工具包构建自定义容器。模型将由多模型服务器提供服务。

  • 构建一个完全自定义的容器,去掉任何推理逻辑,改为实现自己的推理逻辑。

无论是使用单一容器进行训练和部署,还是使用两个不同的容器,都取决于你。许多不同的因素会影响决策:谁构建容器、谁运行容器等等。只有你能决定对你的特定设置来说,哪个选项是最佳的。

现在,让我们运行一些示例吧!

自定义现有框架容器

当然,我们也可以简单地写一个 Dockerfile,引用其中一个深度学习容器镜像 (github.com/aws/deep-learning-containers/blob/master/available_images.md),并添加我们自己的命令。请参见以下示例:

FROM 763104351884.dkr.ecr.us-east-1.amazonaws.com/tensorflow-training:2.4.1-cpu-py37-ubuntu18.04
. . .

相反,让我们在本地机器上自定义并重新构建PyTorch训练和推理容器。这个过程与其他框架类似。

构建环境

需要安装并运行 Docker。为了避免在拉取基础镜像时受到限制,建议你创建一个docker loginDocker Desktop

为了避免奇怪的依赖问题(我在看你,macOS),我还建议你在m5.large实例上构建镜像(应该足够),但请确保分配的存储空间超过默认的 8GB。我推荐 64GB。你还需要确保该 EC2 实例的IAM角色允许你推送和拉取 EC2 镜像。如果你不确定如何创建并连接到 EC2 实例,可以参考这个教程:docs.aws.amazon.com/AWSEC2/latest/UserGuide/EC2_GetStarted.html

在 EC2 上设置构建环境

我们将通过以下步骤开始:

  1. 一旦你的 EC2 实例启动,我们通过ssh连接到它。首先,我们安装 Docker,并将ec2-user添加到docker组。这将允许我们以非 root 用户身份运行 Docker 命令:

    $ sudo yum -y install docker
    $ sudo usermod -a -G docker ec2-user
    
  2. 为了应用此权限更改,我们登出并重新登录。

  3. 我们确保docker正在运行,并登录到 Docker Hub:

    $ service docker start
    $ docker login
    
  4. 我们安装git、Python 3 和pip

    $ sudo yum -y install git python3-devel python3-pip
    

我们的 EC2 实例现在已经准备好,我们可以继续构建容器。

构建训练和推理容器

这可以通过以下步骤完成:

  1. 我们克隆deep-learning-containers仓库,该仓库集中管理 TensorFlow、PyTorch、Apache MXNet 和 Hugging Face 的所有训练和推理代码,并添加了便捷的脚本来构建这些容器:

    $ git clone https://github.com/aws/deep-learning-containers.git
    $ cd deep-learning-containers
    
  2. 我们为我们的账户 ID、运行所在的区域以及我们将在 Amazon ECR 中创建的新仓库的名称设置环境变量:

    $ export ACCOUNT_ID=123456789012
    $ export REGION=eu-west-1
    $ export REPOSITORY_NAME=my-pt-dlc
    
  3. 我们在 Amazon ECR 中创建仓库并登录。有关详细信息,请参阅文档(docs.aws.amazon.com/ecr/index.html):

    $ aws ecr create-repository 
    --repository-name $REPOSITORY_NAME --region $REGION
    $ aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com
    
  4. 我们创建一个虚拟环境,并安装 Python 依赖:

    $ python3 -m venv dlc
    $ source dlc/bin/activate
    $ pip install -r src/requirements.txt
    
  5. 在这里,我们想要为 PyTorch 1.8 构建训练和推理容器,支持 CPU 和 GPU。我们可以在pytorch/training/docker/1.8/py3/找到相应的 Docker 文件,并根据需要进行定制。例如,我们可以将 Deep Graph Library 固定为版本 0.6.1:

    && conda install -c dglteam -y dgl==0.6.1 \
    
  6. 编辑完 Docker 文件后,我们查看最新 PyTorch 版本的构建配置文件(pytorch/buildspec.yml)。我们决定自定义镜像标签,以确保每个镜像都能清楚地识别:

    BuildCPUPTTrainPy3DockerImage:
        tag: !join [ *VERSION, "-", *DEVICE_TYPE, "-", *TAG_PYTHON_VERSION, "-", *OS_VERSION, "-training" ]
    BuildGPUPTTrainPy3DockerImage:
        tag: !join [ *VERSION, "-", *DEVICE_TYPE, "-", *TAG_PYTHON_VERSION, "-", *CUDA_VERSION, "-", *OS_VERSION, "-training" ]
    BuildCPUPTInferencePy3DockerImage:
        tag: !join [ *VERSION, "-", *DEVICE_TYPE, "-", *TAG_PYTHON_VERSION, "-", *OS_VERSION, "-inference" ]
    BuildGPUPTInferencePy3DockerImage:
        tag: !join [ *VERSION, "-", *DEVICE_TYPE, "-", *TAG_PYTHON_VERSION, "-", *CUDA_VERSION, "-", *OS_VERSION, "-inference"]
    
  7. 最后,我们运行设置脚本并启动构建过程:

    $ bash src/setup.sh pytorch
    $ python src/main.py --buildspec pytorch/buildspec.yml --framework pytorch --device_types cpu,gpu --image_types training,inference
    
  8. 稍等片刻,所有四个镜像(加上一个示例镜像)都已构建完成,我们可以在本地 Docker 中看到它们:

    $ docker images
    123456789012.dkr.ecr.eu-west-1.amazonaws.com/my-pt-dlc   1.8.1-gpu-py36-cu111-ubuntu18.04-example-2021-05-28-10-14-15     
    123456789012.dkr.ecr.eu-west-1.amazonaws.com/my-pt-dlc   1.8.1-gpu-py36-cu111-ubuntu18.04-training-2021-05-28-10-14-15    
    123456789012.dkr.ecr.eu-west-1.amazonaws.com/my-pt-dlc   1.8.1-gpu-py36-cu111-ubuntu18.04-inference-2021-05-28-10-14-15
    123456789012.dkr.ecr.eu-west-1.amazonaws.com/my-pt-dlc   1.8.1-cpu-py36-ubuntu18.04-inference-2021-05-28-10-14-15         
    123456789012.dkr.ecr.eu-west-1.amazonaws.com/my-pt-dlc   1.8.1-cpu-py36-ubuntu18.04-training-2021-05-28-10-14-15          
    
  9. 我们也可以在 ECR 仓库中看到它们,如下图所示:图 8.1 – 在 ECR 中查看镜像

    图 8.1 – 在 ECR 中查看镜像

  10. 现在这些镜像可以通过 SageMaker SDK 使用。让我们用新的 CPU 镜像进行训练。我们只需要将其名称传递给PyTorch估算器的image_uri参数即可。请注意,我们可以去掉py_versionframework_version

    Estimator = PyTorch(
        image_uri='123456789012.dkr.ecr.eu-west-1.amazonaws.com/my-pt-dlc:1.8.1-cpu-py36-ubuntu18.04-training-2021-05-28-10-14-15',
        role=sagemaker.get_execution_role(),
        entry_point='karate_club_sagemaker.py',
        hyperparameters={'node_count': 34, 'epochs': 30},
        instance_count=1,
        instance_type='ml.m5.large')
    

如你所见,定制深度学习容器非常简单。现在,让我们深入一步,仅使用训练工具包进行操作。

使用带有 scikit-learn 的 SageMaker 训练工具包

在这个示例中,我们将使用 SageMaker 训练工具包构建一个自定义 Python 容器。我们将使用它在波士顿房价数据集上训练一个 scikit-learn 模型,使用脚本模式和 SKLearn 估算器。

我们需要三个构建模块:

  • 训练脚本。由于脚本模式将可用,我们可以使用与 第七章 中的 scikit-learn 示例完全相同的代码,使用内置框架扩展机器学习服务

  • 我们需要一个 Dockerfile 和 Docker 命令来构建自定义容器。

  • 我们还需要一个配置为使用我们自定义容器的 SKLearn 估算器。

让我们来处理容器:

  1. 一个 Dockerfile 可能会变得相当复杂,但这里我们不需要这么做!我们从 Docker Hub 上提供的官方 Python 3.7 镜像开始 (hub.docker.com/_/python)。我们安装 scikit-learn、numpypandasjoblib 和 SageMaker 训练工具包:

    FROM python:3.7
    RUN pip3 install --no-cache scikit-learn numpy pandas joblib sagemaker-training
    
  2. 我们使用 docker build 命令构建镜像,并将其标记为 sklearn-customer:sklearn

    $ docker build -t sklearn-custom:sklearn -f Dockerfile .
    

    镜像构建完成后,我们可以找到其标识符:

    $ docker images
    REPOSITORY          TAG         IMAGE ID   
    sklearn-custom      sklearn     bf412a511471         
    
  3. 使用 AWS CLI,我们在 Amazon ECR 中创建一个仓库来托管这个镜像,并登录到该仓库:

    $ aws ecr create-repository --repository-name sklearn-custom --region eu-west-1
    $ aws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.eu-west-1.amazonaws.com/sklearn-custom:latest
    
  4. 使用镜像标识符,我们用仓库标识符标记镜像:

    $ docker tag bf412a511471 123456789012.dkr.ecr.eu-west-1.amazonaws.com/sklearn-custom:sklearn
    
  5. 我们将镜像推送到仓库:

    $ docker push 123456789012.dkr.ecr.eu-west-1.amazonaws.com/sklearn-custom:sklearn
    

    现在,镜像已经准备好用于使用 SageMaker 估算器进行训练。

  6. 我们定义一个 SKLearn 估算器,将 image_uri 参数设置为我们刚创建的容器的名称:

    sk = SKLearn(
        role=sagemaker.get_execution_role(),
        entry_point='sklearn-boston-housing.py',
        image_name='123456789012.dkr.ecr.eu-west-1.amazonaws.com/sklearn-custom:sklearn',
        instance_count=1,
        instance_type='ml.m5.large',
        output_path=output,
        hyperparameters={
             'normalize': True,
             'test-size': 0.1
        }
    )
    
  7. 我们设置训练通道的位置,并像往常一样启动训练。在训练日志中,我们看到我们的代码确实以脚本模式被调用:

    /usr/local/bin/python -m sklearn-boston-housing 
    --normalize True --test-size 0.1
    

如你所见,定制训练容器非常简单。得益于 SageMaker 训练工具包,你可以像使用内置框架容器一样工作。我们这里使用了 scikit-learn,你也可以对其他所有框架做同样的操作。

然而,我们不能将这个容器用于部署,因为它不包含任何模型服务代码。我们应该添加定制代码来启动一个 Web 应用程序,这正是我们在下一个示例中要做的。

为 scikit-learn 构建完全自定义的容器

在这个示例中,我们将构建一个完全自定义的容器,里面没有任何 AWS 代码。我们将使用它在波士顿房价数据集上训练一个 scikit-learn 模型,使用通用的 Estimator 模块。使用相同的容器,我们将通过 Flask Web 应用程序部署该模型。

我们将按逻辑步骤进行操作,首先处理训练,然后更新代码以处理部署。

使用完全自定义容器进行训练

由于我们不能再依赖脚本模式,因此需要修改训练代码。这就是修改后的代码,你很容易就能理解它是怎么回事:

#!/usr/bin/env python
import pandas as pd
import joblib, os, json
if __name__ == '__main__':
    config_dir = '/opt/ml/input/config'
    training_dir = '/opt/ml/input/data/training'
    model_dir = '/opt/ml/model'
    with open(os.path.join(config_dir, 
    'hyperparameters.json')) as f:
        hp = json.load(f)
        normalize = hp['normalize']
        test_size = float(hp['test-size'])
        random_state = int(hp['random-state'])
    filename = os.path.join(training_dir, 'housing.csv')
    data = pd.read_csv(filename)
    # Train model
    . . . 
    joblib.dump(regr, 
                os.path.join(model_dir, 'model.joblib'))

使用 SageMaker 容器的标准文件布局,我们从 JSON 文件中读取超参数。然后,我们加载数据集,训练模型,并将其保存在正确的位置。

还有一个非常重要的区别,我们需要深入了解 Docker 来解释它。SageMaker 将以 docker run <IMAGE_ID> train 运行训练容器,并将 train 参数传递给容器的入口点。

如果你的容器有预定义的入口点,train 参数将会传递给它,比如 /usr/bin/python train。如果容器没有预定义的入口点,train 就是将要执行的实际命令。

为了避免烦人的问题,我建议你的训练代码满足以下要求:

  • 命名为 train——没有扩展名,只是 train

  • 使其可执行。

  • 确保它在 PATH 值中。

  • 脚本的第一行应该定义解释器的路径,例如 #!/usr/bin/env python

这应该能保证无论你的容器是否有预定义的入口点,都能正确调用你的训练代码。

我们将在 Dockerfile 中处理这个问题,从官方的 Python 镜像开始。注意,我们不再安装 SageMaker 训练工具包:

FROM python:3.7
RUN pip3 install --no-cache scikit-learn numpy pandas joblib
COPY sklearn-boston-housing-generic.py /usr/bin/train
RUN chmod 755 /usr/bin/train

脚本名称正确。它是可执行的,且 /usr/binPATH 中。

我们应该准备好了——让我们创建自定义容器并用它启动训练任务:

  1. 我们构建并推送镜像,使用不同的标签:

    $ docker build -t sklearn-custom:estimator -f Dockerfile-generic .
    $ docker tag <IMAGE_ID> 123456789012.dkr.ecr.eu-west-1.amazonaws.com/sklearn-custom:estimator
    $ docker push 123456789012.dkr.ecr.eu-west-1.amazonaws.com/sklearn-custom:estimator
    
  2. 我们更新笔记本代码,使用通用的 Estimator 模块:

    from sagemaker.estimator import Estimator
    sk = Estimator(
        role=sagemaker.get_execution_role(),
        image_name='123456789012.dkr.ecr.eu-west-1.amazonaws.com/sklearn-custom:estimator',
        instance_count=1,
        instance_type='ml.m5.large',
        output_path=output,
        hyperparameters={
             'normalize': True,
             'test-size': 0.1,
             'random-state': 123
        }
    )
    
  3. 我们照常进行训练。

现在让我们添加代码来部署这个模型。

部署完全自定义容器

Flask 是一个非常流行的 Python Web 框架(palletsprojects.com/p/flask)。它简单且文档齐全。我们将用它来构建一个托管在容器中的简单预测 API。

就像我们的训练代码一样,SageMaker 要求将部署脚本复制到容器内。镜像将以 docker run <IMAGE_ID> serve 运行。

HTTP 请求将发送到端口 8080。容器必须提供 /ping URL 进行健康检查,并提供 /invocations URL 处理预测请求。我们将使用 CSV 格式作为输入。

因此,你的部署代码需要满足以下要求:

  • 命名为 serve——没有扩展名,只是 serve

  • 使其可执行。

  • 确保它在 PATH 中。

  • 确保容器暴露了端口8080

  • 提供代码处理 /ping/invocations URL。

这是更新后的 Dockerfile。我们安装 Flask,复制部署代码,并开放端口 8080

FROM python:3.7
RUN pip3 install --no-cache scikit-learn numpy pandas joblib
RUN pip3 install --no-cache flask
COPY sklearn-boston-housing-generic.py /usr/bin/train
COPY sklearn-boston-housing-serve.py /usr/bin/serve
RUN chmod 755 /usr/bin/train /usr/bin/serve
EXPOSE 8080

这是我们如何用 Flask 实现一个简单的预测服务:

  1. 我们导入所需的模块。我们从 /opt/ml/model 加载模型并初始化 Flask 应用程序:

    #!/usr/bin/env python
    import joblib, os
    import pandas as pd
    from io import StringIO
    import flask
    from flask import Flask, Response
    model_dir = '/opt/ml/model'
    model = joblib.load(os.path.join(model_dir, 
                        'model.joblib'))
    app = Flask(__name__)
    
  2. 我们实现 /ping URL 来进行健康检查,方法是简单地返回 HTTP 代码 200(OK):

    @app.route("/ping", methods=["GET"])
    def ping():
        return Response(response="\n", status=200)
    
  3. 我们实现了/invocations URL。如果内容类型不是text/csv,我们返回 HTTP 代码 415(不支持的媒体类型)。如果是,我们解码请求体并将其存储在文件样式的内存缓冲区中。然后,我们读取 CSV 样本,进行预测,并发送结果:

    @app.route("/invocations", methods=["POST"])
    def predict():
        if flask.request.content_type == 'text/csv':
            data = flask.request.data.decode('utf-8')
            s = StringIO(data)
            data = pd.read_csv(s, header=None)
            response = model.predict(data)
            response = str(response)
        else:
            return flask.Response(
                response='CSV data only', 
                status=415, mimetype='text/plain')
        return Response(response=response, status=200)
    
  4. 在启动时,脚本在 8080 端口上启动 Flask 应用程序:

    if __name__ == "__main__":
        app.run(host="0.0.0.0", port=8080)
    

    即使您还不熟悉 Flask,这也不算太难。

  5. 我们重新构建并推送镜像,然后使用相同的评估器再次进行训练。这里不需要进行任何更改。

  6. 我们部署模型:

    sk_predictor = sk.deploy(instance_type='ml.t2.medium',
                             initial_instance_count=1)
    

    提醒

    如果您在这里看到一些奇怪的行为(端点未部署、密秘的错误消息等),Docker 可能出了问题。sudo service docker restart应该能解决大多数问题。在/tmp中清理tmp*可能也会有所帮助。

  7. 我们准备了一些测试样本,将内容类型设置为text/csv,并调用预测 API:

    test_samples = ['0.00632, 18.00, 2.310, 0, 0.5380, 6.5750, 65.20, 4.0900, 1,296.0, 15.30, 396.90, 4.98',             
    '0.02731, 0.00, 7.070, 0, 0.4690, 6.4210, 78.90, 4.9671, 2,242.0, 17.80, 396.90, 9.14']
    sk_predictor.serializer =
        sagemaker.serializers.CSVSerializer()
    response = sk_predictor.predict(test_samples)
    print(response)
    

    您应该会看到类似于此的内容。API 已成功调用:

    b'[[29.801388899699845], [24.990809475886078]]'
    
  8. 完成后,我们删除端点:

    sk_predictor.delete_endpoint()
    

在下一个例子中,我们将使用 R 环境来训练和部署模型。这将让我们有机会暂时离开 Python 世界。正如您将看到的那样,事情并没有真正不同。

构建一个完全自定义的 R 容器

R 是一种用于数据探索和分析的流行语言。在本例中,我们将构建一个自定义容器,以在波士顿房屋数据集上训练和部署线性回归模型。

整个过程与为 Python 构建自定义容器类似。我们将使用plumber而不是 Flask 来构建我们的预测 API:

使用 R 和 plumber 进行编码

如果你对 R 不太熟悉,不要担心。这是一个非常简单的例子,我相信你能跟上:

  1. 我们编写一个函数来训练我们的模型。它从常规路径加载超参数和数据集。如果我们请求的话,它会对数据集进行标准化:

    # train_function.R
    library("rjson")
    train <- function() {
        hp <- fromJSON(file = 
              '/opt/ml/input/config/hyperparameters.json')
        normalize <- hp$normalize
        data <- read.csv(file = 
                '/opt/ml/input/data/training/housing.csv', 
                header=T)
        if (normalize) {
            data <- as.data.frame(scale(data))
        }
    

    它训练一个线性回归模型,考虑所有特征来预测房屋的中位数价格(medv列)。最后,它将模型保存在正确的位置:

        model = lm(medv~., data)
        saveRDS(model, '/opt/ml/model/model.rds')
    }
    
  2. 我们编写一个函数来提供预测服务。使用plumber注解,我们为健康检查定义了/ping URL,为预测定义了/invocations URL:

    # serve_function.R
    #' @get /ping
    function() {
      return('')
    }
    #' @post /invocations
    function(req) {
        model <- readRDS('/opt/ml/model/model.rds')
        conn <- textConnection(gsub('\\\\n', '\n', 
                               req$postBody))
        data <- read.csv(conn)
        close(conn)
        medv <- predict(model, data)
        return(medv)
    }
    
  3. 将这两个部分结合在一起,我们编写一个主函数,它将作为我们脚本的入口点。SageMaker 将传递trainserve命令行参数,并在我们的代码中调用相应的函数:

    library('plumber')
    source('train_function.R')
    serve <- function() {
        app <- plumb('serve_function.R')
        app$run(host='0.0.0.0', port=8080)}
    args <- commandArgs()
    if (any(grepl('train', args))) {
        train()
    }
    if (any(grepl('serve', args))) {
        serve()
    }
    

这就是我们需要的所有 R 代码。现在,让我们来处理容器。

构建自定义容器

我们需要构建一个自定义容器,存储 R 运行时和我们的脚本。Dockerfile 如下所示:

  1. 我们从Docker Hub的官方 R 镜像开始,并添加我们需要的依赖项(这些是我在我的机器上需要的;您的情况可能有所不同):

    FROM r-base:latest
    WORKDIR /opt/ml/
    RUN apt-get update
    RUN apt-get install -y libcurl4-openssl-dev libsodium-dev
    RUN R -e "install.packages(c('rjson', 'plumber')) "
    
  2. 然后,我们将我们的代码复制到容器中,并将主函数定义为其显式入口点:

    COPY main.R train_function.R serve_function.R /opt/ml/
    ENTRYPOINT ["/usr/bin/Rscript", "/opt/ml/main.R", "--no-save"]
    
  3. 我们在 ECR 中创建一个新仓库。然后,我们构建镜像(这可能需要一段时间,并涉及编译步骤),并推送它:

    $ aws ecr create-repository --repository-name r-custom --region eu-west-1
    $ aws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.eu-west-1.amazonaws.com/r-custom:latest
    $ docker build -t r-custom:latest -f Dockerfile .
    $ docker tag <IMAGE_ID> 123456789012.dkr.ecr.eu-west-1.amazonaws.com/r-custom:latest
    $ docker push 123456789012.dkr.ecr.eu-west-1.amazonaws.com/r-custom:latest
    

一切准备就绪,让我们开始训练并部署。

在 SageMaker 上训练和部署自定义容器

跳转到 Jupyter 笔记本,我们使用 SageMaker SDK 训练并部署我们的容器:

  1. 我们配置一个带有自定义容器的Estimator模块:

    r_estimator = Estimator(
        role = sagemaker.get_execution_role(),
        image_uri='123456789012.dkr.ecr.eu-west-1.amazonaws.com/r-custom:latest',
        instance_count=1,
        instance_type='ml.m5.large',
        output_path=output,
        hyperparameters={'normalize': False}
    )
    r_estimator.fit({'training':training})
    
  2. 一旦训练任务完成,我们像往常一样部署模型:

    r_predictor = r_estimator.deploy(
        initial_instance_count=1, 
        instance_type='ml.t2.medium')
    
  3. 最后,我们读取完整的数据集(为什么不呢?)并将其发送到端点:

    import pandas as pd
    data = pd.read_csv('housing.csv')
    data.drop(['medv'], axis=1, inplace=True)
    data = data.to_csv(index=False)
    r_predictor.serializer = 
        sagemaker.serializers.CSVSerializer()
    response = r_predictor.predict(data)
    print(response)
    

    输出应该像这样:

    b'[30.0337,25.0568,30.6082,28.6772,27.9288\. . .
    
  4. 完成后,我们删除端点:

    r_predictor.delete_endpoint()
    

无论你使用 Python、R,还是其他语言,构建和部署你自己的自定义容器都相对容易。然而,你仍然需要构建自己的网站应用程序,这可能是你既不知道如何做,也不喜欢做的事。如果我们有一个工具来处理所有这些麻烦的容器和网站问题,那该多好?

事实上,确实有一个平台:MLflow

使用你自己的代码在 MLflow 上进行训练和部署

MLflow 是一个开源机器学习平台(mlflow.org)。它由 Databricks(databricks.com)发起,Databricks 还为我们带来了Spark。MLflow 有许多功能,包括能够将 Python 训练的模型部署到 SageMaker。

本节并非旨在作为 MLflow 教程。你可以在www.mlflow.org/docs/latest/index.html找到文档和示例。

安装 MLflow

在本地机器上,让我们为 MLflow 设置一个虚拟环境并安装所需的库。以下示例是在 MLflow 1.17 上测试的:

  1. 我们首先初始化一个名为mlflow-example的新虚拟环境。然后,我们激活它:

    $ virtualenv mlflow-example
    $ source mlflow-example/bin/activate
    
  2. 我们安装了 MLflow 和训练脚本所需的库:

    $ pip install mlflow gunicorn pandas sklearn xgboost boto3
    
  3. 最后,我们下载已经在第七章中使用过的直接营销数据集,使用内置框架扩展机器学习服务

    $ wget -N https://sagemaker-sample-data-us-west-2.s3-us-west-2.amazonaws.com/autopilot/direct_marketing/bank-additional.zip
    $ unzip -o bank-additional.zip
    

设置完成。让我们开始训练模型。

使用 MLflow 训练模型

训练脚本为此次运行设置了 MLflow 实验,以便我们可以记录元数据(超参数、指标等)。然后,它加载数据集,训练一个 XGBoost 分类器,并记录模型:

# train-xgboost.py
import mlflow.xgboost
import xgboost as xgb
from load_dataset import load_dataset
if __name__ == '__main__':
    mlflow.set_experiment('dm-xgboost')
    with mlflow.start_run(run_name='dm-xgboost-basic') 
    as run:
        x_train, x_test, y_train, y_test = load_dataset(
            'bank-additional/bank-additional-full.csv')
        cls = xgb.XGBClassifier(
                  objective='binary:logistic', 
                  eval_metric='auc')
        cls.fit(x_train, y_train)
        auc = cls.score(x_test, y_test)
        mlflow.log_metric('auc', auc)
        mlflow.xgboost.log_model(cls, 'dm-xgboost-model')
        mlflow.end_run()

load_dataset()函数按其名称所示执行,并记录多个参数:

# load_dataset.py
import mlflow
import pandas as pd
from sklearn.model_selection import train_test_split
def load_dataset(path, test_size=0.2, random_state=123):
    data = pd.read_csv(path)
    data = pd.get_dummies(data)
    data = data.drop(['y_no'], axis=1)
    x = data.drop(['y_yes'], axis=1)
    y = data['y_yes']
    mlflow.log_param("dataset_path", path)
    mlflow.log_param("dataset_shape", data.shape)
    mlflow.log_param("test_size", test_size)
    mlflow.log_param("random_state", random_state)
    mlflow.log_param("one_hot_encoding", True)
    return train_test_split(x, y, test_size=test_size, 
                            random_state=random_state)

让我们训练模型并在 MLflow Web 应用程序中可视化其结果:

  1. 在我们刚刚在本地机器上创建的虚拟环境中,我们像运行任何 Python 程序一样运行训练脚本:

    $ python train-xgboost.py
    INFO: 'dm-xgboost' does not exist. Creating a new experiment
    AUC  0.91442097596504
    
  2. 我们启动 MLflow Web 应用程序:

    $ mlflow ui &
    
  3. 当我们将浏览器指向localhost:5000时,我们可以看到运行的信息,如下图所示:

图 8.2 – 在 MLflow 中查看我们的任务

图 8.2 – 在 MLflow 中查看我们的任务

训练成功了。在我们将模型部署到 SageMaker 之前,我们必须构建一个 SageMaker 容器。事实证明,这是一件非常简单的事。

使用 MLflow 构建 SageMaker 容器

只需在本地机器上执行一个命令:

$ mlflow sagemaker build-and-push-container

MLflow 会自动构建一个与 SageMaker 兼容的 Docker 容器,并包含所有所需的依赖项。然后,它会在 Amazon ECR 中创建一个名为 mlflow-pyfunc 的仓库,并将镜像推送到该仓库。显然,这需要你正确设置 AWS 凭证。MLflow 将使用 AWS CLI 配置的默认区域。

一旦这个命令完成,你应该会在 ECR 中看到镜像,如下图所示:

图 8.3 – 查看我们的容器在 ECR 中

](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_08_3.jpg)

图 8.3 – 查看我们的容器在 ECR 中

我们的容器现在已经准备好部署了。

在本地部署模型使用 MLflow

我们将使用以下步骤部署我们的模型:

  1. 我们可以通过一个命令在本地部署我们的模型,传递其运行标识符(可以在 MLflow 运行的 URL 中看到)和要使用的 HTTP 端口。这将启动一个基于 gunicorn 的本地 Web 应用程序:

    $ mlflow sagemaker run-local -p 8888 -m runs:/d08ab8383ee84f72a92164d3ca548693/dm-xgboost-model
    

    你应该看到类似这样的内容:

    [2021-05-26 20:21:23 +0000] [370] [INFO] Starting gunicorn 20.1.0
    [2021-05-26 20:21:23 +0000] [370] [INFO] Listening at: http://127.0.0.1:8000 (370)
    [2021-05-26 20:21:23 +0000] [370] [INFO] Using worker: gevent
    [2021-05-26 20:21:23 +0000] [381] [INFO] Booting worker with pid: 381 
    
  2. 我们的预测代码非常简单。我们从数据集加载 CSV 样本,将它们转换为 JSON 格式,并使用 requests 库发送到端点。requests 是一个流行的 Python HTTP 库(requests.readthedocs.io):

    # predict-xgboost-local.py 
    import json
    import requests
    from load_dataset import load_dataset
    port = 8888
    if __name__ == '__main__':
        x_train, x_test, y_train, y_test = load_dataset(
            'bank-additional/bank-additional-full.csv')
        input_data = x_test[:10].to_json(orient='split')
        endpoint = 'http://localhost:{}/invocations'
                   .format(port)
        headers = {'Content-type': 'application/json; 
                    format=pandas-split'}
        prediction = requests.post(
            endpoint, 
            json=json.loads(input_data),
            headers=headers)
        print(prediction.text)
    
  3. 在另一个 shell 中运行此代码,调用本地模型并输出预测结果:

    $ source mlflow-example/bin/activate
    $ python predict-xgboost-local.py
    [0.00046298891538754106, 0.10499032586812973, . . . 
    
  4. 完成后,我们使用 Ctrl + C 终止本地服务器。

现在我们确信我们的模型在本地可以正常工作,我们可以将它部署到 SageMaker。

使用 MLflow 在 SageMaker 上部署模型

这又是一个一行命令:

  1. 我们需要传递一个应用程序名称、模型路径和 SageMaker 角色的名称。你可以使用你在前几章中使用过的相同角色:

    $ mlflow sagemaker deploy \
    --region-name eu-west-1 \
    -t ml.t2.medium \
    -a mlflow-xgb-demo \
    -m runs:/d08ab8383ee84f72a92164d3ca548693/dm-xgboost-model \
    -e arn:aws:iam::123456789012:role/Sagemaker-fullaccess
    
  2. 几分钟后,端点进入服务状态。我们使用以下代码调用它。它加载测试数据集,并将前 10 个样本以 JSON 格式发送到以我们的应用程序命名的端点:

    # predict-xgboost.py 
    import boto3
    from load_dataset import load_dataset
    app_name = 'mlflow-xgb-demo'
    region = 'eu-west-1'
    if __name__ == '__main__':
        sm = boto3.client('sagemaker', region_name=region)
        smrt = boto3.client('runtime.sagemaker', 
                            region_name=region)
        endpoint = sm.describe_endpoint(
                  EndpointName=app_name)
        print("Status: ", endpoint['EndpointStatus'])
        x_train, x_test, y_train, y_test = load_dataset(
            'bank-additional/bank-additional-full.csv')
        input_data = x_test[:10].to_json(orient="split")
        prediction = smrt.invoke_endpoint(
            EndpointName=app_name,
            Body=input_data,
            ContentType='application/json;
                         format=pandas-split')
        prediction = prediction['Body']
                     .read().decode("ascii")
        print(prediction)
    

    等一下!我们没有使用 SageMaker SDK。这是怎么回事?

    在这个例子中,我们处理的是一个现有的端点,而不是通过拟合估算器并部署预测器创建的端点。

    我们仍然可以使用 SageMaker SDK 重新构建一个预测器,正如我们将在 第十一章 中看到的那样,部署机器学习模型。不过,我们使用了我们亲爱的老朋友 boto3,AWS 的 Python SDK。我们首先调用 describe_endpoint() API 来检查端点是否在服务中。然后,我们使用 invoke_endpoint() API 来……调用端点!现在,我们暂时不需要了解更多。

    我们在本地机器上运行预测代码,输出结果如下:

    $ python3 predict-xgboost.py
    Status:  InService
    0.00046298891538754106, 0.10499032586812973, 0.016391035169363022, . . .
    
  3. 完成后,我们使用 MLflow CLI 删除端点。这会清理为部署创建的所有资源:

    $ mlflow sagemaker delete -a mlflow-xgb-demo –region-name eu-west-1
    

使用 MLflow 的开发经验非常简单。它还有许多其他功能,您可能想要探索。

到目前为止,我们已经运行了训练和预测的示例。SageMaker 还有一个领域可以让我们使用自定义容器,SageMaker Processing,我们在[第二章中研究了它,处理数据准备技术。为了结束这一章,让我们为 SageMaker Processing 构建一个自定义的 Python 容器。

为 SageMaker 处理构建一个完全自定义的容器

我们将重用来自第六章的新闻头条示例,训练自然语言处理模型

  1. 我们从一个基于最小 Python 镜像的 Dockerfile 开始。我们安装依赖项,添加处理脚本,并将其定义为我们的入口点:

    FROM python:3.7-slim
    RUN pip3 install --no-cache gensim nltk sagemaker
    RUN python3 -m nltk.downloader stopwords wordnet
    ADD preprocessing-lda-ntm.py /
    ENTRYPOINT ["python3", "/preprocessing-lda-ntm.py"]
    
  2. 我们构建镜像并将其标记为sm-processing-custom:latest

    python:3.7 instead of python:3.7-slim. This makes it faster to push and download.
    
  3. 使用 AWS CLI,我们在 Amazon ECR 中创建一个存储库来托管这个镜像,并登录到该存储库:

    $ aws ecr create-repository --repository-name sm-processing-custom --region eu-west-1
    $ aws ecr get-login-password | docker login --username AWS --password-stdin 123456789012.dkr.ecr.eu-west-1.amazonaws.com/sm-processing-custom:latest
    
  4. 使用镜像标识符,我们用存储库标识符为镜像打标签:

    $ docker tag <IMAGE_ID> 123456789012.dkr.ecr.eu-west-1.amazonaws.com/sm-processing-custom:latest
    
  5. 我们将镜像推送到存储库:

    $ docker push 123456789012.dkr.ecr.eu-west-1.amazonaws.com/sm-processing-custom:latest
    
  6. 切换到 Jupyter 笔记本,我们使用新的容器配置一个通用的Processor对象,它相当于我们用于训练的通用Estimator模块。因此,不需要framework_version参数:

    from sagemaker.processing import Processor
    sklearn_processor = Processor( 
        image_uri='123456789012.dkr.ecr.eu-west-1.amazonaws.com/sm-processing-custom:latest',
        role=sagemaker.get_execution_role(),
        instance_type='ml.c5.2xlarge',
        instance_count=1)
    
  7. 使用相同的ProcessingInputProcessingOutput对象,我们运行处理作业。由于我们的处理代码现在存储在容器内,我们不需要像使用SKLearnProcessor时那样传递code参数:

    from sagemaker.processing import ProcessingInput, ProcessingOutput
    sklearn_processor.run(
        inputs=[
            ProcessingInput(
                source=input_data,
                destination='/opt/ml/processing/input')
        ],
        outputs=[
            ProcessingOutput(
                output_name='train_data',
                source='/opt/ml/processing/train/')
        ],
        arguments=[
            '--filename', 'abcnews-date-text.csv.gz'
        ]
    )
    
  8. 一旦训练作业完成,我们可以在 S3 中获取其输出。

这就结束了我们对 SageMaker 中自定义容器的探索。如您所见,只要它适合 Docker 容器,几乎可以运行任何东西。

概述

内置框架非常有用,但有时您需要一些稍微——或完全——不同的东西。无论是从内置容器开始还是从头开始,SageMaker 让您可以完全按照自己的需求构建训练和部署容器。自由为所有人!

在本章中,您学习了如何为数据处理、训练和部署定制 Python 和 R 容器。您还看到了如何使用 SageMaker SDK 及其常规工作流程来使用这些容器。您还了解了 MLflow,这是一个非常好的开源工具,允许您使用 CLI 训练和部署模型。

这就结束了我们对 SageMaker 建模选项的广泛介绍:内置算法、内置框架和自定义代码。在下一章中,您将学习 SageMaker 的功能,帮助您扩展训练作业。

第三部分:深入探索训练

在本节中,你将学习与扩展、模型优化、模型调试和成本优化相关的高级训练技巧。

本节包括以下章节:

  • 第九章扩展你的训练任务

  • 第十章高级训练技巧

第九章:扩展你的训练任务

在前四章中,你学习了如何使用内置算法、框架或你自己的代码来训练模型。

在本章中,你将学习如何扩展训练任务,使其能够在更大的数据集上训练,同时控制训练时间和成本。我们将首先讨论如何根据监控信息和简单的指南来做出扩展决策。你还将看到如何使用Amazon SageMaker Debugger收集分析信息,以了解训练任务的效率。接着,我们将探讨几种扩展的关键技术:管道模式分布式训练数据并行性模型并行性。之后,我们将在大型ImageNet数据集上启动一个大型训练任务,并观察如何进行扩展。最后,我们将讨论用于大规模训练的存储替代方案,即Amazon EFSAmazon FSx for Lustre

我们将讨论以下主题:

  • 理解何时以及如何进行扩展

  • 使用 Amazon SageMaker Debugger 监控和分析训练任务

  • 使用管道模式流式传输数据集

  • 分发训练任务

  • 在 ImageNet 上扩展图像分类模型

  • 使用数据并行性和模型并行性进行训练

  • 使用其他存储服务

技术要求

你需要一个 AWS 账户才能运行本章中包含的示例。如果你还没有账户,请访问aws.amazon.com/getting-started/创建一个。你还应熟悉 AWS 免费套餐(aws.amazon.com/free/),它允许你在一定的使用限制内免费使用许多 AWS 服务。

你需要为你的账户安装并配置 AWS 命令行界面 (CLI) (aws.amazon.com/cli/)。

你需要安装并配置pandasnumpy等工具。

本书中的代码示例可以在 GitHub 上找到,链接为github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装 Git 客户端来访问这些示例(git-scm.com/)。

理解何时以及如何进行扩展

在我们深入了解扩展技术之前,让我们首先讨论在决定是否需要扩展以及如何扩展时应考虑的监控信息。

理解扩展意味着什么

训练日志告诉我们任务持续了多长时间。但单凭这一点,其实并不太有用。多长时间是太长了呢?这个问题似乎非常主观,不是吗?此外,即使在相同的数据集和基础设施上,改变一个超参数也可能显著影响训练时间。批次大小就是一个例子,且这种情况还有很多。

当我们关注训练时间时,我认为我们实际上是在尝试回答三个问题:

  • 训练时间是否与我们的业务需求兼容?

  • 我们是否在充分利用我们所支付的基础设施?我们是否进行了资源不足或过度配置?

  • 我们能在不花更多钱的情况下训练得更快吗?

使训练时间适应业务需求

问问自己这个问题——如果您的训练任务运行速度加倍,直接对您的业务产生什么影响?在很多情况下,诚实的答案应该是没有。没有任何明确的业务指标会因此得到改善。

当然,一些公司运行的训练任务可能需要几天甚至几周——比如自动驾驶或生命科学领域。对他们来说,训练时间的任何显著减少意味着他们能够更快获得结果,进行分析,并启动下一个迭代。

其他一些公司希望拥有最新的模型,并且每小时都会重新训练。当然,训练时间需要控制在一定范围内,以确保能按时完成。

在这两种类型的公司中,扩展能力至关重要。对于其他公司而言,情况就不那么明确。如果您的公司每周或每月训练一次生产模型,训练时间提前 30 分钟能否带来明显的效果?可能不会。

有些人肯定会反对,说他们需要一直训练很多模型。我恐怕这是一种误解。由于 SageMaker 允许您根据需要随时创建按需基础设施,因此训练活动不会受到容量的限制。这种情况适用于物理基础设施,但不适用于云基础设施。即使您每天需要训练 1,000 个 XGBoost 任务,是否真的在乎每个任务需要 5 分钟而不是 6 分钟?可能不。

有人可能会反驳,“训练越快,成本越低。”再次提醒,这是一种误解。SageMaker 训练任务的成本是训练时间(以秒为单位)乘以实例类型的成本,再乘以实例数量。如果您选择更大的实例类型,训练时间很可能会缩短。它是否会缩短足够多,以抵消增加的实例成本?也许会,也许不会。某些训练工作负载能充分利用额外的基础设施,而某些则不能。唯一的办法就是进行测试并做出数据驱动的决策。

正确配置训练基础设施

SageMaker 支持一长串实例类型,看起来像一个非常诱人的糖果店 (aws.amazon.com/sagemaker/pricing/instance-types)。您所需要做的,就是调用一个 API 启动一个 8 GPU 的 EC2 实例——它比您公司允许您购买的任何服务器都要强大。提醒注意——不要忘记 URL 中的“定价”部分!

注意

如果“EC2 实例”这几个词对您来说没有太大意义,我强烈建议您阅读一下关于 Amazon EC2 的资料,docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html

当然,云基础设施不要求你提前支付大量资金购买和托管服务器。然而,AWS 的账单还是会在月底到来。因此,即使使用了诸如 Managed Spot Training(我们将在下一章讨论)等成本优化技术,正确配置你的训练基础设施仍然至关重要。

我的建议始终是一样的:

  • 确定依赖于训练时间的业务需求。

  • 从最小合理的基础设施开始。

  • 测量技术指标和成本。

  • 如果业务需求已满足,你是否过度配置了?有两种可能的答案:

    a) :缩小规模并重复。

    b) :你完成了。

  • 如果业务需求未能满足,识别瓶颈。

  • 进行一些扩展测试(更大的实例类型)和横向扩展(更多实例)。

  • 测量技术指标和成本。

  • 实施最适合你业务环境的解决方案。

  • 重复。

当然,这个过程的效果取决于参与者的水平。要保持批判性思维!“太慢”不是一个数据点——它只是一个意见。

决定何时扩展

在监控信息方面,你可以依赖三个来源:训练日志、Amazon CloudWatch 指标,以及 Amazon SageMaker Debugger 中的性能分析功能。

注意

如果“CloudWatch”对你来说意义不大,我强烈推荐你阅读一下 docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/

训练日志展示了总训练时间和每秒样本数。正如前一部分讨论的,总训练时间不是一个很有用的指标。除非你有非常严格的截止日期,否则最好忽略它。每秒样本数更为有趣。你可以用它来将你的训练任务与研究论文或博客文章中提供的基准进行比较。如果某人在相同的 GPU 上将同一个模型训练速度提高了一倍,你也应该能做到。当你接近那个数字时,你也能知道改进的空间不大,需要考虑其他扩展技术。

CloudWatch 提供了 1 分钟分辨率的粗粒度基础设施指标。对于简单的训练任务,这些指标足以检查你的训练是否高效利用了底层基础设施,并帮助你识别潜在的瓶颈。

对于更复杂的任务(分布式训练、自定义代码等),SageMaker Debugger 提供了细粒度、近实时的基础设施和 Python 指标,分辨率低至 100 毫秒。这些信息能帮助你深入分析并识别复杂的性能和扩展问题。

决定如何扩展

如前所述,你可以选择扩展(迁移到更大的实例)或横向扩展(使用多个实例进行分布式训练)。我们来看看各自的优缺点。

扩展

向上扩展很简单。你只需要更改实例类型。监控保持不变,而且只需要读取一个训练日志。最后但同样重要的是,单实例训练具有可预测性,并且通常能提供最佳的准确性,因为只有一组模型参数需要学习和更新。

另一方面,如果你的算法计算密集度不高或并行性不足,那么额外的计算能力可能不会带来好处。额外的 vCPU 和 GPU 只有在被充分利用时才有用。你的网络和存储层也必须足够快速,以确保它们始终保持繁忙,这可能需要使用 S3 以外的替代方案,从而增加一些额外的工程工作。即使你没有遇到这些问题,也有一个时刻,你会发现再没有更大的实例可以使用了!

通过多 GPU 实例进行扩展

尽管多 GPU 实例非常诱人,但它们也带来了特定的挑战。ml.g4dn.16xlargeml.p3dn.24xlarge 支持 100 Gbit 的网络和超快速的 SSD NVMe 本地存储。但这种性能水平是有代价的,你需要确保它真的是值得的。

你应该记住,更大并不总是更好。无论 GPU 之间的通信多么快速,它都会引入一些开销,这可能会影响较小训练任务的性能。这里,你也需要进行实验,找到最合适的平衡点。

根据我的经验,想要在多 GPU 实例上获得出色的性能需要一些努力。除非模型太大无法放入单个 GPU,或者算法不支持分布式训练,否则我建议首先尝试在单 GPU 实例上进行扩展。

扩展

扩展使你能够将大数据集分发到训练实例的集群中。即使你的训练任务无法线性扩展,与你的单实例训练相比,你仍然会看到明显的加速效果。你可以使用大量较小的实例,这些实例只处理数据集的一个子集,有助于控制成本。

另一方面,数据集需要以能够高效分发到训练集群的格式进行准备。由于分布式训练需要频繁的通信,网络 I/O 也可能成为瓶颈。不过,通常最大的问题是准确性,通常低于单实例训练的准确性,因为每个实例都使用自己的一组模型参数。这可以通过定期同步训练实例的工作来缓解,但这是一项成本高昂的操作,会影响训练时间。

如果你觉得扩展比看起来的要困难,你是对的。让我们尝试用第一个简单的例子将这些概念付诸实践。

扩展 BlazingText 训练任务

第六章训练自然语言处理模型 中,我们使用 BlazingText 和 Amazon 评论数据集训练了情感分析模型。当时,我们只训练了 100,000 条评论。这一次,我们将使用完整的数据集进行训练:180 万条评论(1.51 亿个单词)。

重新利用我们的 SageMaker Processing 笔记本,我们在 ml.c5.9xlarge 实例上处理完整的数据集,将结果存储在 S3 中,并将其馈送到训练作业中。训练集的大小已增长到 720 MB。

为了给 BlazingText 增加额外工作量,我们应用以下超参数来提高任务将学习的词向量的复杂度:

bt.set_hyperparameters(mode='supervised', vector_dim=300, word_ngrams=3, epochs=50)

我们在单个 ml.c5.2xlarge 实例上进行训练。它具有 8 个 vCPU 和 16 GB 的 RAM,并使用 gp2 类型(基于 SSD)。

该任务运行了 2,109 秒(略超过 35 分钟),最大速度为每秒 484 万个词。我们来看一下 CloudWatch 指标:

  1. SageMaker Studio 中的 实验与试验 面板开始,我们定位到训练作业,并右键点击 在试验详情中打开

  2. 然后,我们选择 AWS 设置 标签。向下滚动,我们看到一个名为 查看实例指标 的链接。点击它会直接带我们到训练作业的 CloudWatch 指标页面。

  3. 让我们在 所有指标 中选择 CPUUtilizationMemoryUtilization,并按下图所示进行可视化:

图 9.1 – 查看 CloudWatch 指标

图 9.1 – 查看 CloudWatch 指标

在右侧的 Y 轴上,内存利用率稳定在 20%,所以我们肯定不需要更多的 RAM。

仍然在右侧的 Y 轴上,磁盘利用率在训练过程中约为 3%,当模型被保存时,磁盘利用率会上升到 12%。我们为这个实例分配了太多的存储。默认情况下,SageMaker 实例会获得 30 GB 的 Amazon EBS 存储,那么我们浪费了多少钱呢?在 eu-west-1 区域,SageMaker 的 EBS 成本为每 GB 每月 0.154 美元,因此 30 GB 在 2,117 秒内的费用为 0.15430(2109/(24303600)) = 0.00376 美元。虽然这个金额非常低,但如果每月训练数千个任务,这些费用会累积起来。即使每年节省 10 美元,我们也应该节省!这可以通过在所有估算器中设置 volume_size 参数来轻松实现。

在左侧的 Y 轴上,我们看到 CPU 利用率稳定在 790% 左右,非常接近最大值 800%(8 个 vCPU 在 100% 使用率下)。这个任务显然是计算密集型的。

那么,我们有哪些选择呢?如果 BlazingText 支持监督模式下的分布式训练(但实际上不支持),我们可以考虑使用更小的 ml.c5.xlarge 实例(4 个 vCPU 和 8 GB 的 RAM)进行扩展。那样的 RAM 已经足够了,按小步增长容量是一种良好的实践。这就是右尺寸的核心:不多不少,正好合适。

无论如何,我们此时唯一的选择是扩展实例。查看可用实例列表,我们可以尝试 ml.c5.4xlarge。由于 BlazingText 支持单个 GPU 加速,ml.p3.2xlarge(1 个 NVIDIA V100 GPU)也是一个选项。

注意

写作时,成本效益较高的 ml.g4dn.xlarge 不幸未被 BlazingText 支持。

让我们尝试两者并比较训练时间和成本。

ml.c5.4xlarge 实例在适度价格增加的情况下提供了不错的加速效果。有趣的是,工作负载仍然是计算密集型的,因此我决定尝试更大的 ml.c5.9xlarge 实例(36 个 vCPU),以进一步测试,但加速效果足以抵消成本增加。

GPU 实例速度几乎是原来的 3 倍,因为 BlazingText 已经过优化,能够利用数千个核心。它的费用大约是原来的 3 倍,如果最小化训练时间非常重要,这可能是可以接受的。

这个简单的示例告诉我们,调整训练基础设施并非神秘难懂。通过遵循简单的规则,查看一些指标并运用常识,你可以为你的项目找到合适的实例大小。

现在,让我们介绍 Amazon SageMaker Debugger 中的监控和分析功能,它将为我们提供更多关于训练任务性能的信息。

使用 Amazon SageMaker Debugger 监控和分析训练任务

SageMaker Debugger 包含监控和分析功能,使我们能够以比 CloudWatch 更低的时间分辨率收集基础设施和代码性能信息(通常每 100 毫秒一次)。它还允许我们配置和触发内置或自定义规则,监控训练任务中的不良条件。

分析功能非常容易使用,事实上,它默认是开启的!你可能在训练日志中注意到类似下面这样的行:

2021-06-14 08:45:30 Starting - Launching requested ML instancesProfilerReport-1623660327: InProgress

这告诉我们,SageMaker 正在自动运行一个分析任务,并与我们的训练任务并行进行。分析任务的作用是收集数据点,我们可以在 SageMaker Studio 中显示这些数据点,以便可视化指标并了解潜在的性能问题。

在 SageMaker Studio 中查看监控和分析信息

让我们回到 ml.p3.2xlarge 实例。我们右键点击它,然后选择 打开调试器以获取洞察。这会打开一个新标签,见下图所示:

图 9.2 – 查看监控和分析信息

图 9.2 – 查看监控和分析信息

在顶部,我们可以看到监控默认是开启的,而分析功能没有开启。在 概览 标签页中展开 资源使用情况总结 项目,我们可以看到基础设施指标的总结,如下一个截图所示:

图 9.3 – 查看使用情况总结

图 9.3 – 查看使用情况总结

注意

P50、p95 和 p99 是百分位数。如果你不熟悉这个概念,可以访问 docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Percentiles 了解更多信息。

继续讨论 algo-1。例如,您可以在下一个截图中看到其 GPU 使用情况:

图 9.4 – 查看 GPU 使用情况随时间变化

图 9.4 – 查看 GPU 使用情况随时间变化

我们还可以看到一个非常好的系统使用情况视图,按 vCPU 和 GPU 每个一条线,如下图所示:

图 9.5 – 查看系统使用情况随时间变化

图 9.5 – 查看系统使用情况随时间变化

所有这些信息会在训练任务运行时近乎实时更新。只需启动一个训练任务,打开此视图,几分钟后,图表就会显示并更新。

现在,让我们看看如何在训练任务中启用详细的分析信息。

在 SageMaker Debugger 中启用分析

分析会收集框架指标(TensorFlowPyTorchApache MXNet 和 XGBoost)、数据加载器指标和 Python 指标。对于后者,我们可以使用 CProfilePyinstrument

分析可以在估算器中配置(这是我们将使用的选项)。你也可以在 SageMaker Studio 中的运行任务上手动启用它(见 图 9.2 中的滑块)。

让我们重用我们在 第六章 中的 TensorFlow/Keras 示例,训练计算机视觉模型,并每 100 毫秒收集一次所有分析信息:

  1. 首先,我们创建一个 FrameworkProfile 对象,包含分析、数据加载和 Python 配置的默认设置。对于每一项配置,我们都可以指定精确的时间范围或步骤范围来进行数据收集:

    from sagemaker.debugger import FrameworkProfile, DetailedProfilingConfig, DataloaderProfilingConfig, PythonProfilingConfig, PythonProfiler
    framework_profile_params = FrameworkProfile(
     detailed_profiling_config=DetailedProfilingConfig(), 
     dataloader_profiling_config=DataloaderProfilingConfig(),
     python_profiling_config=PythonProfilingConfig(
       python_profiler=PythonProfiler.PYINSTRUMENT)
    )
    
  2. 然后,我们创建一个 ProfilerConfig 对象,设置框架参数和数据收集的时间间隔:

    from sagemaker.debugger import ProfilerConfig 
    profiler_config = ProfilerConfig(
        system_monitor_interval_millis=100,
        framework_profile_params=framework_profile_params)
    
  3. 最后,我们将这个配置传递给估算器,然后像往常一样进行训练:

    tf_estimator = TensorFlow(
        entry_point='fmnist.py',
        . . .                        
        profiler_config=profiler_config)
    
  4. 当训练任务运行时,分析数据会自动收集并保存在 S3 的默认位置(你可以通过 ProfilingConfig 中的 s3_output_path 参数定义自定义路径)。我们还可以使用 smdebug SDKgithub.com/awslabs/sagemaker-debugger)来加载并检查分析数据。

  5. 训练任务完成后不久,我们会在 概览 标签中看到汇总信息,如下图所示:图 9.6 – 查看分析信息

    图 9.6 – 查看分析信息

  6. 我们还可以下载详细的 HTML 格式报告(见 图 9.2 中的按钮)。例如,它会告诉我们哪些是最昂贵的 GPU 操作。毫无意外地,我们看到我们的 fmnist_model 函数和用于二维卷积的 TensorFlow 操作,如下图所示:

图 9.7 – 查看分析报告

图 9.7 – 查看分析报告

报告还包含训练过程中触发的内置规则信息,提醒我们如 GPU 使用率低、CPU 瓶颈等情况。这些规则有默认设置,如果需要,可以进行自定义。我们将在下一章详细讨论规则,当时我们将讨论如何使用 SageMaker Debugger 调试训练任务。

现在,让我们来看看训练任务中的一些常见扩展问题,以及我们如何解决它们。在此过程中,我们将提到本章后面将介绍的几个 SageMaker 特性。

解决训练中的挑战

我们将深入探讨挑战及其解决方案,如下所示:

我在训练实例上需要大量存储。

如前面的例子所述,大多数 SageMaker 训练实例使用 EBS 卷,你可以在估算器中设置它们的大小。EBS 卷的最大大小为 16 TB,所以应该绰绰有余。如果你的算法需要大量临时存储来存放中间结果,这就是解决方案。

我的数据集非常大,复制到训练实例上需要很长时间。

定义一下“长时间”!如果你在寻找快速解决方案,你可以使用具有高网络性能的实例类型。例如,ml.g4dnml.p3dn 实例支持 弹性结构适配器aws.amazon.com/hpc/efa),可以达到最高 100 Gbit/s 的速度。

如果这还不够,而且如果你正在对单个实例进行训练,你应该使用管道模式,它从 S3 流式传输数据,而不是复制数据。

如果训练是分布式的,你可以将 FullyReplicated 更改为 ShardedbyS3Key,这将仅将数据集的一部分分发到每个实例。这可以与管道模式结合使用,以提高性能。

我的数据集非常大,无法完全装入内存。

如果你想坚持使用单个实例,解决这个问题的快速方法是进行扩展。ml.r5d.24xlargeml.p3dn.24xlarge 实例具有 768 GB 的内存!如果分布式训练是一个选项,那么你应该配置它并应用数据并行处理。

CPU 使用率很低。

假设你没有过度配置,最可能的原因是 I/O 延迟(网络或存储)。CPU 被阻塞,因为它在等待从存储位置获取数据。

首先你应该检查数据格式。如前几章所述,RecordIOTFRecord 文件是不可避免的。如果你使用的是其他格式(如 CSV、单独的图片等),你应该在调整基础设施之前从这些地方入手。

如果数据是从 S3 复制到 EBS 卷的,你可以尝试使用具有更多 EBS 带宽的实例。有关详细信息,请访问以下位置:

docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html

你还可以切换到具有本地 NVMe 存储的实例类型(g4dn 和 p3dn)。如果问题仍然存在,你应该检查读取数据并将其传递给训练算法的代码。它可能需要更多的并行处理。

如果数据是通过管道模式从 S3 流式传输的,那么你不太可能已经达到了 25 GB/s 的最大传输速度,但值得检查 CloudWatch 中的实例指标。如果你确定没有其他原因导致这种情况,你应该转向其他文件存储服务,如 Amazon EFSAmazon FSx for Lustre

GPU 内存利用率低。

GPU 没有从 CPU 获取足够的数据。你需要增加批次大小,直到内存利用率接近 100%。如果增加过多,你将会收到一个令人烦恼的 out of memory 错误信息,如下所示:

/opt/brazil-pkg-cache/packages/MXNetECL/MXNetECL-v1.4.1.1457.0/AL2012/generic-flavor/src/src/storage/./pooled_storage_manager.h:151: cudaMalloc failed: out of memory

在使用数据并行配置的多 GPU 实例时,你应该将传递给估算器的批次大小乘以实例中 GPU 的数量。

增加批次大小时,你需要考虑可用的训练样本数量。例如,我们在 第五章《训练计算机视觉模型》中使用的 Pascal VOC 数据集只有 1,464 个样本,因此可能没有必要将批次大小增加到 64 或 128 以上。

最后,批次大小对作业收敛性有重要影响。非常大的批次可能会使收敛变慢,因此你可能需要相应地增加学习率。

有时,你只能接受 GPU 内存利用率低的事实!

GPU 利用率低。

也许你的模型根本没有足够大,无法充分利用 GPU。你应该尝试在更小的 GPU 上进行缩放。

如果你正在处理一个大型模型,GPU 可能会因为 CPU 无法提供足够的数据而停滞。如果你能够控制数据加载代码,应该尝试增加更多的并行性,例如为数据加载和预处理增加更多的线程。如果无法控制代码,你应该尝试使用更多 vCPU 的更大实例类型。希望数据加载代码能够充分利用它们。

如果数据加载代码中有足够的并行性,那么慢 I/O 很可能是罪魁祸首。你应该寻找更快的替代方案(如 NVMe、EFS 或 FSx for Lustre)。

GPU 利用率高。

这是一个好情况!你在高效地使用你为其支付的基础设施。正如前面示例中讨论的,你可以尝试扩展(更多 vCPU 或更多 GPU),或者横向扩展(更多实例)。将两者结合使用,适用于诸如深度学习之类的高度并行工作负载。

现在我们对作业扩展有了更多的了解,接下来让我们学习更多 SageMaker 的功能,从管道模式开始。

使用管道模式流式传输数据集

估算器的默认设置是将数据集复制到训练实例中,这被称为 文件模式。而 管道模式 则直接从 S3 流式传输数据。该功能的名称来自于它使用 Unix 命名管道(也称为 FIFOs):在每个 epoch 开始时,每个输入通道会创建一个管道。

管道模式消除了将数据复制到训练实例的需求。显然,训练任务开始得更快。它们通常也运行得更快,因为管道模式经过高度优化。另一个好处是,您不必为训练实例上的数据集预置存储。

减少训练时间和存储意味着您可以节省成本。数据集越大,节省的成本就越多。您可以在以下链接找到基准测试:

aws.amazon.com/blogs/machine-learning/accelerate-model-training-using-faster-pipe-mode-on-amazon-sagemaker/

在实践中,您可以开始使用管道模式处理数百兆字节及以上的数据集。事实上,这项功能使您能够处理无限大的数据集。由于存储和内存需求不再与数据集大小挂钩,因此您的算法可以处理的数据量没有实际限制。可以在 PB 级别的数据集上进行训练。

使用管道模式与内置算法

管道模式的主要候选者是内置算法,因为大多数内置算法本身就支持此模式:

  • 线性学习器k-均值k-最近邻主成分分析随机切割森林神经主题建模:RecordIO 封装的 protobuf 或 CSV 数据

  • 因式分解机潜在狄利克雷分配:RecordIO 封装的 protobuf 数据

  • BlazingText(监督模式):增强的清单

  • 图像分类目标检测:使用 RecordIO 封装的 protobuf 数据或增强的清单

  • 语义分割:增强的清单。

您应该已经熟悉 im2rec 工具,该工具具有生成多个列表文件(--chunks)的选项。如果您已有现成的列表文件,当然也可以自己分割它们。

当我们讨论由 SageMaker Ground Truth 标注的数据集时,我们曾经查看过 增强清单 格式,详见 第五章,《训练计算机视觉模型》。对于计算机视觉算法,JSON Lines 文件包含 S3 中图像的位置及其标注信息。您可以在以下链接了解更多内容:

docs.aws.amazon.com/sagemaker/latest/dg/augmented-manifest.html

使用管道模式与其他算法和框架

TensorFlow 支持管道模式,感谢 AWS 实现的 PipeModeDataset 类。以下是一些有用的资源:

对于其他框架以及你自己的自定义代码,仍然可以在训练容器中实现管道模式。一个 Python 示例可以在以下链接找到:

github.com/awslabs/amazon-sagemaker-examples/tree/master/advanced_functionality/pipe_bring_your_own

使用 MLIO 简化数据加载

MLIO (https://github.com/awslabs/ml-io) 是一个 AWS 开源项目,允许你使用管道模式加载存储在内存、本地存储或 S3 中的数据。然后,可以将数据转换为不同的流行格式。

这里是一些高级特性:

  • 输入格式CSVParquet、RecordIO-protobuf、JPEGPNG

  • 转换格式:NumPy 数组、SciPy 矩阵、Pandas DataFrame、TensorFlow 张量、PyTorch 张量、Apache MXNet 数组以及 Apache Arrow

  • API 可用于 Python 和 C++

现在,让我们用管道模式运行一些示例。

使用管道模式训练分解机器

我们将重新访问在 第四章 中使用的示例,训练机器学习模型,当时我们在 MovieLens 数据集上训练了一个推荐模型。当时,我们使用了一个小版本的数据集,限制为 100,000 条评论。这一次,我们将使用最大版本:

  1. 我们下载并解压数据集:

    %%sh
    wget http://files.grouplens.org/datasets/movielens/ml-25m.zip
    unzip ml-25m.zip
    
  2. 该数据集包括来自 162,541 个用户的 25,000,095 条评论,涉及 62,423 部电影。与 100k 版本不同,电影编号不是顺序的。最后一部电影的 ID 为 209,171,这无谓地增加了特征的数量。另一种选择是重新编号电影,但我们这里不这样做:

    num_users=162541
    num_movies=62423
    num_ratings=25000095
    max_movieid=209171
    num_features=num_users+max_movieid
    
  3. 就像在 第四章**,训练机器学习模型 中一样,我们将数据集加载到一个稀疏矩阵(来自 SciPy 的 lil_matrix),并将其拆分为训练集和测试集,然后将这两个数据集转换为 RecordIO 封装的 protobuf。考虑到数据集的大小,这个过程在一个小型 Studio 实例上可能需要 45 分钟。然后,我们将数据集上传到 S3。

  4. 接下来,我们配置两个输入通道,并将它们的输入模式设置为管道模式,而不是文件模式:

    From sagemaker import TrainingInput
    s3_train_data = TrainingInput (
        train_data,                                
        content_type='application/x-recordio-protobuf',
        input_mode='Pipe')
    s3_test_data = TrainingInput (
       test_data,                                        
       content_type='application/x-recordio-protobuf',                                           
       input_mode='Pipe')
    
  5. 然后,我们配置估算器,并像往常一样在 ml.c5.xlarge 实例(4 个虚拟 CPU、8 GB 内存,$0.23 每小时,位于 eu-west-1 区域)上进行训练。

查看训练日志,我们看到如下信息:

2021-06-14 15:02:08 Downloading - Downloading input data
2021-06-14 15:02:08 Training - Downloading the training image...

如预期的那样,复制数据集并未花费任何时间。文件模式下的相同步骤需要 66 秒。即使数据集只有 1.5 GB,管道模式依然非常有效。随着数据集的增大,这一优势将只会增加!

现在,让我们继续进行分布式训练。

分布式训练任务

分布式训练让你通过在 CPU 或 GPU 实例的集群上运行训练作业来扩展训练规模。它可以用来解决两个不同的问题:非常大的数据集和非常大的模型。

理解数据并行和模型并行

一些数据集太大,单个 CPU 或 GPU 在合理时间内无法完成训练。通过使用一种叫做数据并行的技术,我们可以将数据分配到训练集群中。完整的模型仍然加载到每个 CPU/GPU 上,但每个 CPU/GPU 只接收数据集的一部分,而不是整个数据集。理论上,这应该根据参与的 CPU/GPU 数量线性加速训练,但如你所料,现实往往不同。

信不信由你,一些最先进的深度学习模型太大,无法装载到单个 GPU 上。通过使用一种叫做模型并行的技术,我们可以将其拆分,并将各层分布到 GPU 集群中。因此,训练批次将跨多个 GPU 流动,由所有层共同处理。

现在,让我们看看在哪里可以在 SageMaker 中使用分布式训练。

为内置算法分发训练

数据并行几乎适用于所有内置算法(语义分割和 LDA 是显著的例外)。由于它们是用 Apache MXNet 实现的,因此会自动使用其原生的分布式训练机制。

为内置框架分发训练

TensorFlow、PyTorch、Apache MXNet 和Hugging Face都有原生的数据并行机制,并且都支持 SageMaker。Horovod (github.com/horovod/horovod) 也可用。

对于 TensorFlow、PyTorch 和 Hugging Face,你还可以使用更新的SageMaker 分布式数据并行库SageMaker 模型并行库。这两者将在本章后面讨论。

分布式训练通常需要在训练代码中进行框架特定的修改。你可以在框架文档中找到更多信息(例如 www.tensorflow.org/guide/distributed_training),以及在github.com/awslabs/amazon-sagemaker-examples上托管的示例笔记本:

  • sagemaker-python-sdk/tensorflow_script_mode_horovod

    b) advanced_functionality/distributed_tensorflow_mask_rcnn

  • sagemaker-python-sdk/keras_script_mode_pipe_mode_horovod

  • sagemaker-python-sdk/pytorch_horovod_mnist

每个框架都有其独特之处,但我们在前面章节中讨论的内容依然适用。如果你想最大限度地利用你的基础设施,需要关注批处理大小、同步等方面。进行实验、监控、分析并迭代!

为自定义容器分发训练

如果你使用的是自己的定制容器进行训练,你必须实现自己的分布式训练机制。说实话,这会是非常繁琐的工作。SageMaker 仅帮助提供集群实例的名称和容器网络接口的名称。这些信息可以在容器内的/opt/ml/input/config/resourceconfig.json文件中找到。

你可以在以下链接找到更多信息:

docs.aws.amazon.com/sagemaker/latest/dg/your-algorithms-training-algo-running-container.html

现在是时候进行分布式训练示例了!

在 ImageNet 上扩展图像分类模型

第五章,《训练计算机视觉模型》中,我们使用包含狗和猫图像的小型数据集(25,000 张训练图像)来训练图像分类算法。这一次,我们来挑战一个稍大的数据集。

我们将从头开始在ImageNet数据集上训练一个 ResNet-50 网络——这是许多计算机视觉应用的参考数据集(www.image-net.org)。2012 版包含 1,281,167 张训练图像(140 GB)和 50,000 张验证图像(6.4 GB),涵盖 1,000 个类别。

如果你想在较小的规模上进行实验,可以使用数据集的 5-10%。最终的准确度可能不会那么好,但对我们的目的来说无关紧要。

准备 ImageNet 数据集

这需要大量的存储空间——数据集大小为 150 GB,因此请确保你有至少 500 GB 的可用空间,以存储 ZIP 格式和处理后的格式。你还需要大量的带宽和耐心来下载它。我使用的是运行在us-east-1区域的 EC2 实例,我的下载花费了五天

  1. 访问 ImageNet 网站,注册以下载数据集并接受条款。你将获得一个用户名和访问密钥,允许你下载数据集。

  2. 其中一个 TensorFlow 的库包含一个很好的脚本,可以下载并解压数据集。使用nohup非常关键,这样即使会话终止,过程仍然会继续运行:

    $ git clone https://github.com/tensorflow/models.git
    $ export IMAGENET_USERNAME=YOUR_USERNAME
    $ export IMAGENET_ACCESS_KEY=YOUR_ACCESS_KEY
    $ cd models/research/inception/inception/data
    $ mv imagenet_2012_validation_synset_labels.txt synsets.txt
    $ nohup bash download_imagenet.sh . synsets.txt >& download.log &
    
  3. 一旦完成(再次强调,下载需要几天时间),imagenet/train目录包含训练数据集(每个类别一个文件夹)。imagenet/validation目录包含 50,000 张图片,存储在同一个文件夹中。我们可以使用一个简单的脚本按类别组织这些文件夹:

    $ wget https://raw.githubusercontent.com/juliensimon/aws/master/mxnet/imagenet/build_validation_tree.sh
    $ chmod 755 build_validation_tree.sh
    $ cd imagenet/validation
    $ ../../build_validation_tree.sh
    $ cd ../..
    
  4. 我们将使用 Apache MXNet 库中的im2rec工具来构建 RecordIO 文件。让我们先安装依赖项并获取im2rec

    $ sudo yum -y install python-devel python-pip opencv opencv-devel opencv-python
    $ pip3 install mxnet opencv-python –user
    $ wget https://raw.githubusercontent.com/apache/incubator-mxnet/master/tools/im2rec.py
    
  5. imagenet目录中,我们运行im2rec两次——第一次构建列表文件,第二次构建 RecordIO 文件。我们创建了大约 1 GB 大小的 RecordIO 文件(稍后我们会解释为什么这很重要)。我们还将图像的较小尺寸调整为224,这样算法就不需要再做此操作:

    $ cd imagenet
    $ python3 ../im2rec.py --list --chunks 6 --recursive val validation
    $ python3 ../im2rec.py --num-thread 16 --resize 224 val_ validation
    $ python3 ../im2rec.py --list --chunks 140 --recursive train train
    $ python3 ../im2rec.py --num-thread 16 --resize 224 train_ train
    
  6. 最后,我们将数据集同步到 S3:

    $ mkdir -p input/train input/validation
    $ mv train_*.rec input/train
    $ mv val_*.rec input/validation
    $ aws s3 sync input s3://sagemaker-us-east-1-123456789012/imagenet-split/input/
    

数据集现在已经准备好进行训练。

定义我们的训练作业

现在数据集已经准备好,我们需要思考一下训练作业的配置。具体来说,我们需要考虑以下内容:

  • 输入配置,定义数据集的位置和属性

  • 运行训练作业的基础设施要求

  • 配置算法的超参数

让我们详细看看这些项目。

定义输入配置

考虑到数据集的大小,管道模式似乎是一个好主意。出于好奇,我尝试过使用文件模式进行训练。即使拥有 100 Gbit/s 的网络接口,将数据集从 S3 复制到本地存储也花费了近 25 分钟。所以,还是使用管道模式吧!

你可能会想,为什么我们要把数据集拆分成多个文件。原因如下:

  • 一般来说,多个文件创造了更多的并行机会,使得编写快速数据加载和处理代码变得更加容易。

  • 我们可以在每个训练周期开始时对文件进行洗牌,消除由于样本顺序导致的任何潜在偏差。

  • 它使得处理数据集的一部分变得非常容易。

既然我们已经定义了输入配置,那基础设施要求呢?

定义基础设施要求

ImageNet 是一个庞大而复杂的数据集,需要大量训练才能达到良好的准确度。

经过快速测试,单个ml.p3.2xlarge实例在批量大小设置为 128 时,可以以每秒约 335 张图像的速度处理数据集。由于我们有大约 1,281,167 张图像,可以预计一个周期大约需要 3,824 秒(约 1 小时 4 分钟)。

假设我们需要训练 150 个周期以获得合理的准确率,我们预计作业将持续(3,824/3,600)*150 = 158 小时(约 6.5 天)。从商业角度来看,这可能不可接受。为了记录,在us-east-1区域,每小时$3.825 的实例费用下,这个作业大约需要花费$573。

我们来尝试使用ml.p3dn.24xlarge实例来加速我们的作业。每个实例配备八个 NVIDIA V100,每个有 32 GB 的 GPU 内存(是其他p3实例的两倍)。它们还配备 96 个Intel Skylake核心,768 GB 的 RAM,以及 1.8 TB 的本地 NVMe 存储。虽然我们在这里不会使用它,但后者对于长期运行的大规模作业来说是一个极好的存储选项。最后但同样重要的是,这种实例类型具有 100 Gbit/s 的网络连接,适合从 S3 进行数据流传输和实例间通信。

注意

每小时$35.894 的费用,你可能不想在家或工作时尝试,除非获得许可。你的服务配额可能根本不允许你运行如此多的基础设施,而且你必须首先联系 AWS 支持。

在下一章中,我们将讨论托管抢占训练—一种大幅降低训练成本的好方法。我们将在介绍完这一主题后再次回到 ImageNet 示例,因此你现在一定不要进行训练!

在 ImageNet 上训练

让我们配置训练任务:

  1. 我们在两个输入通道上配置了管道模式。训练通道的文件会被打乱,以增加额外的随机性:

    prefix = 'imagenet-split'
    s3_train_path = 
    's3://{}/{}/input/training/'.format(bucket, prefix)
    s3_val_path = 
    's3://{}/{}/input/validation/'.format(bucket, prefix)
    s3_output = 
    's3://{}/{}/output/'.format(bucket, prefix)
    from sagemaker import TrainingInput
    from sagemaker.session import ShuffleConfig
    train_data = TrainingInput(
       s3_train_path
       shuffle_config=ShuffleConfig(59),
       content_type='application/x-recordio',
       input_mode='Pipe')
    validation_data = TrainingInput(
       s3_val_path,
       content_type='application/x-recordio', 
       input_mode='Pipe')
    s3_channels = {'train': train_data, 
                   'validation': validation_data}
    
  2. 首先,我们配置 Estimator 模块,使用一个单独的 ml.p3dn.24xlarge 实例:

    from sagemaker import image_uris
    region_name = boto3.Session().region_name
    container = image_uris.retrieve(
        'image-classification', region)
    ic = sagemaker.estimator.Estimator(
         container,
         role= sagemaker.get_execution_role(),
         instance_count=1,                                 
         instance_type='ml.p3dn.24xlarge',
         output_path=s3_output)
    
  3. 我们设置了超参数,首先使用合理的批量大小 1,024,然后启动训练:

    ic.set_hyperparameters(
        num_layers=50,                 
        use_pretrained_model=0,        
        num_classes=1000,              
        num_training_samples=1281167,
        mini_batch_size=1024,
        epochs=2,
        kv_store='dist_sync',
        top_k=3)         
    

更新批量大小

每个 epoch 的时间是 727 秒。对于 150 个 epoch,这相当于 30.3 小时的训练(1.25 天),成本为 $1,087。好消息是我们加速了 5 倍。坏消息是成本增加了 2 倍。让我们开始扩展。

查看 CloudWatch 中的总 GPU 利用率,我们看到它没有超过 300%。也就是说,每个 GPU 的利用率为 37.5%。这可能意味着我们的批量大小太小,不能让 GPU 完全忙碌。让我们将批量大小提升到 (1,024/0.375)=2730,四舍五入到 2,736,以便能被 8 整除:

注意

根据算法版本,可能会出现 out of memory 错误。

ic.set_hyperparameters(
    num_layers=50,                 
    use_pretrained_model=0,        
    num_classes=1000,              
    num_training_samples=1281167,
    mini_batch_size=2736,         # <--------
    epochs=2,
    kv_store='dist_sync',
    top_k=3)         

再次训练,现在每个 epoch 持续 758 秒。看起来这次将 GPU 内存使用率最大化并没有带来太大的差异。也许它被同步梯度的成本抵消了?无论如何,尽可能保持 GPU 核心的高负载是一个好习惯。

增加更多实例

现在,让我们添加第二个实例以扩展训练任务:

ic = sagemaker.estimator.Estimator(
    container,
    role,
    instance_count=2,                 # <--------
    instance_type='ml.p3dn.24xlarge',
    output_path=s3_output)

现在每个 epoch 的时间是 378 秒!对于 150 个 epoch,这相当于 15.75 小时的训练,成本为 $1,221。与我们最初的任务相比,这速度提高了 2 倍,成本降低了 3 倍!

那四个实例怎么样?看看我们能否继续扩展:

ic = sagemaker.estimator.Estimator(
    container,
    role,
    instance_count=4,                 # <--------
    instance_type='ml.p3dn.24xlarge',
    output_path=s3_output)

现在每个 epoch 的时间是 198 秒!对于 150 个 epoch,这相当于 8.25 小时的训练,成本为 $1,279。我们再次加速了 2 倍,且成本略有增加。

现在,我们是否要训练八个实例?当然!谁不想在 64 个 GPU、327K CUDA 核心和 2 TB(!)GPU 内存上进行训练呢?

ic = sagemaker.estimator.Estimator(
    container,
    role,
    instance_count=8,                 # <--------
    instance_type='ml.p3dn.24xlarge',
    output_path=s3_output)

现在每个 epoch 的时间是 99 秒。对于 150 个 epoch,这相当于 4.12 小时的训练,成本为 $1,277。我们再次加速了 2 倍,而且没有增加任何额外成本。

总结一下

以最初成本的 2 倍,我们通过管道模式、分布式训练和最先进的 GPU 实例将训练任务加速了 38 倍。

图 9.8 训练任务的结果

图 9.8 训练任务的结果

不错!节省训练时间有助于加快迭代速度,更快地得到高质量模型,并更早地进入生产阶段。我很确定这很容易抵消额外的成本。不过,在下一章中,我们将看到如何通过托管的抢占式训练大幅削减训练成本。

既然我们已经熟悉了分布式训练,接下来让我们看看两个新的 SageMaker 库,用于数据并行和模型并行。

使用 SageMaker 数据和模型并行库进行训练

这两个库是在 2020 年底推出的,显著提升了大规模训练任务的性能。

SageMaker 分布式数据并行(DDP)库实现了 GPU 集群上计算的高效分布。它通过消除 GPU 间通信来优化网络通信,最大化它们在训练中所花费的时间和资源。你可以通过以下链接了解更多内容:

aws.amazon.com/blogs/aws/managed-data-parallelism-in-amazon-sagemaker-simplifies-training-on-large-datasets/

DDP 支持 TensorFlow、PyTorch 和 Hugging Face。前两个需要对训练代码进行小的修改,但最后一个则不需要。由于 DDP 仅在大规模、长时间运行的训练任务中才有意义,因此可用的实例类型包括ml.p3.16xlargeml.p3dn24dnxlargeml.p4d.24xlarge

SageMaker 分布式模型并行(DMP)库解决了另一个问题。某些大型深度学习模型过于庞大,无法适应单个 GPU 的内存。另一些模型勉强能够适应,但会迫使你使用非常小的批量大小,从而减慢训练速度。DMP 通过自动将模型分割到 GPU 集群中,并协调数据在这些分区之间流动,解决了这个问题。你可以通过以下链接了解更多内容:

aws.amazon.com/blogs/aws/amazon-sagemaker-simplifies-training-deep-learning-models-with-billions-of-parameters/

DMP 支持 TensorFlow、PyTorch 和 Hugging Face。再次说明,前两个需要对训练代码进行小的修改,而 Hugging Face 则不需要,因为其Trainer API 完全支持 DMP。

让我们通过回顾我们在第七章中的 TensorFlow 和 Hugging Face 示例来尝试这两种方法,使用内置框架扩展机器学习服务

使用 SageMaker DDP 进行 TensorFlow 训练

我们最初的代码使用了高级的 Keras API:compile()fit()等。为了实现 DDP,我们需要重写这些代码,使用tf.GradientTape()并实现一个自定义训练循环。其实并不像听起来那么难,接下来让我们开始吧:

  1. 首先,我们需要导入并初始化 DDP:

    import smdistributed.dataparallel.tensorflow as sdp
    sdp.init()
    
  2. 然后,我们获取实例上存在的 GPU 列表,并为它们分配一个本地的 DDP 排名,这是一个简单的整数标识符。我们还允许内存增长,这是 DDP 所需的 TensorFlow 功能:

    gpus = tf.config.experimental.
                list_physical_devices('GPU')
    if gpus:
        tf.config.experimental.set_visible_devices(
            gpus[sdp.local_rank()], 'GPU')
    for gpu in gpus:
        tf.config.experimental.set_memory_growth(
            gpu, True)
    
  3. 根据文档的建议,我们根据训练集群中 GPU 的数量增加批量大小和学习率。这对任务的准确性至关重要:

    batch_size = args.batch_size*sdp.size()
    lr         = args.learning_rate*sdp.size()
    
  4. 接着,我们创建一个损失函数和优化器。标签在预处理过程中已经进行了独热编码,所以我们使用 CategoricalCrossentropy,而不是 SparseCategoricalCrossentropy。我们还在所有 GPU 上初始化模型和优化器变量:

    loss = tf.losses.CategoricalCrossentropy()
    opt = tf.optimizers.Adam(lr)
    sdp.broadcast_variables(model.variables, root_rank=0)
    sdp.broadcast_variables(opt.variables(), root_rank=0)
    
  5. 接下来,我们需要编写一个 training_step() 函数,并用 @tf.function 装饰它,以便 DDP 能识别它。顾名思义,这个函数负责在训练集群中的每个 GPU 上运行训练步骤:预测一个批次,计算损失,计算梯度并应用它们。它基于 tf.GradientTape() API,我们只是将其包装成 sdp.DistributedGradientTape()。在每个训练步骤的结束时,我们使用 sdp.oob_allreduce() 来计算平均损失,使用来自所有 GPU 的值:

    @tf.function
    def training_step(images, labels):
        with tf.GradientTape() as tape:
            probs = model(images, training=True)
            loss_value = loss(labels, probs)
        tape = sdp.DistributedGradientTape(tape)
        grads = tape.gradient(
            loss_value, model.trainable_variables)
        opt.apply_gradients(
            zip(grads, model.trainable_variables))
        loss_value = sdp.oob_allreduce(loss_value)
        return loss_value
    
  6. 然后,我们编写训练循环。没有特别之处。为了避免日志污染,我们仅打印主 GPU(rank 0)的消息:

    steps = len(train)//batch_size
    for e in range(epochs):
        if sdp.rank() == 0:
            print("Start epoch %d" % (e))
        for batch, (images, labels) in 
        enumerate(train.take(steps)):
            loss_value = training_step(images, labels)
            if batch%10 == 0 and sdp.rank() == 0:
                print("Step #%d\tLoss: %.6f" 
                      % (batch, loss_value))
    
  7. 最后,我们只在 GPU #0 上保存模型:

    if sdp.rank() == 0:
        model.save(os.path.join(model_dir, '1'))
    
  8. 接下来,在我们的笔记本中,我们使用两个 ml.p3.16xlarge 实例来配置此任务,并通过在估算器中添加额外参数启用数据并行:

    from sagemaker.tensorflow import TensorFlow
    tf_estimator = TensorFlow(
        . . .
        instance_count=2, 
        instance_type='ml.p3.16xlarge',
        hyperparameters={'epochs': 10, 
            'learning-rate': 0.0001, 'batch-size': 32},
        distribution={'smdistributed': 
            {'dataparallel': {'enabled': True}}}
    )
    
  9. 我们照常训练,可以在训练日志中看到步骤在进行:

    [1,0]<stdout>:Step #0#011Loss: 2.306620
    [1,0]<stdout>:Step #10#011Loss: 1.185689
    [1,0]<stdout>:Step #20#011Loss: 0.909270
    [1,0]<stdout>:Step #30#011Loss: 0.839223
    [1,0]<stdout>:Step #40#011Loss: 0.772756
    [1,0]<stdout>:Step #50#011Loss: 0.678521
    . . .
    

如你所见,使用 SageMaker DDP 扩展训练任务其实并不难,尤其是当你的训练代码已经使用了低级 API 时。我们这里使用了 TensorFlow,PyTorch 的过程非常相似。

现在,让我们看看如何使用这两个库训练大型 Hugging Face 模型。确实,最先进的 NLP 模型在不断变大和变得更加复杂,它们是数据并行和模型并行的理想候选者。

在 Hugging Face 上使用 SageMaker DDP 进行训练

由于 Hugging Face 的 Trainer API 完全支持 DDP,我们不需要修改训练脚本。太棒了!只需在估算器中添加一个额外的参数,设置实例类型和实例数量,就可以开始了:

huggingface_estimator = HuggingFace(
   . . . 
   distribution={'smdistributed': 
                    {'dataparallel':{'enabled': True}}
                }
)

在 Hugging Face 上使用 SageMaker DMP 进行训练

添加 DMP 也不难。我们的 Hugging Face 示例使用了一个 DistilBERT 模型,大小大约为 250 MB。这个大小足以在单个 GPU 上运行,但我们还是来尝试用 DMP 进行训练:

  1. 首先,我们需要将 processes_per_host 配置为小于或等于训练实例上 GPU 数量的值。在这里,我将使用一个带有 8 个 NVIDIA V100 GPU 的 ml.p3dn.24xlarge 实例:

    mpi_options = {
       'enabled' : True,
       'processes_per_host' : 8
    }
    
  2. 然后,我们配置 DMP 选项。这里,我设置了最重要的参数——我们希望的模型分区数(partitions),以及为了增加并行性而应复制多少次它们(microbatches)。换句话说,我们的模型将被分成四个部分,每个部分会被复制,并且这八个部分将分别运行在不同的 GPU 上。你可以在以下链接找到更多关于所有参数的信息:

    sagemaker.readthedocs.io/en/stable/api/training/smd_model_parallel_general.html

    smp_options = {
        'enabled': True,
        'parameters": {
            'microbatches': 2,
            'partitions': 4
        }
    }
    
  3. 最后,我们配置估算器并按常规进行训练:

    huggingface_estimator = HuggingFace(
        . . .
        instance_type='ml.p3dn.24xlarge',
        instance_count=1,
        distribution={'smdistributed': 
            {'modelparallel': smp_options},
             'mpi': mpi_options}
    )
    

    您可以在这里找到更多示例:

本章结束时,我们将查看您应考虑的针对大规模、高性能训练任务的存储选项。

使用其他存储服务

到目前为止,我们已经使用 S3 存储训练数据。在大规模情况下,吞吐量和延迟可能成为瓶颈,因此有必要考虑其他存储服务:

使用 SageMaker 和 Amazon EFS

EFS 是一个托管存储服务,兼容 NFS v4。它允许您创建可以附加到 EC2 实例和 SageMaker 实例的卷。这是一种方便的数据共享方式,您可以使用它来扩展大规模训练任务的 I/O。

默认情况下,文件存储在标准类中。您可以启用生命周期策略,自动将一段时间内未访问的文件移至低频访问,这种方式较慢,但更加具有成本效益。

您可以选择两种吞吐量模式中的一种:

  • 突发吞吐量:突发积分会随着时间积累,突发容量取决于文件系统的大小:100 MB/s,并且每个 TB 的存储会额外增加 100 MB/s。

  • 预配置吞吐量:您可以设置期望的吞吐量,范围从 1 到 1,024 MB/s。

您还可以选择两种性能模式中的一种:

  • 通用用途:对于大多数应用程序来说,这种模式足够了。

  • 最大 I/O:当数十个或数百个实例访问卷时,使用这个设置。吞吐量将最大化,但会牺牲延迟。

我们创建一个 8 GB 的 EFS 卷。然后,我们将在 EC2 实例上挂载它,复制之前准备好的Pascal VOC数据集,并训练一个目标检测任务。为了保持合理的成本,我们不会扩展该任务,但无论规模大小,整个过程都是相同的。

配置 EFS 卷

EFS 控制台使创建卷变得极其简单。你可以在 docs.aws.amazon.com/efs/latest/ug/getting-started.html 找到详细的操作说明:

  1. 我们将卷名称设置为 sagemaker-demo

  2. 我们选择我们的默认 VPC,并使用 区域 可用性。

  3. 我们创建了卷。一旦卷准备好,你应该会看到类似下面的截图:

图 9.9– 创建 EFS 卷

](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_09_8.jpg)

图 9.9– 创建 EFS 卷

EFS 卷已准备好接收数据。我们现在将创建一个新的 EC2 实例,挂载 EFS 卷,并复制数据集。

创建 EC2 实例

由于 EFS 卷位于 VPC 内部,它们只能由位于同一 VPC 中的实例访问。这些实例必须还拥有一个 安全组,该安全组允许进入的 NFS 流量:

  1. 在 VPC 控制台中(console.aws.amazon.com/vpc/#vpcs:sort=VpcId),我们记录下默认 VPC 的 ID。对我而言,它是 vpc-def884bb

  2. 仍然在 VPC 控制台中,我们转到 子网 部分(console.aws.amazon.com/vpc/#subnets:sort=SubnetId)。我们记录下默认 VPC 中所有子网的子网 ID 和可用区。

    对我来说,它们看起来像下图所示:

    图 9.10 – 查看默认 VPC 的子网

    ](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_09_9.jpg)

    图 9.10 – 查看默认 VPC 的子网

  3. 进入 EC2 控制台,我们创建一个 EC2 实例。我们选择 Amazon Linux 2 镜像和 t2.micro 实例类型。

  4. 接下来,我们设置 eu-west-1a 可用区。我们还将其分配给我们刚刚创建的安全组,IAM 角色分配给具有适当 S3 权限的角色,以及 文件系统分配给我们刚刚创建的 EFS 文件系统。我们还确保勾选了自动创建并附加所需安全组的选项。

  5. 在接下来的界面中,我们保持存储和标签不变,并附加一个允许 ssh 连接的安全组。最后,我们启动实例创建。

访问 EFS 卷

一旦实例准备好,我们可以通过 ssh 连接到它:

  1. 我们看到 EFS 卷已自动挂载:

    [ec2-user]$ mount|grep efs
    127.0.0.1:/ on /mnt/efs/fs1 type nfs4
    
  2. 我们移动到该位置,并从 S3 同步我们的 PascalVOC 数据集。由于文件系统以 root 身份挂载,我们需要使用 sudo

    [ec2-user] cd /mnt/efs/fs1
    [ec2-user] sudo aws s3 sync s3://sagemaker-ap-northeast-2-123456789012/pascalvoc/input input
    

工作完成。我们可以退出并关闭或终止实例,因为我们不再需要它。

现在,让我们使用这个数据集进行训练。

使用 EFS 训练物体检测模型

训练过程是相同的,唯一不同的是输入数据的位置:

  1. 我们不再使用 TrainingInput 对象来定义输入通道,而是使用 FileSystemInput 对象,传入我们 EFS 卷的标识符和卷内的绝对数据路径:

    from sagemaker.inputs import FileSystemInput
    efs_train_data = FileSystemInput(
                     file_system_id='fs-fe36ef34',
                     file_system_type='EFS',
                     directory_path='/input/train')
    efs_validation_data = FileSystemInput(
                          file_system_id='fs-fe36ef34',
                          file_system_type='EFS',
                          directory_path='/input/validation')
    data_channels = {'train': efs_train_data, 
                     'validation': efs_validation_data}
    
  2. 我们配置Estimator模块,传递托管 EFS 卷的 VPC 子网列表。SageMaker 将在那里启动训练实例,以便它们可以挂载 EFS 卷。我们还需要传递一个安全组,允许 NFS 流量。我们可以重用为 EC2 实例自动创建的那个安全组(不是允许 ssh 访问的那个)– 它在实例详情的Security标签中可见,如下图所示:Figure 9.11 – 查看安全组

    from sagemaker import image_uris
    container = image_uris.retrieve('object-detection', 
                                    region)
    od = sagemaker.estimator.Estimator(
         container,
         role=sagemaker.get_execution_role(),
         instance_count=1,                                         
         instance_type='ml.p3.2xlarge',                                         
         output_path=s3_output_location,
         subnets=['subnet-63715206','subnet-cbf5bdbc',
                  'subnet-59395b00'],                                        
         security_group_ids=['sg-0aa0a1c297a49e911']
    )
    
  3. 为了测试目的,我们只训练一个周期。像往常一样,虽然这次数据是从我们的 EFS 卷加载的。

训练完成后,你可以在 EFS 控制台中删除 EFS 卷,以避免不必要的费用。

现在,让我们看看如何使用另一个存储服务——Amazon FSx for Lustre。

使用 SageMaker 和 Amazon FSx for Lustre

非常大规模的工作负载需要高吞吐量和低延迟存储——这正是 Amazon FSx for Lustre 所具备的两个特点。顾名思义,这项服务基于 Lustre 文件系统(lustre.org),这是一个流行的开源选择,适用于HPC应用。

你可以创建的最小文件系统是 1.2 TB(就像我说的,“非常大规模”)。我们可以为 FSx 文件系统选择两种部署选项之一:

  • 持久化:应用于需要高可用性的长期存储。

  • 临时存储:数据不会复制,如果文件服务器发生故障,数据将不会持久化。作为交换,我们获得了高爆发吞吐量,使其成为突发性短期作业的良好选择。

可选地,文件系统可以由 S3 桶支持。对象首次访问时,会自动从 S3 复制到 FSx。

就像 EFS 一样,文件系统存在于 VPC 中,我们需要一个安全组,允许传入 Lustre 流量(端口 988 和 1,021-2,023)。你可以在 EC2 控制台中创建它,它应该类似于以下截图:

Figure 9.12 – 为 FSx for Lustre 创建安全组

图 9.12 – 为 FSx for Lustre 创建安全组

让我们创建文件系统:

  1. 在 FSx 控制台中,我们创建了一个名为sagemaker-demo的文件系统,并选择了临时存储部署类型。

  2. 我们设置存储容量为 1.2 TB。

  3. 在默认 VPC 的eu-west-1a子网中,我们将其分配给我们刚刚创建的安全组。

  4. s3://sagemaker-eu-west-1-123456789012)和前缀(pascalvoc)中。

  5. 在下一个屏幕上,我们检查我们的选择,如下图所示,然后创建文件系统。

    几分钟后,文件系统已投入使用,如下图所示:

Figure 9.13 – 创建 FSx 卷

图 9.13 – 创建 FSx 卷

由于文件系统由 S3 桶支持,我们无需填充它。我们可以直接进行训练。

使用 FSx for Lustre 训练物体检测模型

现在,我们将使用 FSx 训练模型,具体如下:

  1. 类似于我们刚刚在 EFS 中所做的,我们使用FileSystemInput定义输入通道。一个区别是目录路径必须以文件系统挂载点的名称开头。您可以在 FSx 控制台中找到它作为挂载名称

    from sagemaker.inputs import FileSystemInput
    fsx_train_data = FileSystemInput(
      file_system_id='fs-07914cf5a60649dc8',
      file_system_type='FSxLustre',                            
      directory_path='/bmgbtbmv/pascalvoc/input/train')
    fsx_validation_data = FileSystemInput(
      file_system_id='fs-07914cf5a60649dc8',
      file_system_type='FSxLustre',                            
      directory_path='/bmgbtbmv/pascalvoc/input/validation')
    data_channels = {'train': fsx_train_data, 
                     'validation': fsx_validation_data }
    
  2. 所有其他步骤都是相同的。不要忘记更新传递给Estimator模块的安全组名称。

  3. 当我们完成训练后,在控制台中删除 FSx 文件系统。

这结束了我们对 SageMaker 存储选项的探索。总结一下,这里是我的建议:

  • 首先,您应尽可能使用 RecordIO 或 TFRecord 数据。它们便于移动,训练速度更快,并且可以同时使用文件模式和管道模式。

  • 对于开发和小规模生产,文件模式完全没问题。您的主要关注点应始终是您的机器学习问题,而不是无用的优化。即使在小规模下,EFS 也可以作为协作的有趣选择,因为它便于共享数据集和笔记本。

  • 如果您使用内置算法进行训练,管道模式是一个明智的选择,并且应该在每一个机会中使用它。如果您使用框架或自己的代码进行训练,实施管道模式将需要一些工作,可能不值得工程投入,除非您在进行大规模工作(数百 GB 或更多)。

  • 如果您有大规模、分布式的工作负载,涉及数十个实例或更多,性能模式下的 EFS 值得一试。不要接近令人惊叹的 FSx for Lustre,除非您有疯狂的工作负载。

总结

在本章中,您学习了何时以及如何扩展训练作业。您看到,要找到最佳设置,肯定需要进行一些仔细的分析和实验:扩展与扩展,CPU 与 GPU 与多 GPU 等。这应该帮助您为自己的工作负载做出正确的决策,并避免昂贵的错误。

您还学习了如何通过分布式训练、数据并行、模型并行、RecordIO 和管道模式等技术实现显著加速。最后,您学习了如何为大规模训练作业设置 Amazon EFS 和 Amazon FSx for Lustre。

在下一章中,我们将介绍用于超参数优化、成本优化、模型调试等高级功能。

第十章:高级训练技巧

在上一章中,你学习了如何使用Pipe 模式分布式训练等特性来扩展训练任务,以及使用替代的S3存储数据集。

在本章中,我们将结束对训练技巧的探索。在本章的第一部分,你将学习如何通过托管点训练大幅降低训练成本,如何通过自动模型调优从模型中挤出每一滴精度,并且如何使用SageMaker Debugger拆解模型。

在本章的第二部分,我们将介绍两个新的 SageMaker 功能,帮助你构建更高效的工作流和更高质量的模型:SageMaker Feature StoreSageMaker Clarify

本章涵盖以下主题:

  • 使用托管点训练优化训练成本

  • 使用自动模型调优优化超参数

  • 使用 SageMaker Debugger 探索模型

  • 使用 SageMaker Feature Store 管理特征和构建数据集

  • 使用 SageMaker Clarify 检测偏差并解释预测

技术要求

你需要一个 AWS 账户来运行本章中包含的示例。如果你还没有账户,请访问aws.amazon.com/getting-started/来创建一个。你还应该了解 AWS 免费层(aws.amazon.com/free/),它允许你在一定的使用限制内免费使用许多 AWS 服务。

你需要为你的账户安装并配置 AWS 命令行界面CLI)(aws.amazon.com/cli/)。

你将需要一个可用的pandasnumpy等库。

本书中包含的代码示例可在 GitHub 上获取,链接为github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装 Git 客户端来访问它们(git-scm.com/)。

使用托管点训练优化训练成本

在上一章中,我们在ImageNet数据集上训练了图像分类算法。这个任务运行了不到 4 小时。按照每小时$290 计算,这个任务大约花费了我们$1,160。那是一大笔钱……但真的那么贵吗?

比较成本

在你抛开双手,喊出“他在想什么?”之前,请考虑一下让你的组织拥有并运行这个训练集群需要花费多少成本:

  1. 一个粗略的资本支出计算(包括服务器、存储、GPU、100 Gbit/s 的网络设备)至少需要 150 万美元。就运营支出而言,托管成本不会便宜,因为每台等效的服务器需要 4-5 千瓦的电力。这足够填满你典型托管公司的一排机架,因此即使使用高密度机架,你也需要多个。再加上带宽、跨连接等,我的直觉告诉我每月大约需要 1.5 万美元(在某些地区可能更多)。

  2. 我们需要添加硬件支持合同(比如,每年 10%,即 15 万美元)。将这个集群的折旧期设为 5 年,总月成本为($1.5M + 60$15K + 5$150K)/60 = 52.5K 美元。为了计算维护服务器等的人工成本,我们将其四舍五入到 55K 美元。

使用保守估计,这笔开支相当于使用我们为 ImageNet 示例所用的大型每小时$290 集群进行 190 小时的训练。正如我们在本章稍后将看到的,托管的 Spot 训练通常能提供 70%的节省。因此,现在这笔开支相当于每月大约 633 小时的 ImageNet 训练。

这意味着每月 87%的使用率(633/720),而且很不可能你会让你的训练集群保持如此繁忙。再加上停机时间、硬件创新带来的加速折旧、硬件保险费用、未将 150 万美元投资于其他项目的机会成本等等,物理基础设施的商业案例每分钟都在变得更差。

财务问题固然重要,但最糟糕的是你只能拥有一个集群。如果一个潜在的商业机会需要另一个集群怎么办?你会再花 150 万美元吗?如果不会,是否需要共享现有的集群?当然,只有你能决定什么对你的组织最有利。只要确保你从全局角度来看问题。

现在,让我们来看一下如何轻松享受 70%的成本降低。

了解 Amazon EC2 Spot 实例

在任何时候,Amazon EC2的容量都超过了实际需求。这使得客户可以根据需要随时向他们的平台添加按需容量。可以通过 API 调用显式创建按需实例,或者在配置了Auto Scaling的情况下自动创建。一旦客户获得了按需实例,他们将保留它,直到他们决定释放它,无论是显式释放还是自动释放。

Spot 实例是利用这些未使用容量并享受非常显著折扣(通常为 50-70%)的简单方法。您可以以相同的方式请求它们,它们的行为也相同。唯一的区别是,如果 AWS 需要容量来构建按需实例,您的 Spot 实例可能会被回收。在被强制终止之前,它会收到两分钟的中断通知。

这并不像听起来那么糟糕。根据区域和实例系列的不同,抢占式实例可能不会被频繁回收,客户通常可以将它们保留几天甚至更长时间。此外,您可以为此需求架构您的应用程序,例如,在抢占式实例上运行无状态工作负载,并依赖托管服务进行数据存储。成本优势非常明显,不容错过!

查看过去三个月p3dn.24xlarge的情况,抢占式价格比按需价格便宜 60-70%:

图 10.1 – 查看 p3dn.24xlarge 的抢占式价格

图 10.1 – 查看 p3dn.24xlarge 的抢占式价格

这些是 EC2 的价格,但相同的折扣率也适用于 SageMaker 价格。折扣因实例类型、区域,甚至可用区而异。您可以使用describe-spot-price-history API 以编程方式收集这些信息,并将其用于工作流中:

https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-spot-price-history.html

现在,让我们看看这对 SageMaker 意味着什么。

理解托管抢占式训练

使用抢占式实例进行训练在所有 SageMaker 配置中都可用:单实例训练、分布式训练、内置算法、框架以及您自己的算法。

只需设置几个估算器参数即可。您无需担心处理通知和中断,SageMaker 会自动为您处理。

如果训练作业被中断,SageMaker 会恢复足够的抢占式容量并重新启动训练作业。如果算法使用了检查点,训练将从最新的检查点继续。如果没有,作业将从头开始。

实现检查点所需的工作量取决于您使用的算法:

  • 三个用于计算机视觉和XGBoost的内置算法支持检查点功能。

  • 所有其他内置算法则没有此功能。您仍然可以使用抢占式实例进行训练。然而,最大运行时间限制为 60 分钟,以减少潜在的浪费。如果您的训练作业超过 60 分钟,您应该考虑进行扩展。如果仍然不够,您将不得不使用按需实例。

  • 深度学习容器适用于TensorFlowPyTorchApache MXNetHugging Face,并内置了检查点功能,您无需修改训练脚本。

  • 如果您使用其他框架或自己的自定义代码,您需要实现检查点功能。

在训练过程中,检查点会保存在训练容器内。默认路径为/opt/ml/checkpoints,您可以通过估算器参数进行自定义。SageMaker 还会自动将这些检查点持久化到用户定义的 S3 路径。如果您的训练作业被中断并重新启动,检查点会自动复制到容器内。您的代码可以检查它们的存在,并加载适当的检查点来继续训练。

注意

请注意,即使使用按需实例进行训练,检查点功能仍然可用。如果你希望将检查点存储在 S3 中以便进一步检查或进行增量训练,这将非常有用。唯一的限制是本地模式不支持检查点功能。

最后但同样重要的是,检查点功能确实会拖慢任务的速度,尤其是对于大模型来说。然而,这是一个值得支付的小代价,以避免从头开始重新启动长时间运行的任务。

现在,让我们将托管点训练添加到我们在第五章中运行的目标检测任务中,训练计算机视觉模型

使用托管点训练进行目标检测

从按需训练切换到托管点训练非常简单。我们只需设置训练任务的最大持续时间,包括等待 Spot 实例可用的时间。

我们设置了 2 小时的最大运行时间,加上 8 小时的任何点延迟。如果超出了这两个限制,任务将自动终止。这对于终止运行时间过长或因为等待点实例而卡住的任务非常有帮助:

od = sagemaker.estimator.Estimator(
     container,
     role,
     instance_count=2,                                 
     instance_type='ml.p3.2xlarge',                                 
     use_spot_instances=True,
     max_run=7200,                     # 2 hour
     max_wait=36000,                   # +8 hours
     output_path=s3_output_location)

我们使用与之前相同的配置进行训练:管道模式和dist_sync模式。当第一个 epoch 完成时,训练日志告诉我们检查点功能已经激活。每次验证指标改进时,都会自动保存一个新的检查点:

Updating the best model with validation-mAP=1.615789635726003e-05
Saved checkpoint to "/opt/ml/model/model_algo_1-0000.params"

一旦训练任务完成,训练日志会告诉我们节省了多少:

Training seconds: 7794
Billable seconds: 2338
Managed Spot Training savings: 70.0%

这个任务不仅比按需版本便宜 70%,而且价格还不到我们原来单实例任务的一半。这意味着我们可以使用更多的实例,并以相同的预算加速训练任务。实际上,托管点训练让你能够优化任务的持续时间和成本。你可以根据业务需求设定训练预算,然后尽可能地获取基础设施。

让我们尝试另一个例子,在Keras中实现检查点功能。

使用托管点训练和 Keras 中的检查点功能

在这个例子中,我们将在 TensorFlow 2.1 中构建一个简单的Sequential API。

Keras 中的检查点功能

首先,让我们来看一下 Keras 脚本本身。为了简洁起见,这里只展示了重要的步骤。你可以在本书的 GitHub 仓库中找到完整的代码:

  1. 使用脚本模式,我们存储数据集路径和超参数。

  2. 接下来,我们加载数据集并将像素值归一化到[0,1]范围内。我们还对类别标签进行独热编码。

  3. 我们构建了一个Sequential模型:两个卷积块(Conv2D / BatchNormalization / ReLU / MaxPooling2D / Dropout),然后是两个全连接块(Dense / BatchNormalization / ReLU / Dropout),最后是一个用于数据集中 10 个类别的softmax输出层。

  4. 我们使用分类交叉熵损失函数和Adam优化器来编译模型:

    model.compile(
        loss=tf.keras.losses.categorical_crossentropy,
        optimizer=tf.keras.optimizers.Adam(),
        metrics=['accuracy'])
    
  5. 我们定义一个 Keras 回调,每当验证准确率提高时就保存一个检查点:

    from tensorflow.keras.callbacks import ModelCheckpoint
    chk_dir = '/opt/ml/checkpoints'
    chk_name = 'fmnist-cnn-{epoch:04d}'
    checkpointer = ModelCheckpoint(
        filepath=os.path.join(chk_dir, chk_name),
        monitor='val_accuracy')
    
  6. 我们训练模型,添加我们刚刚创建的回调:

    model.fit(x=x_train, y=y_train, 
              validation_data=(x_val, y_val),
              batch_size=batch_size, epochs=epochs,
              callbacks=[checkpointer],
              verbose=1)
    
  7. 训练完成后,我们将模型保存为 TensorFlow Serving 格式,这是在 SageMaker 上部署时所需的格式:

    from tensorflow.keras.models import save_model
    save_model(model, os.path.join(model_dir, '1'),  
               save_format='tf')
    

现在,让我们看看我们的训练笔记本。

使用托管的 spot 训练和检查点保存进行训练

我们使用之前相同的工作流程:

  1. 我们下载 Fashion-MNIST 数据集并将其保存在本地目录。我们将数据集上传到 S3,并定义 SageMaker 应该将检查点复制到的 S3 位置。

  2. 我们配置一个 TensorFlow 估算器,启用托管的 spot 训练,并传递检查点的 S3 输出位置。这次,我们使用的是 ml.g4dn.xlarge 实例。这种非常具成本效益的 GPU 实例(在 eu-west-1 区域的价格为 $0.822)足以应对一个小模型:

    from sagemaker.tensorflow import TensorFlow
    tf_estimator = TensorFlow(
        entry_point='fmnist-1.py',
        role=sagemaker.get_execution_role(),
        instance_count=1,
        instance_type='ml.g4dn.xlarge',     
        framework_version='2.1.0',
        py_version='py3',
        hyperparameters={'epochs': 20},
        output_path=output_path,
        use_spot_instances=True,
        max_run=3600,
        max_wait=7200,
        checkpoint_s3_uri=chk_path)
    
  3. 我们像往常一样启动训练,任务达到了 93.11%的准确率。训练持续了 289 秒,我们只需为 87 秒支付费用, thanks to a 69.9%的折扣。总费用为 1.98 美分!谁说 GPU 训练必须昂贵?

  4. 在训练日志中,我们看到每当验证准确率提高时,都会创建一个检查点:

    INFO:tensorflow:Assets written to /opt/ml/checkpoints/fmnist-cnn-0001/assets
    

    在任务运行时,我们还看到检查点被复制到 S3:

    $ aws s3 ls s3://sagemaker-eu-west-1-123456789012/keras2
    fashion-mnist/checkpoints/
    PRE fmnist-cnn-0001/
    PRE fmnist-cnn-0002/
    PRE fmnist-cnn-0003/
    PRE fmnist-cnn-0006/
    . . .
    

如果我们的 spot 任务被中断,SageMaker 会在容器内复制检查点,以便我们可以使用它们来恢复训练。这需要在我们的 Keras 脚本中添加一些逻辑,以加载最新的检查点。让我们看看如何实现。

从检查点恢复训练

这是一个非常简单的过程——查找检查点,并从最新的检查点继续训练:

  1. 我们列出检查点目录:

    import glob
    checkpoints = sorted(
        glob.glob(os.path.join(chk_dir,'fmnist-cnn-*')))
    
  2. 如果有检查点,我们会找到最新的一个以及它的 epoch 编号。然后,我们加载模型:

    from tensorflow.keras.models import load_model
    if checkpoints :
        last_checkpoint = checkpoints[-1]
        last_epoch = int(last_checkpoint.split('-')[-1])
        model = load_model(last_checkpoint)
        print('Loaded checkpoint for epoch ', last_epoch)
    
  3. 如果没有检查点,我们像往常一样构建模型:

    else:
        last_epoch = 0
        model = Sequential()
        . . .
    
  4. 我们编译模型,启动训练,并传递最后一个 epoch 的编号:

    model.fit(x=x_train, y=y_train, 
              validation_data=(x_val, y_val), 
              batch_size=batch_size,
              epochs=epochs,
              initial_epoch=last_epoch,
              callbacks=[checkpointer],
              verbose=1)
    

我们怎么测试这个呢?没有办法故意造成一个 spot 中断。

诀窍是:用 checkpoint_s3_uri 路径中的现有检查点启动一个新的训练任务,并增加 epoch 的数量。这将模拟恢复一个中断的任务。

将 epoch 数设置为 25,并将检查点保存在 s3://sagemaker-eu-west-1-123456789012/keras2

fashion-mnist/checkpoints,我们再次启动训练任务。

在训练日志中,我们看到最新的检查点被加载,训练从第 21 个 epoch 继续:

Loaded checkpoint for epoch 20
. . .
Epoch 21/25

我们还看到每当验证准确率提高时,新的检查点会被创建,并被复制到 S3:

INFO:tensorflow:Assets written to: /opt/ml/checkpoints/fmnist-cnn-0021/assets

如你所见,在 SageMaker 中设置检查点并不困难,你应该也能在其他框架中做到这一点。得益于此,你可以享受由托管的按需训练提供的深度折扣,而在中断发生时也无需担心丢失任何工作。当然,你也可以单独使用检查点来检查中间训练结果,或用于增量训练。

在接下来的部分,我们将介绍另一个重要特性:自动模型调优。

使用自动模型调优优化超参数

超参数对训练结果有巨大的影响。就像混沌理论中所说的那样,一个单一超参数的微小变化就可能导致准确率的剧烈波动。在大多数情况下,我们无法解释“为什么?”,这让我们对接下来该尝试什么感到困惑。

多年来,已经设计了几种技术来尝试解决选择最佳超参数的问题:

  1. 手动搜索:这意味着依靠我们的最佳判断和经验来选择“最佳”超参数。说实话:这真的不起作用,尤其是在深度学习和众多训练及网络架构参数的情况下。

  2. 网格搜索:这意味着系统地探索超参数空间,集中在热点区域,然后重复这一过程。这比手动搜索要好得多。然而,这通常需要训练成百上千的任务。即使有可扩展的基础设施,时间和预算仍然可能是巨大的。

  3. 随机搜索:指的是随机选择超参数。虽然听起来不合常理,但詹姆斯·伯格斯特拉和约书亚·本吉奥(图灵奖得主)在 2012 年证明,这一技术在相同计算预算下能比网格搜索交付更好的模型。

  4. www.jmlr.org/papers/v13/bergstra12a.html

  5. 超参数优化(HPO):这意味着使用优化技术来选择超参数,例如贝叶斯优化高斯过程回归。在相同的计算预算下,HPO 通常能以比其他技术少 10 倍的训练周期交付结果。

了解自动模型调优

SageMaker 包含一个自动模型调优功能,可以让你轻松探索超参数范围,并通过有限的任务数快速优化任何训练指标。

模型调优支持随机搜索和超参数优化(HPO)。前者是一个有趣的基准,帮助你检查后者是否确实表现更好。你可以在这篇精彩的博客文章中找到非常详细的比较:

https://aws.amazon.com/blogs/machine-learning/amazon-sagemaker-automatic-model-tuning-now-supports-random-search-and-hyperparameter-scaling/

模型调优对你使用的算法完全无关。它适用于内置算法,文档中列出了可以调优的超参数。它也适用于所有框架和自定义容器,且超参数传递方式相同。

对于我们想要优化的每一个超参数,我们需要定义以下内容:

  • 一个名称

  • 一种类型(参数可以是整数、连续的或分类的)

  • 探索的值范围

  • 一种缩放类型(线性、对数、反对数或自动)——这让我们控制特定参数范围的探索方式

我们还定义了要优化的指标。它可以是任何数值,只要它在训练日志中可见,并且您可以传递正则表达式来提取它。

然后,我们启动调优作业,传递所有这些参数以及要运行的训练作业数量和并行度。使用贝叶斯优化,您可以通过顺序作业(无并行)获得最佳结果,因为优化可以在每个作业后应用。话虽如此,运行少量并行作业是可以接受的。随机搜索对并行性没有限制,因为作业之间完全不相关。

调用deploy() API 在调优器对象上部署最佳模型。如果调优仍在进行中,它将部署迄今为止的最佳模型,这对于早期测试非常有用。

让我们使用内置算法运行第一个示例,并了解模型调优 API。

使用自动模型调优进行目标检测

我们将优化我们的目标检测作业。查看文档,我们可以看到可调超参数的列表:

https://docs.aws.amazon.com/sagemaker/latest/dg/object-detection-tuning.html

让我们尝试优化学习率、动量和权重衰减:

  1. 我们使用管道模式设置输入通道。这里没有变化。

  2. 我们还像往常一样配置估算器,设置托管现货训练以最小化成本。我们将在单个实例上训练以获得最高准确度:

    od = sagemaker.estimator.Estimator(
         container,
         role,                                        
         instance_count=1,                                        
         instance_type='ml.p3.2xlarge',                                       
         output_path=s3_output_location,                                        
         use_spot_instances=True,
         max_run=7200,
         max_wait=36000,
         volume_size=1)       
    
  3. 我们使用与之前相同的超参数:

    od.set_hyperparameters(base_network='resnet-50',
                           use_pretrained_model=1,
                           num_classes=20,
                           epochs=30,
                           num_training_samples=16551,
                           mini_batch_size=90)
    
  4. 我们定义了我们希望调优的三个额外超参数。我们明确设置学习率的对数缩放,以确保探索不同数量级:

    from sagemaker.tuner import ContinuousParameter,
    hyperparameter_ranges = {
        'learning_rate': ContinuousParameter(0.001, 0.1, 
                         scaling_type='Logarithmic'), 
        'momentum': ContinuousParameter(0.8, 0.999), 
        'weight_decay': ContinuousParameter(0.0001, 0.001)
    }
    
  5. 我们设置要优化的指标:

    objective_metric_name = 'validation:mAP'
    objective_type = 'Maximize'
    
  6. 我们将所有内容整合在一起,使用HyperparameterTuner对象。我们决定运行 30 个作业,其中两个作业并行运行。我们还启用了早停,以淘汰表现较差的作业,从而节省时间和金钱:

    from sagemaker.tuner import HyperparameterTuner
    tuner = HyperparameterTuner(od,
                objective_metric_name,
                hyperparameter_ranges,
                objective_type=objective_type,
                max_jobs=30,
                max_parallel_jobs=2,
                early_stopping_type='Auto')
    
  7. 我们在调优器对象(而不是估算器)上启动训练,而不等待它完成:

    tuner.fit(inputs=data_channels, wait=False)
    
  8. 目前,SageMaker Studio 没有提供方便的调优作业查看界面。相反,我们可以在 SageMaker 控制台的 超参数调优作业 部分跟踪进度,如下图所示:

图 10.2 – 在 SageMaker 控制台中查看调优作业

](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_10_2.jpg)

图 10.2 – 在 SageMaker 控制台中查看调优作业

作业运行了 17 小时(壁钟时间)。22 个作业完成,8 个作业提前停止。总训练时间为 30 小时 15 分钟。应用 70%的现货折扣,总成本为 25.25 * $4.131 * 0.3 = $37.48。

这个调优作业的表现如何?使用默认超参数,我们的独立训练作业达到了0.2453。我们的调优作业达到了0.6337,如下图所示:

图 10.3 – 调优作业结果

](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_10_3.jpg)

图 10.3 – 调优作业结果

验证 mAP 的图表显示在下一张图片中。它告诉我我们可能需要再训练一段时间,以获得更高的准确度:

图 10.4 – 查看 mAP 指标

图 10.4 – 查看 mAP 指标

一种方法是使用最佳超参数启动单个训练作业,并让它运行更多周期。我们也可以通过在调优器对象上使用deploy()来恢复调优作业,并像任何 SageMaker 模型一样测试我们的模型。

正如你所见,自动模型调优非常强大。通过运行少量作业,我们将指标提高了 158%!与花费时间尝试其他技术相比,成本几乎可以忽略不计。

实际上,使用随机策略运行相同的调优作业可以得到最高准确率 0.52。我们肯定需要运行更多的训练作业才能希望达到 0.6315。

现在,让我们尝试优化我们在本章前面使用的 Keras 示例。

使用 Keras 进行自动模型调优

自动模型调优可以轻松应用于 SageMaker 上的任何算法,当然也包括所有框架。让我们看看 Keras 是如何工作的。

在本章的前面,我们在 Fashion MNIST 数据集上训练了我们的 Keras CNN,训练了 20 个周期,得到了 93.11%的验证准确率。让我们看看能否通过自动模型调优来提高这个结果。在此过程中,我们还将学习如何优化训练日志中存在的任何指标,而不仅仅是 SageMaker 中预定义的指标。

在自定义指标上的优化

修改我们的训练脚本,安装keras-metrics包(github.com/netrack/keras-metrics),并将精度召回率f1 得分指标添加到训练日志中:

import subprocess, sys
def install(package):
    subprocess.call([sys.executable, "-m", "pip",
                     "install", package])
install('keras-metrics')
import keras_metrics
. . . 
model.compile(
    loss=tf.keras.losses.categorical_crossentropy,
    optimizer=tf.keras.optimizers.Adam(),
    metrics=['accuracy',
              keras_metrics.precision(),
              keras_metrics.recall(),
              keras_metrics.f1_score()])

经过 20 个周期后,当前的指标如下所示:

loss: 0.0869 - accuracy: 0.9678 - precision: 0.9072 - recall: 0.8908 - f1_score: 0.8989 - val_loss: 0.2301 - val_accuracy: 0.9310 - val_precision: 0.9078 - val_recall: 0.8915 - val_f1_score: 0.8996

如果我们想优化 f1 得分,可以像这样定义调优器指标:

objective_metric_name = 'val_f1'
objective_type = 'Maximize'
metric_definitions = [
    {'Name': 'val_f1',
     'Regex': 'val_f1_score: ([0-9\\.]+)'
    }]

就是这样。只要在训练日志中打印出某个指标,你就可以用它来调优模型。

优化我们的 Keras 模型

现在,让我们运行我们的调优作业:

  1. 我们像这样为HyperparameterTuner定义指标,优化准确度并同时显示 f1 得分:

    objective_metric_name = 'val_acc'
    objective_type = 'Maximize'
    metric_definitions = [
        {'Name': 'val_f1', 
         'Regex': 'val_f1_score: ([0-9\\.]+)'},
        {'Name': 'val_acc', 
         'Regex': 'val_accuracy: ([0-9\\.]+)'}
    ]
    
  2. 我们定义要探索的参数范围:

    from sagemaker.tuner import ContinuousParameter, IntegerParameter
    hyperparameter_ranges = {
        'learning_rate': ContinuousParameter(0.001, 0.2, 
                         scaling_type='Logarithmic'), 
        'batch-size': IntegerParameter(32,512)
    }
    
  3. 我们使用相同的估算器(20 个周期,使用 Spot 实例),并定义调优器:

    tuner = HyperparameterTuner(
        tf_estimator,
        objective_metric_name,
        hyperparameter_ranges,                          
        metric_definitions=metric_definitions,
        objective_type=objective_type,
        max_jobs=20,
        max_parallel_jobs=2,
        early_stopping_type='Auto')
    
  4. 我们启动调优作业。在作业运行时,我们可以使用SageMaker SDK来显示训练作业及其属性的列表:

    from sagemaker.analytics import HyperparameterTuningJobAnalytics
    exp = HyperparameterTuningJobAnalytics(
       hyperparameter_tuning_job_name=
       tuner.latest_tuning_job.name)
    jobs = exp.dataframe()
    jobs.sort_values('FinalObjectiveValue', ascending=0)
    

    这将打印出下一个截图中可见的表格:

图 10.5 – 查看调优作业的信息

图 10.5 – 查看调优作业的信息

调优作业运行了 2 小时 8 分钟(墙时)。最高验证准确率为 93.46%——相比我们的基准,取得了不错的改进。

我们当然可以通过训练更长时间来做得更好。然而,训练时间越长,过拟合的风险越大。我们可以通过早停法来减轻这一问题,这可以通过 Keras 回调实现。然而,我们应该确保作业报告的是最佳周期的指标,而不是最后一个周期的指标。我们该如何在训练日志中显示这个信息?通过另一个回调!

为早期停止添加回调

为早期停止添加一个 Keras 回调非常简单:

  1. 我们添加了一个基于验证准确率的早期停止内置回调:

    from tensorflow.keras.callbacks import EarlyStopping
    early_stopping = EarlyStopping(
        monitor='val_accuracy',
        min_delta=0.0001,
        patience=10,
        verbose=1,
        mode='auto')
    
  2. 我们添加了一个自定义回调,以便在每个 epoch 结束时保存验证准确率,并在训练结束时显示最佳结果:

    from tensorflow.keras.callbacks import Callback
    class LogBestMetric(Callback):
        def on_train_begin(self, logs={}):
            self.val_accuracy = []
        def on_train_end(self, logs={}):
            print("Best val_accuracy:",
                  max(self.val_accuracy))
        def on_epoch_end(self, batch, logs={}):
            self.val_accuracy.append(
                logs.get('val_accuracy'))
            best_val_metric = LogBestMetric()
    
  3. 我们将这两个回调添加到训练 API 中:

    model.fit(. . . 
        callbacks=[checkpointer, early_stopping, 
                   best_val_metric])
    

    测试几个单独的作业时,训练日志的最后几行现在看起来是这样的:

    Epoch 00048: early stopping
    Best val_accuracy: 0.9259
    
  4. 在笔记本中,我们更新了指标定义,以便提取最佳验证准确率:

    objective_metric_name = 'val_acc'
    objective_type = 'Maximize'
    metric_definitions = [
        {'Name': 'val_acc', 
         'Regex': 'Best val_accuracy: ([0-9\\.]+)'}
    ]
    

这次训练了 60 个 epochs(大约 3 小时的壁钟时间),当前的最高验证准确率为 93.78%。看起来通过调整学习率和批大小,已经达到了最佳效果。

使用自动模型调优进行架构搜索

我们的神经网络还有很多超参数:卷积滤波器的数量、dropout 等等。让我们也尝试优化这些参数:

  1. 我们修改训练脚本,添加命令行参数,以便为模型中 Keras 层使用的以下网络参数提供支持:

    parser.add_argument(
        '--filters1', type=int, default=64)
    parser.add_argument(
        '--filters2', type=int, default=64)
    parser.add_argument(
        '--dropout-conv', type=float, default=0.2)
    parser.add_argument(
        '--dropout-fc', type=float, default=0.2)
    

    正如你猜测的那样,参数允许我们为每一层的卷积滤波器数量、卷积层的 dropout 值和全连接层的 dropout 值设置值。

  2. 相应地,在笔记本中,我们定义了这些超参数及其范围。对于学习率和批大小,我们使用了以之前调优作业发现的最佳值为中心的窄范围:

    from sagemaker.tuner import ContinuousParameter, 
                                IntegerParameter
    hyperparameter_ranges = {
        learning-rate': ContinuousParameter(0.01, 0.14), 
        'batch-size': IntegerParameter(130,160),
        'filters1': IntegerParameter(16,256),
        'filters2': IntegerParameter(16,256),
        'dropout-conv': ContinuousParameter(0.001,0.5, 
                        scaling_type='Logarithmic'),
        'dropout-fc': ContinuousParameter(0.001,0.5, 
                      scaling_type='Logarithmic')
    }
    
  3. 我们启动调优作业,运行 50 个作业,每次运行 2 个,总共训练 100 个 epochs。

调优作业运行了大约 12 小时,总费用约为 15 美元。最高验证准确率达到了 94.09%。与我们的基线相比,自动模型调优提高了模型的准确性近 1 个百分点——这是一个非常显著的提升。如果这个模型每天用于预测 100 万个样本,这将意味着多出 10,000 个准确的预测!

总的来说,我们在调优 Keras 模型上花费了不到 50 美元。无论是什么业务指标从额外的准确性中获益,可以公平地说,这笔支出很快就能收回。正如许多客户告诉我的那样,自动模型调优是自我盈利的,甚至会带来更多回报。

这结束了我们对自动模型调优的探索,这是 SageMaker 中我最喜欢的功能之一。你可以在 github.com/awslabs/amazon-sagemaker-examples/tree/master/hyperparameter_tuning 找到更多示例。

现在,让我们了解一下 SageMaker Debugger,以及它如何帮助我们理解模型内部发生了什么。

使用 SageMaker Debugger 探索模型

SageMaker Debugger 让您为训练作业配置调试规则。这些规则将检查作业的内部状态,并检测在训练过程中可能出现的特定不良条件。SageMaker Debugger 包含一长串内置规则(docs.aws.amazon.com/sagemaker/latest/dg/debugger-built-in-rules.html),并且您可以添加自定义的 Python 编写规则。

此外,您还可以保存和检查模型状态(如梯度、权重等)以及训练状态(如指标、优化器参数等)。在每个训练步骤中,存储这些值的张量可以在接近实时的情况下保存到 S3 桶中,使得在模型训练过程中就可以对其进行可视化。

当然,您可以选择希望保存的张量集合,以及保存的频率等。根据您使用的框架,可用的集合有所不同。您可以在github.com/awslabs/sagemaker-debugger/blob/master/docs/api.md中找到更多信息。最后但同样重要的是,您可以保存原始张量数据或张量的归约结果,以限制涉及的数据量。归约操作包括最小值、最大值、中位数等。

如果您使用的是支持版本的 TensorFlow、PyTorch、Apache MXNet 的内置容器,或者内置的 XGBoost 算法,您可以开箱即用地使用 SageMaker Debugger,而无需在脚本中更改一行代码。没错,您没有看错。您只需向估算器中添加额外的参数,就像我们接下来在示例中展示的那样。

对于其他版本,或者使用您自己的容器,仅需进行最小的修改。您可以在github.com/awslabs/sagemaker-debugger找到最新的信息和示例。

调试规则和保存张量可以在同一个训练作业中进行配置。为了清晰起见,我们将运行两个独立的示例。首先,让我们使用来自第四章的 XGBoost 和波士顿住房示例,训练机器学习模型

调试 XGBoost 作业

首先,我们将配置几个内置规则,训练我们的模型,并检查所有规则的状态:

  1. 查看内置规则列表后,我们决定使用overtrainingoverfit。每个规则都有额外的参数,我们可以进行调整。我们保持默认设置,并相应地配置Estimator

    from sagemaker.debugger import rule_configs, Rule
    xgb_estimator = Estimator(container,
      role=sagemaker.get_execution_role(),
      instance_count=1,
      instance_type='ml.m5.large',
      output_path='s3://{}/{}/output'.format(bucket, prefix),
      rules=[
        Rule.sagemaker(rule_configs.overtraining()),
        Rule.sagemaker(rule_configs.overfit())
      ]
    )
    
  2. 我们设置超参数并启动训练,而无需等待训练作业完成。训练日志将不会在笔记本中显示,但仍然可以在CloudWatch Logs中查看:

    xgb_estimator.set_hyperparameters(
      objective='reg:linear', num_round=100)
    xgb_estimator.fit(xgb_data, wait=False)
    
  3. 除了训练作业外,每个规则下还会运行一个调试作业,我们可以查看它们的状态:

    description = xgb_estimator.latest_training_job.rule_job_summary()
    for rule in description:
      rule.pop('LastModifiedTime')
      rule.pop('RuleEvaluationJobArn')
      print(rule)
    

    这告诉我们调试作业正在运行:

    {'RuleConfigurationName': 'Overtraining',  
     'RuleEvaluationStatus': 'InProgress'}
    {'RuleConfigurationName': 'Overfit', 
     'RuleEvaluationStatus': 'InProgress'}
    
  4. 在训练作业完成后重新运行相同的单元格时,我们看到没有任何规则被触发:

    {'RuleConfigurationName': 'Overtraining',
     'RuleEvaluationStatus': 'NoIssuesFound'}
    {'RuleConfigurationName': 'Overfit', 
     'RuleEvaluationStatus': 'NoIssuesFound'}
    

如果触发了规则,我们会收到错误信息,训练任务将被停止。检查存储在 S3 中的张量有助于我们理解出了什么问题。

检查 XGBoost 任务

让我们配置一个新的训练任务,保存 XGBoost 的所有张量集合:

  1. 我们配置Estimator,传入DebuggerHookConfig对象。在每个训练步骤中,我们保存三种张量集合:指标、特征重要性和平均SHAPgithub.com/slundberg/shap)值。这些有助于我们理解数据样本中每个特征如何影响预测值的增减。

    对于更大的模型和数据集,这可能会生成大量数据,加载和分析这些数据需要较长时间。我们可以增加保存间隔,或保存张量缩减值而非完整张量:

    from sagemaker.debugger import DebuggerHookConfig, CollectionConfig
    save_interval = '1'
    xgb_estimator = Estimator(container,
        role=role,
        instance_count=1,
        instance_type='ml.m5.large',
        output_path='s3://{}/{}/output'.format(bucket,  
                                               prefix),
    
        debugger_hook_config=DebuggerHookConfig(                
            s3_output_path=
            's3://{}/{}/debug'.format(bucket,prefix),
          collection_configs=[
            CollectionConfig(name='metrics',
              parameters={"save_interval": 
                          save_interval}),
            CollectionConfig(name='average_shap',  
              parameters={"save_interval": 
                          save_interval}),
            CollectionConfig(name='feature_importance', 
              parameters={"save_interval": save_interval})
          ]
        )
    )
    
  2. 一旦训练任务开始,我们可以创建一个试验并加载已保存的数据。由于该任务非常短,我们将在一分钟左右查看到所有数据:

    from smdebug.trials import create_trial
    s3_output_path = xgb_estimator.latest_job_debugger_artifacts_path()
    trial = create_trial(s3_output_path)
    
  3. 我们可以列出所有已保存张量的名称:

    trial.tensor_names()
    ['average_shap/f0','average_shap/f1','average_shap/f10', … 
     'feature_importance/cover/f0','feature_importance/cover/f1',…
     'train-rmse','validation-rmse']
    
  4. 我们还可以列出给定集合中所有张量的名称:

    trial.tensor_names(collection="metrics")
    ['train-rmse', 'validation-rmse']
    
  5. 对于每个张量,我们可以访问训练步骤和数值。让我们绘制来自average_shapfeature_importance集合的特征信息:

    def plot_features(tensor_prefix):
        num_features = len(dataset.columns)-1
        for i in range(0,num_features):
        feature = tensor_prefix+'/f'+str(i)
        steps = trial.tensor(feature).steps()
        v = [trial.tensor(feature).value(s) for s in steps]
        plt.plot(steps, v, label=dataset.columns[i+1])
        plt.autoscale()
        plt.title(tensor_prefix)
        plt.legend(loc='upper left')
        plt.show()
    
  6. 我们构建average_shap图:

    plot_features('average_shap')
    
  7. 你可以在以下截图中看到——discrimnox的平均值最大:图 10.6 – 绘制 SHAP 值随时间变化的图

    图 10.6 – 绘制 SHAP 值随时间变化的图

  8. 我们构建feature_importance/weight图:

    plot_features('feature_importance/weight')
    

    你可以在以下截图中看到——crimagedis的权重最大:

图 10.7 – 绘制特征权重随时间变化的图

图 10.7 – 绘制特征权重随时间变化的图

现在,让我们在 Keras 和 Fashion-MNIST 示例中使用 SageMaker Debugger。

调试和检查 Keras 任务

我们可以通过以下步骤检查和调试 Keras 任务:

  1. TensorFlow 2.x 的默认行为是急切模式(eager mode),此时梯度不可用。因此,我们在脚本中禁用急切模式,这是唯一需要修改的地方:

    tf.compat.v1.disable_eager_execution()
    
  2. 我们从相同的估计器开始。数据集包含 70,000 个样本(60,000 个用于训练,10,000 个用于验证)。通过 30 个 epoch 和批次大小 128,我们的训练任务将有大约 16,400 个步骤(70,000 * 30 / 128)。在每个步骤保存张量可能显得有些过头。我们改为每 100 步保存一次:

    from sagemaker.tensorflow import TensorFlow
    from sagemaker.debugger import rule_configs, Rule, DebuggerHookConfig, CollectionConfig
    save_interval = '100'
    tf_estimator = TensorFlow(entry_point='fmnist-5.py',
        role=role,
        instance_count=1,
        instance_type='ml.p3.2xlarge',
        framework_version='2.1.0', 
        py_version='py3',
        hyperparameters={'epochs': 30},
        output_path=output_path,
        use_spot_instances=True,
        max_run=3600,
        max_wait=7200,
    
  3. 查看 TensorFlow 的内建规则后,我们决定设置poor_weight_initializationdead_relucheck_input_images。我们需要指定输入张量中的通道信息索引。对于 TensorFlow 来说,它是 4(批次大小、高度、宽度和通道):

        rules=[      
    Rule.sagemaker(
        rule_configs.poor_weight_initialization()), 
    Rule.sagemaker(
        rule_configs.dead_relu()),
    Rule.sagemaker(
        rule_configs.check_input_images(), 
        rule_parameters={"channel": '3'})
        ],
    
  4. 查看 TensorFlow 的集合后,我们决定保存指标、损失、输出、权重和梯度:

        debugger_hook_config=DebuggerHookConfig(                
            s3_output_path='s3://{}/{}/debug'
                   .format(bucket, prefix),
            collection_configs=[
                CollectionConfig(name='metrics',  
                    parameters={"save_interval": 
                                save_interval}),
                CollectionConfig(name='losses', 
                    parameters={"save_interval": 
                                save_interval}),
                CollectionConfig(name='outputs', 
                    parameters={"save_interval": 
                                save_interval}),
                CollectionConfig(name='weights', 
                    parameters={"save_interval": 
                                save_interval}),
                CollectionConfig(name='gradients', 
                    parameters={"save_interval": 
                                save_interval})
            ],
        )
    )
    
  5. 当训练开始时,我们在训练日志中看到规则被触发:

    ********* Debugger Rule Status *********
    *
    * PoorWeightInitialization: InProgress        
    * DeadRelu: InProgress        
    * CheckInputImages: InProgress        
    *
    ****************************************
    
  6. 训练完成后,我们检查调试规则的状态:

    description = tf_estimator.latest_training_job.rule_job_summary()
    for rule in description:
        rule.pop('LastModifiedTime')
        rule.pop('RuleEvaluationJobArn')
        print(rule)
    {'RuleConfigurationName': 'PoorWeightInitialization', 
     'RuleEvaluationStatus': 'NoIssuesFound'}
    {'RuleConfigurationName': 'DeadRelu',
     'RuleEvaluationStatus': 'NoIssuesFound'}
    {'RuleConfigurationName': 'CheckInputImages', 
     'RuleEvaluationStatus': 'NoIssuesFound'}
    
  7. 我们使用保存在 S3 中的相同张量创建一个试验:

    from smdebug.trials import create_trial
    s3_output_path = tf_estimator.latest_job_debugger_artifacts_path()
    trial = create_trial(s3_output_path)
    
  8. 让我们检查第一层卷积层中的过滤器:

    w = trial.tensor('conv2d/weights/conv2d/kernel:0')
    g = trial.tensor(
    'training/Adam/gradients/gradients/conv2d/Conv2D_grad/Conv2DBackpropFilter:0')
    print(w.value(0).shape)
    print(g.value(0).shape)
    (3, 3, 1, 64)
    (3, 3, 1, 64)
    

    在我们的训练脚本中定义,第一层卷积层有 64 个过滤器。每个过滤器是 3x3 像素,具有单通道(2D)。因此,梯度具有相同的形状。

  9. 我们编写一个函数来绘制过滤器权重和梯度随时间变化的图,并绘制第一层卷积层中最后一个过滤器的权重:

    plot_conv_filter('conv2d/weights/conv2d/kernel:0', 63)
    

    你可以在以下截图中看到图表:

图 10.8 – 绘制卷积过滤器随时间变化的权重

图 10.8 – 绘制卷积过滤器随时间变化的权重

如你所见,SageMaker Debugger 使得检查训练作业变得非常简单。如果你使用内置的支持它的容器,你无需修改代码。所有配置都在估算器中完成。

你可以在github.com/awslabs/amazon-sagemaker-examples找到更多示例,包括一些高级用例,如实时可视化和模型修剪。

这部分内容结束了,我们学习了如何通过托管的临时训练来优化训练作业的成本,通过自动模型调优来提高准确性,以及如何使用 SageMaker Debugger 检查它们的内部状态。

在第二部分中,我们将深入探讨两项高级功能,帮助我们构建更好的训练工作流——SageMaker Feature Store 和 SageMaker Clarify。

使用 SageMaker Feature Store 管理特征和构建数据集

直到现在,我们一直在笔记本或 SageMaker Processing 脚本中工程化我们的训练和验证特征,然后将它们存储为 S3 对象。然后,我们直接使用这些对象来训练和评估模型。这是一个完全合理的工作流。然而,随着你的机器学习工作流的增长和成熟,可能会出现以下问题:

  • 我们如何对特征应用明确定义的模式?

  • 我们如何选择特征的子集来构建不同的数据集?

  • 我们如何存储和管理不同版本的特征?

  • 我们如何发现并重用其他团队的特征工程?

  • 我们如何在预测时访问工程化的特征?

SageMaker Feature Store 被设计用来回答这些问题。让我们将其添加到我们与 BlazingText 和 Amazon Reviews 一起构建的分类训练工作流中,见第六章训练自然语言处理模型

使用 SageMaker Processing 工程化特征

我们几乎可以直接重用之前的 SageMaker Processing 任务。唯一的区别是工程数据的输出格式。在原始任务中,我们将其保存为纯文本文件,按照 BlazingText 期望的输入格式。此格式对于 SageMaker Feature Store 来说不太方便,因为我们需要轻松访问每一列。CSV 格式也不行,因为评论中包含逗号,因此我们决定改用 TSV 格式:

  1. 因此,我们在处理脚本中添加了几行:

    fs_output_dir = '/opt/ml/processing/output/fs/'
    os.makedirs(fs_output_dir, exist_ok=True)
    fs_output_path = os.path.join(fs_output_dir, 'fs_data.tsv')  
    data.to_csv(fs_output_path, index=False,header=True, sep='\t')
    
  2. 和之前一样运行我们的 SageMaker Processing 任务,我们现在看到两个输出:一个是 BlazingText 的纯文本输出(如果我们想直接对完整数据集进行训练),另一个是我们将摄取到 SageMaker Feature Store 中的 TSV 输出:

    s3://sagemaker-us-east-1-123456789012/sagemaker-scikit-learn-2021-07-05-07-54-15-145/output/bt_data
    s3://sagemaker-us-east-1-123456789012/sagemaker-scikit-learn-2021-07-05-07-54-15-145/output/fs_data
    
  3. 让我们将 TSV 文件加载到pandas数据框中,并显示前几行:

    fs_training_output_path = 's3://sagemaker-us-east-1-123456789012/sagemaker-scikit-learn-2021-07-05-07-54-15-145/output/fs_data/fs_data.tsv'
    data = pd.read_csv(fs_training_output_path, sep='\t',
                       error_bad_lines=False, dtype='str')
    data.head()
    

    这将打印出下图所示的表格:

图 10.9 – 查看前几行

图 10.9 – 查看前几行

现在,让我们创建一个特征组,以便我们摄取这些数据。

创建特征组

特征组是一种资源,用于存储相关特征的集合。特征组按行组织,每行都有一个唯一标识符和时间戳。每行包含键值对,其中每一对代表一个特征名称和特征值。

  1. 首先,让我们定义特征组的名称:

    from sagemaker.feature_store.feature_group import FeatureGroup
    feature_group_name = 'amazon-reviews-feature-group-' + strftime('%d-%H-%M-%S', gmtime())
    feature_group = FeatureGroup(
        name=feature_group_name,    
        sagemaker_session=feature_store_session)
    
  2. 接下来,我们设置包含唯一标识符的特征名称——review_id在这里完全适用,你可以使用数据源中任何唯一的值,如主键:

    record_identifier_feature_name = 'review_id'
    
  3. 然后,我们为pandas数据框中的所有行添加了时间戳列。如果你的数据源已经包含时间戳,你可以重用该值,无论是float64格式还是UNIX日期/时间格式:

    event_time_feature_name = 'event_time'
    current_time_sec = int(round(time.time()))
    data = data.assign(event_time=current_time_sec)
    

    现在我们的数据框看起来如下所示:

    图 10.10 – 查看时间戳

    图 10.10 – 查看时间戳

  4. 下一步是为特征组定义模式。我们可以将其显式提供为 JSON 文档,或者让 SageMaker 从 pandas 数据框中自动提取。我们使用第二种方式:

    data['review_id'] = data['review_id']
        .astype('str').astype('string')
    data['product_id'] = data['product_id']
        .astype('str').astype('string')
    data['review_body'] = data['review_body']
        .astype('str').astype('string')
    data['label'] = data['label']
        .astype('str').astype('string')
    data['star_rating'] = data['star_rating']
        .astype('int64')
    data['event_time'] = data['event_time']
        .astype('float64')
    

    接下来,我们加载特征定义:

    feature_group.load_feature_definitions(
        data_frame=data)
    
  5. 最后,我们创建特征组,传递将存储特征的 S3 位置。这是我们查询它们以构建数据集的地方。我们启用在线存储,这将使我们在预测时以低延迟访问特征。我们还添加了描述和标签,便于发现特征组:

    feature_group.create(
      role_arn=role,
      s3_uri='s3://{}/{}'.format(default_bucket, prefix),
      enable_online_store=True,
      record_identifier_name=
          record_identifier_feature_name,
      event_time_feature_name=
          event_time_feature_name,
      description="1.8M+ tokenized camera reviews from the   
                   Amazon Customer Reviews dataset",
      tags=[
          { 'Key': 'Dataset', 
            'Value': 'amazon customer reviews' },
          { 'Key': 'Subset',
            'Value': 'cameras' },
          { 'Key': 'Owner',
            'Value': 'Julien Simon' }
      ])
    

几秒钟后,特征组准备就绪,并在 SageMaker Studio 中可见,位于组件和注册表 / 特征存储下,如下图所示:

图 10.11 – 查看特征组

图 10.11 – 查看特征组

现在,让我们开始数据摄取。

摄取特征

SageMaker Feature Store 允许我们通过三种方式摄取数据:

  • 调用PutRecord() API 以摄取单个记录。

  • 调用ingest() API 上传pandas数据框的内容。

  • 如果我们使用SageMaker 数据 Wrangler进行特征工程,可以使用自动生成的笔记本创建特征组并导入数据。

我们在这里使用第二个选项,它与以下代码一样简单:

feature_group.ingest(data_frame=data, max_workers=10, 
                     wait=True)

一旦数据导入完成,特征将存储在我们指定的 S3 位置以及专用的低延迟后端中。我们可以使用前者来构建数据集。

查询特征以构建数据集

当我们创建特征组时,SageMaker 会自动在 AWS Glue 数据目录 中为其添加一个新表。这使得使用 Amazon Athena 查询数据并按需构建数据集变得更加容易。

假设我们希望构建一个包含至少有 1,000 条评论的畅销相机的数据集:

  1. 首先,我们编写一个 SQL 查询,计算每台相机的平均评分,统计每台相机收到的评论数,仅保留至少有 1,000 条评论的相机,并按平均评分降序排列相机:

    query_string = 
    'SELECT label,review_body FROM 
    "'+ feature_group_table +'"'
    + ' INNER JOIN (
          SELECT product_id FROM (
              SELECT product_id, avg(star_rating) as  
                     avg_rating, count(*) as review_count
              FROM "'+ feature_group_table+ '"' + '
              GROUP BY product_id) 
          WHERE review_count > 1000) tmp 
    ON "'+feature_group_table+'"'
    + '.product_id=tmp.product_id;'
    
  2. 然后,我们使用 Athena 查询我们的特征组,将选中的行存储在 pandas 数据框中,并显示前几行:

    dataset = pd.DataFrame()
    feature_group_query.run(query_string=query_string, output_location='s3://'+default_bucket+'/query_results/')
    feature_group_query.wait()dataset = feature_group_query.as_dataframe()
    dataset.head()
    

这会打印出下一张图片中可见的表格:

图 10.12 – 查看查询结果

图 10.12 – 查看查询结果

从那时起,一切照常。我们可以将这个数据框保存为 CSV 文件,并用它来训练模型。你可以在 GitHub 仓库中找到一个完整的示例。

探索 SageMaker Feature Store 的其他功能

随着时间的推移,我们可以存储同一特征的不同版本——即具有相同标识符但时间戳不同的多个记录。这将允许我们通过简单的 SQL 查询来检索数据集的早期版本——在我们的数据中进行“时光旅行”。

最后但同样重要的是,功能也可以在在线商店中使用。我们可以通过 GetRecord() API 检索单个记录,并在预测时根据需要使用功能。

再次,你将在 GitHub 仓库中找到这两项功能的代码示例。

为了结束本章内容,让我们看看 Amazon SageMaker Clarify,这是一项通过检测数据集和模型中的潜在偏差,帮助我们构建更高质量模型的功能。

使用 SageMaker Clarify 检测数据集中的偏差并解释预测结果

机器学习ML)模型的好坏取决于其构建的 dataset。如果数据集不准确或无法公平地代表其应该捕捉的现实情况,那么相应的模型很可能会学习到这种偏差的表示,并在预测中延续这种偏差。作为机器学习实践者,我们需要意识到这些问题,理解它们如何影响预测,并尽可能地减少这种影响。

在这个示例中,我们将使用成人数据集,该数据集可在UCI 机器学习库中找到(archive.ics.uci.edu/ml,Dua, D.和 Graff, C.,2019)。这个数据集描述了一个二分类任务,我们尝试预测一个人是否年收入超过$50,000。这里,我们想检查这个数据集是否引入了性别偏差。换句话说,它是否有助于我们构建一个对男性和女性的预测效果一样好的模型?

注意

你在 GitHub 仓库中找到的数据集经过了轻微的处理。标签列已经根据 XGBoost 的要求被移到前面。类别变量已经进行了独热编码。

使用 SageMaker Clarify 配置偏差分析

SageMaker Clarify 计算训练前和训练后的指标,帮助我们理解模型的预测情况。

后训练指标显然需要一个已训练的模型,因此我们首先使用 XGBoost 训练一个二分类模型。这是我们已经看过很多次的内容,你可以在 GitHub 仓库中找到相关代码。这个模型的验证 AuC 达到了 92.75%。

一旦训练完成,我们就可以进行偏差分析:

  1. 偏差分析作为 SageMaker 处理任务运行。因此,我们创建一个SageMakerClarifyProcessor对象,指定我们的基础设施需求。由于任务规模较小,我们使用一个实例。对于更大的任务,我们可以使用更多实例,并且分析将自动在Spark上运行:

    from sagemaker import clarify
    clarify_processor = clarify.SageMakerClarifyProcessor(
        role=role,
        instance_count=1,
        instance_type='ml.m5.large',
        sagemaker_session=session)
    
  2. 然后,我们创建一个DataConfig对象,描述要分析的数据集:

    bias_report_output_path = 's3://{}/{}/clarify-bias'.format(bucket, prefix)
    data_config = clarify.DataConfig(
        s3_data_input_path=train_uri,
        s3_output_path=bias_report_output_path,
        label='Label',
        headers=train_data.columns.to_list(),
        dataset_type='text/csv')
    
  3. 同样地,我们创建一个ModelConfig对象,描述要分析的模型:

    model_config = clarify.ModelConfig(
        model_name=xgb_predictor.endpoint_name,
        instance_type='ml.t2.medium',
        instance_count=1,
        accept_type='text/csv')
    
  4. 最后,我们创建一个BiasConfig对象,描述要计算的指标。label_values_or_threshold定义了正向结果的标签值(1,表示年收入高于$50K)。facet_name定义了我们希望分析的特征(Sex_),而facet_values_or_threshold定义了潜在弱势群体的特征值(1,表示女性)。

    bias_config = clarify.BiasConfig(
        label_values_or_threshold=[1],
        facet_name='Sex_',
        facet_values_or_threshold=[1])
    

我们现在准备好运行分析了。

运行偏差分析

将所有内容整合在一起,我们使用以下命令启动分析:

clarify_processor.run_bias(
    data_config=data_config,
    model_config=model_config,
    bias_config=bias_config)

一旦分析完成,结果将在 SageMaker Studio 中可见。报告也会生成并以 HTML、PDF 和 Notebook 格式存储在 S3 中。

实验和试验中,我们找到我们的 SageMaker Clarify 任务,并右键点击打开试验详情。选择偏差报告,我们可以看到偏差指标,如下图所示:

图 10.13 – 查看偏差指标

图 10.13 – 查看偏差指标

分析偏差指标

如果你想了解更多关于偏差指标的信息、它们的含义以及它们是如何计算的,我强烈推荐以下资源:

我们来看两个训练前的度量标准,类别不平衡CI)和标签中正类比例差异DPL),以及一个训练后的度量标准,预测标签中正类比例差异DPPL)。

CI 的非零值表明数据集是不平衡的。这里,男性和女性比例的差异是 0.35。确实,男性组大约占数据集的三分之二,女性组约占三分之一。这并不是一个非常严重的失衡,但我们也应该查看每个类别的正类标签比例。

DPL 衡量每个类别是否具有相同的正类标签比例。换句话说,数据集中男性和女性赚取$50K 的比例是否相同?DPL 的值为非零(0.20),这告诉我们男性的$50K 收入者比例更高。

DPPL 是一个训练后度量,类似于 DPL。它的值(0.18)表明模型不幸地拾取了数据集中的偏差,只是轻微地减少了它。实际上,模型为男性预测了一个更有利的结果(过度预测$50K 收入者),而为女性预测了一个不太有利的结果(低估$50K 收入者)。

这显然是一个问题。尽管模型有一个相当不错的验证 AuC(92.75%),但它并没有同样好地预测两种类别。

在我们深入分析数据并尝试缓解这个问题之前,先进行一次可解释性分析。

运行可解释性分析

SageMaker Clarify 可以计算局部和全局的 SHAP 值(github.com/slundberg/shap)。它们帮助我们理解特征的重要性,以及各个特征值如何影响正面或负面的结果。

偏差分析作为 SageMaker 处理作业运行,过程类似:

  1. 我们创建一个DataConfig对象,描述要分析的数据集:

    explainability_output_path = 's3://{}/{}/clarify-explainability.format(bucket, prefix)
    data_config = clarify.DataConfig(
        s3_data_input_path=train_uri,
        s3_output_path= explainability_output_path,
        label='Label',
        headers=train_data.columns.to_list(),
        dataset_type='text/csv')
    
  2. 我们创建一个SHAPConfig对象,描述我们希望如何计算 SHAP 值——即使用哪个基准(我使用了移除标签后的测试集),使用多少样本(特征数的两倍加 2048,这是一个常见的默认值),以及如何聚合值:

    shap_config = clarify.SHAPConfig(
        baseline=test_no_labels_uri,
        num_samples=2*86+2048,
        agg_method='mean_abs',
        save_local_shap_values=True
    )
    
  3. 最后,我们运行分析:

    clarify_processor.run_explainability(
        data_config=explainability_data_config,
        model_config=model_config,
        explainability_config=shap_config
    )
    

结果可以在 SageMaker Studio 中查看,Sex特征是最重要的,这确认了偏差分析的结果。抛开伦理考虑不谈,从商业角度来看,这似乎并不太合理。像教育程度或资本收益这样的特征应该更为重要。

图 10.14 – 查看特征重要性

](https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_10_14.jpg)

图 10.14 – 查看特征重要性

本地 SHAP 值也已经计算并存储在 S3 中。我们可以使用这些值来了解特征值如何影响每个单独样本的预测。

现在,让我们看看如何尝试缓解我们在数据集中检测到的偏差。

缓解偏差

这个数据集结合了两个问题。首先,它包含更多的男性而非女性。其次,男性组的正向结果比例较高。这两个问题的结合导致数据集中$50K 收入的女性数量比例异常低。这使得模型更难以公平地学习,并且倾向于偏向多数类。

偏差缓解技术包括以下内容:

  • 通过删除多数样本来对多数类进行下采样,以重新平衡数据集

  • 通过复制现有样本来对少数类进行过采样,增加更多样本

  • 通过生成与现有样本具有相似统计属性的新样本,向少数类添加合成样本

    注意

    修改数据不应轻率进行,尤其是在受监管行业中运作的组织中。这可能会产生严重的业务、合规性和法律后果。在生产环境中进行此操作之前,请务必获得批准。

让我们尝试一种结合方法,基于imbalanced-learn开源库(https://imbalanced-learn.org)。首先,我们将使用合成少数类过采样技术SMOTE)算法向少数类添加合成样本,以匹配多数类样本中$50K 收入者的比例。接着,我们将对多数类进行下采样,使其样本数与少数类相匹配。结果将是一个完全平衡的数据集,两个类别的大小相同,$50K 收入者的比例也相同。让我们开始吧:

  1. 首先,我们需要计算两个类别的比例:

    female_male_not_50k_count = train_data['Sex_'].where(
        train_data['Label']==0).value_counts()
    female_male_50k_count = train_data['Sex_'].where(
        train_data['Label']==1).value_counts()
    ratios = female_male_50k_count / 
             female_male_not_50k_count
    print(ratios)
    

    这给出了以下结果,显示多数类(类别 0)拥有远高于$50K 收入者的比例:

    0.0    0.457002
    1.0    0.128281
    
  2. 然后,我们生成少数类的合成样本:

    from imblearn.over_sampling import SMOTE
    female_instances = train_data[train_data['Sex_']==1]
    female_X = female_instances.drop(['Label'], axis=1)
    female_Y = female_instances['Label']
    oversample = SMOTE(sampling_strategy=ratios[0])
    balanced_female_X, balanced_female_Y = oversample.fit_resample(female_X, female_Y)
    balanced_female=pd.concat([balanced_female_X, balanced_female_Y], axis=1)
    
  3. 接下来,我们使用原始多数类和重新平衡的少数类重新构建数据集:

    male_instances = train_data[train_data['Sex_']==0]
    balanced_train_data=pd.concat(
        [male_instances, balanced_female], axis=0)
    
  4. 最后,我们对原始多数类进行下采样,以重新平衡比例:

    from imblearn.under_sampling import RandomUnderSampler
    X = balanced_train_data.drop(['Sex_'], axis=1)
    Y = balanced_train_data['Sex_']
    undersample = RandomUnderSampler(
        sampling_strategy='not minority')
    X,Y = undersample.fit_resample(X, Y)
    balanced_train_data=pd.concat([X, Y], axis=1)
    
  5. 我们再次计算两个类别的样本数,并重新计算它们的比例:

    female_male_count= balanced_train_data['Sex_']    
        .value_counts()
    female_male_50k_count = balanced_train_data['Sex_']
        .where(balanced_train_data['Label']==1)
        .value_counts()
    ratios = female_male_50k_count/female_male_count
    print(female_male_count)
    print(female_male_50k_count)
    print(ratios)
    

    这显示了以下结果:

    1.0    0.313620
    0.0    0.312039
    

在使用这个重新平衡的数据集进行训练,并使用相同的测试集时,我们得到了 92.95%的验证 AuC,相比之下原始模型为 92.75%。进行新的偏差分析时,CI 为零,DPL 和 DPPL 接近零。

我们不仅构建了一个预测更公平的模型,而且还稍微提高了准确性。难得的是,这次我们似乎做到了两全其美!

总结

本章总结了我们对训练技术的探索。你学习了受管训练(managed spot training),这是一种通过减少 70%或更多的训练成本的简单方法。你还了解了检查点(checkpointing)如何帮助恢复被中断的任务。接着,你学习了自动模型调优(automatic model tuning),这是一种通过探索超参数范围从模型中提取更多准确性的有效方法。你了解了 SageMaker 调试器(SageMaker Debugger),这是一个高级功能,能够自动检查训练任务中的不良条件,并将张量集合保存到 S3,以便检查和可视化。最后,我们发现了两个有助于你构建更高质量工作流和模型的功能,SageMaker 特征存储(SageMaker Feature Store)和 SageMaker Clarify。

在下一章,我们将详细学习模型部署。

第四部分:生产环境中的模型管理

在本节中,您将学习如何在多种配置下部署机器学习模型,包括使用 SDK 和若干自动化工具。最后,您将学会如何找到最佳的成本/性能比,以优化预测基础设施。

本节包括以下章节:

  • 第十一章部署机器学习模型

  • 第十二章自动化机器学习工作流

  • 第十三章优化成本与性能

第十一章:部署机器学习模型

在前面的章节中,我们以最简单的方式部署了模型:通过配置估算器,调用 fit()deploy() API 创建实时端点。这是开发和测试中最简单的场景,但并非唯一的方式。

模型也可以被导入。例如,你可以将自己在本地机器上训练的现有模型导入到 SageMaker,并像在 SageMaker 上训练一样进行部署。

此外,模型可以以不同的配置进行部署,如下所示:

  • 一个单一模型部署在实时端点上,正如我们目前所做的那样,也可以在同一端点上部署多个模型变种。

  • 一个最多包含五个模型的序列,称为 推理管道

  • 在同一端点上按需加载的任意数量的相关模型,称为 多模型端点。我们将在 第十三章优化成本与性能 中详细探讨这种配置。

  • 单一模型或一个推理管道,通过一种称为 批量转换 的特性进行批量模式预测。

当然,模型也可以被导出。你可以在 简单存储服务S3)中获取训练工件,提取模型并将其部署到任何地方。

在本章中,我们将讨论以下主题:

  • 检查模型工件并导出模型

  • 在实时端点上部署模型

  • 在批处理转换器上部署模型

  • 在推理管道上部署模型

  • 使用 Amazon SageMaker 模型监控器监控预测质量

  • 在容器服务上部署模型

  • 让我们开始吧!

技术要求

你需要一个 亚马逊网络服务AWS)账户来运行本章中的示例。如果你还没有账户,请访问 aws.amazon.com/getting-started/ 创建一个。你还应该熟悉 AWS 免费套餐(aws.amazon.com/free/),它让你可以在某些使用限制内免费使用许多 AWS 服务。

你需要安装并为你的账户配置 AWS 命令行界面CLI)(aws.amazon.com/cli/)。

你需要一个正常工作的 Python 3.x 环境。安装 Anaconda 发行版(www.anaconda.com/)不是强制的,但强烈建议安装,因为它包含了许多我们需要的项目(Jupyter、pandasnumpy 等)。

本书中的代码示例可以在 GitHub 上找到,网址为 github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装一个 Git 客户端才能访问它们(git-scm.com/)。

检查模型工件并导出模型

模型工件包含一个或多个由训练作业生成的文件,这些文件是模型部署所必需的。这些文件的数量和性质取决于所训练的算法。如我们所见,模型工件通常存储为model.tar.gz文件,位于估算器定义的 S3 输出位置。

让我们看看不同的示例,在这些示例中,我们重用之前训练作业中的工件。

检查和导出内置模型

几乎所有内置的算法都使用Apache MXNet实现,其工件也反映了这一点。有关 MXNet 的更多信息,请访问mxnet.apache.org/

让我们看看如何直接加载这些模型。另一种选择是使用多模型服务器MMS)(github.com/awslabs/multi-model-server),但我们将按如下方式继续:

  1. 让我们从我们在第四章中训练的线性学习者模型的工件开始,如以下代码片段所示:

    $ tar xvfz model.tar.gz
    x model_algo-1
    $ unzip model_algo-1
    archive:  model_algo-1
    extracting: additional-params.json
    extracting: manifest.json
    extracting: mx-mod-symbol.json
    extracting: mx-mod-0000.params
    
  2. 我们加载符号文件,该文件包含模型的JavaScript 对象表示法JSON)定义,如下所示:

    import json
    sym_json = json.load(open('mx-mod-symbol.json'))
    sym_json_string = json.dumps(sym_json)
    
  3. 我们使用这个 JSON 定义来实例化一个新的 Gluon 模型。我们还定义了它的输入符号(data),如下所示:

    import mxnet as mx
    from mxnet import gluon
    net = gluon.nn.SymbolBlock(
        outputs=mx.sym.load_json(sym_json_string),
        inputs=mx.sym.var('data')) 
    
  4. 现在,我们可以轻松地绘制模型,如下所示:

    mx.viz.plot_network(
        net(mx.sym.var('data'))[0],   
        node_attrs={'shape':'oval','fixedsize':'false'})
    

    这将生成以下输出:

    图 11.1 – 线性学习者模型

    图 11.1 – 线性学习者模型

  5. 然后,我们加载在训练过程中学到的模型参数,如下所示:

    net.load_parameters('mx-mod-0000.params', 
                        allow_missing=True)
    net.collect_params().initialize()
    
  6. 我们定义一个存储在 MXNet NDArrayhttps://mxnet.apache.org/versions/1.6/api/python/docs/api/ndarray/index.html)中的测试样本,如下所示:

    test_sample = mx.nd.array(
    [0.00632,18.00,2.310,0,0.5380,6.5750,65.20,4.0900,1,296.0,15.30,4.98])
    
  7. 最后,我们将其通过模型并读取输出,如下所示:

    response = net(test_sample)
    print(response)
    

    该房子的预测价格为美元USD)30,173,如下图所示:

    array([[30.173424]], dtype=float32)
    

该技术应该适用于所有基于 MXNet 的算法。现在,让我们看看计算机视觉CV)的内置算法。

检查和导出内置的计算机视觉(CV)模型

这三个内置的计算机视觉算法也基于 Apache MXNet。过程完全相同,如此处所述:

  1. 以下是我们在第五章中训练的图像分类模型的工件,训练计算机视觉模型

    $ tar xvfz model.tar.gz
    x image-classification-0010.params
    x model-shapes.json
    x image-classification-symbol.json
    
  2. 加载模型及其参数,如下所示:

    import mxnet, json
    from mxnet import gluon
    sym_json = json.load(
               open('image-classification-symbol.json'))
    sym_json_string = json.dumps(sym_json)
    net = gluon.nn.SymbolBlock(
        outputs=mx.sym.load_json(sym_json_string),
        inputs=mx.sym.var('data'))
    net.load_parameters(
        'image-classification-0010.params',  
        allow_missing=True)
    net.collect_params().initialize()
    
  3. 输入形状是一个 300x300 的彩色图像,具有三个通道(红色、绿色和蓝色,或 RGB)。因此,我们使用随机值创建一张假图像。我们将其通过模型并读取结果,如下所示:

    test_sample = mx.ndarray.random.normal(
        shape=(1,3,300,300))
    response = net(test_sample)
    print(response)
    

    有趣的是,这张随机图片被分类为猫,正如以下代码片段中所定义的:

    array([[0.99126923, 0.00873081]], dtype=float32)
    

重用目标检测更加复杂,因为训练网络需要针对预测进行修改。你可以在github.com/aws-samples/amazon-sagemaker-aws-greengrass-custom-object-detection-model/找到一个示例。

现在,让我们来看一下极限梯度提升XGBoost)工件。

检查和导出 XGBoost 模型

一个 XGBoost 工件包含一个文件——模型本身。然而,模型的格式取决于你使用 XGBoost 的方式。

使用内置算法时,模型是一个存储Booster对象的 pickle 文件。一旦工件被提取,我们只需解压并加载模型,如下所示:

$ tar xvfz model.tar.gz
x xgboost-model
$ python
>>> import pickle
>>> model = pickle.load(open('xgboost-model', 'rb'))
>>> type(model)
<class 'xgboost.core.Booster'>

使用内置框架时,模型只是一个保存的模型。一旦工件被提取,我们直接加载该模型,如下所示:

$ tar xvfz model.tar.gz
x xgb.model
$ python
>>> import xgboost as xgb
>>> bst = xgb.Booster({'nthread': 4})
>>> model = bst.load_model('xgb.model')
>>> type(bst)
<class 'xgboost.core.Booster'>

现在,让我们来看一下scikit-learn工件。

检查和导出 scikit-learn 模型

Scikit-learn 模型是通过joblibhttps://joblib.readthedocs.io)保存和加载的,下面的代码片段展示了这一点。这个库提供了一组轻量级的流水线工具,但我们只使用它来保存模型:

$ tar xvfz model.tar.gz
x model.joblib
$ python
>>> import joblib
>>> model = joblib.load('model.joblib')
>>> type(model)
<class 'sklearn.linear_model._base.LinearRegression'>

最后,让我们来看一下TensorFlow工件。

检查和导出 TensorFlow 模型

TensorFlow 和Keras模型以TensorFlow Serving格式保存,下面的代码片段展示了这一点:

$ mkdir /tmp/models
$ tar xvfz model.tar.gz -C /tmp/models
x 1/
x 1/saved_model.pb
x 1/assets/
x 1/variables/
x 1/variables/variables.index
x 1/variables/variables.data-00000-of-00002
x 1/variables/variables.data-00001-of-00002

提供这种模型最简单的方式是运行 TensorFlow Serving 的Docker镜像,下面的代码片段展示了这一点。你可以在www.tensorflow.org/tfx/serving/serving_basic找到更多细节:

$ docker run -t --rm -p 8501:8501
  -v "/tmp/models:/models/fmnist"
  -e MODEL_NAME=fmnist
  tensorflow/serving

让我们来看一个最终的例子,其中我们导出一个 Hugging Face 模型。

检查和导出 Hugging Face 模型

Hugging Face 模型可以在 TensorFlow 或 PyTorch 上进行训练。让我们重用我们在第七章中的 Hugging Face 示例,使用内置框架扩展机器学习服务,我们使用 PyTorch 训练了一个情感分析模型,然后按以下步骤进行:

  1. 我们从 S3 复制模型工件并进行解压,像这样:

    $ tar xvfz model.tar.gz
    training_args.bin
    config.json
    pytorch_model.bin
    
  2. 在 Jupyter 笔记本中,我们使用 Hugging Face API 加载模型配置。然后我们使用DistilBertForSequenceClassification对象构建模型,该对象对应我们在 SageMaker 上训练的模型。以下是实现此操作的代码:

    from transformers import AutoConfig, DistilBertForSequenceClassification
    config = AutoConfig.from_pretrained(
             './model/config.json')
    model = DistilBertForSequenceClassification
           .from_pretrained('./model/pytorch_model.bin',  
                            config=config)
    
  3. 接下来,我们获取与模型关联的分词器,如下所示:

    from transformers import AutoTokenizer
    tokenizer = AutoTokenizer.from_pretrained(
                'distilbert-base-uncased') 
    
  4. 我们写一个简短的函数,将softmax应用于模型输出层返回的激活值,如下所示:

    import torch
    def probs(logits):
        softmax = torch.nn.Softmax(dim=1)
        pred = softmax(logits).detach().numpy()
        return pred
    
  5. 最后,我们定义一个示例并使用我们的模型进行预测,如下所示:

    inputs = tokenizer("The Phantom Menace was a really bad movie. What a waste of my life.", return_tensors='pt')
    outputs = model(**inputs)
    print(probs(outputs.logits))
    

    正如预期的那样,情感非常负面,正如我们在这里看到的:

    [[0.22012234 0.7798777 ]]
    

这部分内容结束了从 SageMaker 导出模型的内容。如你所见,这其实一点也不难。

现在,让我们学习如何在实时端点上部署模型。

在实时端点上部署模型

SageMaker 端点使用托管在完全托管基础设施上的模型进行实时预测。它们可以使用 SageMaker boto3 创建和管理。

您可以在 SageMaker Studio 中的 SageMaker 资源/端点 下找到有关您端点的信息。

现在,让我们更详细地看一下 SageMaker SDK。

使用 SageMaker SDK 管理端点

SageMaker SDK 允许您以多种方式使用端点,如下所述:

  • 配置估算器,使用 fit() 进行训练,使用 deploy() 部署端点,并使用 predict() 调用它

  • 导入和部署模型

  • 调用现有端点

  • 更新现有端点

到目前为止,我们已经在许多示例中使用了第一个场景。让我们看看其他的。

导入和部署 XGBoost 模型

当您希望导入不在 SageMaker 上训练的模型或重新部署 SageMaker 模型时,这非常有用。在前一节中,我们看到了模型工件的样子,以及如何使用它们来打包模型。我们将按照以下步骤进行:

  1. 从我们使用 save_model() 在本地训练并保存的 XGBoost 模型开始,我们首先通过运行以下代码创建一个模型工件:

    $ tar cvfz model-xgb.tar.gz xgboost-model
    
  2. 在 Jupyter 笔记本中,我们将模型工件上传到我们的默认存储桶,如下所示:

    import sagemaker
    sess = sagemaker.Session()
    prefix = 'export-xgboost'
    model_path = sess.upload_data(
        path=model-xgb.tar.gz', 
        key_prefix=prefix)
    
  3. 然后,我们创建一个 XGBoostModel 对象,传递工件的位置和推断脚本(稍后将详细介绍)。我们还选择了一个框架版本,它应该与我们用来训练模型的版本匹配。代码在以下片段中说明:

    from sagemaker.xgboost.model import XGBoostModel
    xgb_model = XGBoostModel(
        model_data=model_path,
        entry_point='xgb-script.py',
        framework_version='1.3-1',
        role=sagemaker.get_execution_role())
    
  4. 推断脚本非常简单。它只需要包含一个加载模型的函数,就像我们在 第七章 中讨论部署框架模型时所解释的那样。代码在以下片段中说明:

    import os
    import xgboost as xgb
    def model_fn(model_dir):
        model = xgb.Booster()
        model.load_model(
            os.path.join(model_dir,'xgboost-model'))
        return model
    
  5. 回到笔记本,然后我们像往常一样部署和预测,如下所示:

    xgb_predictor = xgb_model.deploy(. . .)
    xgb_predictor.predict(. . .)
    

现在,让我们用 TensorFlow 模型做同样的事情。

导入和部署 TensorFlow 模型

这个过程非常类似,我们将在下面看到:

  1. 我们首先使用 tar 打包了一个在 TensorFlow Serving 格式中训练并保存的 TensorFlow 模型。我们的工件应该像这样(请不要忘记创建顶级目录!):

    $ tar tvfz model.tar.gz
    1/
    1/saved_model.pb
    1/assets/
    1/variables/
    1/variables/variables.index
    1/variables/variables.data-00000-of-00002
    1/variables/variables.data-00001-of-00002
    
  2. 然后,我们按如下方式将工件上传到 S3:

    import sagemaker
    sess = sagemaker.Session()
    prefix = 'byo-tf'
    model_path = sess.upload_data(
       path='model.tar.gz', 
       key_prefix=prefix)
    
  3. 接下来,我们从工件创建一个 SageMaker 模型。默认情况下,我们不必提供推断脚本。如果需要自定义预处理和后处理处理程序用于特征工程、异构序列化等等,我们会通过。您可以在 https://sagemaker.readthedocs.io/en/stable/frameworks/tensorflow/using_tf.html#deploying-from-an-estimator 找到更多信息。代码在以下片段中说明:

    from sagemaker.tensorflow.model import TensorFlowModel
    tf_model = TensorFlowModel(
        model_data=model_path,
        framework_version='2.3.1',
        role=sagemaker.get_execution_role())
    
  4. 然后,由于TensorFlow深度学习容器DLC),我们像往常一样部署和预测。

现在,让我们做一个最终的示例,在这个示例中,我们导入并部署一个 Hugging Face 模型,使用 PyTorch 的 DLC 和一个用于模型加载及自定义处理的推理脚本。

使用 PyTorch 导入并部署 Hugging Face 模型

我们将重用 Hugging Face 的示例,并首先关注推理脚本。它包含四个函数:模型加载、预处理、预测和后处理。我们将按以下步骤进行:

  1. 模型加载函数使用我们导出模型时所用的相同代码。唯一的不同是我们从model_dir加载文件,这个路径由 SageMaker 传递给 PyTorch 容器。我们还只加载一次分词器。代码如下所示:

    tokenizer = AutoTokenizer.from_pretrained(
                'distilbert-base-uncased')
    def model_fn(model_dir):
      config_path='{}/config.json'.format(model_dir)
      model_path='{}/pytorch_model.bin'.format(model_dir)
      config=AutoConfig.from_pretrained(config_path)
      model= DistilBertForSequenceClassification
             .from_pretrained(model_path, config=config)
      return model
    
  2. 预处理和后处理函数很简单。它们只检查正确的内容和接受的类型。您可以在以下代码片段中看到它们:

    def input_fn(serialized_input_data, 
                 content_type=JSON_CONTENT_TYPE):  
      if content_type == JSON_CONTENT_TYPE:
        input_data = json.loads(serialized_input_data)
        return input_data
      else:
        raise Exception('Unsupported input type: ' 
                        + content_type)
    def output_fn(prediction_output, 
                  accept=JSON_CONTENT_TYPE):
      if accept == JSON_CONTENT_TYPE:
        return json.dumps(prediction_output), accept
      else:
        raise Exception('Unsupported output type: '
                        + accept)
    
  3. 最后,预测函数对输入数据进行分词,进行预测,并返回最可能的类别名称,如下所示:

    CLASS_NAMES = ['negative', 'positive']
    def predict_fn(input_data, model):
        inputs = tokenizer(input_data['text'], 
                           return_tensors='pt')
        outputs = model(**inputs)
        logits = outputs.logits
        _, prediction = torch.max(logits, dim=1)
        return CLASS_NAMES[prediction]
    

现在我们的推理脚本已经准备好,接下来我们进入笔记本,导入模型并进行部署,如下所示:

  1. 我们创建一个PyTorchModel对象,传递模型文件在 S3 中的位置和推理脚本的位置,如下所示:

    from sagemaker.pytorch import PyTorchModel
    model = PyTorchModel(
        model_data=model_data_uri,
        role=sagemaker.get_execution_role(), 
        entry_point='torchserve-predictor.py',
        source_dir='src',
        framework_version='1.6.0',
        py_version='py36')
    
  2. 我们使用model.deploy()进行部署。然后,我们创建两个样本并将它们发送到我们的端点,如下所示:

    positive_data = {'text': "This is a very nice camera, I'm super happy with it."}
    negative_data = {'text': "Terrible purchase, I want my money back!"}
    prediction = predictor.predict(positive_data)
    print(prediction)
    prediction = predictor.predict(negative_data)
    print(prediction)
    

    如预期,输出结果为positivenegative

本节关于导入和部署模型的内容到此结束。接下来,让我们学习如何调用已经部署的端点。

调用现有端点

当你想要与一个实时端点交互,但没有访问预测器的权限时,这非常有用。我们只需要知道端点的名称,按以下步骤进行:

  1. 为我们之前部署的端点构建一个TensorFlowPredictor预测器。该对象是框架特定的。代码如下所示:

    from sagemaker.tensorflow.model import TensorFlowPredictor
    another_predictor = TensorFlowPredictor(
        endpoint_name=tf_endpoint_name,
        serializer=sagemaker.serializers.JSONSerializer()
    )
    
  2. 然后,像往常一样进行预测,如下所示:

    another_predictor.predict(…)
    

现在,让我们学习如何更新端点。

更新现有端点

update_endpoint() API 允许你以非破坏性的方式更新端点的配置。端点仍然在服务中,你可以继续使用它进行预测。

让我们在 TensorFlow 端点上尝试这个,如下所示:

  1. 我们将实例数量设置为2并更新端点,如下所示:

    another_predictor.update_endpoint(
        initial_instance_count=2,
        instance_type='ml.t2.medium')
    
  2. 端点会立即更新,如下图所示。图 11.2 – 端点正在更新

    图 11.2 – 端点正在更新

  3. 一旦更新完成,端点将由两个实例支持,如下图所示:

图 11.3 – 由两个实例支持的端点

图 11.3 – 由两个实例支持的端点

如你所见,使用 SageMaker SDK 导入、部署、重新部署和更新模型非常容易。然而,某些操作要求我们与更底层的 API 进行交互。它们可以在 AWS 语言 SDK 中找到,我们将利用我们亲爱的朋友boto3来演示这些操作。

使用 boto3 SDK 管理端点

boto3是 AWS 的 Python SDK(aws.amazon.com/sdk-for-python/)。它包括所有 AWS 服务的 API(除非它们没有 API!)。SageMaker API 可通过 https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sagemaker.html 访问。

boto3 API 是服务级别的 API,它们赋予我们对所有服务操作的完全控制。让我们来看看它们如何帮助我们以 SageMaker SDK 无法实现的方式部署和管理端点。

使用 boto3 SDK 部署端点

使用boto3部署端点是一个四步操作,概述如下:

  1. 使用create_model() API 创建一个或多个模型。或者,我们可以使用已经训练或通过 SageMaker SDK 导入的现有模型。为了简洁起见,我们将在此进行演示。

  2. 定义一个或多个生产变体,列出每个模型的基础设施需求。

  3. 创建create_endpoint_config() API,传递之前定义的生产变体,并为每个变体分配一个权重。

  4. 使用create_endpoint() API 创建端点。

让我们利用这些 API,部署一个运行我们在波士顿住房数据集上训练的 XGBoost 模型的端点,该端点包含两个变体,如下所示:

  1. 我们定义了两个变体;它们都由一个单一实例支持。然而,它们将分别接收九成和一成的请求——也就是说,“变体权重/权重总和”。如果我们想要在生产环境中引入一个新模型并确保它正常工作,再向其发送流量时,可以使用这种设置。代码如以下片段所示:

    production_variants = [
      { 'VariantName': 'variant-1',
        'ModelName': model_name_1,
        'InitialInstanceCount': 1,
        'InitialVariantWeight': 9,
        'InstanceType': 'ml.t2.medium'},
      { 'VariantName': 'variant-2',
        'ModelName': model_name_2,
        'InitialInstanceCount': 1,
        'InitialVariantWeight': 1,
        'InstanceType': 'ml.t2.medium'}]
    
  2. 我们通过传递这两个变体并设置可选标签来创建端点配置,如下所示:

    import boto3
    sm = boto3.client('sagemaker')
    endpoint_config_name = 'xgboost-two-models-epc'
    response = sm.create_endpoint_config(
        EndpointConfigName=endpoint_config_name,
        ProductionVariants=production_variants,
        Tags=[{'Key': 'Name', 
               'Value': endpoint_config_name},
              {'Key': 'Algorithm', 'Value': 'xgboost'}])
    

    我们可以使用list_endpoint_configs()列出所有端点配置,并使用describe_endpoint_config() boto3 API 描述特定配置。

  3. 我们基于此配置创建一个端点:

    endpoint_name = 'xgboost-two-models-ep'
    response = sm.create_endpoint(
        EndpointName=endpoint_name,
        EndpointConfigName=endpoint_config_name,
        Tags=[{'Key': 'Name','Value': endpoint_name},
              {'Key': 'Algorithm','Value': 'xgboost'},
              {'Key': 'Environment',
               'Value': 'development'}])
    

    我们可以使用list_endpoints()列出所有端点,并使用describe_endpoint() boto3 API 描述特定端点。

  4. 创建boto3等待器是一种便捷的方式,用于等待端点投入使用。你可以在这里看到创建过程:

    waiter = sm.get_waiter('endpoint_in_service')
    waiter.wait(EndpointName=endpoint_name)
    
  5. 几分钟后,端点已投入使用。如以下截图所示,现在它使用了两个生产变体:图 11.4 – 查看生产变体

    图 11.4 – 查看生产变体

  6. 然后,我们调用端点,如以下代码片段所示。默认情况下,预测请求会根据变体的权重转发:

    smrt = boto3.Session().client(
        service_name='runtime.sagemaker') 
    response = smrt.invoke_endpoint(
       EndpointName=endpoint_name,
       ContentType='text/csv',
       Body=test_sample)
    
  7. 我们还可以选择接收预测请求的变体。这对于 A/B 测试非常有用,在这种测试中,我们需要将用户固定在一个特定的模型上。以下代码示例展示了如何实现这一点:

    variants = ['variant-1', 'variant-2']
    for v in variants:
      response = smrt.invoke_endpoint(
                     EndpointName=endpoint_name, 
                     ContentType='text/csv',
                     Body=test_sample,
                     TargetVariant=v)
      print(response['Body'].read())
    

    这将产生以下输出:

    b'[0.0013231043703854084]'
    b'[0.001262241625227034]'
    
  8. 我们还可以更新权重——例如,赋予两个变体相等的权重,使它们接收到相同的流量份额——如下所示:

    response = sm.update_endpoint_weights_and_capacities(
        EndpointName=endpoint_name,
        DesiredWeightsAndCapacities=[
            { 'VariantName': 'variant-1', 
              'DesiredWeight': 5},
            { 'VariantName': 'variant-2', 
              'DesiredWeight': 5}])
    
  9. 我们可以完全移除一个变体,并将所有流量发送到剩下的变体。这里,端点在整个过程中保持服务状态,且没有流量丢失。代码示例如下:

    production_variants_2 = [
      {'VariantName': 'variant-2',
       'ModelName': model_name_2,
       'InitialInstanceCount': 1,
       'InitialVariantWeight': 1,
       'InstanceType': 'ml.t2.medium'}]
    endpoint_config_name_2 = 'xgboost-one-model-epc'
    response = sm.create_endpoint_config(
        EndpointConfigName=endpoint_config_name_2,
        ProductionVariants=production_variants_2,
        Tags=[{'Key': 'Name',
               'Value': endpoint_config_name_2},
              {'Key': 'Algorithm','Value': 'xgboost'}])
    response = sm.update_endpoint(
        EndpointName=endpoint_name,
        EndpointConfigName=endpoint_config_name_2)
    
  10. 最后,我们通过删除端点和两个端点配置来进行清理,如下所示:

    sm.delete_endpoint(EndpointName=endpoint_name)
    sm.delete_endpoint_config(
      EndpointConfigName=endpoint_config_name)
    sm.delete_endpoint_config(
      EndpointConfigName=endpoint_config_name_2)
    

如您所见,boto3 API 更加冗长,但它也为我们提供了进行机器学习ML)操作所需的灵活性。在下一章中,我们将学习如何自动化这些操作。

在批量转换器上部署模型

一些用例并不需要实时端点。例如,您可能希望每周一次性预测 10 千兆字节GB)的数据,获取结果,并将其输入到下游应用程序。批量转换器是一种非常简单的实现方式。

在本示例中,我们将使用在波士顿房价数据集上训练的 scikit-learn 脚本,在第七章使用内建框架扩展机器学习服务中进行训练。让我们开始,如下所示:

  1. 按照惯例配置估算器,运行以下代码:

    from sagemaker.sklearn import SKLearn
    sk = SKLearn(entry_point='sklearn-boston-housing.py',
       role=sagemaker.get_execution_role(),
       instance_count=1,
       instance_type='ml.m5.large',
       output_path=output,
       hyperparameters=
           {'normalize': True, 'test-size': 0.1})
    sk.fit({'training':training})
    
  2. 让我们在批处理模式下预测训练集。我们去除目标值,将数据集保存为逗号分隔值CSV)文件,并将其上传至 S3,如下所示:

    import pandas as pd
    data = pd.read_csv('housing.csv')
    data.drop(['medv'], axis=1, inplace=True)
    data.to_csv('data.csv', header=False, index=False)
    batch_input = sess.upload_data(
        path='data.csv', 
        key_prefix=prefix + '/batch')
    
  3. 创建转换器对象并启动批处理,如下所示:

    sk_transformer = sk.transformer(
        instance_count=1, 
        instance_type='ml.m5.large')
    sk_transformer.transform(
        batch_input, 
        content_type='text/csv', 
        wait=True, logs=True)
    
  4. 在训练日志中,我们可以看到 SageMaker 创建了一个临时端点并用它来预测数据。对于大规模任务,我们可以通过对样本进行小批量预测(使用strategy参数)来优化吞吐量,增加预测并发性(max_concurrent_transforms),以及增大最大负载大小(max_payload)。

  5. 一旦作业完成,预测结果将在 S3 中可用,如下所示:

    print(sk_transformer.output_path)
    s3://sagemaker-us-east-1-123456789012/sagemaker-scikit-learn-2020-06-12-08-28-30-978
    
  6. 使用 AWS CLI,我们可以通过运行以下代码轻松地获取这些预测:

    %%bash -s "$sk_transformer.output_path"
    aws s3 cp $1/data.csv.out .
    head -1 data.csv.out
    [[29.73828574177013], [24.920634119498292], …
    
  7. 就像训练一样,转换器使用的基础设施在作业完成后立即关闭,因此无需进行清理。

在接下来的部分中,我们将讨论推理管道,以及如何使用它们部署一系列相关模型。

在推理管道上部署模型

现实中的机器学习场景通常涉及多个模型;例如,您可能需要对传入的数据执行预处理步骤,或者使用主成分分析PCA)算法降低其维度。

当然,你可以将每个模型部署到独立的端点。然而,必须编写编排代码来按顺序将预测请求传递给每个模型。增加多个端点也会带来额外的成本。

相反,推理管道 允许你在同一个端点上部署最多五个模型,或者用于批量转换,并自动处理预测顺序。

假设我们想先运行 PCA,然后再运行 Linear Learner。构建推理管道将如下所示:

  1. 在输入数据集上训练 PCA 模型。

  2. 使用 PCA 处理训练集和验证集,并将结果存储在 S3 中。批量转换是实现这一目标的一个好方法。

  3. 使用 PCA 处理后的数据集作为输入训练 Linear Learner 模型。

  4. 使用 create_model() API 创建推理管道,如下所示:

    response = sagemaker.create_model(
        ModelName='pca-linearlearner-pipeline',
            Containers=[
                {
                 'Image': pca_container,
                 'ModelDataUrl': pca_model_artifact,
                  . . .
                },
                {
                 'Image': ll_container,
                 'ModelDataUrl': ll_model_artifact,
                  . . .
                }
            ],
            ExecutionRoleArn=role
    )
    
  5. 按照常规方式创建端点配置和端点。我们还可以使用批量转换器来使用管道。

你可以在 github.com/awslabs/amazon-sagemaker-examples/tree/master/sagemaker-python-sdk/scikit_learn_inference_pipeline 找到一个完整的示例,使用了 scikit-learn 和 Linear Learner。

Spark 是一种非常流行的数据处理工具,SageMaker 允许你使用内置的 SparkML Serving 容器(https://github.com/aws/sagemaker-sparkml-serving-container)部署 Spark 模型,该容器使用 mleap 库(github.com/combust/mleap)。当然,这些模型可以作为 推理管道 的一部分。你可以在 github.com/awslabs/amazon-sagemaker-examples/tree/master/advanced_functionality 找到几个示例。

这就是我们关于模型部署的讨论。接下来,我们将介绍一个 SageMaker 功能,帮助我们检测影响预测质量的数据问题:SageMaker Model Monitor

使用 Amazon SageMaker Model Monitor 监控预测质量

SageMaker Model Monitor 具有两个主要功能,如下所述:

  • 捕获发送到端点的数据以及端点返回的预测结果。这对于进一步分析或在新模型的开发和测试过程中重放真实流量非常有用。

  • 将传入流量与基于训练集构建的基准进行比较,并发送关于数据质量问题的警报,例如缺失特征、拼写错误的特征和统计属性差异(也称为“数据漂移”)。

我们将使用来自第四章线性学习器示例,训练机器学习模型,在该章节中我们使用波士顿房价数据集训练了一个模型。首先,我们将向端点添加数据捕获功能。然后,我们将建立一个基准并设置监控计划,以便定期将接收到的数据与基准进行比较。

捕获数据

我们可以在部署端点时设置数据捕获过程。我们也可以使用我们刚才在生产变体中使用的update_endpoint() API 在现有端点上启用它。

在撰写本文时,有一些注意事项需要你了解,具体内容如下:

  • 如果要进行模型监控,你一次只能发送一个样本。虽然小批量预测会被捕获,但它们会导致监控任务失败。

  • 同样,数据样本和预测必须是平坦的、表格化的数据。结构化数据(如列表中的列表和嵌套的 JSON)会被捕获,但模型监控任务无法处理它。你可以选择添加预处理脚本和后处理脚本将其扁平化。你可以在docs.aws.amazon.com/sagemaker/latest/dg/model-monitor-pre-and-post-processing.html找到更多信息。

  • 内容类型和接受类型必须是相同的。你可以使用 CSV 或 JSON,但不能混合使用。

  • 如果端点附加了监控计划,你无法删除该端点。你必须先删除监控计划,然后才能删除端点。

既然知道了这一点,我们来捕获一些数据吧!开始:

  1. 训练像往常一样进行。你可以在 GitHub 仓库中找到代码。

  2. 我们为 100%的预测请求和响应创建了一个数据捕获配置,将所有数据存储在 S3 中,如下所示:

    from sagemaker.model_monitor.data_capture_config import DataCaptureConfig
    capture_path = 's3://{}/{}/capture/'.format(bucket, prefix)
    ll_predictor = ll.deploy(
        initial_instance_count=1,
        instance_type='ml.t2.medium',
        data_capture_config = DataCaptureConfig(     
             enable_capture = True,                    
             sampling_percentage = 100,                
             capture_options = ['REQUEST', 'RESPONSE'],
             destination_s3_uri = capture_path))
    
  3. 一旦端点投入使用,我们就会发送数据进行预测。大约一两分钟内,我们可以在 S3 中看到捕获的数据,然后将其复制到本地,如下所示:

    %%bash -s "$capture_path"
    aws s3 ls --recursive $1
    aws s3 cp --recursive $1 .
    
  4. 打开其中一个文件,我们可以看到样本和预测,如下所示:

    {"captureData":{"endpointInput":{"observedContentType":"text/csv","mode":"INPUT","data":"0.00632,18.00,2.310,0,0.5380,6.5750,65.20,4.0900,1,296.0,15.30,4.98","encoding":"CSV"},"endpointOutput":{"observedContentType":"text/csv; charset=utf-8","mode":"OUTPUT","data":"30.4133586884","encoding":"CSV"}},"eventMetadata":{"eventId":"8f45e35c-fa44-40d2-8ed3-1bcab3a596f3","inferenceTime":"2020-07-30T13:36:30Z"},"eventVersion":"0"}
    

如果这是实时数据,我们可以使用它来测试新的模型,以便将其性能与现有模型进行比较。

现在,让我们学习如何从训练集创建基准。

创建基准

SageMaker 模型监控包含一个内置容器,我们可以用它来构建基准,并可以直接与DefaultModelMonitor对象一起使用。你也可以带上自己的容器,在这种情况下你将使用ModelMonitor对象。让我们开始,如下所示:

  1. 基准只能建立在 CSV 数据集和 JSON 数据集上。我们的数据集是空格分隔的,需要将其转换为 CSV 文件,如下所示。然后我们可以将其上传到 S3:

    data.to_csv('housing.csv', sep=',', index=False)
    training = sess.upload_data(
       path='housing.csv', 
       key_prefix=prefix + "/baseline")
    

    注意

    这里有一个小注意事项:基线作业是在 SageMaker Processing 中运行的 Spark 作业。因此,列名称需要符合 Spark 规范,否则作业将以难以理解的方式失败。特别是,列名中不允许使用点号。我们这里没有这个问题,但请记住这一点。

  2. 定义基础设施需求、训练集的位置及其格式如下:

    from sagemaker.model_monitor import DefaultModelMonitor
    from sagemaker.model_monitor.dataset_format import DatasetFormat
    ll_monitor = DefaultModelMonitor(role=role,
        instance_count=1, instance_type='ml.m5.large')
    ll_monitor.suggest_baseline(baseline_dataset=training,
        dataset_format=DatasetFormat.csv(header=True))
    
  3. 正如您猜测的那样,这是作为 SageMaker 处理作业运行的,您可以在 /aws/sagemaker/ProcessingJobs 前缀中找到其日志。

    其输出位置有两个 JSON 文档:statistics.jsonconstraints.json。我们可以通过运行以下代码使用 pandas 查看它们的内容:

    baseline = ll_monitor.latest_baselining_job
    constraints = pd.io.json.json_normalize(
        baseline.suggested_constraints()
        .body_dict["features"])
    schema = pd.io.json.json_normalize(
        baseline.baseline_statistics().body_dict["features"])
    
  4. 如下截图所示,constraints 文件提供了每个特征的推断类型、数据集的完整性以及是否包含负值:图 11.5 – 查看推断模式

    图 11.5 – 查看推断模式

  5. statistics 文件添加了基本统计信息,如下截图所示:

图 11.6 – 查看数据统计

图 11.6 – 查看数据统计

它还包括基于 KLL sketches 的分布信息(https://arxiv.org/abs/1603.05346v2),这是定义分位数的一种紧凑方式。

创建了基线后,我们可以设置一个监控计划,以便将流入的流量与基线进行比较。

设置监控计划

我们只需传递端点名称、统计数据、约束条件以及分析应运行的频率。我们将选择每小时运行,这是允许的最短频率。代码如下片段所示:

from sagemaker.model_monitor import CronExpressionGenerator
ll_monitor.create_monitoring_schedule(
    monitor_schedule_name='ll-housing-schedule',
    endpoint_input=ll_predictor.endpoint,
    statistics=ll_monitor.baseline_statistics(),
    constraints=ll_monitor.suggested_constraints(),
    schedule_cron_expression=CronExpressionGenerator.hourly())

在这里,分析将由内置容器执行。我们也可以提供自定义容器与特定的分析代码。您可以在docs.aws.amazon.com/sagemaker/latest/dg/model-monitor-byoc-containers.html找到更多信息。

现在,让我们向端点发送一些恶劣的数据,并查看 SageMaker Model Monitor 是否能够检测到它。

发送不良数据

不幸的是,模型有时会接收到不正确的数据。也许源头已经损坏,也许负责调用端点的应用程序有 bug 等等。让我们模拟这种情况,看看这对预测质量有多大影响,如下所示:

  1. 从有效样本开始,我们得到了正确的预测,如此所示:

    test_sample = '0.00632,18.00,2.310,0,0.5380,6.5750,65.20,4.0900,1,296.0,15.30,4.98'
    ll_predictor.serializer =    
        sagemaker.serializers.CSVSerializer()
    ll_predictor.deserializer =  
        sagemaker.deserializers.CSVDeserializer()
    response = ll_predictor.predict(test_sample)
    print(response)
    

    这所房子的价格是 USD 30,173:

    [['30.1734218597']]
    
  2. 现在,让我们将第一个特征乘以 10,000,如下代码片段所示。在应用程序代码中,缩放和单位错误是相当常见的:

    bad_sample_1 = '632.0,18.00,2.310,0,0.5380,6.5750,65.20,4.0900,1,296.0,15.30,4.98'
    response = ll_predictor.predict(bad_sample_1)
    print(response)
    

    哎呀!价格是负数,正如我们在这里所见。显然,这是一个错误的预测:

    [['-35.7245635986']]
    
  3. 让我们尝试通过运行以下代码否定最后一个特征:

    bad_sample_2 = '0.00632,18.00,2.310,0,0.5380,6.5750,65.20,4.0900,1,296.0,15.30,-4.98'
    response = ll_predictor.predict(bad_sample_2)
    print(response)
    

    预测值比应该有的要高得多,正如我们在下面的代码片段中看到的那样。这是一个更隐蔽的问题,意味着它更难被发现,且可能带来严重的业务后果:

    [['34.4245414734']]
    

您应该尝试使用错误数据进行实验,看看哪些特征最脆弱。所有这些流量都会被 SageMaker 模型监控捕获。一旦监控作业运行完成,您应该在其违规报告中看到条目。

检查违规报告

之前,我们创建了一个每小时监控的作业。如果您需要超过 1 小时才能看到结果,请不要担心;作业执行是由后端负载均衡的,短暂的延迟是很常见的:

  1. 我们可以在 SageMaker 控制台中找到更多关于我们监控作业的信息,在 describe_schedule() API 中查看,并通过 list_executions() API 列出执行记录,具体如下:

    ll_executions = ll_monitor.list_executions()
    print(ll_executions)
    

    在这里,我们可以看到三次执行:

    [<sagemaker.model_monitor.model_monitoring.MonitoringExecution at 0x7fdd1d55a6d8>,
    <sagemaker.model_monitor.model_monitoring.MonitoringExecution at 0x7fdd1d581630>,
    <sagemaker.model_monitor.model_monitoring.MonitoringExecution at 0x7fdce4b1c860>]
    
  2. 违规报告以 JSON 文件的形式存储在 S3 中。我们可以读取它,并使用 pandas 显示其内容,具体如下:

    violations = ll_monitor.latest_monitoring_constraint_violations()
    violations = pd.io.json.json_normalize(
        violations.body_dict["violations"])
    violations
    

    这将打印出上次监控作业检测到的违规情况,如下图所示:

    图 11.7 – 查看违规情况

    图 11.7 – 查看违规情况

  3. 当然,我们也可以在 S3 中获取文件并显示其内容,具体如下:

    %%bash -s "$report_path"
    echo $1
    aws s3 ls --recursive $1
    aws s3 cp --recursive $1 .
    

    这是一个示例条目,警告我们模型接收到了一个chas特征的分数值,尽管在模式中它被定义为整数:

    {
        "feature_name" : "chas",
        "constraint_check_type" : "data_type_check",
        "description" : "Data type match requirement is not met.
            Expected data type: Integral, Expected match: 100.0%.  
            Observed: Only 0.0% of data is Integral."
    }
    

    我们还可以将这些违规情况发送到 CloudWatch 指标,并触发警报以通知开发人员潜在的数据质量问题。您可以在docs.aws.amazon.com/sagemaker/latest/dg/model-monitor-interpreting-cloudwatch.html找到更多信息。

  4. 完成后,别忘了删除监控计划和端点本身,具体如下:

    response = ll_monitor.delete_monitoring_schedule()
    ll_predictor.delete_endpoint()
    

如您所见,SageMaker 模型监控帮助您捕获进来的数据和预测,这是一个对模型测试非常有用的功能。此外,您还可以使用内置容器或您自己的容器执行数据质量分析。

在接下来的部分,我们将不再关注端点,而是学习如何将模型部署到容器服务。

将模型部署到容器服务

之前,我们看到如何在 S3 中获取模型工件并如何从中提取实际的模型。了解这一点后,将其部署到容器服务上就变得相当简单,比如Amazon Elastic Container ServiceECS)、Amazon Elastic Kubernetes ServiceEKS)或Amazon Fargate

也许公司政策要求所有内容都部署在容器中,也许您就是喜欢容器,或者两者兼而有之!无论什么原因,您都完全可以做到。这里没有特定于 SageMaker 的内容,AWS 对这些服务的文档会告诉您所有需要了解的内容。

一个高层次的示例流程可能是这样的:

  1. 在 SageMaker 上训练一个模型。

  2. 当训练完成时,获取工件并提取模型。

  3. 将模型推送到 Git 仓库。

  4. 编写任务定义(适用于 ECS 和 Fargate)或 Pod 定义(适用于 EKS)。它可以使用内置容器之一,也可以使用您自己的容器。然后,它可以运行模型服务器或您自己的代码,从 Git 仓库克隆模型,加载它并提供预测服务。

  5. 使用此定义,在您的集群上运行容器。

让我们将其应用到 Amazon Fargate。

在 SageMaker 上训练并在 Amazon Fargate 上部署

Amazon Fargate让您在完全托管的基础设施上运行容器(aws.amazon.com/fargate)。无需创建和管理集群,这使得它非常适合那些不希望涉及基础设施细节的用户。然而,请注意,在撰写本文时,Fargate 尚不支持图形处理单元GPU)容器。

准备模型

我们通过以下步骤来准备模型:

  1. 首先,我们在 Fashion-MNIST 数据集上训练一个 TensorFlow 模型。一切照常进行。

  2. 我们找到 S3 中模型工件的位置,并将其设置为环境变量,如下所示:

    %env model_data {tf_estimator.model_data}
    
  3. 我们从 S3 下载工件并将其提取到本地目录,如下所示:

    %%sh
    aws s3 cp ${model_data} .
    mkdir test-models
    tar xvfz model.tar.gz -C test-models
    
  4. 我们打开终端并将模型提交到公共 Git 仓库,如以下代码片段所示。我在这里使用的是我的一个仓库(gitlab.com/juliensimon/test-models);您应将其替换为您的仓库:

    <initialize git repository>
    $ cd test-models
    $ git add model
    $ git commit -m "New model"
    $ git push
    

配置 Fargate

现在模型已在仓库中,我们需要配置 Fargate。这次我们将使用命令行。您也可以使用boto3或任何其他语言的 SDK 做同样的事情。我们按以下步骤进行:

  1. ecs-cli是一个方便的 CLI 工具,用于管理集群。让我们通过运行以下代码来安装它:

    %%sh
    sudo curl -o /usr/local/bin/ecs-cli https://amazon-ecs-cli.s3.amazonaws.com/ecs-cli-linux-amd64-latest
    sudo chmod 755 /usr/local/bin/ecs-cli
    
  2. 我们使用它来“创建”一个 Fargate 集群。实际上,这并不涉及创建任何基础设施;我们只是在定义一个集群名称,用于运行任务。请确保您有ecs:CreateCluster权限。如果没有,请在继续之前添加它。代码如下所示:

    %%sh 
    aws ecs create-cluster --cluster-name fargate-demo
    ecs-cli configure --cluster fargate-demo --region eu-west-1
    
  3. 我们在CloudWatch中创建一个日志组,容器将在其中写入输出。我们只需要做一次。以下是实现此操作的代码:

    %%sh
    aws logs create-log-group --log-group-name awslogs-tf-ecs
    
  4. 我们需要一个8500端口用于 Google 远程过程调用(8501端口用于表述性状态转移REST)API)。如果您还没有,您可以在弹性计算云EC2)控制台轻松创建一个。这里,我在我的默认虚拟私有云VPC)中创建了一个。它看起来像这样:

图 11.8 – 查看安全组

图 11.8 – 查看安全组

定义任务

现在,我们需要编写一个JSON文件,其中包含任务定义:要使用的容器镜像、入口点以及其系统和网络属性。我们开始吧,如下所示:

  1. 首先,我们定义任务允许消耗的中央处理单元CPU)和内存的数量。与 ECS 和 EKS 不同,Fargate 只允许一组有限的值,详情请见 docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html。我们将选择 4 个 虚拟 CPUvCPU)和 8 GB 随机存取内存RAM),如下面的代码片段所示:

    {
      "requiresCompatibilities": ["FARGATE"],
      "family": "inference-fargate-tf-230",
      "memory": "8192",
      "cpu": "4096",
    
  2. 接下来,我们定义一个容器,加载我们的模型并进行预测。我们将使用 TensorFlow 2.3.0 的 DLC。你可以在 github.com/aws/deep-learning-containers/blob/master/available_images.md 找到完整的列表。代码如下所示:

      "containerDefinitions": [{
        "name": "dlc-tf-inference",
        "image": "763104351884.dkr.ecr.us-east-1.amazonaws.com/tensorflow-inference:2.3.2-cpu-py37-ubuntu18.04",
        "essential": true,
    
  3. 它的入口点创建一个目录,克隆我们推送模型的代码库,并启动 TensorFlow Serving,具体如下:

        "command": [
           "mkdir -p /test && cd /test && git clone https://gitlab.com/juliensimon/test-models.git && tensorflow_model_server --port=8500 
    --rest_api_port=8501 --model_name=1 
    --model_base_path=/test/test-models/model"
        ],
        "entryPoint": ["sh","-c"],
    
  4. 因此,我们映射了两个 TensorFlow Serving 端口,如下所示:

        "portMappings": [
            {
              "hostPort": 8500,
              "protocol": "tcp",
              "containerPort": 8500
            },
            {
              "hostPort": 8501,
              "protocol": "tcp",
              "containerPort": 8501
            }
        ],
    
  5. 我们定义了日志配置,指向我们之前创建的 CloudWatch 日志组,如下所示:

        "logConfiguration": {
          "logDriver": "awslogs",
            "options": {
              "awslogs-group": "awslogs-tf-ecs",
              "awslogs-region": "eu-west-1",
              "awslogs-stream-prefix": "inference"
            }
        }
      }],
    
  6. 我们设置容器的网络模式,如下代码片段所示。awsvpc 是最灵活的选项,它将允许我们的容器公开访问,具体说明请见 docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html。它将在我们选择的子网中创建一个弹性网络接口

      "networkMode": "awsvpc"
    
  7. 最后,我们为任务定义一个 IAM 角色。如果这是你第一次使用 ECS,你应该在 IAM 控制台中创建这个角色。你可以在 docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html 找到相关说明。代码如下所示:

      "executionRoleArn":  
      "arn:aws:iam::123456789012:role/ecsTaskExecutionRole"
    }
    

运行任务

我们现在准备使用之前创建的安全组和默认 VPC 中的一个子网来运行任务。具体操作如下:

  1. 我们使用 run-task API 启动任务,传递任务定义的家族名称(不是文件名!)。请注意版本号,因为每次你注册新版本的任务定义时,它会自动增加,所以请确保使用的是最新版本。代码如下所示:

    %%sh
    aws ecs run-task
      --cluster fargate-demo 
      --task-definition inference-fargate-tf-230:1 
      --count 1
      --launch-type FARGATE
      --network-configuration 
        "awsvpcConfiguration={subnets=[$SUBNET_ID],
         securityGroups=[$SECURITY_GROUP_ID],
         assignPublicIp=ENABLED}"
    
  2. 几秒钟后,我们可以看到我们的预测容器正在运行(显示任务标识符ID)、状态、端口和任务定义),如下所示:

    %%sh
    ecs-cli ps --desired-status RUNNING
    a9c9a3a8-8b7c-4dbb-9ec4-d20686ba5aec/dlc-tf-inference  
    RUNNING  
    52.49.238.243:8500->8500/tcp, 
    52.49.238.243:8501->8501/tcp                         inference-fargate-tf230:1
    
  3. 使用容器的公共互联网协议IP)地址,我们构建一个包含 10 张样本图片的 TensorFlow Serving 预测请求,并将其发送到我们的容器,如下所示:

    import random, json, requests
    inference_task_ip = '52.49.238.243'
    inference_url = 'http://' +   
                    inference_task_ip +  
                    ':8501/v1/models/1:predict'
    indices = random.sample(range(x_val.shape[0] - 1), 10)
    images = x_val[indices]/255
    labels = y_val[indices]
    data = images.reshape(num_samples, 28, 28, 1)
    data = json.dumps(
        {"signature_name": "serving_default", 
         "instances": data.tolist()})
    headers = {"content-type": "application/json"}
    json_response = requests.post(
        inference_url, 
        data=data, 
        headers=headers)
    predictions = json.loads(
        json_response.text)['predictions']
    predictions = np.array(predictions).argmax(axis=1)
    print("Labels     : ", labels)
    print("Predictions: ", predictions)
    Labels     :  [9 8 8 8 0 8 9 7 1 1]
    Predictions:  [9 8 8 8 0 8 9 7 1 1]
    
  4. 当我们完成任务后,我们使用 run-task API 停止任务并删除集群,如下代码片段所示。当然,你也可以使用 ECS 控制台:

    %%sh
    aws ecs stop-task --cluster fargate-demo \
                      --task $TASK_ARN
    ecs-cli down --force --cluster fargate-demo
    

ECS 和 EKS 的流程非常相似。您可以在 gitlab.com/juliensimon/dlcontainers 找到简单的示例。如果您希望构建自己的工作流,这些示例应该是一个不错的起点。

Kubernetes 爱好者还可以使用 kubectl 来训练和部署模型。有关详细的教程,请访问 sagemaker.readthedocs.io/en/stable/workflows/kubernetes/index.html

总结

在本章中,您了解了模型工件、它们包含的内容,以及如何将模型导出到 SageMaker 之外。您还学习了如何导入和部署现有模型,并详细了解了如何使用 SageMaker SDK 和 boto3 SDK 管理端点。

然后,我们讨论了使用 SageMaker 的替代部署场景,既可以使用批量转换或推理管道,也可以使用容器服务在 SageMaker 之外进行部署。

最后,您学习了如何使用 SageMaker Model Monitor 捕获端点数据并监控数据质量。

在下一章,我们将讨论如何使用三种不同的 AWS 服务自动化 ML 工作流:AWS CloudFormationAWS 云开发工具包AWS CDK)和 Amazon SageMaker Pipelines

第十二章:自动化机器学习工作流

在上一章中,你学习了如何使用 boto3 SDK 在不同配置中部署机器学习模型。我们在 Jupyter Notebooks 中使用了它们的 API——这也是快速实验和迭代的首选方式。

然而,将笔记本用于生产任务并不是一个好主意。即使你的代码经过了仔细测试,监控、日志记录、创建其他 AWS 资源、处理错误、回滚等问题怎么办?做到这一切正确将需要大量额外的工作和代码,这可能导致更多的错误。需要一种更工业化的方法。

在本章中,你将首先学习如何通过AWS CloudFormationAWS Cloud Development KitCDK)配置 SageMaker 资源——这两个 AWS 服务专为实现可重复性、可预测性和可靠性而构建。你将看到如何在应用更改之前预览它们,以避免不可控和潜在的破坏性操作。

然后,你将学习如何使用另外两个服务——AWS Step FunctionsAmazon SageMaker Pipelines——来自动化端到端的机器学习工作流。你将看到如何使用简单的 API 构建工作流,并如何在 SageMaker Studio 中可视化结果。

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

  • 使用 AWS CloudFormation 自动化

  • 使用 AWS CDK 自动化

  • 使用 AWS Step Functions 构建端到端工作流

  • 使用 Amazon SageMaker Pipelines 构建端到端工作流

技术要求

你将需要一个 AWS 账户来运行本章中包含的示例。如果你还没有账户,请访问 aws.amazon.com/getting-started/ 创建一个。你还应该熟悉 AWS 免费套餐 (aws.amazon.com/free/),该套餐让你在某些使用限制内免费使用许多 AWS 服务。

你需要为你的账户安装并配置 AWS Command Line InterfaceCLI)(aws.amazon.com/cli/)。

你需要一个可用的 pandasnumpy 等。

本书中包含的代码示例可以在 GitHub 上找到,地址是 github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装一个 Git 客户端来访问这些代码(git-scm.com/)。

使用 AWS CloudFormation 自动化

AWS CloudFormation 长期以来一直是自动化 AWS 上基础设施构建和操作的首选方式 (aws.amazon.com/cloudformation)。虽然你完全可以写一本关于这个话题的书,但我们在本节中将仅介绍基础知识。

使用 CloudFormation 的第一步是编写一个模板——即一个描述你想要构建的资源(如 EC2 实例或 S3 存储桶)的JSONYAML 文本文件。几乎所有 AWS 服务的资源都可以使用,SageMaker 也不例外。如果我们查看 docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_SageMaker.html,我们可以看到我们可以创建 SageMaker Studio 应用程序,部署端点等等。

模板可以(并且应该)包含参数和输出。前者有助于使模板尽可能通用,后者提供可以被下游应用程序使用的信息,如端点 URL 或存储桶名称。

一旦你编写好了模板文件,就将它传递给 CloudFormation 来创建一个堆栈——也就是一组 AWS 资源。CloudFormation 会解析模板并自动创建所有资源。依赖关系也会自动管理,资源会按正确的顺序创建。如果堆栈无法正确创建,CloudFormation 会回滚操作,删除已经创建的资源。

通过应用更新后的模板修订版,可以更新堆栈。CloudFormation 会分析更改,并相应地创建、删除、更新或替换资源。得益于更改集,你可以在执行之前验证更改,然后决定是否继续进行。

当然,堆栈也可以被删除,CloudFormation 会自动销毁所有其资源,这是清理构建并避免留下垃圾文件的好方法。

让我们运行一个示例,部署一个模型到实时端点。

编写模板

这个堆栈等同于我们在 第十一章部署机器学习模型》中学习的调用 boto3 API:create_model()create_endpoint_configuration()create_endpoint()。因此,我们将定义三个 CloudFormation 资源(一个模型,一个端点配置和一个端点)及其参数:

  1. 创建一个名为 endpoint-one-model.yml 的新 YAML 文件,我们首先在 Parameters 部分定义堆栈的输入参数。每个参数都有名称、描述和类型。我们还可以选择提供默认值:

    AWSTemplateFormatVersion: 2010-09-09
    Parameters:
        ModelName:
            Description: Model name
            Type: String
        ModelDataUrl:
            Description: Location of model artifact
            Type: String
        ContainerImage:
            Description: Container used to deploy the model
            Type: String
        InstanceType:
            Description: Instance type
            Type: String
            Default: ml.m5.large
        InstanceCount:
            Description: Instance count
            Type: String
            Default: 1
        RoleArn:
            Description: Execution Role ARN
            Type: String
    
  2. Resources 部分,我们定义一个模型资源,使用 Ref 内置函数来引用适当的输入参数:

    Resources:
        Model:
            Type: "AWS::SageMaker::Model"
            Properties:
                Containers:
                    -
                        Image: !Ref ContainerImage
                        ModelDataUrl: !Ref ModelDataUrl
                ExecutionRoleArn: !Ref RoleArn
                ModelName: !Ref ModelName
    
  3. 然后,我们定义一个端点配置资源。我们使用 GetAtt 内置函数来获取模型资源的名称。当然,这要求模型资源已经存在,CloudFormation 会确保资源按正确的顺序创建:

        EndpointConfig:
            Type: "AWS::SageMaker::EndpointConfig"
            Properties:
                ProductionVariants:
                    -
                     ModelName: !GetAtt Model.ModelName
                     VariantName: variant-1
                     InitialInstanceCount: !Ref InstanceCount
                     InstanceType: !Ref InstanceType
                     InitialVariantWeight: 1.0
    
  4. 最后,我们定义一个端点资源。同样,我们使用 GetAtt 来获取端点配置的名称:

        Endpoint:
            Type: "AWS::SageMaker::Endpoint"
            Properties:
                EndpointConfigName: !GetAtt 
                EndpointConfig.EndpointConfigName
    
  5. Outputs 部分,我们返回端点的 CloudFormation 标识符以及其名称:

    Outputs:
        EndpointId:
            Value: !Ref Endpoint
        EndpointName:
            Value: !GetAtt Endpoint.EndpointName
    

现在模板已经完成(endpoint-one-model.yml),我们可以创建堆栈。

注意

请确保您的 IAM 角色有权限调用 CloudFormation API。如果没有,请将 AWSCloudFormationFullAccess 管理策略添加到该角色中。

将模型部署到实时端点

让我们使用 boto3 API 创建一个堆栈,部署一个 TensorFlow 模型。我们将重用一个用 KerasFashion MNIST 上训练的模型:

注意

由于我们的模板完全不依赖于区域,您可以选择任何您想要的区域。只需确保您已经在该区域训练了模型,并且正在使用适当的容器镜像。

  1. 我们将需要 boto3 客户端来访问 SageMaker 和 CloudFormation:

    import boto3
    sm = boto3.client('sagemaker')
    cf = boto3.client('cloudformation')
    
  2. 我们描述了训练任务以查找其工件的位置,以及其执行角色:

    training_job = 
        'tensorflow-training-2021-05-28-14-25-57-394'
    job = sm.describe_training_job(
          TrainingJobName=training_job)
    model_data_url =    
        job['ModelArtifacts']['S3ModelArtifacts']
    role_arn = job['RoleArn']
    
  3. 我们设置了用于部署的容器。在某些情况下,这是不必要的,因为相同的容器既用于训练也用于部署。对于 TensorFlow 和其他框架,SageMaker 使用两个不同的容器。您可以在 github.com/aws/deep-learning-containers/blob/master/available_images.md 上找到更多信息:

    container_image = '763104351884.dkr.ecr.us-east-1.amazonaws.com/tensorflow-inference:2.1.0-cpu-py36-ubuntu18.04'
    
  4. 然后,我们读取模板,创建一个新堆栈,并传递所需的参数:

    import time
    timestamp = time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime())
    stack_name='endpoint-one-model-'+timestamp
    with open('endpoint-one-model.yml', 'r') as f:
      response = cf.create_stack(
          StackName=stack_name,
          TemplateBody=f.read(),
          Parameters=[
               { "ParameterKey":"ModelName",      
                 "ParameterValue":training_job+
                                  '-'+timestamp },
               { "ParameterKey":"ContainerImage",  
                 "ParameterValue":container_image },
               { "ParameterKey":"ModelDataUrl",   
                 "ParameterValue":model_data_url },
               { "ParameterKey":"RoleArn",       
                 "ParameterValue":role_arn }
          ]
    )
    
  5. 跳转到 CloudFormation 控制台,我们看到堆栈正在创建,如下图所示。请注意,资源是按正确的顺序创建的:模型、端点配置和端点:图 12.1 – 查看堆栈创建

    图 12.1 – 查看堆栈创建

    正如我们预期的那样,我们也可以在 SageMaker Studio 中看到端点,如下图所示:

    图 12.2 – 查看端点创建

    图 12.2 – 查看端点创建

  6. 一旦堆栈创建完成,我们可以使用其输出查找端点的名称:

    response = cf.describe_stacks(StackName=stack_name)
    print(response['Stacks'][0]['StackStatus'])
    for o in response['Stacks'][0]['Outputs']:
        if o['OutputKey']=='EndpointName':
             endpoint_name = o['OutputValue']
    print(endpoint_name)
    

    这将打印出堆栈状态以及 CloudFormation 自动生成的端点名称:

    CREATE_COMPLETE

    Endpoint-MTaOIs4Vexpt

  7. 我们可以像往常一样测试端点。然后,我们可以删除堆栈及其资源:

    cf.delete_stack(StackName=stack_name)
    

然而,让我们暂时不要删除堆栈。相反,我们将使用更改集来更新它。

使用更改集修改堆栈

在这里,我们将更新支撑端点的实例数量:

  1. 我们使用相同的模板和参数创建一个新的更改集,唯一不同的是 InstanceCount,我们将其设置为 2

    response = cf.create_change_set(
        StackName=stack_name,
        ChangeSetName='add-instance',
        UsePreviousTemplate=True,
        Parameters=[
          { "ParameterKey":"InstanceCount", 
            "ParameterValue": "2" },
          { "ParameterKey":"ModelName",
            "UsePreviousValue": True },
          { "ParameterKey":"ContainerImage",
            "UsePreviousValue": True },
          { "ParameterKey":"ModelDataUrl",
            "UsePreviousValue": True },
          { "ParameterKey":"RoleArn",
            "UsePreviousValue": True }
        ]
    )
    
  2. 我们可以在 CloudFormation 控制台看到更改集的详细信息,如下图所示。我们也可以使用 describe_change_set() API:图 12.3 – 查看更改集

    图 12.3 – 查看更改集

    这告诉我们端点配置和端点需要被修改,甚至可能被替换。正如我们从第十一章《部署机器学习模型》所知,一个新的端点将会创建,并以非破坏性方式应用于现有的端点。

    注意

    在使用 CloudFormation 时,了解资源的 替换策略至关重要。每种资源类型的文档中都有详细信息。

  3. 通过点击 execute_change_set() API。正如预期的那样,端点立即更新,如下图所示:图 12.4 – 更新端点

    图 12.4 – 更新端点

  4. 更新完成后,我们可以在 CloudFormation 控制台中看到事件序列,如下图所示。已创建并应用了新的端点配置,之前的端点配置已被删除:图 12.5 – 更新堆栈

    图 12.5 – 更新堆栈

  5. 我们可以检查端点现在由两个实例支持:

    r = sm.describe_endpoint(EndpointName=endpoint_name)
    print r(['ProductionVariants'][0]
            ['CurrentInstanceCount'])
    

    这会打印出支持生产变种的实例数量:

    2
    

让我们继续处理变更集,并向端点添加第二个生产变种。

向端点添加第二个生产变种

我们的初始模板只定义了一个生产变种。我们将更新它并添加另一个变种(endpoint-two-models.yml):

  1. Parameters 部分,我们为第二个模型添加了条目:

        ModelName2:
           Description: Second model name
           Type: String
        ModelDataUrl2:
           Description: Location of second model artifact
           Type: String
        VariantWeight2:
           Description: Weight of second model
           Type: String
        Default: 0.0
    
  2. 我们在 Resources 部分做同样的事情:

        Model2:
           Type: "AWS::SageMaker::Model"
           Properties:
               Containers:
                   - 
                       Image: !Ref ContainerImage
                       ModelDataUrl: !Ref ModelDataUrl2
           ExecutionRoleArn: !Ref RoleArn
           ModelName: !Ref ModelName2
    
  3. 返回我们的笔记本,我们获取到另一个训练任务的信息。然后我们创建一个变更集,读取更新后的模板并传递所有必需的参数:

    training_job_2 = 'tensorflow-training-2020-06-08-07-32-18-734'
    job_2=sm.describe_training_job(
          TrainingJobName=training_job_2)
    model_data_url_2=
          job_2['ModelArtifacts']['S3ModelArtifacts']
    with open('endpoint-two-models.yml', 'r') as f:
        response = cf.create_change_set(
            StackName=stack_name,
            ChangeSetName='add-model',
            TemplateBody=f.read(),
            Parameters=[
                 { "ParameterKey":"ModelName",      
                   "UsePreviousValue": True },
                 { "ParameterKey":"ModelDataUrl",   
                  "UsePreviousValue": True },
                { "ParameterKey":"ContainerImage", 
                  "UsePreviousValue": True },
                { "ParameterKey":"RoleArn",        
                  "UsePreviousValue": True }, 
                { "ParameterKey":"ModelName2",     
                  "ParameterValue": training_job_2+'-
                                    '+timestamp},
                { "ParameterKey":"ModelDataUrl2",  
                   "ParameterValue": model_data_url_2 }
            ]
        )
    
  4. 查看 CloudFormation 控制台,我们可以看到由变更集引起的变化。创建一个新模型并修改端点配置和端点:图 12.6 – 查看变更集

    图 12.6 – 查看变更集

  5. 我们执行变更集。完成后,我们看到端点现在支持两个生产变种。注意,实例数量恢复到初始值,因为我们在更新的模板中将其定义为 1

图 12.7 – 查看生产变种

图 12.7 – 查看生产变种

新的生产变种的权重为 0,因此不会用于预测。让我们看看如何使用 金丝雀部署逐步引入它。

实现金丝雀部署

金丝雀部署是一种流行的渐进式应用部署技术(martinfowler.com/bliki/CanaryRelease.html),也可用于机器学习模型。

简单来说,我们将通过一系列堆栈更新,逐步增加第二个生产变体的权重,每次增加 10%,直到它完全替代第一个生产变体。我们还将创建一个 CloudWatch 警报,监控第二个生产变体的延迟——如果警报被触发,变更集将被回滚:

  1. 我们创建了一个 CloudWatch 警报,监控第二个生产变体的 60 秒平均延迟。我们将阈值设置为 500 毫秒:

    cw = boto3.client('cloudwatch')
    alarm_name = 'My_endpoint_latency'
    response = cw.put_metric_alarm(
        AlarmName=alarm_name,
        ComparisonOperator='GreaterThanThreshold',
        EvaluationPeriods=1,
        MetricName='ModelLatency',
        Namespace='AWS/SageMaker',
        Period=60,
        Statistic='Average',
        Threshold=500000.0,
        AlarmDescription=
            '1-minute average latency exceeds 500ms',
        Dimensions=[
            { 'Name': 'EndpointName', 
              'Value': endpoint_name },
            { 'Name': 'VariantName', 
              'Value': 'variant-2' }
        ],
        Unit='Microseconds'
    )
    
  2. 我们找到警报的 ARN:

    response = cw.describe_alarms(AlarmNames=[alarm_name])
    for a in response['MetricAlarms']:
        if a['AlarmName'] == alarm_name:
            alarm_arn = a['AlarmArn']
    
  3. 然后,我们循环遍历权重并更新堆栈。在这里不需要更改集,因为我们清楚地知道从资源的角度会发生什么。我们将 CloudWatch 警报设置为回滚触发器,每次更新后给它五分钟的时间触发,然后再进行下一步:

    for w in list(range(10,110,10)):
        response = cf.update_stack(
            StackName=stack_name,
            UsePreviousTemplate=True,
            Parameters=[
                { "ParameterKey":"ModelName",      
                  "UsePreviousValue": True },
                { "ParameterKey":"ModelDataUrl",
                  "UsePreviousValue": True },
                { "ParameterKey":"ContainerImage", 
                  "UsePreviousValue": True },
                { "ParameterKey":"RoleArn",   
                  "UsePreviousValue": True }, 
                { "ParameterKey":"ModelName2",
                  "UsePreviousValue": True },
                { "ParameterKey":"ModelDataUrl2",
                  "UsePreviousValue": True },
                { "ParameterKey":"VariantWeight",
                  "ParameterValue": str(100-w) },
                { "ParameterKey":"VariantWeight2", 
                  "ParameterValue": str(w) }
            ],
            RollbackConfiguration={
                'RollbackTriggers': [
                   { 'Arn': alarm_arn,: 
                     'AWS::CloudWatch::Alarm' }
                ],
                'MonitoringTimeInMinutes': 5
            }
        )
        waiter = cf.get_waiter('stack_update_complete')
        waiter.wait(StackName=stack_name)
        print("Sending %d% of traffic to new model" % w)
    

就这些。很酷,不是吗?

这个单元格会运行几个小时,所以不要停止它。在另一个笔记本中,下一步是开始向端点发送一些流量。为了简洁起见,我不会包括代码,它与我们在第七章《使用内置框架扩展机器学习服务》中使用的代码相同。你可以在本书的 GitHub 仓库中找到该笔记本(Chapter12/cloudformation/Predict Fashion MNIST images.ipynb)。

现在,我们只需要坐下来,喝杯茶,享受模型正在安全、自动地部署的事实。由于端点更新是无缝的,客户端应用程序不会察觉任何变化。

几个小时后,部署完成。下一张截图显示了两种变体随时间的调用情况。正如我们所见,流量逐渐从第一个变体转移到第二个变体:

图 12.8 – 监控金丝雀部署

图 12.8 – 监控金丝雀部署

延迟保持在我们 500 毫秒的限制之下,警报没有被触发,正如下一张截图所示:

图 12.9 – 查看 CloudWatch 警报

图 12.9 – 查看 CloudWatch 警报

这个示例可以作为你自己部署的起点。例如,你可以添加一个监控4xx5xx HTTP 错误的警报。你还可以直接监控受预测延迟和准确性影响的业务指标,如点击率、转化率等。一个有用的补充是添加警报通知(电子邮件、短信,甚至 Lambda 函数),以便在模型部署失败时触发下游操作。可能性是无限的!

完成后,别忘了删除堆栈,可以在 CloudFormation 控制台中删除,也可以使用delete_stack()API。这样会自动清理堆栈创建的所有 AWS 资源。

蓝绿部署是另一种流行的技术。让我们看看如何在 SageMaker 上实现它。

实现蓝绿部署

蓝绿部署需要两个生产环境(martinfowler.com/bliki/BlueGreenDeployment.html):

  • 运行版本 n 的实时生产环境(blue

  • 运行版本 n+1 的这个环境的副本(green

我们来看两个可能的场景,这些场景可以通过我们为金丝雀发布所使用的相同 API 来实现。

使用单一端点实现蓝绿部署

从一个运行当前版本模型的现有端点开始,我们将执行以下步骤:

  1. 创建一个包含两个生产版本的新端点配置:一个用于当前模型,一个用于新模型。初始权重分别设置为 10

  2. 将其应用到端点。

  3. 在新生产版本上运行测试,使用 invoke_endpoint() 中的 TargetVariant 参数显式选择它。

  4. 当测试通过后,更新权重为 01。这将无缝地将流量切换到新模型。如果出现问题,可以将权重还原为 10

  5. 部署完成后,更新端点以删除第一个生产版本。

这是一个简单而可靠的解决方案。然而,更新端点需要几分钟时间,这使得整个过程不如预期那样快速。让我们看看如何通过使用两个端点来解决这个问题。

使用两个端点实现蓝绿部署

从一个运行当前版本模型的现有端点开始,我们将实施以下步骤:

  1. 创建一个运行新版本模型的第二个端点。

  2. 在这个新端点上运行测试。

  3. 当测试通过后,将所有流量切换到新端点。可以通过不同方式实现此操作;例如,更新业务应用中的参数,或更新私有 DNS 条目。如果出现问题,可以恢复到先前的设置。

  4. 部署完成后,删除旧的端点。

这个设置稍微复杂一些,但它让你能够即时从一个模型版本切换到下一个,无论是部署还是回滚。

CloudFormation 是一个出色的自动化工具,任何学习它的时间都会得到回报。然而,一些 AWS 用户更喜欢编写代码而非编写模板,这就是我们引入 CDK 的原因。

使用 AWS CDK 自动化

AWS CDK 是一个多语言的 SDK,让你编写代码来定义 AWS 基础设施(github.com/aws/aws-cdk)。使用 CDK CLI,你可以在幕后使用 CloudFormation 来配置这些基础设施。

安装 CDK

CDK 是原生实现的,npm 工具需要安装在你的机器上(www.npmjs.com/get-npm)。

安装 CDK 过程就这么简单:

$ npm i -g aws-cdk
$ cdk --version
1.114.0 (build 7e41b6b)

我们创建一个 CDK 应用并部署一个端点。

创建一个 CDK 应用

我们将部署与 CloudFormation 一起部署的相同模型。我将使用 Python,你也可以使用JavaScriptTypeScriptJava.NET。API 文档可以在docs.aws.amazon.com/cdk/api/latest/python/找到:

  1. 首先,我们创建一个名为endpoint的 Python 应用程序:

    $ mkdir cdk
    $ cd cdk
    $ cdk init --language python --app endpoint
    
  2. 这会自动创建一个虚拟环境,我们需要激活它:

    $ source .venv/bin/activate
    
  3. 这还会为我们的 CDK 代码创建一个默认的app.py文件,一个用于应用配置的cdk.json文件,以及一个用于安装依赖的requirements.txt文件。相反,我们将使用 GitHub 仓库中现有的文件:

  4. requirements.txt文件中,我们安装 CDK 的 S3 和 SageMaker 包。每个服务需要不同的包。例如,我们需要为 S3 添加aws_cdk.aws_s3

    -e .
    aws_cdk.aws_s3
    aws_cdk.aws_sagemaker
    
  5. 然后,我们像往常一样安装依赖:

    $ pip install -r requirements.txt
    
  6. cdk.json文件中,我们存储应用程序的上下文。也就是可以被应用程序读取的键值对,用于配置(docs.aws.amazon.com/cdk/latest/guide/context.html):

    {
      "app": "python3 app.py",
      "context": {
        "role_arn": "arn:aws:iam::123456789012:role/Sagemaker-fullaccess"
        "model_name": "tf2-fmnist",
        "epc_name": "tf2-fmnist-epc",
        "ep_name": "tf2-fmnist-ep",
        "image": "763104351884.dkr.ecr.us-east-1.amazonaws.com/tensorflow-inference:2.1-cpu",
        "model_data_url": "s3://sagemaker-us-east-1-123456789012/keras2-fashion-mnist/output/tensorflow-training-2020-06-08-07-46-04-367/output/model.tar.gz"
        "instance_type": "ml.t2.xlarge",
        "instance_count": 1
      }
    }
    

    这是将值传递给应用程序的首选方法。你应该使用版本控制来管理此文件,以便追踪堆栈的构建过程。

  7. 我们可以使用cdk context命令查看我们应用程序的上下文:

图 12.10 – 查看 CDK 上下文

图 12.10 – 查看 CDK 上下文

现在,我们需要编写实际的应用程序。

编写 CDK 应用程序

所有代码都放在app.py文件中,我们将在接下来的步骤中实现:

  1. 我们导入所需的包:

    import time
    from aws_cdk import (
        aws_sagemaker as sagemaker,
        core
    )
    
  2. 我们扩展core.Stack类来创建我们自己的堆栈:

    class SagemakerEndpoint(core.Stack):
     def __init__(self, app: core.App, id: str, **kwargs) -> None:
         timestamp = 
             '-'+time.strftime(
                     "%Y-%m-%d-%H-%M-%S",time.gmtime())
         super().__init__(app, id, **kwargs)
    
  3. 我们添加一个CfnModel对象,读取适当的上下文值:

         model = sagemaker.CfnModel(
             scope = self,
             id="my_model",
             execution_role_arn= 
                 self.node.try_get_context("role_arn"),
             containers=[{ 
               "image": 
                 self.node.try_get_context("image"),
               "modelDataUrl":                  
                 self.node.try_get_context("model_data_url")
             }],           
             model_name= self.node.try_get_context(
                         "model_name")+timestamp
         )
    
  4. 我们添加一个CfnEndpointConfig对象,使用内置的get_att()函数将其与模型关联。这会创建一个依赖关系,CloudFormation 将用来按正确的顺序构建资源:

         epc = sagemaker.CfnEndpointConfig(
              scope=self,
              id="my_epc",
              production_variants=[{
                  "modelName": core.Fn.get_att(
                                   model.logical_id, 
                                   'ModelName'
                               ).to_string(),
                  "variantName": "variant-1",
                  "initialVariantWeight": 1.0,
                  "initialInstanceCount": 1,
                  "instanceType": 
                      self.node.try_get_context(
                      "instance_type")
              }],
              endpoint_config_name=                   
                      self.node.try_get_context("epc_name")
                      +timestamp
        )
    
  5. 我们添加一个CfnEndpoint对象,使用内置的get_att()函数将其与端点配置关联起来:

         ep = sagemaker.CfnEndpoint(
             scope=self,
             id="my_ep",
             endpoint_config_name=
                 core.Fn.get_att(
                     epc.logical_id,
                     'EndpointConfigName'
                 ).to_string(),
             endpoint_name=
                 self.node.try_get_context("ep_name")
                 +timestamp
         )
    
  6. 最后,我们实例化应用程序:

    app = core.App()
    SagemakerEndpoint(
        app, 
        "SagemakerEndpoint", 
        env={'region': 'eu-west-1'}
    )
    app.synth()
    

我们的代码完成了!

部署 CDK 应用程序

我们现在可以部署端点:

  1. 我们可以列出可用的堆栈:

    $ cdk list
    SagemakerEndpointEU
    
  2. 我们还可以看到实际的 CloudFormation 模板。它应该与我们在前一节中编写的模板非常相似:

    $ cdk synth SagemakerEndpointEU
    
  3. 部署堆栈同样简单,如下图所示:图 12.11 – 部署端点

    图 12.11 – 部署端点

  4. 查看 CloudFormation,我们看到堆栈是使用变更集创建的。几分钟后,端点已投入使用。

  5. 编辑app.py时,我们将初始实例数设置为2。然后我们要求 CDK 部署堆栈,但不执行变更集,如下图所示:图 12.12 – 创建变更集

    图 12.12 – 创建变更集

  6. 如果我们对变更集感到满意,可以在 CloudFormation 控制台中执行它,或者再次运行之前的命令,去掉 --no-execute。如预期所示,且如下图所示,端点已更新:图 12.13 – 更新端点

    图 12.13 – 更新端点

  7. 当我们完成时,可以销毁堆栈:

    $ cdk destroy SagemakerEndpointEU
    

如您所见,CDK 是一个有趣的替代方案,能够直接编写模板,同时仍然受益于 CloudFormation 的严格性和健壮性。

我们尚未完成的一件事是实现一个从训练到部署的端到端自动化工作流。我们将通过 AWS Step Functions 来完成这个任务。

使用 AWS Step Functions 构建端到端工作流

AWS Step Functions 让您基于 状态机 定义和运行工作流 (aws.amazon.com/step-functions/)。状态机是步骤的组合,这些步骤可以是顺序的、并行的或条件的。每个步骤接收来自前一个步骤的输入,执行操作,然后将输出传递给下一个步骤。Step Functions 与许多 AWS 服务集成,如 Amazon SageMaker、AWS Lambda、容器服务、Amazon DynamoDBAmazon EMRAWS Glue 等。

状态机可以使用 JSON 和 Amazon States Language 定义,您可以在服务控制台中可视化它们。状态机执行是完全托管的,因此您无需为其运行提供任何基础设施。

对于 SageMaker,Step Functions 提供了一个专门的 Python SDK,奇怪地被命名为 Data Science SDK (github.com/aws/aws-step-functions-data-science-sdk-python)。

让我们运行一个示例,自动化训练和部署一个基于 Boston Housing 数据集训练的 scikit-learn 模型。

设置权限

首先,请确保您的用户或笔记本实例的 IAM 角色具有调用 Step Functions API 的权限。如果没有,请将 AWSStepFunctionsFullAccess 管理策略添加到该角色中。

然后,我们需要为 Step Functions 创建一个服务角色,允许它代表我们调用 AWS API:

  1. 从 IAM 控制台开始 (console.aws.amazon.com/iam/home#/roles),我们点击 创建角色

  2. 我们选择 AWS 服务Step Functions

  3. 我们点击接下来的页面,直到可以输入角色名称。我们将其命名为 StepFunctionsWorkflowExecutionRole,然后点击 创建角色

  4. 选择该角色后,我们点击它的 权限 标签页,再点击 添加内联策略

  5. 选择 JSON 标签,我们用 Chapter12/step_functions/service-role-policy.json 文件的内容替换空的策略,然后点击 查看策略

  6. 我们将策略命名为 StepFunctionsWorkflowExecutionPolicy,然后点击 创建策略

  7. 我们记下角色的 ARN,然后关闭 IAM 控制台。

设置现在已经完成。现在,让我们创建一个工作流。

实现我们的第一个工作流

在这个工作流中,我们将按以下步骤顺序进行:训练模型、创建模型、使用模型进行批量转换、创建端点配置,并将模型部署到端点:

  1. 我们将训练集上传到 S3,以及一个去除目标属性的测试集。我们将使用后者进行批量转换:

    import sagemaker
    import pandas as pd
    sess = sagemaker.Session()
    bucket = sess.default_bucket()   
    prefix = 'sklearn-boston-housing-stepfunc'
    training_data = sess.upload_data(
        path='housing.csv', 
        key_prefix=prefix + "/training")
    data = pd.read_csv('housing.csv')
    data.drop(['medv'], axis=1, inplace=True)
    data.to_csv('test.csv', index=False, header=False)
    batch_data = sess.upload_data(
        path='test.csv', 
        key_prefix=prefix + "/batch")
    
  2. 我们像往常一样配置估算器:

    from sagemaker.sklearn import SKLearn
    output = 's3://{}/{}/output/'.format(bucket,prefix)
    sk = SKLearn(
        entry_point='sklearn-boston-housing.py',
        role=sagemaker.get_execution_role(),
        framework_version='0.23-1',
        train_instance_count=1,
        train_instance_type='ml.m5.large',
        output_path=output,
        hyperparameters={
            'normalize': True,
            'test-size': 0.1
        }
    )
    
  3. 我们还定义了用于批量转换的 transformer:

    sk_transformer = sk.transformer(
        instance_count=1,
        instance_type='ml.m5.large')
    
  4. 我们导入工作流所需的 Step Functions 对象。您可以在aws-step-functions-data-science-sdk.readthedocs.io/en/latest/找到 API 文档:

    import stepfunctions
    from stepfunctions import steps
    from stepfunctions.steps import TrainingStep, ModelStep, TransformStep
    from stepfunctions.inputs import ExecutionInput
    from stepfunctions.workflow import Workflow
    
  5. 我们定义工作流的输入。我们将传递给它一个训练任务名称、一个模型名称和一个端点名称:

    execution_input = ExecutionInput(schema={
        'JobName': str,
        'ModelName': str,
        'EndpointName': str}
    )
    
  6. 工作流的第一步是训练步骤。我们将其传递估算器、数据集在 S3 中的位置以及一个训练任务名称:

    from sagemaker.inputs import TrainingInput
    training_step = TrainingStep(
      'Train Scikit-Learn on the Boston Housing dataset',
      estimator=sk,
      data={'training': TrainingInput(
           training_data,content_type='text/csv')},
      job_name=execution_input['JobName']
    )
    
  7. 下一步是模型创建步骤。我们将其传递已在前一步中训练的模型位置和模型名称:

    model_step = ModelStep(
        'Create the model in SageMaker',
        model=training_step.get_expected_model(),
        model_name=execution_input['ModelName']
    )
    
  8. 下一步是在测试数据集上运行批量转换。我们传递transformer对象、测试数据集在 S3 中的位置及其内容类型:

    transform_step = TransformStep(
        'Transform the dataset in batch mode',
        transformer=sk_transformer,
        job_name=execution_input['JobName'],    
        model_name=execution_input['ModelName'],
        data=batch_data,
        content_type='text/csv'
    )
    
  9. 下一步是创建端点配置:

    endpoint_config_step = EndpointConfigStep(
        "Create an endpoint configuration for the model",
        endpoint_config_name=execution_input['ModelName'],
        model_name=execution_input['ModelName'],
        initial_instance_count=1,
        instance_type='ml.m5.large'
    )
    
  10. 最后一步是创建端点:

    endpoint_step = EndpointStep(
        "Create an endpoint hosting the model",
        endpoint_name=execution_input['EndpointName'],
        endpoint_config_name=execution_input['ModelName']
    )
    
  11. 现在,所有步骤都已经定义,我们按顺序将它们链起来:

    workflow_definition = Chain([
        training_step,
        model_step,
        transform_step,
        endpoint_config_step,
        endpoint_step
    ])
    
  12. 我们现在构建我们的工作流,使用工作流定义和输入定义:

    import time
    timestamp = time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime())
    workflow_execution_role = "arn:aws:iam::0123456789012:role/
    StepFunctionsWorkflowExecutionRole"
    workflow = Workflow(
        name='sklearn-boston-housing-workflow1-{}'
             .format(timestamp),
        definition=workflow_definition,
        role=workflow_execution_role,
        execution_input=execution_input
    )
    
  13. 我们可以可视化状态机,这是检查我们是否按预期构建它的一个简便方法,如下图所示:

    workflow.render_graph(portrait=True)
    

    图 12.14 – 查看状态机

    图 12.14 – 查看状态机

  14. 我们创建工作流:

    workflow.create()
    
  15. 它可以在 Step Functions 控制台中查看,如下图所示。我们可以看到它的图形表示以及基于 Amazon States Language 的 JSON 定义。如果需要,我们也可以编辑工作流:图 12.15 – 在控制台中查看状态机

    图 12.15 – 在控制台中查看状态机

  16. 我们运行工作流:

    execution = workflow.execute(
     inputs={
       'JobName': 'sklearn-boston-housing-{}'
                  .format(timestamp),
       'ModelName': 'sklearn-boston-housing-{}'
                    .format(timestamp),
       'EndpointName': 'sklearn-boston-housing-{}'
                       .format(timestamp)
     }
    )
    
  17. 我们可以通过render_progress()list_events()API 跟踪其进度。我们也可以在控制台中看到它,如下图所示。请注意,我们还可以看到每个步骤的输入和输出,这对故障排除问题非常有帮助:图 12.16 – 运行状态机

    图 12.16 – 运行状态机

  18. 当工作流完成时,您可以像往常一样测试端点。完成后别忘了在 SageMaker 控制台删除它

这个例子展示了使用这个 SDK 构建 SageMaker 工作流是多么简单。不过,我们可以通过让批量转换和端点创建并行运行来进一步改进它。

向工作流添加并行执行

下一张截图展示了我们将要构建的工作流。这些步骤本身完全相同,我们只是修改了它们的连接方式:

图 12.17 – 查看并行状态机

图 12.17 – 查看并行状态机

我们将按照以下步骤开始:

  1. 我们的工作流有两个分支—一个用于批量转换,另一个用于端点:

    batch_branch = Chain([
      transform_step
    ])
    endpoint_branch = Chain([
      endpoint_config_step,
      endpoint_step
    ]) 
    
  2. 我们创建一个 Parallel 步骤,以便允许这两个分支并行执行:

    parallel_step = Parallel('Parallel execution')
    parallel_step.add_branch(batch_branch)
    parallel_step.add_branch(endpoint_branch)
    
  3. 我们将所有内容整合在一起:

    workflow_definition = Chain([
        training_step,
        model_step,
        parallel_step
    ])
    

就是这样!现在我们可以像在之前的示例中那样创建并运行这个工作流。

查看 Step Functions 控制台,我们可以看到工作流确实并行执行了两个分支。不过,存在一个小问题。端点创建步骤显示为已完成,尽管端点仍在创建中。你可以在 SageMaker 控制台中看到该端点的状态为 Creating。如果客户端应用程序在工作流完成后立即尝试调用该端点,可能会出现问题。

让我们通过增加一个额外的步骤来改进这个流程,等待端点进入服务状态。我们可以通过 Lambda 函数轻松实现这一点,从而让我们在工作流中的任何位置运行自己的代码。

将 Lambda 函数添加到工作流

如果你从未了解过 AWS Lambda (aws.amazon.com/lambda),那你就错过了!Lambda 是无服务器架构的核心,在这种架构下,你可以编写并部署在完全托管基础设施上运行的短小函数。这些函数可以由各种 AWS 事件触发,也可以按需调用。

设置权限

创建 Lambda 函数很简单。唯一的前提是创建一个 DescribeEndpoint API,并且有权限在 CloudWatch 中创建日志。我们将使用 boto3 API 来实现这一点。你可以在 docs.aws.amazon.com/lambda/latest/dg/lambda-permissions.html 查找更多信息:

  1. 我们首先为角色定义一个 信任策略,允许 Lambda 服务假设该角色:

    {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Principal": {
          "Service": "lambda.amazonaws.com"
        },
        "Action": "sts:AssumeRole"
      }]
    }
    
  2. 我们创建一个角色并附加信任策略:

    iam = boto3.client('iam')
    with open('trust-policy.json') as f:
        policy = f.read()
        role_name = 'lambda-role-sagemaker-describe-endpoint'
    response = iam.create_role(
        RoleName=role_name,
        AssumeRolePolicyDocument=policy,
        Description='Allow function to invoke all SageMaker APIs'
    )
    role_arn = response['Role']['Arn']
    
  3. 我们定义一个列出允许 API 的策略:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": "sagemaker:DescribeEndpoint",
          "Resource": "*"
        },
        {
          "Effect": "Allow",
          "Action": [
              "logs:CreateLogGroup",
              "logs:CreateLogStream",
              "logs:PutLogEvents"
          ],
          "Resource": "*"
         }
      ]
    }
    
  4. 我们创建策略并将其添加到角色中:

    with open('policy.json') as f:
        policy = f.read()
    policy_name = 'Sagemaker-describe-endpoint'
    response = iam.create_policy(
        PolicyName=policy_name,
        PolicyDocument=policy,
        Description='Allow the DescribeEndpoint API'
    )
    policy_arn = response['Policy']['Arn']
    response = iam.attach_role_policy(
        RoleName=role_name,
        PolicyArn=policy_arn
    )
    

IAM 设置现已完成。

编写 Lambda 函数

现在我们可以编写一个简短的 Lambda 函数。它接收一个 JSON 事件作为输入,事件中存储了 EndpointStep 步骤所创建的端点的 ARN。它简单地从 ARN 中提取端点名称,创建一个 boto3 等待器,并等待直到端点服务就绪。下面的截图展示了 Lambda 控制台中的代码:

图 12.18 – 我们的 Lambda 函数

图 12.18 – 我们的 Lambda 函数

让我们部署这个函数:

  1. 我们为 Lambda 函数创建一个部署包并将其上传到 S3:

    $ zip -9 lambda.zip lambda.py
    $ aws s3 cp lambda.zip s3://my-bucket
    
  2. 我们创建了一个超时为 15 分钟的函数,这是 Lambda 函数的最大运行时间。端点通常在不到 10 分钟的时间内部署完成,因此这个时间应该足够:

    lambda_client = boto3.client('lambda')
    response = lambda_client.create_function(
        FunctionName='sagemaker-wait-for-endpoint',
        Role=role_arn,
        Runtime='python3.6',
        Handler='lambda.lambda_handler',
        Code={
            'S3Bucket': bucket_name,
            'S3Key': 'lambda.zip'
        },
        Description='Wait for endpoint to be in service',
        Timeout=900,
        MemorySize=128
    )
    
  3. 现在 Lambda 函数已经创建完成,我们可以轻松地将其添加到现有的工作流中。我们定义一个 LambdaStep 并将其添加到端点分支。它的有效负载是从 EndpointStep 输出中提取的端点 ARN:

    lambda_step = LambdaStep(
        'Wait for endpoint to be in service',
        parameters={
            'FunctionName': 'sagemaker-wait-for-endpoint',
            'Payload': {"EndpointArn.$": "$.EndpointArn"}
        },
        timeout_seconds=900
    )
    endpoint_branch = steps.Chain([
        endpoint_config_step,
        endpoint_step,
        lambda_step
    ])
    
  4. 再次运行工作流,我们在下面的截图中看到,新的步骤接收端点 ARN 作为输入,并等待端点处于服务状态:

图 12.19 – 使用 Lambda 运行状态机

图 12.19 – 使用 Lambda 运行状态机

您还可以通过多种方式将 Lambda 函数与 SageMaker 一起使用。您可以提取训练指标、在端点上预测测试集等。可能性无穷无尽。

现在,让我们使用 Amazon SageMaker Pipelines 自动化端到端工作流。

使用 Amazon SageMaker Pipelines 构建端到端工作流

Amazon SageMaker Pipelines 允许我们基于 SageMaker 步骤(用于训练、调优、批处理转换和处理脚本)创建和运行端到端的机器学习 工作流,使用的 SageMaker API SDK 与我们在 Step Functions 中使用的非常相似。

与 Step Functions 相比,SageMaker Pipelines 增加了以下功能:

  • 能够在 SageMaker Studio 中直接编写、运行、可视化和管理工作流,而无需跳转到 AWS 控制台。

  • 模型注册表,使得管理模型版本、仅部署经过批准的版本以及跟踪 血统 更加容易。

  • MLOps 模板 – 通过 AWS 服务目录 发布的一组 CloudFormation 模板,帮助您自动化模型的部署。提供了内置模板,您还可以添加自己的模板。您(或您的运维团队)可以在 docs.aws.amazon.com/sagemaker/latest/dg/sagemaker-projects.html 上了解更多信息。

    注意

    SageMaker Pipelines 的一个缺点是缺少与其他 AWS 服务的集成。在撰写本文时,SageMaker Pipelines 仅支持 SQS,而 Step Functions 支持许多计算和大数据服务。在 SageMaker Pipelines 中,假设您的训练数据已经处理好,或者您将使用 SageMaker Processing 步骤来处理数据。

现在我们了解了 SageMaker Pipelines,接下来让我们基于 Amazon Reviews 数据集和我们在 第六章 训练自然语言处理模型第十章 高级训练技术 中使用的 BlazingText 算法,运行一个完整的示例,结合到目前为止学到的许多服务。我们的工作流将包含以下步骤:

  • 处理步骤,我们使用 SageMaker Processing 准备数据集。

  • 一个数据摄取步骤,在此步骤中我们将处理过的数据集加载到 SageMaker Feature Store 中。

  • 一个数据集构建步骤,在此步骤中我们使用 Amazon Athena 查询离线存储并将数据集保存到 S3。

  • 一个训练步骤,在此步骤中我们在数据集上训练 BlazingText 模型。

  • 一个模型创建步骤,在此步骤中我们将训练好的模型保存为 SageMaker 模型。

  • 一个模型注册步骤,在此步骤中我们将模型添加到 SageMaker Pipelines 模型注册表中。

在实际操作中,你一开始不需要过于担心自动化问题。你应该首先使用 Jupyter Notebooks 进行实验,并迭代这些步骤。然后,随着项目的成熟,你可以开始自动化每个步骤,最终将它们组装成一个管道。

我的建议是首先自动化每个处理步骤,使用独立的 SageMaker Processing 任务。这不仅在开发阶段非常有用,而且会为完全自动化创建一个简单且逐步的路径。事实上,一旦步骤在 SageMaker Processing 上顺利运行,将它们与 SageMaker Pipelines 结合起来几乎不需要额外的努力。实际上,你可以使用完全相同的 Python 脚本。你只需要用 Pipelines SDK 编写代码。正如你将看到的,它与 Processing SDK 非常相似。

这是我在以下示例中采用的方法。在 GitHub 仓库中,你会找到用于数据处理、数据摄取和数据集构建步骤的 SageMaker Processing 笔记本,以及另一本用于端到端工作流的笔记本。在这里,我们将重点关注后者。让我们开始吧!

定义工作流参数

就像 CloudFormation 模板一样,你可以(并且应该)在工作流中定义参数。这样可以更方便地在其他项目中重用它们。参数可以是字符串、整数和浮点数,并可以设置一个可选的默认值。

  1. 我们为 AWS 区域以及我们希望用于处理和训练的实例创建参数:

    from sagemaker.workflow.parameters import ParameterInteger, ParameterString
    region = ParameterString(
        name='Region',
        default_value='eu-west-1')
    processing_instance_type = ParameterString(
        name='ProcessingInstanceType',
        default_value='ml.m5.4xlarge')
    processing_instance_count = ParameterInteger(
        name='ProcessingInstanceCount',
        default_value=1)
    training_instance_type = ParameterString(
        name='TrainingInstanceType',
        default_value='ml.p3.2xlarge')
    training_instance_count = ParameterInteger(
        name='TrainingInstanceCount',
        default_value=1)
    
  2. 我们还为输入数据的位置、模型名称以及在模型注册表中设置的模型状态创建了参数(稍后会详细介绍)。

    input_data = ParameterString(name='InputData')
    model_name = ParameterString(name='ModelName')
    model_approval_status = ParameterString(
        name='ModelApprovalStatus',
        default_value='PendingManualApproval')
    

现在,让我们定义数据处理步骤。

使用 SageMaker Processing 处理数据集

我们重用了在 第六章 中编写的处理脚本(preprocessing.py)。

  1. 我们创建一个 SKLearnProcessor 对象,并使用我们刚才定义的参数:

    from sagemaker.sklearn.processing import SKLearnProcessor
    sklearn_processor = SKLearnProcessor(
        framework_version='0.23-1',
        role=role,
        instance_type=processing_instance_type,
        instance_count=processing_instance_count)
    
  2. 然后,我们定义数据处理步骤。请记住,它会创建两个输出:一个是 BlazingText 格式,另一个是用于摄取到 SageMaker Feature Store 的格式。如前所述,SageMaker Pipelines 语法与 SageMaker Processing 语法非常相似(输入、输出和参数)。

    from sagemaker.workflow.steps import ProcessingStep
    from sagemaker.processing import ProcessingInput, ProcessingOutput
    step_process = ProcessingStep(
        name='process-customer-reviews'
        processor=sklearn_processor,
        inputs=[
            ProcessingInput(source=input_data, 
                destination="/opt/ml/processing/input")],
        outputs=[
            ProcessingOutput(output_name='bt_data',
                source='/opt/ml/processing/output/bt'),
            ProcessingOutput(output_name='fs_data',
                source='/opt/ml/processing/output/fs')],
        code='preprocessing.py',
        job_arguments=[
            '--filename', 
            'amazon_reviews_us_Camera_v1_00.tsv.gz',
            '--library', 
            'spacy']
    )
    

现在,让我们定义数据摄取步骤。

使用 SageMaker Processing 将数据集摄取到 SageMaker Feature Store 中

我们重用了在 第十章 中编写的处理脚本(ingesting.py)。

  1. 我们首先为特征组定义一个名称:

    feature_group_name = 'amazon-reviews-feature-group-' + strftime('%d-%H-%M-%S', gmtime())
    
  2. 然后,我们定义了一个处理步骤,将数据输入设置为第一个处理作业的输出。为了演示步骤链式处理,我们定义了一个输出,指向由脚本保存的文件,该文件包含特征组的名称:

    step_ingest = ProcessingStep(
        name='ingest-customer-reviews',
        processor=sklearn_processor,
        inputs=[
           ProcessingInput(
           source=
            step_process.properties.ProcessingOutputConfig
            .Outputs['fs_data'].S3Output.S3Uri,
           destination="/opt/ml/processing/input")],
        outputs = [
           ProcessingOutput(
           output_name='feature_group_name',
           source='/opt/ml/processing/output/')],
        code='ingesting.py',
        job_arguments=[
           '--region', region,
           '--bucket', bucket,
           '--role', role,
           '--feature-group-name', feature_group_name,
           '--max-workers', '32']
    )
    

现在,让我们处理数据集构建步骤。

使用 Amazon Athena 和 SageMaker 处理构建数据集

我们重用了在 第十章 中编写的处理脚本 (querying.py)。

我们将输入设置为摄取步骤的输出,以便检索特征组的名称。我们还为训练集和验证集数据集定义了两个输出:

step_build_dataset = ProcessingStep(
    name='build-dataset',
    processor=sklearn_processor,
    inputs=[
      ProcessingInput(
        source=
          step_ingest.properties.ProcessingOutputConfig
          .Outputs['feature_group_name'].S3Output.S3Uri,
        destination='/opt/ml/processing/input')],
    outputs=[
      ProcessingOutput(
        output_name='training',
        source='/opt/ml/processing/output/training'),
      ProcessingOutput(
        output_name='validation',               
        source='/opt/ml/processing/output/validation')],
      code='querying.py',
      job_arguments=[
        '--region', region,
        '--bucket', bucket,]
)

现在,让我们继续进行训练步骤。

训练模型

没有意外:

  1. 我们为这个任务定义了一个 Estimator 模块:

    container = image_uris.retrieve(
        'blazingtext', 
        str(region))     # region is a ParameterString
    prefix = 'blazing-text-amazon-reviews'
    s3_output = 's3://{}/{}/output/'.format(bucket, prefix)
    bt = Estimator(container,
                   role,
                   instance_count=training_instance_count, 
                   instance_type=training_instance_type,
                   output_path=s3_output)
    bt.set_hyperparameters(mode='supervised')
    
  2. 我们接着定义训练步骤,将训练集和验证集数据集作为输入:

    from sagemaker.workflow.steps import TrainingStep
    from sagemaker.inputs import TrainingInput
    step_train = TrainingStep(
        name='train-blazing-text',
        estimator=bt,
        inputs={
          'train': TrainingInput(s3_data=
    step_build_dataset.properties.ProcessingOutputConfig
    .Outputs['training'].S3Output.S3Uri,
          content_type='text/plain'),
           'validation': TrainingInput(s3_data=
    step_build_dataset.properties.ProcessingOutputConfig
    .Outputs['validation'].S3Output.S3Uri,
          content_type='text/plain')
        }
    )
    

现在,让我们处理模型创建和模型注册步骤(管道中的最后几个步骤)。

在 SageMaker Pipelines 中创建并注册模型

一旦模型训练完成,我们需要将其创建为 SageMaker 模型并在模型注册表中注册它。

  1. 我们创建模型,传递训练容器和模型工件的位置:

    from sagemaker.model import Model
    from sagemaker.workflow.steps import CreateModelStep
    model = Model(
        image_uri=container,
        model_data=step_train.properties
                   .ModelArtifacts.S3ModelArtifacts,
        sagemaker_session=session,
        name=model_name,   # workflow parameter
        role=role)
    step_create_model = CreateModelStep(
        name='create-model',
        model=model,
        inputs=None)
    
  2. 然后,我们将模型注册到模型注册表中,传递允许的实例类型列表以供部署,以及审批状态。我们将其关联到一个模型包组,该组将保存此模型以及我们未来训练的更多版本:

    from sagemaker.workflow.step_collections import RegisterModel
    step_register = RegisterModel(
        name='register-model',
        estimator=bt,
        model_data=step_train.properties.ModelArtifacts
                   .S3ModelArtifacts,
        content_types=['text/plain'],
        response_types=['application/json'],
        inference_instances=['ml.t2.medium'],
        transform_instances=['ml.m5.xlarge'],
        model_package_group_name='blazing-text-on-amazon-customer-reviews-package',
        approval_status=model_approval_status
    )
    

所有步骤现在都已定义,让我们将它们组合成一个管道。

创建管道

我们只需将所有步骤和它们的参数组合在一起。然后,我们创建管道(如果之前已存在,则更新它):

from sagemaker.workflow.pipeline import Pipeline
pipeline_name = 'blazing-text-amazon-customer-reviews'
pipeline = Pipeline(
    name=pipeline_name,
    parameters=[region, processing_instance_type, processing_instance_count, training_instance_type, training_instance_count, model_approval_status, input_data, model_name],
    steps=[step_process, step_ingest, step_build_dataset, step_train, step_create_model, step_register])
pipeline.upsert(role_arn=role)

一切准备就绪。让我们运行管道吧!

运行管道

只需一行代码即可启动管道执行:

  1. 我们为数据位置和模型名称参数分配值(其他参数使用默认值):

    execution = pipeline.start(
        parameters=dict(
            InputData=input_data_uri,
            ModelName='blazing-text-amazon-reviews')
    )
    
  2. 在 SageMaker Studio 中,我们进入 SageMaker 资源 / 管道,并看到管道正在执行,如下图所示:图 12.20 – 执行管道

    图 12.20 – 执行管道

    一个半小时后,管道完成,如下图所示:

    图 12.21 – 可视化管道

    图 12.21 – 可视化管道

  3. 最后,对于管道中的每个步骤,我们都可以看到所有工件的血缘关系:

    from sagemaker.lineage.visualizer import LineageTableVisualizer
    viz = LineageTableVisualizer(session)
    for execution_step in reversed(execution.list_steps()):
        print(execution_step)
    display(viz.show(
        pipeline_execution_step=execution_step))
    

    例如,训练步骤的输出在下图中展示。我们可以清楚地看到使用了哪些数据集和容器来训练模型:

图 12.22 – 查看训练步骤的血缘关系

图 12.22 – 查看训练步骤的血缘关系

让我们看看如何部署这个模型。

从模型注册表部署模型

进入 SageMaker 资源 / 模型注册表,我们还可以看到该模型已经在模型注册表中注册,如下图所示。如果我们训练了更多版本的模型,它们也会出现在这里:

图 12.23 – 在模型注册表中查看模型

图 12.23 – 在模型注册表中查看模型

由于它的状态是 Pending,因此暂时无法部署。我们需要将其更改为 Approved,以便允许部署。这是一种安全的方式,可以确保只有经过所有适当测试的优质模型被部署。

我们右键点击模型并选择 Approved。我们还注意到模型 ARN,它在 设置 标签中是可见的。

现在,我们可以部署并测试模型:

  1. 在我们的 Jupyter Notebook 中,我们创建一个指向我们想要部署的模型版本的 ModelPackage 对象:

    from sagemaker import ModelPackage
    model_package_arn = 'arn:aws:sagemaker:eu-west-1:123456789012:model-package/blazing-text-on-amazon-customer-reviews-package/1'
    model = sagemaker.ModelPackage(
        role = role,
        model_package_arn = model_package_arn)
    
  2. 我们像往常一样调用 deploy()

    model.deploy(
        initial_instance_count = 1,
        instance_type = 'ml.t2.medium',
        endpoint_name='blazing-text-on-amazon-reviews')
    
  3. 我们创建一个 Predictor 并发送一个测试样本进行预测:

    from sagemaker.predictor import Predictor
    bt_predictor = Predictor(
        endpoint_name='blazing-text-on-amazon-reviews',
        serializer=
            sagemaker.serializers.JSONSerializer(),       
        deserializer=
            sagemaker.deserializers.JSONDeserializer())
    instances = [' I really love this camera , it takes amazing pictures . ']
    payload = {'instances': instances, 
               'configuration': {'k': 3}}
    response = bt_predictor.predict(payload)
    print(response)
    

    这会打印出所有三个类别的概率:

    [{'label': ['__label__positive__', '__label__neutral__', '__label__negative__'],
    'prob': [0.9999945163726807, 2.51355941145448e-05, 1.0307396223652177e-05]},
    
  4. 一旦完成,我们可以删除终端节点。

    注意

    为了完全清理,你还应该删除管道、特征存储和模型包组。你可以在 GitHub 仓库中找到清理的笔记本。

如你所见,SageMaker Pipelines 为你提供了强大且高效的工具,帮助你构建、运行和跟踪端到端的机器学习工作流。这些工具在 SageMaker Studio 中得到了很好的集成,应该能帮助你提高生产力,并更快地将高质量模型投入生产。

总结

在这一章中,你首先学习了如何使用 AWS CloudFormation 部署和更新终端节点。你还看到了它如何用于实现金丝雀部署和蓝绿部署。

然后,你了解了 AWS CDK,这是一个专门为使用多种编程语言轻松生成和部署 CloudFormation 模板而构建的 SDK。

最后,你通过 AWS Step Functions 和 Amazon SageMaker Pipelines 构建了完整的端到端机器学习工作流。

在下一章也是最后一章中,你将了解更多关于 SageMaker 的功能,帮助你优化预测的成本和性能。

第十三章:优化预测成本和性能

在上一章中,你学习了如何自动化训练和部署工作流。

在本章的最后,我们将重点讨论如何优化预测基础设施的成本和性能,而预测基础设施通常占 AWS 客户机器学习开支的 90%。这个数字可能会让人吃惊,直到我们意识到,一个由单次训练任务构建的模型可能会在多个端点上运行,且这些端点会全天候运行,规模庞大。

因此,必须格外小心地优化预测基础设施,以确保你能最大化成本效益!

本章内容包括以下主题:

  • 自动扩展端点

  • 部署多模型端点

  • 使用 Amazon Elastic Inference 部署模型

  • 使用 Amazon SageMaker Neo 编译模型

技术要求

你需要一个 AWS 账户才能运行本章中的示例。如果你还没有账户,请访问 aws.amazon.com/getting-started/ 创建一个。你还应该熟悉 AWS 免费使用层 (aws.amazon.com/free/),该服务让你在一定的使用限制内免费使用许多 AWS 服务。

你需要为你的账户安装并配置 AWS 命令行界面CLI)(aws.amazon.com/cli/)。

你需要一个工作中的 Python 3.x 环境。安装 Anaconda 发行版 (www.anaconda.com/) 不是必须的,但强烈建议这样做,因为它包含了我们需要的许多项目(Jupyter、pandasnumpy 等)。

本书中的代码示例可以在 GitHub 上获取,地址为 github.com/PacktPublishing/Learn-Amazon-SageMaker-second-edition。你需要安装 Git 客户端来访问它们 (git-scm.com/)。

自动扩展端点

自动扩展长期以来一直是根据流量调整基础设施规模的最重要技术,并且它在 SageMaker 端点中可用。然而,它基于 应用程序自动扩展 而非 EC2 自动扩展 (docs.aws.amazon.com/autoscaling/application/userguide/what-is-application-auto-scaling.html),尽管这两者的概念非常相似。

让我们为我们在波士顿房价数据集上训练的 XGBoost 模型设置自动扩展:

  1. 我们首先创建一个 端点配置,然后使用它来构建端点。这里,我们使用的是 m5 实例系列;不建议使用 t2 和 t3 实例系列进行自动扩展,因为它们的突发行为使得很难衡量其实际负载:

    model_name = 'sagemaker-xgboost-2020-06-09-08-33-24-782'
    endpoint_config_name = 'xgboost-one-model-epc'
    endpoint_name = 'xgboost-one-model-ep'
    production_variants = [{
        'VariantName': 'variant-1',
        'ModelName': model_name,
        'InitialInstanceCount': 2,
        'InitialVariantWeight': 1,
        'InstanceType': 'ml.m5.large'}]
    sm.create_endpoint_config(
        EndpointConfigName=endpoint_config_name,
        ProductionVariants=production_variants)
    sm.create_endpoint(
        EndpointName=endpoint_name,
        EndpointConfigName=endpoint_config_name)
    
  2. 一旦端点投入使用,我们定义希望扩展的目标值,即支持该端点的实例数量:

    app = boto3.client('application-autoscaling')
    app.register_scalable_target(
     ServiceNamespace='sagemaker',
     ResourceId=
         'endpoint/xgboost-one-model-ep/variant/variant-1',
     ScalableDimension=
        'sagemaker:variant:DesiredInstanceCount',
     MinCapacity=2,
     MaxCapacity=10)
    
  3. 然后,我们为这个目标值应用扩展策略:

    policy_name = 'xgboost-scaling-policy'
    app.put_scaling_policy(
     PolicyName=policy_name,
     ServiceNamespace='sagemaker',
     ResourceId=
       'endpoint/xgboost-one-model-ep/variant/variant-1',
     ScalableDimension=
       'sagemaker:variant:DesiredInstanceCount',
     PolicyType='TargetTrackingScaling',
    
  4. 我们使用 SageMaker 中唯一的内置指标 SageMakerVariantInvocationsPerInstance。如果需要,我们也可以定义自定义指标。我们将指标阈值设置为每分钟 1,000 次调用。这个值有点随意。在实际操作中,我们会对单个实例进行负载测试,并监控模型延迟,以找到应该触发自动扩展的实际值。你可以在 docs.aws.amazon.com/sagemaker/latest/dg/endpoint-scaling-loadtest.html 查找更多信息。我们还定义了 60 秒的冷却时间,用于扩展和收缩,这是平滑过渡流量波动的好做法:

     TargetTrackingScalingPolicyConfiguration={
       'TargetValue': 1000.0,
       'PredefinedMetricSpecification': {
           'PredefinedMetricType': 
           'SageMakerVariantInvocationsPerInstance'
        },
       'ScaleInCooldown': 60,
       'ScaleOutCooldown': 60
     }
    )
    
  5. 如下图所示,端点上已经配置了自动扩展:图 13.1 – 查看自动扩展

    图 13.1 – 查看自动扩展

  6. 使用无限循环,我们向端点发送一些流量:

    test_sample = '0.00632, 18.00, 2.310, 0, 0.5380, 6.5750, 65.20, 4.0900, 1, 296.0, 15.30, 396.90, 4.98'
    smrt=boto3.Session().client(service_name='runtime.sagemaker') 
    while True:
        smrt.invoke_endpoint(EndpointName=endpoint_name,
                             ContentType='text/csv',
                             Body=test_sample)
    
  7. 如下图所示,通过查看端点的CloudWatch指标,我们发现每个实例的调用次数超过了我们定义的阈值:1.42k 对比 1k:图 13.2 – 查看 CloudWatch 指标

    图 13.2 – 查看 CloudWatch 指标

  8. 自动扩展迅速启动,并决定添加另一个实例,如下图所示。如果负载更高,它可能会决定一次添加多个实例:图 13.3 – 查看自动扩展

    图 13.3 – 查看自动扩展

  9. 几分钟后,额外的实例已经投入服务,每个实例的调用次数现在低于阈值(935 对比 1,000):图 13.4 – 查看 CloudWatch 指标

    图 13.4 – 查看 CloudWatch 指标

    当流量减少时,会发生类似的过程。

  10. 完成后,我们删除所有内容:

    app.delete_scaling_policy(
     PolicyName=policy_name,
     ServiceNamespace='sagemaker',
     ScalableDimension='sagemaker:variant :DesiredInstanceCount',
     ResourceId='endpoint/xgboost-one-model-ep/variant/variant-1')
    sm.delete_endpoint(EndpointName=endpoint_name)
    sm.delete_endpoint_config(
      EndpointConfigName=endpoint_config_name)
    

设置自动扩展非常简单。它帮助你根据变化的业务条件自动调整预测基础设施及相关成本。

现在,让我们学习另一种技术,当你需要处理大量模型时,这种技术会非常有用:多模型端点

部署多模型端点

多模型端点在你需要处理大量模型的情况下非常有用,在这种情况下,将每个模型部署到单独的端点是没有意义的。例如,想象一下一个 SaaS 公司为其 10,000 个客户构建回归模型。显然,他们不希望管理(并为此支付)10,000 个端点!

理解多模型端点

一个多模型终端可以提供来自任意数量存储在 S3 中的模型的 CPU 基础预测(截至写作时,不支持 GPU)。每个预测请求中都会传递所使用模型的路径。模型会根据使用情况和终端可用内存动态加载和卸载。你还可以通过简单地复制或删除 S3 中的工件来添加或删除终端中的模型。

为了提供多个模型,你的推理容器必须实现一组特定的 API,终端会调用这些 API:LOAD MODEL、LIST MODEL、GET MODEL、UNLOAD MODEL 和 INVOKE MODEL。你可以在 docs.aws.amazon.com/sagemaker/latest/dg/mms-container-apis.html 获取详细信息。

截至写作时,最新的内置容器如 scikit-learnTensorFlowApache MXNetPyTorch 原生支持这些 API。XGBoostkNNLinear LearnerRandom Cut Forest 内置算法也支持这些 API。

对于其他算法和框架,最佳选择是构建一个包含 SageMaker 推理工具包 的自定义容器,因为它已经实现了所需的 API (github.com/aws/sagemaker-inference-toolkit)。

该工具包基于多模型服务器 (github.com/awslabs/multi-model-server),你也可以直接从 CLI 使用它来提供来自多个模型的预测。你可以在 docs.aws.amazon.com/sagemaker/latest/dg/build-multi-model-build-container.html 获取更多信息。

使用 scikit-learn 构建多模型终端

让我们使用 scikit-learn 构建一个多模型终端,托管在波士顿住房数据集上训练的模型。仅支持 scikit-learn 0.23-1 及以上版本:

  1. 我们将数据集上传到 S3:

    import sagemaker, boto3
    sess = sagemaker.Session()
    bucket = sess.default_bucket()
    prefix = 'sklearn-boston-housing-mme'
    training = sess.upload_data(path='housing.csv', 
                                key_prefix=prefix + 
                                '/training')
    output = 's3://{}/{}/output/'.format(bucket,prefix)
    
  2. 我们使用不同的测试大小训练三个模型,并将它们的名称存储在字典中。在这里,我们使用最新版本的 scikit-learn,它是第一个支持多模型终端的版本:

    from sagemaker.sklearn import SKLearn
    jobs = {}
    for test_size in [0.2, 0.1, 0.05]:
        sk = SKLearn(entry_point=
                    'sklearn-boston-housing.py',
            role=sagemaker.get_execution_role(),
            framework_version='0.23-1',
            instance_count=1,
            instance_type='ml.m5.large',
            output_path=output,
            hyperparameters={ 'normalize': True,
                              'test-size': test_size }
        )
        sk.fit({'training':training}, wait=False)
        jobs[sk.latest_training_job.name] = {}
        jobs[sk.latest_training_job.name]['test-size'] =   
            test_size
    
  3. 我们找到模型工件的 S3 URI 及其前缀:

    import boto3
    sm = boto3.client('sagemaker')
    for j in jobs.keys():
        job = sm.describe_training_job(TrainingJobName=j)
        jobs[j]['artifact'] =
            job['ModelArtifacts']['S3ModelArtifacts']
        jobs[j]['key'] = '/'.join(
            job['ModelArtifacts']['S3ModelArtifacts']
            .split('/')[3:])
    
  4. 我们删除 S3 中存储的任何先前的模型:

    %%sh -s "$bucket" "$prefix"
    aws s3 rm --recursive s3://$1/$2/models
    
  5. 我们将三个模型工件复制到这个位置:

    s3 = boto3.client('s3')
    for j in jobs.keys():
        copy_source = { 'Bucket': bucket, 
                        'Key': jobs[j]['key'] }
        s3.copy_object(CopySource=copy_source,  
                       Bucket=bucket, 
                       Key=prefix+'/models/'+j+'.tar.gz')
    response = s3.list_objects(Bucket=bucket, 
                               Prefix=prefix+'/models/')
    for o in response['Contents']:
        print(o['Key'])
    

    这会列出模型工件:

    sklearn-boston-housing-mme/models/sagemaker-scikit-learn-2021-09-01-07-52-22-679
    sklearn-boston-housing-mme/models/sagemaker-scikit-learn-2021-09-01-07-52-26-399
    sklearn-boston-housing-mme/models/sagemaker-scikit-learn-2021-09-01-08-05-33-229
    
  6. 我们定义脚本的名称以及我们将上传代码归档的 S3 位置。在这里,我传递了训练脚本,其中包括一个 model_fn() 函数来加载模型。这个函数是唯一用于提供预测的函数:

    script = 'sklearn-boston-housing.py'
    script_archive = 's3://{}/{}/source/source.tar.gz'.
                     format(bucket, prefix)
    
  7. 我们创建代码归档并将其上传到 S3:

    %%sh -s "$script" "$script_archive"
    tar cvfz source.tar.gz $1
    aws s3 cp source.tar.gz $2
    
  8. 我们通过 create_model() API 创建多模型终端,并相应设置 Mode 参数:

    import time
    model_name = prefix+'-'+time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime())
    response = sm.create_model(
      ModelName = model_name,
      ExecutionRoleArn = role,
      Containers = [{
        'Image': sk.image_uri,
        'ModelDataUrl':'s3://{}/{}/models/'.format(bucket, 
                        prefix),
        'Mode': 'MultiModel',
        'Environment': {
            'SAGEMAKER_PROGRAM' : script,
            'SAGEMAKER_SUBMIT_DIRECTORY' : script_archive
        }
      }]
    )
    
  9. 我们像往常一样创建终端配置:

    epc_name = prefix+'-epc'+time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime())
    response = sm.create_endpoint_config(
        EndpointConfigName = epc_name,
        ProductionVariants=[{
            'InstanceType': 'ml.m5.large',
            'InitialInstanceCount': 1,
            'InitialVariantWeight': 1,
            'ModelName': model_name,
            'VariantName': 'variant-1'}]
    )
    
  10. 我们像往常一样创建终端:

    ep_name = prefix+'-ep'+time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime())
    response = sm.create_endpoint(
        EndpointName=ep_name,
        EndpointConfigName=epc_name)
    
  11. 一旦终端节点投入使用,我们就从数据集中加载样本并将其转换为 numpy 数组:

    import pandas as pd
    import numpy as np
    from io import BytesIO
    data = pd.read_csv('housing.csv')
    payload = data[:10].drop(['medv'], axis=1)
    buffer = BytesIO()
    np.save(buffer, payload.values)
    
  12. 我们使用这三种模型对这些样本进行预测,并将要使用的模型名称传递给每个预测请求,例如 sagemaker-scikit-learn-2021-09-01-08-05-33-229

    smrt = boto3.client('runtime.sagemaker')
    for j in jobs.keys():
        model_name=j+'.tar.gz'
        response = smrt.invoke_endpoint(
            EndpointName=ep_name,
            TargetModel=model_name,
            Body=buffer.getvalue(),
            ContentType='application/x-npy')
        print(response['Body'].read())
    
  13. 我们可以训练更多的模型,将它们的工件复制到相同的 S3 位置,并直接使用它们,而无需重新创建终端节点。我们还可以删除那些不需要的模型。

  14. 一旦完成,我们就删除终端节点:

    sm.delete_endpoint(EndpointName=ep_name)
    sm.delete_endpoint_config(EndpointConfigName=epc_name)
    

如您所见,多模型终端节点是一个很好的方式,可以从单一终端节点提供您想要的多个模型,而且设置起来并不困难。

在下一节中,我们将研究另一种成本优化技术,它可以帮助您在 GPU 预测中节省大量费用:Amazon Elastic Inference

使用 Amazon Elastic Inference 部署模型

部署模型时,您需要决定它是应该运行在 CPU 实例上,还是 GPU 实例上。在某些情况下,这个问题并不复杂。例如,有些算法根本无法从 GPU 加速中获益,因此应该部署到 CPU 实例上。另一方面,用于计算机视觉或自然语言处理的复杂深度学习模型,最好运行在 GPU 上。

在许多情况下,情况并不是那么简单明确。首先,您需要知道您应用程序的最大预测延迟是多少。如果您正在为实时广告技术应用预测点击率,那么每毫秒都至关重要;如果您是在后台应用程序中预测客户流失,那么就没那么重要。

此外,即使是能够从 GPU 加速中受益的模型,可能也不够大或复杂,无法充分利用现代 GPU 上数千个核心。在这种情况下,您会陷入两难境地:在 CPU 上部署可能会对您的需求稍显缓慢,而在 GPU 上部署则不具备成本效益。

这是 Amazon Elastic Inference 旨在解决的问题 (aws.amazon.com/machine-learning/elastic-inference/)。它让您可以将分段 GPU 加速附加到任何 EC2 实例上,包括笔记本实例和终端节点实例。加速器有三种不同的尺寸(中型、大型和超大型),可以帮助您找到最适合您应用的性价比。

Elastic Inference 可用于TensorFlowPyTorchApache MXNet。您可以通过 AWS 提供的扩展,在 EC2 实例上运行自己的代码,使用 深度学习 AMI。您还可以与 深度学习容器 一起使用。更多信息请访问 docs.aws.amazon.com/elastic-inference/latest/developerguide/working-with-ei.html

当然,Elastic Inference可以在 SageMaker 上使用。你可以在创建时将加速器附加到Notebook Instance,并使用内置的conda环境。你还可以将加速器附加到端点,接下来我们会展示如何操作。

使用 Amazon Elastic Inference 部署模型

让我们重新使用在 第五章 中训练的图像分类模型,这个模型基于 18 层ResNet网络,在卷积神经网络中算是比较小的:

  1. 一旦模型训练完成,我们像往常一样将其部署到两个端点:一个由ml.c5.large实例支持,另一个由ml.g4dn.xlarge实例支持,后者是 SageMaker 上最具成本效益的 GPU 实例:

    import time
    endpoint_name = 'c5-'+time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime())
    c5_predictor = ic.deploy(initial_instance_count=1,
                             instance_type='ml.c5.large',
                             endpoint_name=endpoint_name,
                             wait=False)
    endpoint_name = 'g4-'+time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime())
    g4_predictor = ic.deploy(
        initial_instance_count=1,
        instance_type='ml.g4dn.xlarge',
        endpoint_name=endpoint_name,
        wait=False)
    
  2. 然后,我们下载一张测试图像,进行 1,000 次预测,并测量总时间:

    with open(file_name, 'rb') as f:
        payload = f.read()
        payload = bytearray(payload)
    def predict_images(predictor, iterations=1000):
        total = 0
        for i in range(0, iterations):
            tick = time.time()
            response = runtime.invoke_endpoint(
                EndpointName=predictor.endpoint_name,                                 
                ContentType='application/x-image',
                Body=payload)
            tock = time.time()
            total += tock-tick
        return total/iterations
    predict_images(c5_predictor)
    predict_images(g4_predictor)
    
  3. 结果显示在下表中(us-east-1 价格):

    不出所料,GPU 实例大约是 CPU 实例的两倍速度。然而,CPU 实例更具成本效益,因为它的成本比 GPU 实例低四倍多。换句话说,你可以用四个 CPU 实例代替一个 GPU 实例,且以相同的成本获得更多的吞吐量。这也说明了为何了解你应用的延迟要求如此重要。 "快"和"慢"是非常相对的概念!

  4. 然后,我们在三个不同的端点上部署相同的模型,这些端点由ml.c5.large实例支持,并通过中型、大型和超大型deploy() API 加速。以下是中型端点的代码:

    endpoint_name = 'c5-medium-'
       +time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime())
    c5_medium_predictor = ic.deploy(
        initial_instance_count=1,
        instance_type='ml.c5.large',
        accelerator_type='ml.eia2.medium',
        endpoint_name=endpoint_name,
        wait=False)
    predict_images(c5_medium_predictor)
    

    你可以在下表中看到结果:

    相比裸机 CPU 端点,我们的速度提升了最多 20%,而且成本低于使用 GPU 实例的情况。让我们继续调整:

  5. 细心的读者可能已经注意到,前面的表格中包含了 32 位和 16 位浮点值的 teraFLOP 值。事实上,这两种数据类型都可以用来存储模型参数。查看图像分类算法的文档,我们发现实际上可以通过precision_dtype参数选择数据类型,默认值是float32。这就引出了一个问题:如果我们以float16模式训练模型,结果会有所不同吗?只有一个办法知道答案,不是吗?

    ic.set_hyperparameters(
        num_layers=18,                       
        use_pretrained_model=0,
        num_classes=2
        num_training_samples=22500,
        mini_batch_size=128,
        precision_dtype='float16',
        epochs=10)                   
    
  6. 重新训练后,我们达到了与float32模式下相同的模型精度。再次进行部署基准测试,我们得到了以下结果:

在裸机实例上看不出明显的差异。使用FP-16模型在大规模和超大规模加速器上进行预测,相比于FP-32模型,能帮助我们将预测速度提高大约 10%。相当不错!与裸机 CPU 实例相比,这个性能水平无疑是一个不错的升级,而与 GPU 实例相比,它在成本上更具优势。

实际上,将单个终端实例从 ml.g4dn.xlarge 切换到 ml.c5.large+ml.eia2.large,每月将节省($0.736–$0.438)x 24 x 30 = $214 美元。这可是大笔钱!

如你所见,Amazon Elastic Inference 非常易于使用,并且为你提供了额外的部署选项。一旦你定义了应用程序的预测延迟要求,你就可以快速进行实验,并找到最佳的性价比。

现在,让我们来谈谈另一个 SageMaker 功能,它可以让你为特定硬件架构编译模型:Amazon Neo

使用 Amazon SageMaker Neo 编译模型

嵌入式软件开发人员早已学会如何编写高度优化的代码,使其既能快速运行,又能节约硬件资源。从理论上讲,同样的技术也可以用于优化机器学习预测。但在实践中,由于机器学习库和模型的复杂性,这是一项艰巨的任务。

这是 Amazon SageMaker Neo 旨在解决的问题。

了解 Amazon SageMaker Neo

Amazon Neo 有两个组件:一个优化模型以适应底层硬件的模型编译器和一个名为深度学习运行时DLR)的小型运行时,用于加载优化后的模型并进行预测(aws.amazon.com/sagemaker/neo)。

Amazon SageMaker Neo 可以编译以下训练的模型:

训练照常进行,使用你选择的估算器。然后,使用 compile_model() API,我们可以轻松地为以下硬件目标编译模型:

  • 以下系列的 Amazon EC2 实例:c4c5m4m5p2p3inf1(我们将在本章后面讨论),以及 Lambda

  • AI 驱动的相机:AWS DeepLens 和 Acer aiSage

  • NVIDIA Jetson 平台:TX1、TX2、Nano 和 Xavier

  • 树莓派

  • 来自 Rockchip、Qualcomm、Ambarella 等的系统级芯片平台

模型编译执行架构优化(如融合层)和代码优化(将机器学习运算符替换为硬件优化版本)。生成的工件存储在 S3 中,包含原始模型及其优化后的形式。

然后使用 DLR 加载模型并进行预测。当然,它也可以独立使用,例如在 Raspberry Pi 上。您可以在 neo-ai-dlr.readthedocs.io 找到安装说明。由于 DLR 是开源的(github.com/neo-ai/neo-ai-dlr),您也可以从源代码构建它,并为自己的硬件平台定制它!

当涉及使用 DLR 和 SageMaker 时,情况要简单得多。SageMaker 提供了带有 Neo 支持的内置容器,这些容器是您应该用于部署使用 Neo 编译的模型的容器(正如前面提到的,训练容器保持不变)。您可以在 docs.aws.amazon.com/sagemaker/latest/dg/neo-deployment-hosting-services-cli.html 找到 Neo 支持的容器列表。

最后但并非最不重要的,DLR 的一个好处是其体积小。例如,适用于 p2 和 p3 实例的 Python 包仅为 5.4 MB,比您通常使用的深度学习库及其依赖项小得多。这显然对嵌入式环境至关重要,同时也受到 SageMaker 的欢迎,因为容器也会更小。

让我们重用我们的图像分类示例,看看 Neo 是否能加速它。

在 SageMaker 上编译和部署图像分类模型

为了让 Neo 多做一些工作,这次我们训练一个 50 层的 ResNet。然后,我们将其编译并部署到端点,并与原始模型进行比较:

  1. num_layers 设置为 50,我们对模型进行 30 个 epochs 的训练。然后,像往常一样将其部署到 ml.c5.4xlarge 实例上:

    ic_predictor = ic.deploy(initial_instance_count=1,
        instance_type='ml.c5.4xlarge',                         
        endpoint_name=ic_endpoint_name)
    
  2. 我们使用 Neo 编译模型,目标是 EC2 c5 实例系列。我们还定义了模型的输入形状:一张图像,三个通道(红色、绿色、蓝色),以及 224 x 224 像素(图像分类算法的默认值)。由于内置算法是使用 Apache MXNet 实现的,我们相应地设置了框架:

    output_path = 's3://{}/{}/output-neo/'
                  .format(bucket, prefix)
    ic_neo_model = ic.compile_model(
        target_instance_family='ml_c5',
        input_shape={'data':[1, 3, 224, 224]},
        role=role,
        framework='mxnet',
        framework_version='1.5.1',
        output_path=output_path)
    
  3. 然后像往常一样部署编译后的模型,明确将预测容器设置为启用 Neo 的图像分类版本:

    ic_neo_model.image = get_image_uri(
        session.boto_region_name, 
        'image-classification-neo', 
        repo_version='latest')
    ic_neo_predictor = ic_neo_model.deploy(
        endpoint_name=ic_neo_endpoint_name,
        initial_instance_count=1,
        instance_type='ml.c5.4xlarge')
    
  4. 下载一个测试图像,并使用与 Amazon Elastic Inference 相同的基准测试函数,测量预测 1,000 张图像所需的时间:

    predict_images(ic_predictor)
    predict_images(ic_neo_predictor)
    

    使用原始模型进行预测需要 87 秒。使用经过 Neo 优化的模型仅需 28.5 秒,快了三倍!那个编译步骤确实值回票价。您还会高兴地了解到,Neo 模型的编译是免费的,所以确实没有理由不尝试。

让我们来看看这些编译后的模型。

探索使用 Neo 编译的模型

查看传递给 compile_model() API 的输出位置,我们看到了 Neo 生成的模型文件:

$ aws s3 ls s3://sagemaker-eu-west-1-123456789012/dogscats/output-neo/
model-ml_c5.tar.gz

将其本地复制并解压缩,我们看到其中包含原始模型及其编译版本:

$ aws s3 cp s3://sagemaker-eu-west-1-123456789012/dogscats/output-neo/model-ml_c5.tar.gz .
$ tar xvfz model-ml_c5.tar.gz
compiled.meta
model-shapes.json
compiled.params
compiled_model.json
compiled.so

特别地,compiled.so 文件是一个本地文件,包含模型操作符的硬件优化版本:

$ file compiled.so
compiled.so: ELF 64-bit LSB shared object, x86-64
$ nm compiled.so | grep conv | head -3
0000000000005880 T fused_nn_contrib_conv2d_NCHWc
00000000000347a0 T fused_nn_contrib_conv2d_NCHWc_1
0000000000032630 T fused_nn_contrib_conv2d_NCHWc_2

我们本可以查看这些的汇编代码,但直觉告诉我,大多数人可能并不特别喜欢这个。开个玩笑,实际上这是完全不必要的。我们需要知道的仅仅是如何使用 Neo 编译和部署模型。

现在,我们来看看如何在 Raspberry Pi 上部署我们的模型?

在 Raspberry Pi 上部署图像分类模型

Raspberry Pi 是一款非常棒的设备,尽管其计算和内存能力有限,但它完全能够用复杂的深度学习模型进行图像预测。在这里,我使用的是 Raspberry Pi 3 Model B,配备 1.2 GHz 四核 ARM 处理器和 1 GB 内存。虽然这不算强大,但它足以运行原生 Apache MXNet 模型。

不可思议的是,Raspberry Pi 没有 MXNet 的预编译版本,从源代码构建它是一项既漫长又难以预测的过程。(我在看你,OOM 错误!)幸运的是,借助 DLR,我们可以摆脱所有这些麻烦!

  1. 在我们的 SageMaker 笔记本中,我们为 Raspberry Pi 编译模型:

    output_path = 's3://{}/{}/output-neo/'
                  .format(bucket, prefix)
    ic_neo_model = ic.compile_model(
        target_instance_family='rasp3b',
        input_shape={'data':[1, 3, 224, 224]},
        role=role,
        framework='mxnet',
        framework_version='1.5.1',
        output_path=output_path)
    
  2. 在我们的本地机器上,我们从 S3 获取已编译的模型文件并将其复制到 Raspberry Pi 上:

    $ aws s3 cp s3://sagemaker-eu-west-1-123456789012/dogscats/output-neo/model-rasp3b.tar.gz .
    $ scp model-rasp3b.tar.gz pi@raspberrypi:~
    
  3. 转到 Raspberry Pi,我们将已编译的模型提取到resnet50目录:

    $ mkdir resnet50
    $ tar xvfz model-rasp3b.tar.gz -C resnet50
    
  4. 安装 DLR 非常简单。我们在 github.com/neo-ai/neo-ai-dlr/releases 找到合适的安装包,下载并使用pip进行安装:

    $ wget https://neo-ai-dlr-release.s3-us-west-2.amazonaws.com/v1.9.0/rasp3b/dlr-1.9.0-py3-none-any.whl 
    $ pip3 install dlr-1.9.0-py3-none-any.whl
    
  5. 我们首先编写一个函数,从文件中加载图像,将其调整为 224 x 224 像素,并将其形状调整为(1, 3, 224, 224)的numpy数组,这是我们模型正确的输入形状:

    import numpy as np
    from PIL import Image
    def process_image(filename):
        image = Image.open(filename)
        image = image.resize((224,224))   
        image = np.asarray(image)         # (224,224,3)
        image = np.moveaxis(image, 2, 0). # (3,224,224)
        image = image[np.newaxis, :].     # (1,3,224,224)
        return image
    
  6. 然后,我们导入 DLR 并从resnet50目录加载已编译的模型:

    from dlr import DLRModel
    model = DLRModel('resnet50')
    
  7. 接着,我们加载一张狗的图片……或者一张猫的图片。由你选择!

    image = process_image('dog.jpg')
    #image = process_image('cat.png')
    input_data = {'data': image}
    
  8. 最后,我们预测图像 100 次,将预测结果打印出来,以避免 MXNet 可能实现的延迟评估:

    import time
    total = 0
    for i in range(0,100):
        tick = time.time()
        out = model.run(input_data)
        print(out[0])
        tock = time.time()
        total+= tock-tick
    print(total)
    

以下的狗和猫图像分别被预测为 [2.554065e-09 1.000000e+00] 和 [9.9967313e-01 3.2689856e-04],考虑到我们模型的验证精度(约 84%),这是非常不错的:

图 13.5 – 测试图像(来源:Wikimedia)

图 13.5 – 测试图像(来源:Wikimedia)

每张图像的预测时间约为 1.2 秒,虽然较慢,但对于许多嵌入式应用来说,完全足够。使用原生模型进行预测大约需要 6-7 秒,所以加速效果非常显著。

如你所见,编译模型是一项非常有效的技术。在下一部分,我们将重点介绍 Neo 的一个目标,AWS Inferentia

在 AWS Inferentia 上部署模型

AWS Inferentia 是一款专为高吞吐量和低成本预测而设计的定制芯片(aws.amazon.com/machine-learning/inferentia)。Inferentia 芯片托管在EC2 inf1实例上。这些实例有不同的大小,分别包含 1、4 或 16 颗芯片。每颗芯片包含四个NeuronCores,实现了高性能的矩阵乘法引擎,加速了典型的深度学习操作,如卷积。NeuronCores 还包含大量缓存,减少了外部内存访问。

为了在 Inferentia 上运行,模型需要使用 Neuron SDK 进行编译和部署(github.com/aws/aws-neuron-sdk)。这个 SDK 允许你与 TensorFlow、PyTorch 和 Apache MXNet 模型进行工作。

你可以在 EC2 实例上使用 Neuron SDK,自己进行模型的编译和部署。再一次,SageMaker 简化了整个过程,因为 inf1 实例是 Neo 可以编译模型的目标架构之一。

你可以在github.com/awslabs/amazon-sagemaker-examples/tree/master/sagemaker_neo_compilation_jobs/deploy_tensorflow_model_on_Inf1_instance找到一个示例。

为了结束本章,让我们总结一下在整本书中讨论的所有成本优化技术。

构建成本优化清单

即使在机器学习项目的初期阶段,你也应该时刻关注成本。即使你没有支付 AWS 账单,肯定有人在支付,我敢肯定,如果你花得太多,很快就会知道是谁支付的。

定期检查以下清单将帮助你尽可能少花钱,获得最大化的机器学习效益,并希望能够避免财务团队的干扰!

数据准备的成本优化

由于在优化训练和部署上投入了大量精力,很容易忽视数据准备。然而,机器学习工作流中的这个关键环节可能会产生非常显著的成本。

提示#1

抵制在基于实例的服务上构建临时 ETL 工具的冲动。

显然,你的工作流将需要以定制的方式处理数据,例如应用特定领域的特征工程。通过使用像Amazon GlueAmazon AthenaAmazon SageMaker Data Wrangler这样的托管服务,你无需配置任何基础设施,只需为你使用的部分付费。

作为第二选择,Amazon EMR是一个不错的服务,前提是你了解如何优化其成本。尽可能避免运行长时间存在、低使用率的集群。相反,你应该运行临时集群,并大量依赖Spot Instances作为任务节点。你可以在以下网站找到更多信息:

同样的建议适用于Amazon EC2实例。

提示 #2

使用 SageMaker Ground Truth 和自动标注来降低数据标注成本。

如果你需要标注大量的非结构化数据集,启用SageMaker Ground Truth中的自动标注可以节省大量的时间和金钱,相比于手动标注所有数据。你可以阅读相关内容:docs.aws.amazon.com/sagemaker/latest/dg/sms-automated-labeling.html

实验成本优化

实验是另一个常被忽视的领域,你应该应用以下提示来最小化相关的开支。

提示 #3

你不必使用 SageMaker Studio。

正如在第一章中解释的,介绍 Amazon SageMaker,你可以轻松地在本地计算机或本地开发服务器上使用 SageMaker Python SDK。

提示 #4

在不需要时停止 Studio 实例。

这听起来像是显而易见的事情,但你真的在做吗?实际上没有理由运行空闲实例;提交你的工作,停止它们,然后在需要时再重新启动。存储是持久化的。

提示 #5

在小规模上进行实验,并使用正确大小的实例。

你真的需要完整的数据集来开始可视化数据和评估算法吗?可能不需要。通过处理数据集的一小部分,你可以使用更小的笔记本实例。举个例子:假设 5 个开发人员每天工作 10 小时,在各自的ml.c5.2xlarge笔记本实例上。每日成本为 5 x 10 x $0.557 = $27.85\。

将实例调整为 ml.t3.xlarge(较少的 RAM,具有突发行为),每日成本将降低到 5 x 10 x $0.233 = $11.65\。你每月可以节省 $486,这笔钱肯定可以用来进行更多实验、更多训练和更多自动模型调优

如果你需要进行大规模清理和处理,请花时间将这项工作迁移到托管服务(参见提示 #1),而不是整天在一个巨大的实例上工作。不要说“我?从不!”我知道你正在这么做!

提示 #6

使用本地模式。

我们在第七章中看到,通过内建框架扩展机器学习服务,如何使用本地模式避免在 AWS 云中启动托管基础设施。这是一个在实验阶段以零成本快速迭代的绝佳技巧!

模型训练成本优化

你可以使用很多技术,我们已经讨论过其中的大部分。

提示 #7

不要在 Studio 实例上进行训练。

我要在这里重复一次,但这是一个重要的点。不幸的是,这种反模式似乎非常常见。人们选择一个大型实例(如ml.p3.2xlarge),在笔记本中启动一个大任务,放着不管,忘记它,最终在任务完成后,支付了大量费用给一个闲置了好几个小时的实例。

相反,请在托管实例上运行您的训练任务。得益于分布式 训练,您将更快地获得结果,而且由于实例在训练完成后立即终止,您永远不会为训练支付过多费用。

作为额外好处,您将不再受制于清理脚本(或过于热心的管理员)在半夜将所有笔记本实例终止(“因为它们什么都不做,对吧?”)。

提示 #8

将您的数据集打包成 RecordIO/TFRecord 文件。

这样可以更轻松、更快速地移动您的数据集,并将其分发到训练实例。我们在第五章训练计算机视觉模型第六章训练自然语言处理模型中详细讨论了这一点。

提示 #9

使用管道模式。

管道模式将您的数据集直接从 Amazon S3 流式传输到您的训练实例。没有复制过程,这节省了启动时间。我们在第九章扩展训练任务中详细讨论了这一功能。

提示 #10

合理选择训练实例的大小。

我们在第九章扩展训练任务中看到了如何实现这一点。一个词:CloudWatch 指标。

提示 #11

使用托管的 Spot 训练。

我们在第十章高级训练技巧中详细讨论了这一点。如果这还没说服您,那就没有什么能说服您了!说真的,托管的 Spot 训练几乎没有不应使用的情况,它应该是您笔记本中的默认设置。

提示 #12

使用 AWS 提供的 TensorFlow、Apache MXNet 等版本。

我们有专门的团队致力于从 AWS 上的深度学习库中提取最后一丝性能。没有冒犯的意思,但如果您认为可以通过pip install来加速,您的时间可能最好投入到其他地方。您可以通过以下链接找到更多信息:

优化模型部署成本

本章专门讲解了这些技术中的几个。我将再添加一些想法,进一步降低成本。

提示 #13

如果你不需要在线预测,使用批量转换。

一些应用程序不需要实时端点。它们完全可以使用批量转换,我们在第十一章部署机器学习模型中研究过。额外的好处是,底层实例在批处理作业完成后会自动终止,这意味着你永远不会因为没有理由让端点运行一周而多付预测费用。

提示 #14

删除不必要的端点。

这一点不需要解释,我在本书中已经写了“完成后删除端点”十多次了。然而,这仍然是一个常见的错误。

提示 #15

正确设置端点并使用自动扩展。

提示 #16

使用多模型端点来整合模型。

提示 #17

使用 Amazon Neo 编译模型,以减少硬件资源的使用。

提示 #18

在大规模使用时,使用 AWS Inferentia 而不是 GPU 实例。

当然,还有针对 AWS 所有事项的最大秘诀,这就是为什么我们专门为其写了一整章(第十二章自动化机器学习工作流)。

提示 #19

自动化,自动化,再自动化!

提示 #20

购买 Amazon SageMaker 的节省计划。

节省计划是一种灵活的定价模型,提供 AWS 使用的低价,作为对一年或三年期内承诺一致使用量的交换(aws.amazon.com/savingsplans/)。

现在,SageMaker 也支持节省计划,你可以在控制台中找到它:console.aws.amazon.com/cost-management/home?/savings-plans/

内置推荐帮助你选择合适的承诺并在几分钟内购买计划。根据期限和承诺,你可以在所有基于实例的 SageMaker 成本上节省高达 72%(!)。你可以在aws.amazon.com/blogs/aws/slash-your-machine-learning-costs-with-instance-price-reductions-and-savings-plans-for-amazon-sagemaker/找到演示。

配备了这份清单,你不仅能削减机器学习预算,还能构建更强大、更灵活的工作流。罗马不是一天建成的,所以请慢慢来,运用常识,应用目前最重要的技术,并进行迭代。

总结

在本章的最后部分,你学习了不同的技巧,这些技巧有助于通过 SageMaker 降低预测成本。首先,你了解了如何使用自动扩展根据来访流量来扩展预测基础设施。接着,你学习了如何借助多模型端点将任意数量的模型部署到同一个端点上。

我们还介绍了 Amazon Elastic Inference,它允许你为基于 CPU 的实例添加分数 GPU 加速,并为你的应用找到合适的成本效益比。然后,我们讲解了 Amazon SageMaker Neo,这是一项创新功能,它能够为特定硬件架构编译模型,适用于 EC2 实例和嵌入式设备。最后,我们创建了一个成本优化清单,将在你未来的 SageMaker 项目中派上用场。

你已经学完了全部内容,恭喜你!现在,你对 SageMaker 了解颇多。快去获取一个数据集,构建一些酷东西,并告诉我你的成果!

Packt.com

订阅我们的在线数字图书馆,全面访问超过 7,000 本书籍和视频,以及业内领先的工具,帮助您规划个人发展并推动职业生涯。欲了解更多信息,请访问我们的网站。

第十四章:为什么订阅?

  • 花更少的时间学习,花更多的时间编程,使用来自 4,000 多位行业专家的实用电子书和视频

  • 使用专门为您打造的技能计划提升您的学习

  • 每月免费获取一本电子书或视频

  • 完全可搜索,便于快速访问关键信息

  • 复制、粘贴、打印和收藏内容

您知道 Packt 提供每本出版书籍的电子书版本,并且提供 PDF 和 ePub 格式的文件吗?您可以在 packt.com 升级到电子书版本,作为印刷书籍的客户,您有资格享受电子书的折扣。详情请联系我们 customercare@packtpub.com

在 www.packt.com 上,您还可以阅读一系列免费的技术文章,注册多种免费的新闻通讯,并获得 Packt 书籍和电子书的独家折扣和优惠。

您可能喜欢的其他书籍

如果您喜欢这本书,您可能也会对 Packt 出版的其他书籍感兴趣:

使用 Amazon SageMaker 机器学习 Cookbook

Joshua Arvin Lat

ISBN: 9781800567030

  • 训练和部署 NLP、时间序列预测及计算机视觉模型,解决不同的业务问题

  • 使用自定义容器镜像推动 SageMaker 定制化的极限

  • 使用 SageMaker Autopilot 的 AutoML 功能创建高质量的模型

  • 使用有效的数据分析和准备技术

  • 探索用于调试和管理 ML 实验及部署的解决方案

  • 使用 SageMaker Clarify 处理偏差检测和 ML 可解释性需求

  • 使用各种解决方案自动化中等复杂的部署和工作流程

Amazon Redshift Cookbook

Shruti Worlikar, Thiyagarajan Arumugam, Harshida Patel

ISBN: 9781800569683

  • 使用 Amazon Redshift 构建具有大规模灵活性的千亿字节级数据仓库

  • 使用 AWS 上专为此目的构建的功能和服务,将您的数据仓库解决方案与数据湖集成

  • 使用有用的配方,从数据来源到消费,构建端到端的分析解决方案

  • 利用 Redshift 全面的安全功能满足最苛刻的业务需求

  • 在使用分析配方时,专注于架构洞察和逻辑

  • 发现处理大数据的最佳实践,操作全托管解决方案

Packt 正在寻找像您这样的作者

如果你有兴趣成为 Packt 的作者,请访问 authors.packtpub.com 并今天就申请。我们与成千上万的开发者和技术专业人士合作,帮助他们将自己的见解分享给全球技术社区。你可以提交一个通用申请,申请我们正在招聘作者的特定热门话题,或者提交你自己的创意。

分享你的想法

现在你已经完成了《学习 Amazon Sagemaker(第二版)》,我们非常希望听到你的想法!如果你是从 Amazon 购买的这本书,请点击这里直接前往 Amazon 的书评页面,分享你的反馈或在你购买该书的网站上留下评价。

你的评论对我们和技术社区非常重要,它将帮助我们确保提供优质的内容。

posted @ 2025-07-14 17:27  绝不原创的飞龙  阅读(7)  评论(0)    收藏  举报