亚马逊机器学习高效指南(二)
原文:
annas-archive.org/md5/6b6457e6a024266a78a39c9504226f74译者:飞龙
第六章:预测和性能
是时候做出一些预测了!在第四章,“加载数据集和准备数据”,我们将Titanic数据集分为两个子集,分别是训练集和保留集,分别占原始数据集的 70%和 30%,并且随机打乱。我们在第五章“模型创建”中广泛使用了训练子集,以训练和选择最佳的分类模型。但到目前为止,我们还没有使用保留集。在本章中,我们将我们的模型应用于这个保留集,对未见数据做出预测,并对我们模型的性能和鲁棒性进行最终评估。
Amazon ML 提供两种类型的预测:批量预测和流式预测。批量预测需要一个数据源。你想要预测的样本将以批量模式一次性提供给模型。流式预测,也称为实时或在线预测,需要创建一个 API 端点,并包括通过 HTTP 请求逐个提交样本序列。实时预测不涉及创建数据源。
我们将从对Titanic保留集的批量预测开始。我们将确认我们的不同模型在保留数据集上的表现与在验证子集上的表现相似,假设所有子集具有相似的变量分布。在第五章“模型创建”中,我们得出结论,在我们的三个数据源——建议的量分箱(QB)食谱、无 QB 的食谱和扩展数据集——中,包含额外变量(如deck、title、log_fare等)的那个数据源在验证子集上得到了最佳的分数。我们将验证这一点也适用于保留集。
本章分为两部分。在第一部分,我们查看Titanic数据集的批量预测。在第二部分,我们查看基于 UCI 存储库的新文本量分箱的实时、流式预测。Spam数据集足够大,可以模拟流数据。我们将创建一个 Amazon ML 端点,并使用 Python SDK 发送和检索分类预测。
在本章中,我们将涵盖以下主题:
-
制作批量预测
-
制作实时预测
在现实世界的分类问题或回归问题中,你想要对其做出预测的先前未见数据将不包括目标值。在我们的案例中,保留数据集确实包含解决方案,这使我们能够评估模型在先前未见数据上的性能。但在现实世界的问题中,你不会有这样的奢侈,你将不得不信任你的模型。
制作批量预测
在 Amazon ML 上制作批量预测的过程简单明了,遵循以下步骤:
-
从仪表板创建一个新的批量预测。
-
选择模型。
-
选择应用模型的数据源。
-
设置预测输出文件夹并授予权限。
-
审查并启动。
我们称预测数据集或数据源为,我们想要进行预测的数据。在本章中,我们处于测试环境中,预测数据集是我们从整个原始数据集中提取的保留数据集。在现实世界的情况下,预测数据集指的是全新的数据,并且不包含目标变量。
预测只有在预测数据集的分布与模型训练所用的训练数据集的分布相似时才能工作。预测数据源和训练数据源还必须共享相同的模式,唯一的区别是预测数据集不需要包含目标变量。Amazon ML 将验证为您的训练数据定义的模式是否与您的预测数据相关,如果数据集不相似,将发出警告。
为了方便起见,我们已重新创建了本章的数据集、数据源和模型。所有数据集和脚本均可在 GitHub 仓库中找到,网址为 github.com/alexperrier/packt-aml/tree/master/ch6。由于我们对原始的泰坦尼克号数据进行了重新排序,因此评估分数将与之前相同数据集获得的分数不同。
创建批量预测作业
要创建批量预测,请转到 Amazon ML 仪表板并点击创建新的批量预测:
然后选择模型。我们选择与泰坦尼克号数据集相关的原始模型,使用 Amazon ML 建议的食谱进行分位数分箱:
-
所有数值变量的分位数分箱
-
L2 轻度正则化
在模型选择之后,进行数据源选择。如果您尚未为保留集创建数据源,现在可以创建。首先,将您的预测数据集上传到 S3,并指定数据的 S3 路径:
当您点击验证时,Amazon ML 将检查预测数据集是否遵循与模型训练所用的训练数据集相同的模式:
解释预测输出
亚马逊机器学习预测作业的输出将包括两个文件:清单文件和以压缩 CSV 文件格式提供的实际预测结果。亚马逊机器学习将在 S3 上的指定位置s3://bucket/folder创建这些文件,您必须指定该路径。我们使用与数据路径相同的路径:s3://aml.packt/data/。亚马逊机器学习将在/batch_prediction文件夹中创建文件,其中它将写入清单文件以及一个额外的子文件夹/results,实际预测的 CSV 文件将写入该子文件夹。总结一下,在我们的场景中,清单文件将位于 s3://aml.packt/data/batch_prediction文件夹中,压缩的 CSV 结果文件将位于s3://aml.packt/data/batch_prediction/results/文件夹中。分配给批量预测的名称将决定清单和结果文件的命名:
预测定价:如果您刚刚创建了批量预测的数据源,亚马逊机器学习尚未获得计算预测成本所需的数据统计信息。在这种情况下,它将简单地通知您价格,每 1,000 次预测 0.10 美元。如果预测数据源已经过验证,并且亚马逊机器学习知道记录数,则估计价格将是行数乘以每次预测的价格,四舍五入到最接近的美分。亚马逊机器学习无法预测的无效样本不会产生费用。更多详细信息请参阅
docs.aws.amazon.com/machine-learning/latest/dg/pricing.html。
查看并点击创建批量预测按钮。批量预测作业将需要几分钟才能完成。完成后,它将在 S3 中创建清单和结果文件,并在亚马逊机器学习仪表板的批量预测部分显示为已完成。
读取清单文件
清单文件包含 JSON 格式的数据,将输入文件映射到预测结果文件,如下所示:
{S3 location of the batch prediction input file.csv : S3 location of the prediction results file}
在我们的场景中,清单文件包含以下行:
{"s3://aml.packt/data/ch6_titanic_heldout.csv":"s3://aml.packt/batch-prediction/result/bp-yTDNSArMqa6-ch6_titanic_heldout.csv.gz"}
多个输入文件:如果您的输入数据被分割成几个文件,并且所有文件都存储在同一个 S3 位置s3://examplebucket/input/,所有输入文件都将被批量预测作业考虑。然后清单文件将包含不同输入文件到相关结果文件的映射。例如,如果您有三个名为data1.csv、data2.csv和data3.csv的输入文件,并且它们都存储在 S3 位置s3://examplebucket/input/,您将看到一个如下所示的映射字符串:
{"s3://examplebucket/input/data1.csv":"s3://examplebucket/output/batch-prediction/result/bp-example-data1.csv.gz", "s3://examplebucket/input/data2.csv":"
s3://examplebucket/output/batch-prediction/result/bp-example-data2.csv.gz", "s3://examplebucket/input/data3.csv":"
s3://examplebucket/output/batch-prediction/result/bp-example-data3.csv.gz"}
预测的最大大小:Amazon ML 允许预测文件的最大数据量为 1TB。如果你想要进行预测的数据量更大,可以将数据分割成几个文件,上传到特定的 S3 位置,Amazon ML 将处理不同的文件,并通过并行运行多个批次生成与输入文件数量相等的预测结果文件。清单文件将包含所有不同的输入/输出对,{input_file.csv : prediction_results.csv.gz},针对你的不同批量预测文件。
读取结果文件
输出结果文件使用 gzip 压缩,源自 UNIX 世界,提供的压缩效果优于更常见的 zip 压缩。简单点击即可打开和解压缩 gzip 压缩的结果文件,将其转换为可读的 CSV 文件。或者,可以从命令行调用 gunzip 命令。查看www.gzip.org/获取不同系统的安装信息。
对于二元分类,解压缩的结果文件包含两列或三列,具体取决于初始输入文件是否包含目标值。在我们的二元分类案例中,结果文件包含以下列:trueLabel、bestAnswer和score,其中trueLabel是初始的survived列。如果你的初始批量预测数据集没有包含目标值,结果文件将只包含bestAnswer和score列:
-
trueLabel是输入文件中包含的原始目标值 -
bestAnswer是分类结果:0 或 1 -
Score是以科学记数法表示的那个分类的概率
分数的概率分类截止阈值默认为 0.5,或者在评估模型时设置的阈值值。
对于具有N个潜在目标类别的多类分类,结果文件将包含N+1或N+2列。trueLabel、bestAnswer和N列分别包含 N 个类别中每个类别的概率分数。所选的类别将是具有最高概率分数的类别。
对于回归模型,结果文件将只包含一个/两个分数列,包含预测值,可能还有trueLabel列。
评估我们的预测
由于我们知道保留样本的真实类别,我们可以计算ROC-AUC分数和其他指标,以查看我们的预测和验证分数有多接近。假设我们的数据子集具有非常相似的分布,这两个分数最终应该非常接近。差异仅来自验证和保留集样本中的随机性。
以下 Python 脚本使用了scikit-learn库(scikit-learn.org/)以及 pandas 库。只需几行 Python 代码即可计算模型在该预测数据集上的 AUC 得分。首先,从 S3 下载压缩文件,然后在 Python 笔记本或控制台中运行以下代码:
import pandas as pd
from sklearn import metrics
# open file the csv file on your local
df = pd.read_csv(path/location_of_the_unzipped_results_csv_file)
# calculate the true and false positive rate
fpr, tpr, threshold = metrics.roc_curve(df.trueLabel, df.score)
roc_auc = metrics.auc(fpr, tpr)
Python 环境:本书中的所有 Python 代码都是针对 Python 3.5 或更高版本。有关 Anaconda 库的更多信息,请参阅www.continuum.io/downloads。Anaconda 是一个惊人的强大开源数据科学平台,在 Python 中。它包含最重要的库(numpy、pandas、scikit-learn、matplotlib等)以及 Jupyter 笔记本环境。我们使用 IPython 控制台,因为它使用简单,并且有许多魔法命令(ipython.readthedocs.io/en/stable/interactive/magics.html)。
在预测结果上运行之前的 Python 脚本,我们在保留集数据集上获得 AUC 为 0.84,这非常接近我们在第五章“模型创建”中验证集上获得的 AUC(0.85)。我们可以得出结论,我们的模型在面对新的、之前未预见的数据时相当稳定和健壮。
下面的图表显示了所选模型的验证集(虚线)和保留集(实线)的 ROC 曲线。对于较高的阈值,验证集略好。这种差异反映了两个数据集中数据分布的不同:
评估保留集
在第五章“模型创建”中,我们评估了我们的不同模型在训练数据源的一个切片上的性能。我们对每个模型都获得了 AUC 得分,并选择了具有最佳 AUC 得分的 AUC。我们依赖 Amazon ML 创建验证集,通过将训练数据集分成两部分,其中 70%用于训练,30%的数据用于验证。我们本可以自己进行分割,创建验证数据源,并指定用于模型评估的数据源。
事实上,我们没有任何阻止我们在保留集数据集上运行模型评估。如果您转到模型摘要页面,您会在评估部分注意到一个“执行另一个评估”按钮:
点击它。您将被要求选择用于评估的数据源。选择保留集;Amazon ML 将验证数据是否遵循相同的模式,并且与训练数据相似。您最终会在模型上获得两个评估:
如预期的那样,保留集的评估 AUC 与我们通过下载结果并在 Python 中计算 AUC 获得的 AUC 相等:
发现谁将生存
然而,预测的真正价值并不在于验证我们模型的鲁棒性;它在于在我们的预测数据集、我们的环境中进行预测,对保留数据集中的这个“新”乘客列表进行生存预测。
结果文件中的行与预测文件中的行顺序完全相同。我们可以将保留文件的前几行和结果文件的前几行并排放置,并看到survived和trueLabel列是相同的:
乘法试验
在各种模型和数据集版本上的评估分数在一定程度上取决于评估集中的样本。如果我们在这三个数据集上多次运行以下实验,我们会看到分数的某些变化:
-
打乱并分割数据集为三个部分——训练、验证和保留,并创建相应的数据源
-
在训练数据集上训练模型,保持默认的亚马逊机器学习设置(轻微的 L2 正则化)
-
在评估和保留数据集上评估模型
下面的图表显示了三个模型在多次试验中的相应性能。图表上写有平均 AUC。我们看到平均而言,扩展数据集的性能优于默认配方下的原始数据集(AUC = 0.84)和没有分位数分箱的原始数据集(AUC = 0.83)。我们还注意到,在某些试验中,扩展数据集的性能不如原始数据集。对于试验 3,默认配方甚至略逊于没有分位数分箱的配方:
这显示了内部数据分布变异性的重要性。当尝试使用不同特征和处理的多个数据集变体时,基于您模型的多次运行得出结论是很重要的。单次评估可能会导致错过最佳模型。
进行实时预测
使用批量预测时,您可以通过创建数据源一次将所有希望模型预测的样本提交给亚马逊机器学习。使用实时预测(也称为流式或在线预测),想法是每次发送一个样本到一个 API 端点、一个 URL,通过 HTTP 查询,并为每个样本接收预测和信息。
在模型上设置实时预测包括了解预测 API 端点 URL 和编写一个脚本,该脚本可以读取您的数据,将每个新样本发送到该 API URL,并检索预测的类别或值。我们将在下一节中提供一个基于 Python 的示例。
亚马逊机器学习(Amazon ML)还提供了一种方法,可以在预测页面上对您实时创建的数据进行预测。我们可以输入一个潜在的乘客在“泰坦尼克号”上的档案,并查看该档案是否能够幸存。这是一种探索数据集变量对结果影响的绝佳方式。
在设置流式 API 之前,让我们看看通过提交几个单个乘客档案我们能收集到什么信息。我们甚至可以尝试回答这个问题——你会在大西洋号上幸存下来吗?
手动探索变量影响
前往模型摘要页面,然后点击页面左侧的“尝试实时预测”链接。接下来的页面显示一个表单,您可以在其中填写我们数据集中变量的值,除了目标变量。
让我们看看亚历克斯·佩里埃先生,一位头等舱乘客,他在南安普顿和他的家人(3 个兄弟姐妹和 2 个孩子)登船,并支付了 100 英镑的船费,他是否会幸存。好吧,在这种情况下,模型给出了非常低的生存概率(0.001),这意味着模型有信心预测这位乘客不会幸存。他的 12 岁女儿有更大的生存机会(概率为 0.56),尽管模型对此不太确定。然而,如果那个女孩独自旅行(兄弟姐妹=孩子=0),在头等舱的条件下,她的生存机会会激增到 0.98。在三等舱,她将不太幸运(概率:0.28):
因此,通过逐个改变数据中的变量,我们可以更好地理解每个变量对结果的影响。
设置实时预测
为了演示实时预测,我们将使用来自 UCI 仓库的Spam数据集。这个数据集由 5,574 条标注为垃圾邮件或非垃圾邮件(非垃圾邮件)的短信组成。没有缺失值,只有两个变量:短信的性质(垃圾邮件或非垃圾邮件)和短信文本,没有其他内容。《Spam》数据集以原始形式可在archive.ics.uci.edu/ml/datasets/SMS+Spam+Collection找到,并在本书的 GitHub 仓库github.com/alexperrier/packt-aml/tree/master/ch6中提供。我们简单地将目标从分类值:垃圾邮件和非垃圾邮件转换为二进制值:1(代表垃圾邮件)和 0(代表非垃圾邮件),这样 Amazon ML 就能理解预测为二分类类型。
AWS SDK
AWS 为其众多服务提供了几个 API 和 软件开发工具包 (SDKs)。您可以使用编程方式管理 S3 上的文件,设置 EC2 实例,并在 Amazon ML 上创建数据源、模型和评估,而无需使用基于网页的用户界面。AWS API 是低级端点。通常,使用 SDKs 更简单、更高效,SDKs 是 API 的包装器,并支持多种语言(Python、Ruby、Java、C++ 等)。在这本书中,我们将使用基于 Boto3 库的 Python SDK。我们将在第七章 Command Line and SDK 中详细探讨 Python SDK 的使用。现在,我们只将使用 predict() 方法,这是实时预测所必需的。但首先,我们需要通过在我们的本地机器上设置 AWS 凭据来启用对 AWS 的访问。
设置 AWS 凭据
为了以编程方式访问 AWS,我们首先需要通过命令行访问 AWS。这需要以下内容:
-
在 AWS IAM 为您的用户创建访问密钥
-
在本地安装
AWS-CLI命令行界面 -
配置
AWS-CLI使用 AWS 访问密钥
AWS 访问密钥
如果您还记得第三章 Overview of an Amazon Machine Learning Workflow,我们为 AML@Packt 用户创建了访问密钥。访问密钥是基于用户的,由两部分组成:访问密钥 ID,它始终在 IAM 的用户安全凭证选项卡中可用,以及秘密访问密钥,仅在创建时显示。在创建这些访问密钥时,您有下载它们的机会。如果您当时没有这样做,现在可以为您的用户重新创建访问密钥。转到 IAM 控制台 console.aws.amazon.com/iam,点击您的用户配置文件,选择安全凭证选项卡,然后点击创建访问密钥按钮。这次请确保在您的本地机器上下载密钥或将它们复制到其他地方。请注意,每个用户最多只能有两套访问密钥。如果您已经有两个与您的用户关联的密钥,您必须删除现有的密钥才能创建新的密钥:
设置 AWS CLI
到目前为止,我们只使用过 AWS 网页界面,在 AWS 网站上逐页点击。另一种与 AWS 服务交互的方式是通过终端窗口中的命令行,使用 aws cli 库。CLI 代表命令行界面。
要安装 aws cli 库,打开一个终端窗口。对于基于 Python 的环境(Python 2 版本 2.6.5+ 或 Python 3 版本 3.3+),安装 aws cli 包括在终端中运行以下命令:
pip install awscli
其他环境下的安装完整说明可在docs.aws.amazon.com/cli/latest/userguide/installing.html找到。一旦 AWS-CLI 安装完成,运行以下命令来配置它:
aws configure
您将被要求输入您的访问密钥、默认区域和格式。有关更深入的说明,请参阅docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-quick-configuration。
简而言之,AWS-CLI 命令遵循以下语法:
aws {service name} {command} {parameters}
通过运行以下命令来测试您的设置:
aws s3 ls aml.packt
您应该能看到您 s3 账户中的所有存储桶、文件夹和文件列表。以下是我列出aml.packt存储桶中的文件和文件夹时的输出:
我们将在第七章[efe6f699-a4eb-4c88-8e81-1408d6c3c5c4.xhtml],Command Line and SDK中详细探讨如何使用 CLI 运行您的 Amazon ML 项目。
Python SDK
在本章中,我们将不再使用 AWS-CLI,而是切换到 Python SDK。我们需要为 AWS CLI 设置凭证,以便我们的 SDK 脚本能够访问我们的 AWS 账户。要使用 Python SDK,我们需要安装Boto3库,该库包含在 Anaconda 发行版中。如果您使用 Anaconda 作为 Python 环境,您应该已经安装了boto3包。如果没有,您可以使用以下命令使用pip安装它:
pip install boto3
Boto3将使用我们为 AWS CLI 配置的凭证。无需进行特定设置。Boto3 适用于大多数 AWS 服务。完整文档可在boto3.readthedocs.io/找到。我们最小化使用Boto3,只需指定我们想要使用的服务,即机器学习,然后使用predict()方法将适当的数据发送到模型。作为回报,我们获得所需的预测。以下 Python 代码初始化一个客户端以访问机器学习服务。
import boto3
client = boto3.client('machinelearning')
predict()方法需要以下参数:
-
MLModelId: 您想要用于预测的模型的 ID -
PredictEndpoint: 您模型对应的 Amazon ML 端点 URL -
Record: 样本的 JSON 格式版本
MLModelId和PredictEndpoint URL 可以从模型摘要页面获取。Record是一个 JSON 格式的字符串。我们将通过打开保留的样本集,循环遍历每个样本,并通过predict()方法发送它来模拟一个流式应用程序。
我们将初始数据集分为一个包含 4,400 个样本的训练集和一个包含 1,174 个样本的保留集。这些子集可在 GitHub 仓库中找到。我们为训练子集创建一个数据源,并使用默认设置(轻微的 L2 正则化)创建一个模型及其评估。我们保留推断的架构(二进制和文本),建议的配方(除了对文本变量的标记化之外没有转换),并使用默认模型参数(10 次遍历和轻微的 L2 正则化)。训练数据集进一步由 Amazon ML 分割为较小的训练数据集和验证数据集,分别为初始4,400样本的 70%和 30%。在验证集上获得的 AUC 分数非常高,为0.98:
要获取ModelID和endpoint URL,请访问模型的摘要页面。从页面顶部复制ModelID。然后向下滚动到预测部分,点击创建endpoint按钮:
在这一点上,您将获得模型实时预测定价的估计,并要求确认创建endpoint。
您的模型大小为 502.1 KB。每当您的endpoint处于活动状态时,您将产生 0.001 美元的预留容量费用。实时预测的预测费用为每次预测 0.0001 美元,向上取整到最接近的美分**。**
几分钟后,endpoint将被创建,您将获得endpoint URL:
现在我们知道了endpoint URL,我们可以编写一个简单的 Python 代码,将短信消息、简单的文本发送到我们的预测模型,看看这条消息是否被预测为垃圾邮件或正常邮件。我们将文本Hello world, my name is Alex发送以分类为正常邮件,而文本Call now to get dating contacts for free, no cash no credit card可能因为包含free、cash、dating等词语而被检测为垃圾邮件。
代码的初始化/声明部分如下:
import boto3
import json # for parsing the returned predictions
# Initialize the client
client = boto3.client('machinelearning')
# The endpoint url is obtained from the model summary
endpoint_url = "https://realtime.machinelearning.us-east-1.amazonaws.com/"
# replace with your own model ID
model_id = "ml-kJmiRxxxxxx"
# The actual sample to be predicted. JSON formatted
record = { "nature": "Hello world, my name is Alex" }
我们现在使用机器学习服务 SDK 的predict()函数:
response = client.predict(
MLModelId = model_id,
Record = record,
PredictEndpoint = endpoint_url
)
最后,格式化打印响应:
print(json.dumps(response, indent=4))
这返回以下 JSON 格式的字符串:
{
"ResponseMetadata": {
"RetryAttempts": 0,
"HTTPHeaders": {
"content-type": "application/x-amz-json-1.1",
"content-length": "143",
"date": "Tue, 10 Jan 2017 16:20:49 GMT",
"x-amzn-requestid": "bfab2af0-d750-11e6-b8c2-45ac3ab2f186"
},
"HTTPStatusCode": 200,
"RequestId": "bfab2af0-d750-11e6-b8c2-45ac3ab2f186"
},
"Prediction": {
"predictedScores": {
"0": 0.001197131467051804
},
"predictedLabel": "0",
"details": {
"PredictiveModelType": "BINARY",
"Algorithm": "SGD"
}
}
}
JSON 响应由两部分组成:第一部分与请求本身相关,ResponseMetadata,第二部分与Prediction相关。ResponseMetadata部分的HTTPStatusCode告诉我们我们的查询是成功的("HTTPStatusCode": 200)。
预测部分的解释很简单。短信被预测为垃圾邮件,概率极低,为 0.12%,因此被分类为正常邮件,这正是我们对文本Hello world, my name is Alex的预期。
我们预计文本Call now to get dating contacts for free, no cash no credit card将被分类为垃圾邮件,因为free、call和dating等词语通常是垃圾邮件的强烈指示。我们得到的以下结果:
{
"predictedScores": { "1": 0.810875654220581 },
"predictedLabel": "1",
}
文本被分类为垃圾邮件,这正是我们预期的。从这两个简单的例子来看,我们的模型似乎运行良好。一旦我们可以通过 API 调用逐个样本地获取预测,就可以将数据流连接到端点并获取实时预测。
为了模拟这个流程,我们可以使用 Python 读取整个新样本文件,将每个样本发送到模型,并捕获结果。让我们用保留的 Spam 样本集来做这件事。
以下 Python 代码读取文件,将其加载到 pandas 数据框中,并遍历数据框中的每一行。我们使用iterrows()遍历数据框中的每一行。这种方法比itertuples()慢,但代码可读性更好。以下代码没有经过优化:
import boto3
import json
import pandas as pd
# Initialize the Service, the Model ID and the endpoint url
client = boto3.client('machinelearning')
# replace with your own endpoint url and model ID
endpoint_url = "https://realtime.machinelearning.us-east-1.amazonaws.com"
model_id = "ml-kJmiRHyn1UM"
# Memorize which class is spam and which is ham
spam_label = {'0': 'ham', '1':'spam'}
# Load the held out dataset into a panda DataFrame
df = pd.read_csv('held-out.csv')
# Loop over each DataFrame rows
for index, row in df.iterrows():
# The record
record = { "body": row['sms'] }
response = client.predict(
MLModelId = model_id,
Record = record,
PredictEndpoint = endpoint_url
)
# get the label and score from the response
predicted_label = response['Prediction']['predictedLabel']
predicted_score = response['Prediction']['predictedScores'][predicted_label]
print("[%s] %s (%0.2f):t %s "% (spam_label[str(row['nature'])],
spam_label[predicted_label],
predicted_score,
row['sms'] )
)
Amazon ML 的响应速度极快。数千个样本在几秒钟内被处理。以下是我们的结果摘要:
在这里,每一行格式如下:
[Predicted class] trueLabel (spam probability): SMS message
注意,在检测到的三个垃圾短信中,只有两个实际上是垃圾短信。文本“Money i have won wining number 946 wot do i do next”可能因为包含“Money”或“wining”等词语而被检测为垃圾短信,但实际上是一条正常信息。
总体而言,在整个预测过程中,概率非常接近 0 或 1,这表明模型在分类上非常果断,没有犹豫。保留数据集的 ROC 曲线显示了高水平的确切性:
摘要
在本章中,我们探讨了 Amazon ML 工作流程的最后一步,即预测。Amazon ML 提供了几种方法将您的模型应用于新的数据集以进行预测。批处理模式涉及一次性将所有新数据提交给模型,并在 S3 上返回实际的预测 csv 文件。另一方面,实时预测是基于逐个发送样本到 API 并获取预测结果。我们探讨了如何在 Amazon ML 平台上创建 API。我们还开始使用命令行和 Python SDK 与 Amazon ML 服务交互——我们将在第七章“命令行和 SDK”中更深入地探讨这一点。
如前几章所述,Amazon ML 服务是围绕随机梯度下降(SGD)算法构建的。这个算法已经存在很多年了,被用于许多不同的领域和应用,从信号处理和自适应滤波到预测分析或深度学习。
在下一章中,我们将介绍算法及其一些版本,并揭示它在处理不同类型的数据和预测问题时的行为。我们将解释为什么在我们的情况下,通常被轻视的数值分位数装箱是性能和稳定性的提升者。
第七章:命令行和 SDK
使用 AWS 网页界面来管理和运行项目是耗时的。在本章中,我们将远离网页界面,开始通过命令行使用 AWS 命令行界面(AWS CLI)和 Python SDK 的 Boto3 库来运行我们的项目。
第一步将是通过 AWS CLI 驱动整个项目,包括上传文件到 S3、创建数据源、模型、评估和预测。正如您将看到的,脚本将极大地简化使用 Amazon ML。我们将利用这些新能力通过执行交叉验证和特征选择来扩展我们的数据科学能力。
到目前为止,我们已经将原始数据集分为三个数据块:训练、验证和测试。然而,我们已经看到模型选择可能强烈依赖于数据分割。打乱数据——可能不同的模型会被认为是最好的。交叉验证是一种通过平均模型在多个数据分割上的性能来减少这种依赖性的技术。交叉验证涉及创建许多用于训练、验证和测试的数据源,使用网页界面将会耗时。AWS CLI 将使我们能够快速创建新的数据源和模型,并有效地执行交叉验证。
数据科学中的另一个重要技术是特征消除。在数据集中拥有大量特征,无论是由于密集的特征工程结果还是因为它们存在于原始数据集中,都可能影响模型的表现。通过选择和保留最佳且最有意义的特征,同时拒绝不那么重要的特征,可以显著提高模型的预测能力。有许多特征选择方法。我们将实现一个简单而高效的方法,称为递归特征选择。通过 Boto3 库可访问的 AWS Python SDK 将使我们能够构建围绕 Amazon ML 的代码,以实现递归特征选择。
在本章中,您将学习以下内容:
-
如何通过 AWS 命令行和 AWS Python SDK 处理整个项目工作流程:
-
管理数据上传到 S3
-
创建和评估模型
-
制作和导出预测
-
-
如何使用 AWS CLI 实现交叉验证
-
如何使用 AWS Python SDK 实现递归特征选择
开始使用和设置
从原始数据创建一个表现良好的预测模型需要许多尝试和错误,很多来回调整。创建新特征、清理数据以及尝试为模型设定新参数都是确保模型稳健性的必要步骤。在数据、模型和评估之间需要不断的来回调整。通过 AWS CLI 或 Boto3 Python 库脚本化这个工作流程,将使我们能够加快创建、测试、选择的循环。
使用 CLI 与 SDK 的比较
AWS 提供了多种方式,除了 UI 之外,还可以与它的服务进行交互,包括 CLI、API 和多种语言的 SDK。尽管 AWS CLI 和 SDKs 并不包含所有 AWS 服务。例如,Athena SQL 是一项新服务,在撰写本文时,它尚未包含在 AWS CLI 模块或任何 AWS SDK 中。
AWS 命令行界面(CLI)是一个命令行外壳程序,允许您从您的 shell 终端管理您的 AWS 服务。一旦安装并设置了适当的权限,您就可以编写命令来管理您的 S3 文件、AWS EC2 实例、Amazon ML 模型以及大多数 AWS 服务。
一般而言,软件开发工具包(SDK),简称 SDK,是一组可用于开发针对特定平台的应用软件的工具。简而言之,SDK 是 API 的包装器。API 包含核心交互方法,而 SDK 包括调试支持、文档以及高级函数和方法。API 可以被视为 AWS 支持的最低公共基数,而 SDK 则是 API 的高级实现。
AWS SDKs 可用 12 种不同的语言,包括 PHP、Java、Ruby 和 .NET。在本章中,我们将使用 Python SDK。
使用 AWS CLI 或 SDK 需要设置我们的凭证,我们将在下一节中完成此操作。
安装 AWS CLI
为了设置您的 CLI 凭证,您需要您的访问密钥 ID 和您的秘密访问密钥。您很可能在之前的章节中下载并保存了它们。如果不是这样,您应该简单地从 IAM 控制台(console.aws.amazon.com/iam)创建新的凭证。
导航到“用户”,选择您的 IAM 用户名,然后点击“安全凭证”选项卡。选择创建访问密钥并下载 CSV 文件。将密钥存储在安全位置。我们将在几分钟内需要该密钥来设置 AWS CLI。但首先,我们需要安装 AWS CLI。
Docker 环境 – 本教程将帮助您在 Docker 容器中使用 AWS CLI:blog.flowlog-stats.com/2016/05/03/aws-cli-in-a-docker-container/。运行 AWS CLI 的 Docker 镜像可在 hub.docker.com/r/fstab/aws-cli/ 找到。
没有必要重写 AWS CLI 的安装文档。它是完整且最新的,可在 docs.aws.amazon.com/cli/latest/userguide/installing.html 找到。简而言之,安装 CLI 需要您已经安装了 Python 和 pip。
然后,运行以下命令:
$ pip install --upgrade --user awscli
将 AWS 添加到您的 $PATH:
$ export PATH=~/.local/bin:$PATH
重新加载 bash 配置文件(这是针对 OSX 的):
$ source ~/.bash_profile
请使用以下命令检查一切是否正常工作:
$ aws --version
您应该会看到以下类似的输出:
$ aws-cli/1.11.47 Python/3.5.2 Darwin/15.6.0 botocore/1.5.10
安装完成后,我们需要配置 AWS CLI 类型:
$ aws configure
现在输入您刚刚创建的访问密钥:
$ aws configure
AWS Access Key ID [None]: ABCDEF_THISISANEXAMPLE
AWS Secret Access Key [None]: abcdefghijk_THISISANEXAMPLE
Default region name [None]: us-west-2
Default output format [None]: json
选择离您最近且您喜欢的格式(JSON、文本或表格)。默认格式是 JSON。
AWS configure 命令创建两个文件:一个config文件和一个凭证文件。在 OSX 上,这些文件是~/.aws/config和~/.aws/credentials。您可以直接编辑这些文件来更改您的访问或配置。如果您需要访问多个 AWS 账户,您需要创建不同的配置文件。您可以通过 AWS configure 命令这样做:
$ aws configure --profile user2
您也可以直接在config和credential文件中这样做:
~/.aws/config
[default]
output = json
region = us-east-1
[profile user2]
output = text
region = us-west-2
您可以按照以下方式编辑Credential文件:
~/.aws/credentials
[default]
aws_secret_access_key = ABCDEF_THISISANEXAMPLE
aws_access_key_id = abcdefghijk_THISISANEXAMPLE
[user2]
aws_access_key_id = ABCDEF_ANOTHERKEY
aws_secret_access_key = abcdefghijk_ANOTHERKEY
请参阅 AWS CLI 设置页面以获取更深入的信息:
docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html
学习 CLI 语法
任何 AWS CLI 命令的整体格式如下:
$ aws <service> [options] <command> <subcommand> [parameters]
这里术语如下所述:
-
<service>:您正在管理的服务的名称:S3、机器学习和 EC2 -
[options]:允许您设置区域、配置文件和命令的输出 -
<command> <subcommand>:这是您想要执行的真正命令 -
[parameters]:是这些命令的参数
一个简单的例子将帮助您更好地理解语法。要列出名为aml.packt*的 S3 存储桶的内容,命令如下:
$ aws s3 ls aml.packt
在这里,s3是服务,ls是命令,aml.packt是参数。aws help命令将输出所有可用服务的列表。
要获取特定服务及其命令的帮助,请编写以下内容:
$ aws <service> help
例如,aws s3 help将通知您,在单个对象上可用的s3命令有 ls、mv 和 rm,用于列出、移动和删除,并且基本的aws s3命令遵循以下格式:
$ aws s3 <command> sourceURI destinationURI [parameters]
在这里,sourceURI或destinationURI可以是您本地机器上的文件(或多个文件)和 S3 上的文件,也可以是 S3 上的两个文件。以下是一个例子:
$ aws s3 cp /tmp/foo/ s3://The_Bucket/ --recursive --exclude "*" --include "*.jpg"
这将复制您本地机器上/tmp/foo文件夹中的所有(多亏了参数——递归)JPG 文件(仅*.jpg文件)到名为The_Bucket的 S3 存储桶。
AWS 文档中有更多示例和解释,可在以下链接找到:
docs.aws.amazon.com/cli/latest/userguide/cli-chap-using.html。
使用 JSON 文件传递参数
对于某些服务和命令,参数列表可能会变得很长,难以检查和维护。
例如,要通过命令行界面(CLI)创建一个 Amazon ML 模型,您至少需要指定七个不同的元素:模型 ID、名称、类型、模型的参数、训练数据源的 ID 以及食谱名称和 URI(aws machinelearning create-ml-model help)。
在可能的情况下,我们将使用 CLI 功能从 JSON 文件中读取参数,而不是在命令行中指定它们。AWS CLI 还提供了一种生成 JSON 模板的方法,您可以使用正确的参数使用该模板。要生成该 JSON 参数文件模型(JSON 框架),只需在命令名称后添加 --generate-cli-skeleton。例如,要生成机器学习服务创建模型命令的 JSON 框架,请编写以下内容:
$ aws machinelearning create-ml-model --generate-cli-skeleton
这将产生以下输出:
{
"MLModelId": "",
"MLModelName": "",
"MLModelType": "",
"Parameters": {
"KeyName": ""
},
"TrainingDataSourceId": "",
"Recipe": "",
"RecipeUri": ""
}
您可以根据自己的喜好进行配置。
要使骨架命令生成 JSON 文件而不是在终端中简单地输出骨架,请添加 > filename.json:
$ aws machinelearning create-ml-model --generate-cli-skeleton > filename.json
这将创建一个包含 JSON 模板的 filename.json 文件。一旦指定了所有必需的参数,您就可以使用以下命令创建模型(假设 filename.json 在当前文件夹中):
$ aws machinelearning create-ml-model file://filename.json
在我们进一步通过 CLI 深入探讨机器学习工作流程之前,我们需要介绍本章将使用的数据集。
介绍 Ames Housing 数据集
在本章中,我们将使用由 迪安·德·科克 编制的 Ames Housing 数据集,用于数据科学教育。它是流行的但较旧的 Boston Housing 数据集的一个很好的替代品。Ames Housing 数据集在 Kaggle 网站的“高级回归技术”挑战赛中被使用:www.kaggle.com/c/house-prices-advanced-regression-techniques/。数据集的原始版本可在:www.amstat.org/publications/jse/v19n3/decock/AmesHousing.xls 和本章的 GitHub 仓库中找到。
Ames Housing 数据集包含 79 个解释变量,描述了爱荷华州艾姆斯市住宅的各个方面(几乎涵盖所有方面),目的是预测每套住宅的售价。该数据集有 2930 行。变量数量众多,使该数据集成为特征选择的良好候选。
关于该数据集的起源以及不同变量的深入解释,请阅读由 迪安·德·科克 撰写的论文,该论文可在 PDF 格式下在 ww2.amstat.org/publications/jse/v19n3/decock.pdf 获取。
如同往常,我们将首先将数据集分割成训练集和验证集,并在训练集上构建模型。训练集和验证集都可在 GitHub 仓库中找到,分别命名为 ames_housing_training.csv 和 ames_housing_validate.csv。整个数据集位于 ames_housing.csv 文件中。
使用 shell 命令分割数据集
命令行是数据科学家经常忘记但非常强大的盟友。许多非常强大的数据处理操作可以通过正确的 shell 命令实现,并且执行速度极快。为了说明这一点,我们将使用 shell 命令来打乱、分割,并创建Ames Housing数据集的训练和验证子集:
- 首先,将第一行提取到单独的文件
ames_housing_header.csv中,并从原始文件中删除:
$ head -n 1 ames_housing.csv > ames_housing_header.csv
- 我们只需将第一行之后的全部行尾接到同一个文件中:
$ tail -n +2 ames_housing.csv > ames_housing_nohead.csv
- 然后将行随机排序到一个临时文件中。(
gshuf是 OSX 中 Linux shuf shell命令的等价物。可以通过brew install coreutils安装。)
$ gshuf ames_housing_nohead.csv -o ames_housing_nohead.csv
- 提取前 2,050 行作为训练文件,最后 880 行作为验证文件:
$ head -n 2050 ames_housing_nohead.csv > ames_housing_training.csv
$ tail -n 880 ames_housing_nohead.csv > ames_housing_validate.csv
- 最后,将标题添加回训练和验证文件中:
$ cat ames_housing_header.csv ames_housing_training.csv > tmp.csv
$ mv tmp.csv ames_housing_training.csv
$ cat ames_housing_header.csv ames_housing_validate.csv > tmp.csv
$ mv tmp.csv ames_housing_validate.csv
使用 CLI 的一个简单项目
我们现在准备好使用 CLI 执行一个简单的 Amazon ML 工作流程。这包括以下内容:
-
在 S3 上上传文件
-
创建数据源和配方
-
创建模型
-
创建评估
-
预测批量和实时
让我们从上传训练和验证文件到 S3 开始。在以下行中,将存储桶名称aml.packt替换为您自己的存储桶名称。
要将文件上传到 S3 位置s3://aml.packt/data/ch8/,运行以下命令行:
$ aws s3 cp ./ames_housing_training.csv s3://aml.packt/data/ch8/
upload: ./ames_housing_training.csv to s3://aml.packt/data/ch8/ames_housing_training.csv
$ aws s3 cp ./ames_housing_validate.csv s3://aml.packt/data/ch8/
upload: ./ames_housing_validate.csv to s3://aml.packt/data/ch8/ames_housing_validate.csv
Amazon ML CLI 命令概述
S3 部分到此结束。现在让我们探索 Amazon 机器学习服务的 CLI。
所有 Amazon ML CLI 命令均可在docs.aws.amazon.com/cli/latest/reference/machinelearning/找到。共有 30 个命令,可以根据对象和动作进行分组。
您可以执行以下操作:
-
create: 创建对象 -
describe: 根据某些参数(位置、日期、名称等)搜索对象 -
get: 给定一个对象 ID,返回信息 -
update: 给定一个对象 ID,更新对象 -
delete: 删除一个对象
这些可以在以下元素上执行:
-
datasource
-
create-data-source-from-rds -
create-data-source-from-redshift -
create-data-source-from-s3 -
describe-data-sources -
delete-data-source -
get-data-source -
update-data-source
-
-
ml-model
-
create-ml-model -
describe-ml-models -
get-ml-model -
delete-ml-model -
update-ml-model
-
-
评估
-
create-evaluation -
describe-evaluations -
get-evaluation -
delete-evaluation -
update-evaluation
-
-
批量预测
-
create-batch-prediction -
describe-batch-predictions -
get-batch-prediction -
delete-batch-prediction -
update-batch-prediction
-
-
实时端点
-
create-realtime-endpoint -
delete-realtime-endpoint -
predict
-
您还可以处理标签并设置等待时间。
注意,AWS CLI 允许您从 S3、Redshift 和 RDS 创建数据源,而 Web 界面只允许从 S3 和 Redshift 创建数据源。
创建数据源
我们将首先创建数据源。让我们首先通过生成以下骨架来查看需要哪些参数:
$ aws machinelearning create-data-source-from-s3 --generate-cli-skeleton
这生成了以下 JSON 对象:
{
"DataSourceId": "",
"DataSourceName": "",
"DataSpec": {
"DataLocationS3": "",
"DataRearrangement": "",
"DataSchema": "",
"DataSchemaLocationS3": ""
},
"ComputeStatistics": true
}
不同的参数大多一目了然,更多详细信息可以在 AWS 文档中找到,链接为docs.aws.amazon.com/cli/latest/reference/machinelearning/create-data-source-from-s3.html。
关于模式的话:当从 Web 界面创建数据源时,您有使用向导的可能性,以指导您创建模式。如您所回忆的,您将通过几个屏幕进行指导,在这些屏幕上您可以指定所有列的类型,以及目标变量和索引列的存在。向导通过猜测变量的类型来简化过程,从而提供一个默认的模式,您可以对其进行修改。
通过 AWS CLI 没有默认的模式可用。您必须自己定义整个模式,无论是在DataSchema字段中的 JSON 格式,还是在DataSchemaLocationS3字段中上传一个模式文件并指定其位置。
由于我们的数据集包含许多变量(79 个),我们采取了捷径,使用向导创建了一个默认的模式,并将其上传到 S3。在整个章节的其余部分,我们将指定模式位置而不是其 JSON 定义。
在本例中,我们将创建以下数据源参数文件,dsrc_ames_housing_001.json:
{
"DataSourceId": "ch8_ames_housing_001",
"DataSourceName": "[DS] Ames Housing 001",
"DataSpec": {
"DataLocationS3":
"s3://aml.packt/data/ch8/ames_housing_training.csv",
"DataSchemaLocationS3":
"s3://aml.packt/data/ch8/ames_housing.csv.schema"
},
"ComputeStatistics": true
}
对于验证子集(保存为dsrc_ames_housing_002.json):
{
"DataSourceId": "ch8_ames_housing_002",
"DataSourceName": "[DS] Ames Housing 002",
"DataSpec": {
"DataLocationS3":
"s3://aml.packt/data/ch8/ames_housing_validate.csv",
"DataSchemaLocationS3":
"s3://aml.packt/data/ch8/ames_housing.csv.schema"
},
"ComputeStatistics": true
}
由于我们已经将数据分为训练集和验证集,因此无需指定数据DataRearrangement字段。
或者,我们也可以避免分割数据集,并在原始数据集上指定以下DataRearrangement,假设它已经被洗牌了:(保存为dsrc_ames_housing_003.json):
{
"DataSourceId": "ch8_ames_housing_003",
"DataSourceName": "[DS] Ames Housing training 003",
"DataSpec": {
"DataLocationS3":
"s3://aml.packt/data/ch8/ames_housing_shuffled.csv",
"DataRearrangement":
"{"splitting":{"percentBegin":0,"percentEnd":70}}",
"DataSchemaLocationS3":
"s3://aml.packt/data/ch8/ames_housing.csv.schema"
},
"ComputeStatistics": true
}
对于验证集(保存为dsrc_ames_housing_004.json):
{
"DataSourceId": "ch8_ames_housing_004",
"DataSourceName": "[DS] Ames Housing validation 004",
"DataSpec": {
"DataLocationS3":
"s3://aml.packt/data/ch8/ames_housing_shuffled.csv",
"DataRearrangement":
"{"splitting":{"percentBegin":70,"percentEnd":100}}",
},
"ComputeStatistics": true
}
在这里,ames_housing.csv文件之前已经使用gshuf命令行进行了洗牌,并上传到了 S3:
$ gshuf ames_housing_nohead.csv -o ames_housing_nohead.csv
$ cat ames_housing_header.csv ames_housing_nohead.csv > tmp.csv
$ mv tmp.csv ames_housing_shuffled.csv
$ aws s3 cp ./ames_housing_shuffled.csv s3://aml.packt/data/ch8/
注意,我们不需要创建这四个数据源;这些只是创建数据源的替代方法示例。
我们然后通过运行以下命令创建这些数据源:
$ aws machinelearning create-data-source-from-s3 --cli-input-json file://dsrc_ames_housing_001.json
我们可以检查数据源创建是否挂起:
作为回报,我们得到了之前指定的数据源 ID:
{
"DataSourceId": "ch8_ames_housing_001"
}
我们可以使用以下方式获取该数据源的信息:
$ aws machinelearning get-data-source --data-source-id ch8_ames_housing_001
这返回以下内容:
{
"Status": "COMPLETED",
"NumberOfFiles": 1,
"CreatedByIamUser": "arn:aws:iam::178277xxxxxxx:user/alexperrier",
"LastUpdatedAt": 1486834110.483,
"DataLocationS3": "s3://aml.packt/data/ch8/ames_housing_training.csv",
"ComputeStatistics": true,
"StartedAt": 1486833867.707,
"LogUri": "https://eml-prod-emr.s3.amazonaws.com/178277513911-ds-ch8_ames_housing_001/.....",
"DataSourceId": "ch8_ames_housing_001",
"CreatedAt": 1486030865.965,
"ComputeTime": 880000,
"DataSizeInBytes": 648150,
"FinishedAt": 1486834110.483,
"Name": "[DS] Ames Housing 001"
}
注意,我们有访问操作日志 URI 的权限,这可能在稍后分析模型训练时很有用。
创建模型
使用create-ml-model命令创建模型遵循相同的步骤:
- 使用以下步骤生成骨架:
$ aws machinelearning create-ml-model --generate-cli-skeleton >
mdl_ames_housing_001.json
- 编写配置文件:
{
"MLModelId": "ch8_ames_housing_001",
"MLModelName": "[MDL] Ames Housing 001",
"MLModelType": "REGRESSION",
"Parameters": {
"sgd.shuffleType": "auto",
"sgd.l2RegularizationAmount": "1.0E-06",
"sgd.maxPasses": "100"
},
"TrainingDataSourceId": "ch8_ames_housing_001",
"RecipeUri": "s3://aml.packt/data/ch8
/recipe_ames_housing_001.json"
}
注意算法的参数。在这里,我们使用了温和的 L2 正则化和 100 次迭代。
- 使用以下步骤启动模型创建:
$ aws machinelearning create-ml-model --cli-input-json
file://mdl_ames_housing_001.json
- 返回模型 ID:
{
"MLModelId": "ch8_ames_housing_001"
}
- 这个
get-ml-model命令会给你操作的状态更新以及日志的 URL。
$ aws machinelearning get-ml-model --ml-model-id
ch8_ames_housing_001
watch命令允许你每 n 秒重复一次 shell 命令。要每 10s 获取模型创建的状态,只需写下以下命令:
$ watch -n 10 aws machinelearning get-ml-model --ml-model-id
ch8_ames_housing_001
get-ml-model 的输出将每 10 秒刷新一次,直到你将其终止。
通过 AWS CLI 命令无法创建默认的食谱。你总是可以定义一个空白食谱,该食谱不会对数据进行任何转换。然而,默认食谱已被证明对模型性能有积极影响。为了获得此默认食谱,我们通过 Web 界面创建它,将其复制到一个我们上传到 S3 的文件中。生成的文件 recipe_ames_housing_001.json 可在我们的 GitHub 仓库中找到。由于数据集有 79 个变量,其内容相当长,这里为了简洁起见没有展示。
使用 create-evaluation 评估我们的模型
我们现在的模型已经训练好了,我们想在评估子集上评估它。为此,我们将使用 create-evaluation CLI 命令:
- 生成骨架:
$ aws machinelearning create-evaluation --generate-cli-skeleton >
eval_ames_housing_001.json
- 配置参数文件:
{
"EvaluationId": "ch8_ames_housing_001",
"EvaluationName": "[EVL] Ames Housing 001",
"MLModelId": "ch8_ames_housing_001",
"EvaluationDataSourceId": "ch8_ames_housing_002"
}
- 启动评估创建:
$ aws machinelearning create-evaluation --cli-input-json
file://eval_ames_housing_001.json
- 获取评估信息:
$ aws machinelearning get-evaluation --evaluation-id
ch8_ames_housing_001
- 从那个输出中,我们以 RMSE 的形式获得了模型的性能:
"PerformanceMetrics": {
"Properties": {
"RegressionRMSE": "29853.250469108018"
}
}
这个值可能看起来很大,但它相对于房屋的 salePrice 变量的范围是相对的,该变量的均值为 181300.0,标准差为 79886.7。因此,RMSE 为 29853.2 是一个不错的分数。
你不必等待数据源创建完成就可以启动模型训练。Amazon ML 将简单地等待父操作完成后再启动依赖的操作。这使得操作链式执行成为可能。
下一步将是进行批量预测或创建实时端点。这些将遵循与模型创建和评估完全相同的步骤,这里没有展示。
到目前为止,我们有一个训练和评估好的模型。我们选择了一组特定的参数,并通过默认的食谱对数据进行了一定的预处理。我们现在想知道是否可以通过尝试新的算法参数和进行一些创造性的特征工程来改进该模型和特征集。然后我们将训练新的模型并在验证子集上评估它们。正如我们之前看到的,这种方法的问题在于我们的评估分数可能高度依赖于评估子集。通过打乱数据来生成新的训练和验证集可能会导致不同的模型性能,并使我们选择错误的模型。尽管我们已经打乱了数据以避免顺序模式,但我们无法确保我们的分割是真正中立的,并且两个子集显示出相似的数据分布。其中一个子集可能呈现异常,如异常值或缺失数据,而另一个子集没有。为了解决这个问题,我们转向交叉验证。
什么是交叉验证?
为了降低对每个分割中数据分布的依赖,想法是并行运行许多试验,每个试验都有不同的数据分割,并平均结果。这被称为交叉验证。
简单来说,就是将模型性能在 K 次试验中平均,其中每次试验都是基于原始数据集的不同分割。有许多分割数据集的策略。最常见的一种称为 k 折交叉验证,它包括将数据集分割成 K 个块,并在每个试验中使用 K-1 个块来聚合训练模型,剩余的块来评估它。另一种策略,称为 留一法(LOO),是将这个想法推向极端,其中 K 是样本数量。你将在所有样本中除了一个之外训练你的模型,并在剩余的样本上估计误差。LOO 显然更耗费资源。
我们将实施的策略称为 蒙特卡洛交叉验证,其中在每次试验中,初始数据集被随机分割成训练集和验证集。这种方法相对于 k 折交叉验证的优势在于,训练/验证分割的比例不依赖于迭代次数(K)。它的缺点是,一些样本可能永远不会被选入验证子集,而另一些样本可能被选中多次。验证子集可能重叠。
让我们来看一个 k =5 次试验的例子。我们将重复这些步骤五次来评估一个模型(例如,L2 轻度正则化):
-
打乱 Ames 住房数据集。
-
将数据集分为训练集和验证集。
-
在训练集上训练模型。
-
在验证集上评估模型。
到目前为止,我们有五个衡量模型性能的指标;我们将它们平均以获得整体模型性能的衡量标准。我们重复上述五个步骤来评估另一个模型(例如,L1 中等正则化)。一旦我们测试了所有模型,我们将选择在试验中给出最佳平均性能的模型。
这就是为什么脚本变得必要。为了测试一个模型设置,进行带有 K trials(K 折或蒙特卡洛)的交叉验证需要 2*K 数据源,K 个模型和 K 个评估。如果仅通过网络界面完成,这肯定会非常耗时。这就是为什么整个过程的脚本化变得极其有用且效率更高。
实际上创建不同子集文件进行交叉验证的方法有很多。最简单的方法可能是使用带有随机排序的电子表格编辑器,并进行一些剪切和粘贴操作。R 和 Python 库,如流行的 scikit-learn 库或 Caret 包,提供了丰富的开箱即用的方法。然而,由于本章是关于 AWS 命令行界面,我们将使用 shell 命令来生成文件。我们还将编写 shell 脚本来生成 AWS CLI 命令的序列,以避免手动编辑不同数据文件和模型的相同命令。
实施蒙特卡洛交叉验证
我们现在将使用 shell 命令和 AWS CLI 实施一个包含五个试验的蒙特卡洛交叉验证策略。我们将使用这种评估方法来比较两个模型,一个在 Ames Housing 数据集上具有 L2 轻度正则化,另一个具有 L1 重度正则化。交叉验证将使我们能够以一定程度的信心得出哪个模型表现更好的结论。
生成打乱的数据集
我们将使用数据源创建的 DataRearrangement 字段来将数据分成训练集和验证集。因此,最初我们只需要创建五个打乱顺序的数据文件。
以下 shell 脚本将创建五个 Ames Housing 数据集的打乱版本,并将文件上传到 S3。您可以将该代码保存为具有 .sh 扩展名的文件(datasets_creation.sh),或者通过 sh ./datasets_creation.sh 运行它:
#!/bin/bash
for k in 1 2 3 4 5
do
filename="data/ames_housing_shuffled_$k.csv"
gshuf data/ames_housing_nohead.csv -o data/ames_housing_nohead.csv
cat data/ames_housing_header.csv data/ames_housing_nohead.csv > tmp.csv;
mv tmp.csv $filename
aws s3 cp ./$filename s3://aml.packt/data/ch8/
done
注意,在本章中,代码是围绕以下文件夹结构组织的。所有命令行都是从根目录运行的,例如,运行一个 Python 脚本:python py/the_script.py,列出数据文件 ls data/,以及运行 shell 脚本:sh ./shell/the_script.sh。
.
├── data
├── images
├── py
└── shell 所有 shell 脚本和命令都是基于 bash shell 的,可能需要适应其他 shell,如 zsh。
我们的数据集已经创建并上传到 S3。现在的总体策略是为 Amazon ML CLI 命令所需的每个参数 JSON 文件创建模板:创建数据源、模型和评估。我们将为以下内容创建模板文件:
-
训练数据源
-
评估数据源
-
L2 模型
-
L1 模型
-
L2 评估
-
L1 评估
在所有这些模板文件中,我们将使用 {k} 来索引文件名,并使用 sed 命令行工具将 {k} 替换为适当的索引(1 到 5)。一旦我们有了模板文件,我们就可以使用一个简单的 shell 脚本来生成数据源、模型和评估的实际 JSON 参数文件。最终我们将得到以下内容:
-
10 个数据源配置文件(五个用于训练,五个用于评估)
-
10 个模型配置文件(五个用于 L2,五个用于 L1)
-
10 个评估配置文件(每个模型一个)
最后,我们将获得 L2 模型的五个 RMSE 结果和 L1 模型的五个 RMSE 结果,它们的平均值将告诉我们哪个模型是最好的,应该选择哪种正则化类型来在 Ames 住房数据集上预测销售价格。
让我们从编写配置文件开始。
生成数据源模板
训练文件模板如下:
{
"DataSourceId": "CH8_AH_training_00{k}",
"DataSourceName": "[DS AH] training 00{k}",
"DataSpec": {
"DataLocationS3": "s3://aml.packt/data/ch8/shuffled_{k}.csv",
"DataSchemaLocationS3":"s3://aml.packt/data/ch8
/ames_housing.csv.schema",
"DataRearrangement": "{"splitting":
{"percentBegin":0,"percentEnd":70}}"
},
"ComputeStatistics": true
}
验证数据源模板如下:
{
"DataSourceId": "CH8_AH_evaluate_00{k}",
"DataSourceName": "[DS AH] evaluate 00{k}",
"DataSpec": {
"DataLocationS3": "s3://aml.packt/data/ch8/shuffled_{k}.csv",
"DataSchemaLocationS3":"s3://aml.packt/data/ch8
/ames_housing.csv.schema",
"DataRearrangement": "{"splitting":
{"percentBegin":70,"percentEnd":100}}"
},
"ComputeStatistics": true
}
训练和验证模板之间的唯一区别是DataRearrangement字段中的名称/ID 和分割比率。我们将这些文件分别保存到dsrc_training_template.json和dsrc_validate_template.json。
生成模型模板
对于具有 L2 正则化的模型,模型模板如下:
{
"MLModelId": "CH8_AH_L2_00{k}",
"MLModelName": "[MDL AH L2] 00{k}",
"MLModelType": "REGRESSION",
"Parameters": {
"sgd.shuffleType": "auto",
"sgd.l1RegularizationAmount": "0.0",
"sgd.l2RegularizationAmount": "1.0E-06",
"sgd.maxPasses": "100"
},
"TrainingDataSourceId": "CH8_AH_training_00{k}",
"RecipeUri": "s3://aml.packt/data/ch8/recipe_ames_housing_001.json"
}
对于具有 L1 正则化的模型,模型模板如下:
{
"MLModelId": "CH8_AH_L1_00{k}",
"MLModelName": "[MDL AH L1] 00{k}",
"MLModelType": "REGRESSION",
"Parameters": {
"sgd.shuffleType": "auto",
"sgd.l1RegularizationAmount": "1.0E-04",
"sgd.l2RegularizationAmount": "0.0",
"sgd.maxPasses": "100"
},
"TrainingDataSourceId": "CH8_AH_training_00{k}",
"RecipeUri": "s3://aml.packt/data/ch8/recipe_ames_housing_001.json"
}
注意,相同的配方用于两个模型。如果我们想比较数据预处理策略的性能,我们可以修改两个模型中使用的配方。模板文件非常相似。唯一的不同在于模型名称和 ID 以及l1RegularizationAmount和l2RegularizationAmount的值。我们将这些文件分别保存到mdl_l2_template.json和mdl_l1_template.json****。**
生成评估模板
对于具有 L2 正则化的模型,评估模板如下:
{
"EvaluationId": "CH8_AH_L2_00{k}",
"EvaluationName": "[EVL AH L2] 00{k}",
"MLModelId": "CH8_AH_L2_00{k}",
"EvaluationDataSourceId": "CH8_AH_evaluate_00{k}"
}
对于具有 L1 正则化的模型,评估模板如下:
{
"EvaluationId": "CH8_AH_L1_00{k}",
"EvaluationName": "[EVL AH L1] 00{k}",
"MLModelId": "CH8_AH_L1_00{k}",
"EvaluationDataSourceId": "CH8_AH_evaluate_00{k}"
}
将这些文件保存到eval_l2_template.json和eval_l1_template.json分别。
我们现在将使用这些模板文件来生成数据源、模型和评估的不同配置文件。为了保持独立,所有生成的文件都在子文件夹cfg/中。
下面的 shell 脚本生成了我们将要提供给 AWS CLI 机器学习命令的实际配置文件。它使用sed命令查找并替换{k}实例为 1 到 5 的数字。输出被写入配置文件。由于将生成许多配置文件,这些文件被写入/data下的/cfg子文件夹中。现在的文件夹结构如下:
.
├── data
│ └── cfg
│ └── templates
├── images
├── py
└── shell
#!/bin/bash
for k in 1 2 3 4 5
do
# training datasource
sed 's/{k}/1/g' data/templates/dsrc_training_template.json > data/cfg
/dsrc_training_00$k.json
# evaluation datasource
sed 's/{k}/1/g' data/templates/dsrc_validate_template.json > data/cfg
/dsrc_validate_00$k.json
# L2 model
sed 's/{k}/1/g' data/templates/mdl_l2_template.json > data/cfg
/mdl_l2_00$k.json
# L2 evaluation
sed 's/{k}/1/g' data/templates/eval_l2_template.json > data/cfg
/eval_l2_00$k.json
# L1 model
sed 's/{k}/1/g' data/templates/mdl_l1_template.json > data/cfg
/mdl_l1_00$k.json
# L1 evaluation
sed 's/{k}/1/g' data/templates/eval_l1_template.json > data/cfg
/eval_l1_00$k.json
done
最后一个剩余步骤是执行 AWS 命令,这些命令将在 Amazon ML 中创建对象。我们同样使用 shell 循环来执行 AWS CLI 命令。
创建用于训练和评估的数据源:
#!/bin/bash
for k in 1 2 3 4 5
do
aws machinelearning create-data-source-from-s3 --cli-input-json
file://data/cfg/dsrc_kfold_training_00$k.json
aws machinelearning create-data-source-from-s3 --cli-input-json
file://data/cfg/dsrc_kfold_validate_00$k.json
done
使用 L2 和 L1 正则化训练模型:
#!/bin/bash
for k in 1 2 3 4 5
aws machinelearning create-ml-model --cli-input-json file://data
/cfg/mdl_l2_00$k.json
aws machinelearning create-ml-model --cli-input-json file://data
/cfg/mdl_l1_00$k.json
done
评估训练模型:
#!/bin/bash
for k in 1 2 3 4 5
aws machinelearning create-evaluation --cli-input-json file://cfg
/eval_l2_00$k.json
aws machinelearning create-evaluation --cli-input-json file://cfg
/eval_l1_00$k.json
done
您可以使用get-data-source、get-ml-model和get-evaluationCLI 命令或在美国机器学习仪表板上检查不同作业的状态。一旦所有评估完成,您首先创建几个文件来接收 RMSE 分数,然后运行以下最终 shell 循环:
#!/bin/bash
for k in 1 2 3 4 5
aws machinelearning get-evaluation --evaluation-id CH8_AH_L2_00$k |
grep RegressionRMSE >> l2_model_rmse.log
aws machinelearning get-evaluation --evaluation-id CH8_AH_L1_00$k |
grep RegressionRMSE >> l1_model_rmse.log
done
给定评估的 ID,get-evaluation 命令返回一个 JSON 格式的字符串,该字符串被传递给 grep 命令并添加到 l1/l2_model_rmse.log 文件中。
结果
我们得到了两个模型以下的结果:
l1 | 26570.0 | 28880.4 | 27287.8 | 29815.7 | 27822.0]
L2 | 36670.9 | 25804.3 | 28127.2 | 30539.0 | 24740.4
平均而言,L1 给出 RMSE 为 28075.2(标准差:1151),而 L2 给出 RMSE 为 29176.4(标准差:4246.7)。L1 模型不仅性能更好,而且在处理数据变化时也更为稳健,因为其标准差较低。
结论
仅通过 shell 实现交叉验证可能过于耗时。需要创建和协调许多文件。使用 scikit-learn(Python 的库)或 Caret(R 的库)等库实现交叉验证有更简单的方法,其中整个模型训练和评估循环只需几行代码即可覆盖多个训练和验证集。然而,我们展示了使用 Amazon ML 实现交叉验证是可能的。交叉验证是数据科学工作流程的关键组成部分。如果不能使用 Amazon ML 进行交叉验证,这将是在服务中的一个重大缺陷。最终,AWS CLI 对于机器学习是一个非常强大且有用的工具,可以执行一系列试验并比较不同模型、数据集、食谱和特征的结果。
Boto3,Python SDK
另一个在网页界面之外与 Amazon ML 服务交互的工具是 SDK。简单来说,SDK 是 API 的包装器,它使得与服务交互变得更加简单和高效,因为许多交互的细节都得到了处理。AWS 为最广泛使用的语言提供了 SDK,如 PHP、Java、Ruby、.Net,当然还有 Python。在本章中,我们将专注于通过 Python SDK 与 Amazon ML 服务交互。Python SDK 需要 Boto3 模块。
Boto3 模块的安装是通过 pip 完成的。如果您需要更多信息或故障排除,请参阅可用的快速入门指南boto3.readthedocs.io/en/latest/guide/quickstart.html。
pip install boto3
Boto3 可用于大多数 AWS 服务。完整的列表可以在boto3.readthedocs.io/en/latest/reference/services/index.html找到。我们将专注于 Boto3 的 S3 和 Amazon ML。
设置 SDK 访问权限可以通过我们在本章开头提到的 aws configure 命令来完成,或者直接将您的访问密钥添加到 ~/.aws/credentials 文件中。
总体来说,Boto3 逻辑与 AWS CLI 逻辑非常相似,遵循类似的步骤:声明要使用的服务并运行带有适当参数集的命令。让我们从一个简单的例子开始,围绕 S3 使用以下 Python 脚本,该脚本将列出您账户中的所有存储桶:
import boto3
# Initialize the S3 client
s3 = boto3.resource('s3')
# List all the buckets in out account
for bucket in s3.buckets.all():
print(bucket.name)
将本地文件上传到存储桶可以通过以下方式实现:
# load the file
data = open('data/ames_housing_nohead.csv', 'rb')
s3.Object('aml.packt', 'data/ames_housing_nohead.csv').put(Body=data)
put 命令返回一个 JSON 字符串,其中包含一个 HTTPStatusCode 字段,其值为 200,表示上传成功。
使用亚马逊机器学习的 Python SDK
可用方法的列表可以在 boto3.readthedocs.io/en/latest/reference/services/machinelearning.html 找到,并且紧密遵循围绕主要对象(数据源、模型、评估、批量预测和实时端点)组织的 AWS CLI 机器学习服务的可用命令列表。对于每个对象,方法有:创建、更新、描述、获取和删除。
我们现在将实现标准的亚马逊机器学习工作流程。但首先,让我们为我们将要创建的对象定义一个命名方法。工作流程的一个重要部分是围绕对象名称和 ID 的命名约定。当使用 CLI 时,我们即时创建了名称和 ID。这次我们将使用以下函数来命名我们的对象:
def name_id_generation(prefix, mode, trial):
Id = '_'.join([prefix, mode, "%02d"%int(trial)])
name = "[%s] %s %02d"% (prefix, mode, int(trial) )
return {'Name':name, 'Id':Id}
此函数接受两个字符串和一个整数作为参数,一个用于对象类型(数据源、模型等)的前缀,一个用于指定训练或验证数据源的模式,以及一个用于轻松增加实验的试验值。该函数返回一个字典。
让我们现在定义一些将在脚本中稍后使用的变量:
# The iteration number of our experiements
trial = 5
# The S3 location of schemas and files
data_s3 = 's3://aml.packt/data/ch8/ames_housing_shuffled.csv'
schema_s3 = 's3://aml.packt/data/ch8/ames_housing.csv.schema'
recipe_s3 = 's3://aml.packt/data/ch8/recipe_ames_housing_001.json'
# And the parameters for the SGD algrithm
sgd_params = {
"sgd.shuffleType": "auto",
"sgd.l1RegularizationAmount": "1.0E-04",
"sgd.maxPasses": "100"
}
我们需要导入以下库:
import boto3
import time
import json
声明我们想要与机器学习服务交互:
client = boto3.client('machinelearning')
我们现在已准备好使用以下方式创建我们的训练和验证数据源:
# Create datasource for training
resource = name_id_generation('DS', 'training', trial)
print("Creating datasources for training (%s)"% resource['Name'] )
response = client.create_data_source_from_s3(
DataSourceId = resource['Id'] ,
DataSourceName = resource['Name'],
DataSpec = {
'DataLocationS3' : data_s3,
'DataSchemaLocationS3' : schema_s3,
'DataRearrangement':'{"splitting":{"percentBegin":0,"percentEnd":70}}'
},
ComputeStatistics = True
)
# Create datasource for validation
resource = name_id_generation('DS', 'validation', trial)
print("Creating datasources for validation (%s)"% resource['Name'] )
response = client.create_data_source_from_s3(
DataSourceId = resource['Id'] ,
DataSourceName = resource['Name'],
DataSpec = {
'DataLocationS3': data_s3,
'DataSchemaLocationS3': schema_s3,
'DataRearrangement':'{"splitting":{"percentBegin":0,"percentEnd":70}}'
},
ComputeStatistics = True
)
在这两种情况下,我们调用之前定义的命名函数来生成数据源的名称和 ID,并在调用 create_data_source_from_s3 Boto3 方法时使用该字典。
我们使用以下方式启动模型的训练:
# Train model with existing recipe
resource = name_id_generation('MDL', '', trial)
print("Training model (%s) with params:n%s"%
(resource['Name'], json.dumps(sgd_params, indent=4)) )
response = client.create_ml_model(
MLModelId = resource['Id'],
MLModelName = resource['Name'],
MLModelType = 'REGRESSION',
Parameters = sgd_params,
TrainingDataSourceId= name_id_generation('DS', 'training', trial)['Id'],
RecipeUri = recipe_s3
)
然后创建评估:
resource = name_id_generation('EVAL', '', trial)
print("Launching evaluation (%s) "% resource['Name'] )
response = client.create_evaluation(
EvaluationId = resource['Id'],
EvaluationName = resource['Name'],
MLModelId = name_id_generation('MDL', '', trial)['Id'],
EvaluationDataSourceId = name_id_generation('DS', 'validation', trial)
['Id']
)
现在,您可以访问亚马逊机器学习仪表板并验证您有两个数据源、一个模型和一个评估处于“进行中”或“待处理”状态:
等待操作完成
所有这些对象创建操作默认情况下都是由亚马逊机器学习链式执行的。这意味着亚马逊机器学习将在启动模型训练之前等待数据源准备就绪,并在尝试运行评估之前等待模型训练完成。然而,在这个阶段,我们仍然需要等待评估完成才能访问其结果。同样,我们需要等待不同对象被下一个操作使用后才能删除它们。
这就是等待方法变得有用的地方。等待器是简单地等待 AWS 操作完成、状态为 完成 的方法。所有 AWS 操作和服务都有等待器。亚马逊机器学习为模型、数据源、评估和批量预测提供了四个等待器:
-
MachineLearning.Waiter.BatchPredictionAvailable -
MachineLearning.Waiter.DataSourceAvailable -
MachineLearning.Waiter.EvaluationAvailable -
MachineLearning.Waiter.MLModelAvailable
机器学习服务员遵循以下语法——首先,声明服务员需要监控的对象,例如一个评估:
waiter = client.get_waiter('evaluation_available')
然后在您刚刚声明的服务员上调用wait方法:
waiter.wait(FilterVariable='Name', EQ='the name of the evaluation')
一旦调用等待方法,Python 脚本就会挂起,直到操作达到Completed状态。等待函数需要以下内容:
-
一个过滤值:
FilterVariable = CreatedAt,LastUpdatedAt,Status,Name,IAMUser,MLModelId,DataSourceId,DataURI -
操作符:EQ, GT, LT, GE, LE, NE
-
其他依赖于对象性质的参数
使用这种参数结构,您可以让脚本等待特定对象的完成,或者根据数据源、模型或甚至用户名等待所有对象。如果我们要对基于相同验证数据源的多个模型进行评估,我们只需为每个模型调用一个服务员即可:
waiter.wait(FilterVariable='DataSourceId', EQ='the DatasourceId')
总结基于 Python 的工作流程
现在我们知道了如何等待所有评估完成,我们仍然需要获取评估结果并删除我们创建的模型和数据源。正如get-evaluation AWS CLI 命令的情况一样,Boto3 的get_evaluation方法返回一个包含模型性能度量、回归情况下的 RMSE 的 JSON 字符串。以下脚本总结了我们的试验:
t0 = time.time()
# declare the waiter and call the wait method on the evaluation
waiter = client.get_waiter('evaluation_available')
print("Waiting on evaluation to finish ")
waiter.wait(FilterVariable='Name', EQ=name_id_generation('EVAL', '', trial)['Name'])
t = time.time() - t0
print("Evaluation has finished after %sm %ss"% (int(t/60), t%60) )
# get the evaluation results
response = client.get_evaluation(
EvaluationId=name_id_generation('EVAL', '', trial)['Id']
)
RMSE =float(response['PerformanceMetrics']['Properties']['RegressionRMSE'])
print("[trial %0.2f] RMSE %0.2f"% (trial, RMSE) )
# and delete the resources
print("Deleting datasources and model")
response = client.delete_data_source(
DataSourceId=name_id_generation('DS', 'training', trial)['Id']
)
response = client.delete_data_source(
DataSourceId=name_id_generation('DS', 'validation', trial)['Id']
)
response = client.delete_ml_model(
MLModelId=name_id_generation('MDL', '', trial)['Id']
)
将所有代码块放在一起返回以下输出:
Creating datasources for training ([DS] training 04)
Creating datasources for validation ([DS] validation 04)
Training model ([MDL] 04) with params:
{
"sgd.shuffleType": "auto",
"sgd.l1RegularizationAmount": "1.0E-04",
"sgd.maxPasses": "100"
}
Launching evaluation ([EVAL] 04)
Waiting on evaluation to finish
Evaluation has finished after 11m 43.78s
[trial 4] RMSE 22437.33
Deleting datasources and model
使用 Boto3 实现递归特征选择
在许多实际案例中,原始数据集可能包含大量变量。随着维度的增加,对更大样本集的需求也增加。这被称为维度诅咒,是一个经典的预测分析问题。简单来说,如果没有足够的多样性来推断某些变量的代表性分布,算法将无法从这些变量中提取相关信息。这些低信号变量会拖累算法的性能,而不会通过向模型添加无用的复杂性来增加任何数据燃料。一种策略是减少模型训练的变量数量。然而,这暗示了需要确定哪些特征可以被删除而不会导致信息损失。
有许多技术可以减少特征数量:
-
包装器技术:这些技术使用规则和标准来选择最佳和最具影响力的特征。
-
过滤技术:这些技术使用统计测试来衡量每个特征的重要性。与目标的相关性测量可能是一种简单的方法来删除非显著变量。
-
嵌入式方法:对于某些模型,例如随机森林,可以在特征子集上迭代训练模型,因此可以评估每次迭代中省略的特征的影响,从而推断每个特征的重要性。
适用于 Amazon 机器学习环境的方法是递归评估每个特征的重要性,通过测量在丢弃某个特征时性能显著下降来过滤掉最不重要的特征。这是递归特征消除的暴力版本。
它遵循以下步骤:
-
使用所有N个特征构建一个初始模型。
-
然后通过以下方式识别并移除最不重要的特征:
-
构建 N 个子集,每个子集中删除不同的特征
-
为每个子集构建模型并评估其性能
-
识别对模型性能影响最小的特征
-
-
现在您有N-1个特征。重复步骤 1 到 3 以识别并移除下一个最不重要的特征。
-
当您注意到与初始 N 特征模型相比模型性能显著下降时停止。
该算法的逆版本从N个模型开始,每个模型仅使用一个特征构建,并在每次迭代中添加一个新特征。选择新特征为能带来最佳性能提升的新特征。当添加新特征不再导致模型性能显著提升时停止。
在本章的其余部分,我们将展示如何在 Python 中实现这种特征选择策略。
管理模式和配方
直接删除或添加数据集的特征会直接影响模式和配方。模式用于创建数据源时使用,而配方需要用于训练模型,因为它指定了在模型训练之前将执行哪些数据转换。
通过简单地将变量的名称添加到excludedAttributeNames字段中,可以修改模式以从数据集中删除特征。我们可以从初始模式开始,每次从初始特征列表中删除一个特征时,就将其添加到excludedAttributeNames列表中。步骤如下:
-
将 JSON 格式的模式打开到模式字典中
-
将特征名称追加到模式
['excludedAttributeNames'] -
将模式保存到正确缩进的 JSON 文件中
-
将文件上传到 S3
在创建数据源时,我们将指向我们刚刚更新的模式的 S3 位置。
亚姆住房数据集默认由 Amazon ML 生成的初始配方对某些数值特征应用不同的分位数分箱转换。配方的分组部分如下:
"groups": {
"NUMERIC_VARS_QB_50": "group('LotFrontage','KitchenAbvGr','BsmtFinSF1','GarageCars','1stFlrSF','ScreenPorch','LowQualFinSF','LotArea','OverallCond','2ndFlrSF','GarageArea','EnclosedPorch','HalfBath')",
"NUMERIC_VARS_QB_100": "group('BsmtFinSF2','WoodDeckSF','BsmtHalfBath','MiscVal','GrLivArea','Fireplaces')",
"NUMERIC_VARS_QB_500": "group('OverallQual')",
"NUMERIC_VARS_QB_20": "group('TotalBsmtSF')",
"NUMERIC_VARS_QB_200": "group('MSSubClass','OpenPorchSF','YearRemod/Add','BsmtFullBath','MasVnrArea')",
"NUMERIC_VARS_QB_10": "group('PoolArea','BedroomAbvGr','TotRmsAbvGrd','YearBuilt','MoSold','YrSold','GarageYrBlt','FullBath','BsmtUnfSF','3SsnPorch')"
}
在该结构中添加或删除变量名称需要比在列表中添加元素更复杂的脚本。由于这样的脚本不会给本书带来很多教育价值,我们决定使用一个默认的简单配方,不对数据集进行任何转换。只要我们有包含所有特征的基线 RMSE,递归特征消除策略仍然有效。唯一的区别是,由于没有对数值数据应用任何分位数分箱,整体 RMSE 分数可能会更高。我们使用的配方如下:
{
"groups" : {},
"assignments" : { },
"outputs" : [ "ALL_INPUTS" ]
}
这可以在我们的示例中找到,位于 S3 位置s3://aml.packt/data/ch8/recipe_ames_housing_default.json。使用该配方评估我们的基线模型给出基线 RMSE 为61507.35。我们将使用该基线 RMSE 来查看移除特征是否提高了(降低)或降低了模型性能。
以下脚本分为三个部分:
-
初始化和函数
-
启动 Amazon ML 工作流程
-
获取评估结果和删除资源
脚本可以在我们的 GitHub 仓库中找到,其完整内容如下。我们使用相同的策略来创建一个生成名称和 ID 的函数。我们以下面的脚本初始化变量并声明函数:
import pandas as pd
import boto3
import json
# Local schema with all the features
original_schema_filename = 'data/ames_housing.csv.schema'
# Initialize boto3 objects
s3 = boto3.resource('s3')
client = boto3.client('machinelearning')
# load dataset and feature_ names
df = pd.read_csv('data/ames_housing.csv')
original_features = df.columns.difference(['SalePrice', 'Order'])
# load original schema with all the features
schema = json.load( open(original_schema_filename) )
# SGD parameters: L1 heavy regularization
sgd_parameters = {
"sgd.shuffleType": "auto",
"sgd.l1RegularizationAmount": "1.0E-04",
"sgd.maxPasses": "100"
}
# memorize all object Ids for future deletion
baseline_rmse = 61507.35
datasource_ids = []
model_ids = []
evaluation_ids = []
features_rmse = {}
def generate_trial(n):
n = "X" + str(n).zfill(3)
return {
'schema_filename': "rfs_ames_housing_%s.schema"% n,
'recipe_s3': 's3://aml.packt/RFS/recipe_ames_housing_default.json',
'data_s3': 's3://aml.packt/RFS/ames_housing_shuffled.csv',
'datasource_training_id': "rfs_training_%s"% n,
'datasource_training_name': "[DS RFS] training %s"% n,
'datasource_validation_id': "rfs_validation_%s"% n,
'datasource_validation_name': "[DS RFS] validation %s"% n,
'model_id': "rfs_%s"% n,
'model_name': "[MDL RFS] %s"% n,
'evaluation_id': "rfs_%s"% n,
'evaluation_name': "[EVAL RFS] %s"% n,
}
我们现在启动数据源、模型和评估的创建。脚本只查看前 10 个特征,而不是整个 79 个特征的集合,以节省资源。
你会注意到我们在 Amazon ML 对象的编号中添加了前缀"X"。我们发现有时,如果 ID 和名称已被用于现在已删除的先前对象,Amazon ML 可能无法创建对象。这个问题可能在一段时间后消失。无论如何,确保所有新的数据源、模型、评估和批量预测都有从未使用过的名称和 ID,可以消除任何命名问题。
第二部分启动数据源、模型和评估的创建:
for k in range(10):
print("="* 10 + " feature: %s"% original_features[k])
trial = generate_trial(k)
# remove feature[k] from schema and upload to S3
schema['excludedAttributeNames'] = [original_features[k]]
with open("data/%s"%trial['schema_filename'], 'w') as fp:
json.dump(schema, fp, indent=4)
s3.Object('aml.packt', "RFS/%s"% trial['schema_filename']).put(Body=open("data/%s"%trial['schema_filename'], 'rb'))
# create datasource
print("Datasource %s"% trial['datasource_training_name'])
datasource_ids.append( trial['datasource_training_id'] )
response = client.create_data_source_from_s3(
DataSourceId = trial['datasource_training_id'] ,
DataSourceName= trial['datasource_training_name'] ,
DataSpec={
'DataLocationS3': trial['data_s3'],
'DataRearrangement': '{"splitting":
{"percentBegin":0,"percentEnd":70}}',
'DataSchemaLocationS3': "s3://aml.packt/RFS/%s"%
trial['schema_filename']
},
ComputeStatistics=True
)
# Create datasource for validation
print("Datasource %s"% trial['datasource_validation_name'])
datasource_ids.append( trial['datasource_validation_id'] )
response = client.create_data_source_from_s3(
DataSourceId = trial['datasource_validation_id'] ,
DataSourceName= trial['datasource_validation_name'] ,
DataSpec={
'DataLocationS3': trial['data_s3'],
'DataRearrangement': '{"splitting":{"percentBegin":70,"percentEnd":100}}',
'DataSchemaLocationS3': "s3://aml.packt/RFS/%s"% trial['schema_filename']
},
ComputeStatistics=True
)
# Train model with existing recipe
print("Model %s"% trial['model_name'])
model_ids.append(trial['model_id'] )
response = client.create_ml_model(
MLModelId = trial['model_id'],
MLModelName = trial['model_name'],
MLModelType = 'REGRESSION',
Parameters = sgd_parameters,
TrainingDataSourceId = trial['datasource_training_id'] ,
RecipeUri = trial['recipe_s3']
)
print("Evaluation %s"% trial['evaluation_name'])
evaluation_ids.append(trial['evaluation_id'])
response = client.create_evaluation(
EvaluationId = trial['evaluation_id'],
EvaluationName = trial['evaluation_name'],
MLModelId = trial['model_id'],
EvaluationDataSourceId= trial['datasource_validation_id']
)
最后,第三部分等待评估完成,记录每个移除特征的 RMSE,并删除数据源和模型(我们保留了评估结果,以避免需要重新运行整个脚本来获取结果):
for k in range(10):
trial = generate_trial(k)
waiter = client.get_waiter('evaluation_available')
print("Waiting on evaluation %s to finish "% trial['evaluation_name'])
waiter.wait(FilterVariable='Name', EQ=trial['evaluation_name'])
print("Evaluation has finished ")
response = client.get_evaluation( EvaluationId=trial['evaluation_id'] )
features_rmse[original_features[k]] = float(response['PerformanceMetrics']['Properties']['RegressionRMSE'])
print("[%s] RMSE %0.2f"% (original_features[k], float(response['PerformanceMetrics']['Properties']['RegressionRMSE'])) )
# Now delete the resources
print("Deleting datasources and model")
response = client.delete_data_source(
DataSourceId = trial['datasource_training_id']
)
response = client.delete_data_source(
DataSourceId = trial['datasource_validation_id']
)
response = client.delete_ml_model(
MLModelId = trial['model_id']
)
print("removing the feature increased the RMSE by ")
for k,v in features_rmse.items():
print("%s t%0.2f %% "% (k, (baseline_rmse - v)/ baseline_rmse *100.0 ) )
最后,我们得到前 10 个特征的以下 RMSE 变化:
-
1stFlrSF 0.07% -
2ndFlrSF -18.28% -
BedroomAbvGr -0.02 % -
BsmtExposure -0.56 % -
BsmtFinSF2 -0.50 % -
BsmtCond 0.00 % -
BsmtFinSF1 -2.56 % -
Alley 0.00 % -
3SsnPorch -4.60 % -
BldgType -0.00 %
例如,移除2ndFlrSF特征将 RMSE 提高了近 20%。这个特征对于预测销售价格无疑非常重要。同样,特征3SsnPorch和BsmtFinSF1对于模型也很重要,因为移除它们会增加 RMSE。另一方面,移除1stFlrSF、Alley、BedroomAbvGr或BldgType仅使 RMSE 变化小于 0.10%。我们可能可以移除这些特征,而对模型性能的影响不大。
摘要
在本章中,我们已经从 Amazon ML 的 Web 界面转向,学习了如何通过 AWS CLI 和 Python SDK 与该服务交互。这两种交互类型的命令和方法非常相似。函数和命令执行从创建到删除 Amazon ML 对象的标准操作集:数据源、模型、评估和批量预测。Amazon ML 将依赖对象创建的序列链式化,这使得您可以在创建下游对象(模型或评估)之前,无需等待上游对象(数据源或模型)完成,一次性创建所有对象。等待方法使得在检索结果和进行必要的对象删除之前,能够等待所有评估完成。
我们展示了如何通过脚本化 Amazon ML 来实现机器学习方法,如交叉验证和递归特征选择,这两种方法在预测分析中都非常有用。尽管我们最终需要创建许多数据源和对象来进行交叉验证和特征选择,但总体成本仍然保持在可控范围内。
在下一章中,我们将开始使用其他 AWS 服务来扩展 Amazon ML 的功能。我们将探讨 S3 之外的其它数据源,例如 Redshift 和 RDS,以及如何使用 Amazon Lambda 进行机器学习。
第八章:从 Redshift 创建数据源
在本章中,我们将利用 SQL 查询的力量来处理非线性数据集。在创建数据源之前,在 Redshift 或 RDS 中创建数据源为我们提供了上游基于 SQL 的特征工程潜力。我们在第四章,加载数据集和准备数据中实施了一种类似的方法,通过利用新的 AWS Athena 服务在创建数据源之前对数据进行初步转换。
这使我们能够通过创建新的特征,如“甲板”号,用其对数替换“票价”,或替换“年龄”变量的缺失值来扩展“泰坦尼克”数据集。SQL 转换很简单,但使我们能够非常灵活地扩展原始数据集。AWS Athena服务是基于 S3 的。它允许我们在 S3 上托管的数据集中运行 SQL 查询,并将结果存储在 S3 桶中。我们仍然从 S3 创建亚马逊 ML 数据源,但只是添加了一个额外的数据预处理层来整理数据集。
AWS 还提供了另外两种 SQL 服务,可以从这些服务中创建数据源:RDS 和 Redshift。RDS 和 Redshift 的数据源创建非常相似,我们将重点介绍通过 Python SDK Boto3在 Redshift 中创建数据源。对我们来说,关键点是基于 RDS/Redshift 的数据源是通过 SQL 查询直接创建的,这为我们提供了进一步的特征工程的机会。Redshift 还与 AWS Kinesis 无缝集成,我们将在第九章,构建流数据分析管道中探讨该服务。
亚马逊机器学习建立在本质上线性的模型之上,利用良好的结果量级分箱数据转换作为处理数据集中非线性的一种方法。多项式回归是另一种重要的机器学习方法,用于处理非线性数据集。我们将利用我们新的 SQL 功能,通过亚马逊 ML 实现多项式回归。
在本章中,你将学习以下内容:
-
在 RDS 和 Redshift 之间进行选择
-
如何使用 PostgreSQL 创建 Redshift 数据库
-
如何将您的 S3 数据加载到 Redshift
-
如何从 Redshift 创建数据源
-
什么是多项式回归
-
如何使用亚马逊 ML 进行多项式回归
在 RDS 和 Redshift 之间进行选择
AWS 提供了不少于六种不同的云数据库和 SQL/NoSQL 服务:RDS、Aurora、DynamoDB、Redshift、Athena 和 AWS 数据库迁移服务!在所有这些服务中,只有两个与亚马逊机器学习兼容:RDS 和 Redshift。您可以在任一服务中存储数据,并从这些来源创建数据源。这两个服务的源数据创建方法具有类似的参数,但在底层 AWS 服务通信方面差异很大。
RDS 和 Redshift 是两种非常不同的服务。Redshift 是一个数据仓库,用于在大数据集上回答一些复杂且运行时间较长的查询,而 RDS 是为频繁、小规模和快速查询而设计的。Redshift 更适合进行大规模并行处理,以最小延迟执行数百万行数据的操作,而 RDS 提供了一个运行特定数据库的服务器实例。RDS 提供多种不同的数据库类型——MySQL、PostgreSQL、MariaDB、Oracle、SQL Server 和 Amazon Aurora,而 Redshift 是基于ParAccel技术的亚马逊自己的分析数据库,运行的是 PostgreSQL 的一个分支。您可以使用标准的 ODBC 和 JDBC 连接连接到 Redshift。
在您在 Redshift 中构建数据库时,亚马逊 Redshift 和 PostgreSQL 之间存在许多非常重要的差异,您必须注意。许多函数、数据类型和 PostgreSQL 功能在亚马逊 Redshift 中不受支持。更多信息可在docs.aws.amazon.com/redshift/latest/dg/c_redshift-and-postgres-sql.html找到。
关于 RDS 和 Redshift 之间差异的更深入解释,可以在本线程中找到:www.quora.com/What-is-the-difference-between-redshift-and-RDS
在亚马逊机器学习(Amazon ML)的背景下,这两种服务之间的重要区别在于,亚马逊机器学习 Web 控制台仅允许从 S3 和 Redshift 创建数据源,但不能从 RDS 创建,如本截图所示:
然而,Python SDK 和 AWS CLI 都允许从 RDS 和 Redshift 创建数据源。
SDK:
-
create_data_source_from_rds():boto3.readthedocs.io/en/latest/reference/services/machinelearning.html#MachineLearning.Client.create_data_source_from_rds -
create_data_source_from_redshift():boto3.readthedocs.io/en/latest/reference/service/machinelearning.html#MachineLearning.Client.create_data_source_from_redshift
CLI:
-
create-data-source-from-rds:docs.aws.amazon.com/cli/latest/reference/machinelearning/create-data-source-from-rds.html -
create-data-source-from-redshift:docs.aws.amazon.com/cli/latest/reference/machinelearning/create-data-source-from-redshift.html
现在我们比较 Python SDK 连接到任一服务所需的参数:
- Redshift 参数:
{
"DatabaseInformation": {
"DatabaseName": "string",
"ClusterIdentifier": "string"
},
"SelectSqlQuery": "string",
"DatabaseCredentials": {
"Username": "string",
"Password": "string"
},
"S3StagingLocation": "string",
"DataRearrangement": "string",
"DataSchema": "string",
"DataSchemaUri": "string"
}
- RDS 参数:
{
"DatabaseInformation": {
"DatabaseName": "string"
"InstanceIdentifier": "string",
},
"SelectSqlQuery": "string",
"DatabaseCredentials": {
"Username": "string",
"Password": "string"
},
"S3StagingLocation": "string",
"DataRearrangement": "string",
"DataSchema": "string",
"DataSchemaUri": "string",
"ResourceRole": "string",
"ServiceRole": "string",
"SubnetId": "string",
"SecurityGroupIds": ["string", ...]
}
这两组参数之间的区别在于我们允许访问数据存储的方式。两组都包括 DatabaseInformation、DatabaseCredentials、SelectSqlQuery、DataSchema 和 DataRearrangement。RDS 还需要手动设置两个具有正确策略的角色:ResourceRole: DataPipelineDefaultRole 和 ServiceRole:DataPipelineDefaultResourceRole。
RDS 更适合我们的数据量,我们应该在机器学习项目中使用 RDS 而不是 Redshift。然而,我们之前发现手动创建 RDS 的角色和策略需要深入了解 AWS 内部权限的工作方式,这对于本书来说过于复杂。尽管从 RDS 和 Redshift 创建数据源时的参数和概念非常相似,但在后台,它们有很大的不同。RDS 数据源创建涉及创建 AWS 数据管道,这是另一个 AWS 服务,允许您在不同 AWS 计算和存储服务之间处理和移动数据。必须设置数据管道为整个项目增加了非平凡的复杂性层。
另一方面,Redshift 不需要构建数据管道和设置权限、角色和策略来创建数据源。最终,这种额外的简单性使 Redshift 更适合本书,因为我们希望保持对机器学习方面的关注,而不是深入研究 AWS 访问角色和策略的复杂性,尽管 RDS 对于我们低数据量来说可能更合适。
Redshift:深入介绍 Redshift 超出了本书的范围。我们推荐阅读由 Stefan Bauer, Packt 编写的 《Amazon Redshift 入门》 书籍 (www.packtpub.com/big-data-and-business-intelligence/getting-started-amazon-redshift)),AWS 文档 (aws.amazon.com/redshift/getting-started/) 以及这篇关于集群配置的博客文章 (www.periscopedata.com/amazon-redshift-guide/cluster-configuration),以获得对集群配置的良好介绍。
让我们从 Redshift 开始,使用 AWS Redshift 控制台创建一个基于 PostgreSQL 的实例,并加载我们已经在 S3 存储桶中可用的 Titanic 数据集。
创建 Redshift 实例
登录您的 AWS 账户,并转到 Redshift 仪表板,链接为 console.aws.amazon.com/redshift/。
在 Redshift 中创建数据库非常简单,并且由 AWS Redshift 向导很好地处理。首先,点击“启动集群”按钮。在第一个屏幕中,我们定义集群标识符*为 amlpackt,数据库名称为 amlpacktdb,以及主用户名,如以下截图所示:
在下一屏幕中,我们选择默认参数来配置节点,如下面的截图所示:
在下一个配置屏幕中选择默认设置,但请确保集群是公开可访问的。您不需要公共 IP:
选择机器学习/RDS VPC 安全组:
在启动集群之前的最终验证屏幕将显示相关的成本,如下所示:
点击最终的启动集群按钮后,集群准备就绪可能需要几分钟。我们将使用 Psql 连接到新创建的数据库。其他外部连接类型可通过 JDBC 和 ODBC 获得。集群信息页面显示了您需要连接到新创建数据库的端点 URL:
通过命令行连接
Psql 是一个命令行程序,充当 PostgreSQL 数据库的主要前端。它提供了一系列的 shell 命令(例如,pg_dump 用于转储数据库内容,createdb 用于创建数据库,以及其他许多命令)。它还具有许多元命令来列出元素和显示信息(请参阅 Psql 快速参考 信息框)。
Psql 快速参考:
q: 退出/退出
d __table__: 显示表定义,包括触发器
dt : 列出表
df: 列出函数
dv: 列出视图
x: 以更美观的格式显示查询结果,而不是不那么有用的 ASCII 表
connect __database__: 连接到数据库
l: 列出数据库
在 d 命令上添加一个 + 将显示更多结果:例如,比较 d titanic 与 d+ titanic
有关 Psql 的更多信息,请参阅postgresguide.com/utilities/psql.html
您现在可以使用以下命令从终端连接到您的数据库使用 Psql,使用端点 URL (amlpackt.cenllwot8v9r.us-east-1.redshift.amazonaws.com) 作为主机:
$ psql --host=amlpackt.cenllwot8v9r.us-east-1.redshift.amazonaws.com --port=5439 --username=alexperrier --password --dbname=amlpacktdb
当然,amlpacktdb 数据库是空的:
alexperrier@amlpacktdb=> dt
No relations found.
我们有多种方式可以将数据导入 Redshift 数据库。为了简化,我们将使用复制命令将 S3 上可用的 CSV 文件上传到 Redshift 表,如下所示:
alexperrier@amlpacktdb=> copy <table name> from '<s3 path to csv file>' CREDENTIALS 'aws_access_key_id=<aws access key id>;aws_secret_access_key=<aws secret access key>' CSV;
当使用复制命令时,Redshift 需要与 S3 服务进行认证。AWS 服务之间的认证可以通过两种方式实现:
-
基于用户的:通过传递用户访问密钥进行认证。这是一种在 AWS 服务之间授予访问权限的更简单方式,但它并不总是可用。
-
基于角色的:认证需要创建具有正确策略和权限的角色。与基于用户的认证相比,这是一种更受欢迎且更安全的认证方式。然而,它需要额外的角色和策略创建步骤,并且设置起来不太直接。
关于 AWS 服务的用户与基于角色的身份验证的更多信息,请参阅docs.aws.amazon.com/redshift/latest/mgmt/redshift-iam-authentication-access-control.html。在我们的复制示例中,我们计划使用我们主要 AWS 用户的 aws 访问密钥。但在我们可以将数据复制到表中之前,我们首先需要使用 SQL 查询创建它。对于我们一直在工作的Titanic CSV 文件,创建表的查询如下:
CREATE TABLE IF NOT EXISTS titanic (
id integer primary key,
pclass integer,
survived boolean,
name varchar(255),
sex varchar(255),
age real,
sibsp integer,
parch integer,
ticket varchar(255),
fare real,
cabin varchar(255),
embarked char(1),
boat varchar(8),
body varchar(8),
home_dest varchar(255)
);
如dt命令所示,该表现在存在于我们的 Redshift 数据库中:
alexperrier@amlpacktdb=> dt
List of relations
Schema | Name | Type | Owner
--------+---------+-------+-------------
public | titanic | table | alexperrier
(1 row)
表结构如预期所示,如d+命令所示:
alexperrier@amlpacktdb=> d+ titanic
Table "public.titanic"
Column | Type | Modifiers | Storage | Stats target | Description
-----------+------------------------+------------------------------------------------------+----------+--------------+-------------
id | integer | not null default nextval('titanic_id_seq'::regclass) |
plain | | pclass | integer | | plain | |
survived | boolean | | plain | |
name | character varying(255) | | extended | |
sex | character varying(255) | | extended | |
age | real | | plain | |
sibsp | integer | | plain | |
parch | integer | | plain | |
ticket | character varying(255) | | extended | |
fare | real | | plain | |
cabin | character varying(255) | | extended | |
embarked | character(1) | | extended | |
boat | character varying(8) | | extended | |
body | character varying(8) | | extended | |
home_dest | character varying(255) | | extended | |
Indexes:
"titanic_pkey" PRIMARY KEY, btree (id)
我们现在可以将 CSV 文件上传到 S3,并从终端运行以下命令来填充我们的表:
# Load file on S3
$ aws s3 cp data/titanic.csv s3://aml.packt/data/ch9/
# connect to database via psql
$ psql --host=amlpackt.cenllwot8v9r.us-east-1.redshift.amazonaws.com --port=5439 --username=alexperrier --password --dbname=amlpacktdb
# upload data from your S3 location into the titanic table
$ copy titanic from 's3://aml.packt/data/ch9/titanic.csv' CREDENTIALS 'aws_access_key_id=<access key id>;aws_secret_access_key=<access secret key>' CSV;
注意,CSV 文件不应包含 CSV 标题。为了验证复制命令是否成功,我们可以通过运行以下查询来计算titanic表中的记录数:
alexperrier@amlpacktdb=> select count(*) from titanic;
-[ RECORD 1 ]
count | 1309
结果显示,我们现在在titanic表中有了 1309 条记录。
使用 Psql 执行 Redshift 查询
我们已经看到我们可以使用以下Psql命令连接到我们的数据库:
$ psql -h amlpackt.cenllwot8v9r.us-east-1.redshift.amazonaws.com -p 5439 -U alexperrier --password -d amlpacktdb
然后,我们需要输入我们的密码。为了缩短行并避免每次都输入密码,我们可以将连接字符串和密码都设置为 shell 环境变量。在你的终端中,执行以下命令以创建全局REDSHIFT_CONNECT shell 变量:
$ export REDSHIFT_CONNECT='-h amlpackt.cenllwot8v9r.us-east-1.redshift.amazonaws.com -p 5439 -U alexperrier -d amlpacktdb'
对于密码,执行以下命令:
$ export PGPASSWORD=your_password
从现在开始,你可以使用以下简单命令连接到数据库:
$ psql $REDSHIFT_CONNECT
注意,REDSHIFT_CONNECT是我们选择的变量名,而PGPASSWORD是一个由 Psql 识别的预定义 shell 变量名。
我们现在可以选择在 Redshift 数据库上运行查询的方式。我们可以执行以下步骤中的任何一个:
- 使用
Psql进入数据库 shell 并输入一些 SQL 查询:
$ psql $REDSHIFT_CONNECT
alexperrier@amlpacktdb=> select count(*) from titanic;
- 将 SQL 查询写入文件(例如,
my_file.sql),然后从终端运行以下命令:
$ psql $REDSHIFT_CONNECT -f my_file.sql
- 使用
Psql命令直接运行查询:
$ psql $REDSHIFT_CONNECT -c 'SELECT count(*) FROM my_table'
我们现在可以开始处理我们的数据集了。由于我们已经对泰坦尼克号数据集进行了广泛的研究,我们将使用另一个数据集来完成本章的剩余部分。让我们创建一个表现出强烈非线性模式的合成数据集。
创建我们自己的非线性数据集
创建非线性数据集的一个好方法是混合不同相位的正弦波。我们将在本章中使用以下 Python 脚本创建数据集,并将其导出为 CSV 文件:
import numpy as np
n_samples = 1000
de_linearize = lambda X: np.cos(1.5 * np.pi * X) + np.cos( 5 * np.pi * X )
X = np.sort(np.random.rand(n_samples)) * 2
y = de_linearize(X) + np.random.randn(n_samples) * 0.1
如常,X是预测变量,y是结果。你可以通过修改该脚本轻松生成其他非线性数据集。注意,我们使用了lambda函数,这是一种在需要时即时声明函数的 Python 方式。然后我们通过随机排序(np.random.rand(n_samples))对数据集进行洗牌。然后我们使用Pandas数据框将数据保存到 CSV 文件(nonlinear.csv)中:
import pandas as pd
df = pd.DataFrame( {'X':X, 'y': y} )
df = df.sample(frac=1) # shuffles the entire dataframe
df.to_csv('data/nonlinear.csv', index = False)
绘制数据给出以下图表:
显然不是线性的。一条线根本无法近似,更不用说从预测变量X预测结果y了。现在我们有一个高度非线性的数据集可用,我们需要将其上传到 Redshift。
将非线性数据上传到 Redshift
我们首先需要创建一个将托管数据的表。我们将把这个表称为nonlinear。它只有三个列:一个索引,预测变量X和结果y:
CREATE TABLE IF NOT EXISTS nonlinear (
id integer primary key,
x1 real,
y real
);
一旦创建了表,我们就可以将 CSV 文件上传到 S3,连接到数据库,并使用以下命令将数据导入非线性表:
copy nonlinear from 's3://aml.packt/data/ch9/nonlinear.csv' CREDENTIALS 'aws_access_key_id=<access key id>;aws_secret_access_key=<access secret key>' CSV;
我们可以通过查询来验证nonlinear表现在有一千行:
$ psql $REDSHIFT_CONNECT -c "select count(*) from nonlinear"
> count
> 1000
>(1 row)
我们的数据已经上传到 Redshift。我们准备创建数据源并训练和评估模型!但在我们深入到这个数据集上的模型构建之前,让我们介绍多项式回归方法,这将使我们能够处理这个高度非线性的数据集。
介绍多项式回归
在二维空间中,我们有一个预测变量和一个结果,线性建模就是找到最佳直线来近似你的数据。在三维空间(两个预测变量和一个结果)中,想法是找到最佳平面,或者最佳平坦表面,来近似你的数据。在N维空间中,表面变成了超平面,但目标始终相同——找到维度为N-1的超平面,以给出回归的最佳近似或最好地分离类。那个超平面总是平坦的。
回到我们创建的非常非线性二维数据集,很明显,没有任何一条线能够恰当地近似预测变量和结果之间的关系。有许多不同的方法可以用来建模非线性数据,包括多项式回归、阶梯函数、样条曲线和广义加性模型(GAM)。参见由 James, Witten, Hastie 和 Tibshirani 编写的《统计学习引论》的第七章,那里对这些方法有很好的介绍。这本书的 PDF 版本可在www-bcf.usc.edu/~gareth/ISL/找到。我们将应用多项式回归方法。
多项式回归包括用标准线性模型替换:
在这里,ŷ是预测结果,x是预测变量,(w[0], w[1])是线性模型的系数。通过一个阶数为k的多项式函数:
多项式回归方法的优势在于我们可以使用与线性模型相同的线性建模方法,因此我们仍然可以使用 Amazon ML SGD 来找到多项式回归方程的系数{w[k]}。在下一节中,我们将通过增加多项式的次数来训练连续的模型。
建立基线
我们首先需要建立一个基准。Amazon ML 的量级分箱转换是处理数据集中非线性问题的 Amazon 机器学习首选方法。让我们看看一个简单的线性模型使用 Amazon 的默认配方表现如何。我们将使用常规的 AWS 控制台工具创建一个基准分数。这次,我们选择从 Redshift 而不是 S3 创建数据源。填写下一张截图所示的信息,然后点击“测试访问”以检查您的访问权限,同时让 Amazon ML 创建必要的 IAM 角色。完成后,点击“验证”:
Amazon ML 通过创建以下资源在后台处理所有角色和策略的创建:
-
一个新角色:AmazonMLRedshift_us-east-1_amlpackt。我们将使用与此角色相关的 arn 在 Python SDK 创建数据源时使用。
-
附属于此角色的两个新策略:
-
AmazonMLS3LocationAccess_aml.packt -
AmazonMLRedshiftAccess_us-east-1_amlpackt
-
-
AWS 还设置了
信任关系,使得roleAmazonMLRedshift_us-east-1_amlpackt能够承担machinelearning服务角色。
手动创建这些角色和策略需要深入了解 AWS 中服务之间的访问权限。使用控制台创建它们可以节省大量时间。接下来的步骤是标准模式和目标定义,以及数据源创建。Amazon ML 生成的默认模式如下:
{
"version" : "1.0",
"rowId" : null,
"rowWeight" : null,
"targetAttributeName" : "y",
"dataFormat" : "CSV",
"dataFileContainsHeader" : false,
"attributes" : [ {
"attributeName" : "x",
"attributeType" : "NUMERIC"
}, {
"attributeName" : "y",
"attributeType" : "NUMERIC"
} ],
"excludedAttributeNames" : [ ]
}
我们稍后通过将 JSON 字符串保存到data/nonlinear.schema文件并将它上传到 S3(使用aws s3 cp data/nonlinear.schema s3://aml.packt/data/ch9/)来重用该模式。
一旦数据源可用,我们就可以通过控制台创建和评估一个模型。Amazon ML 在模型创建期间生成的配方对预测变量使用 500 个分箱的量级分箱转换,这可能看起来像是一个很大的值,因为我们只有 700 个训练数据集样本。自动生成的 Amazon ML 配方如下:
{
"groups": {
"NUMERIC_VARS_QB_500": "group('x')"
},
"assignments": {},
"outputs": [
"ALL_CATEGORICAL",
"quantile_bin(NUMERIC_VARS_QB_500,500)"
]
}
我们使用 L2 轻微正则化和 100 次迭代训练一个模型,并在我们数据集的 30%上评估该模型。我们得到了以下结果:
-
使用量级分箱
-
RMSE:0.1540
-
基准 RMSE:1.034
-
-
不使用量级分箱
-
RMSE:1.0207
-
基准 RMSE:1.025
-
量级分箱正确处理了非线性,并得到了相当不错的分数,而原始线性模型的表现并没有比基准好多少。在线性回归的情况下,Amazon ML 的基准仅仅是训练数据集中结果的平均值。
让我们看看我们是否能通过多项式回归击败这些结果。
Amazon ML 中的多项式回归
我们将使用Boto3和 Python SDK,并遵循我们在*第七章,命令行和 SDK中使用的方法来生成数据源的参数,进行蒙特卡洛验证:我们将生成对应于x的 2 次幂到x*的P次幂的特征,并运行N次蒙特卡洛交叉验证。伪代码如下:
for each power from 2 to P:
write sql that extracts power 1 to P from the nonlinear table
do N times
Create training and evaluation datasource
Create model
Evaluate model
Get evaluation result
Delete datasource and model
Average results
在这个练习中,我们将从x的 2 次幂到 5 次幂进行,并为每个模型进行 5 次试验。使用create_data_source_from_rds()从 Redshift 创建数据源的 Python 代码如下:
response = client.create_data_source_from_redshift(
DataSourceId='string',
DataSourceName='string',
DataSpec={
'DatabaseInformation': {
'InstanceIdentifier': 'amlpackt',
'DatabaseName': 'amlpacktdb'
},
'SelectSqlQuery': 'select x, y from nonlinear order by random()',
'DatabaseCredentials': {
'Username': 'alexperrier',
'Password': 'my_password'
},
'S3StagingLocation': 's3://aml.packt/data/ch9/',
'DataRearrangement': '{"splitting":{"percentBegin":0,"percentEnd":70 }
}',
'DataSchemaUri': 's3://aml.packt/data/ch9/nonlinear.csv.schema'
},
RoleARN='arn:aws:iam::178277513911:role/service-role/AmazonMLRedshift_us-east-1_amlpackt',
ComputeStatistics=True
)
除了明显的参数(数据库信息、数据模式 URI、数据源 ID和数据源名称)之外,你还需要找到 Role ARN 标识符的值。转到 IAM 控制台,点击角色,然后点击 AmazonMLRedshift_us-east-1_amlpackt 角色,以找到 Role ARN 字符串:
DataRearrangement字符串将取决于数据源的性质,训练数据源为 0%到 70%的分割,评估数据源为 70%到 100%。SelectSqlQuery是我们将要进行特征工程并创建新的变量作为x的幂的地方。
例如,以下查询生成一个x的 2 次幂变量:
select x, power(x,2) as x2, y from nonlinear order by random()
这个查询还生成一个x的三次幂变量:
select x, power(x,2) as x2, power(x,3) as x3, y from nonlinear order by random()
除了为每个新的集合或变量生成新的查询之外,我们还需要生成一个新的模式。原始的非线性数据集模式如下:
{
"version" : "1.0",
"rowId" : null,
"rowWeight" : null,
"targetAttributeName" : "y",
"dataFormat" : "CSV",
"dataFileContainsHeader" : false,
"attributes" : [ {
"attributeName" : "x",
"attributeType" : "NUMERIC"
}, {
"attributeName" : "y",
"attributeType" : "NUMERIC"
} ],
"excludedAttributeNames" : [ ]
}
我们通过向每个新的x变量幂的模式属性列表中添加以下元素来修改这个原始模式:
{
"attributeName" : "x{N}",
"attributeType" : "NUMERIC"
}
为了运行我们的试验,比较不同的特征集,并进行交叉验证以选择最佳模型,我们需要编写一组 Python 函数。
在 Python 中驱动试验
到目前为止,我们已经在 Python 中编写了顺序代码。最终编写简单的面向对象代码总是节省时间。代码更组织化,易于维护,并且不太可能在一段时间后变得不可用。花时间编写具有清晰初始化、实例和类方法的简单类,最终会使你的代码更加简单和健壮。考虑到这一点,我们现在将为我们的实验编写一个NonLinear类。
让我们先写下那个类中不同的函数,这些函数生成一些依赖于多项式回归幂的域:
- 这个函数接受一个幂
p并返回一个 SQL 查询:
def generate_sql(self, p):
powers = [ 'power(x,{0}) as x{0}'.format(i) for i in range(1,p+1) ]
return 'select ' + ','.join(powers) + ', y from nonlinear order by
random()'
- 这个函数接受数据分割的名称(训练与评估),并返回一个格式为 JSON 的字符串,这在数据源创建过程中是必需的:
def generate_data_rearrangement(self,split):
if split == 'training':
pct_begin = 0
pct_end = 70
else:
pct_begin = 70
pct_end = 100
return json.dumps( { "splitting":
{"percentBegin":pct_begin,"percentEnd":pct_end } } )
- 最后,以下函数接受幂
p并返回模式 JSON 字符串:
def generate_schema(self, p):
attributes = [ { "attributeName" : "x{0}".format(i), "attributeType"
: "NUMERIC" } for i in range(1,p+1) ]
attributes.append({ "attributeName" : "y", "attributeType" : "NUMERIC"
})
return json.dumps({ "version" : "1.0",
"rowId" : None,
"rowWeight" : None,
"targetAttributeName" : "y",
"dataFormat" : "CSV",
"dataFileContainsHeader" : False,
"attributes" : attributes,
"excludedAttributeNames" : [ ]
})
下面的三个函数使用机器学习客户端来创建数据源、模型和评估。它们与我们写的Chapter 7, 命令行和 SDK中的脚本非常相似。
- 数据源创建接受一个幂
p和一个交叉验证的索引k,并分割创建的数据源的性质。脚本调用generate_sql和generate_data_rearrangement方法:
def create_datasource(self, p, k, split ):
print("Create datasource {0} {1} {2} {3}".format(p,k,split,
self.prefix))
return self.client.create_data_source_from_redshift(
DataSourceId = "ds_{2}_{3}_p{0}_{1}".format(p,k,split, self.prefix),
DataSourceName = "DS {2} {3} p{0} {1}".format(p,k,split,
self.prefix),
DataSpec = {
'DatabaseInformation': {
'DatabaseName': 'amlpacktdb',
'ClusterIdentifier': 'amlpackt'
},
'SelectSqlQuery': self.generate_sql(p),
'DatabaseCredentials': {
'Username': 'alexperrier',
'Password': 'password'
},
'S3StagingLocation': 's3://aml.packt/data/ch9/',
'DataRearrangement': self.generate_data_rearrangement(split),
'DataSchema': self.generate_schema(p)
},
RoleARN='arn:aws:iam::178277513911:role/service-role
/AmazonMLRedshift_us-east-1_amlpackt',
ComputeStatistics=True
)
- 创建模型方法也接受幂
p和索引k:
def create_model(self, p, k):
print("Create model {0} {1} {2}".format(p, k, self.prefix))
return self.client.create_ml_model(
MLModelId = "mdl_{2}_p{0}_{1}".format(p,k, self.prefix),
MLModelName = "MDL {2} p{0} {1}".format(p,k, self.prefix),
MLModelType = 'REGRESSION',
Parameters = self.sgd_parameters,
TrainingDataSourceId = self.ds_training['DataSourceId'] ,
Recipe = json.dumps(self.recipe)
)
- 最后,创建评估方法如下:
def create_evaluation(self, p, k):
print("Create evaluation {0} {1} {2}".format(p, k, self.prefix))
return self.client.create_evaluation(
EvaluationId = "eval_{2}_p{0}_{1}".format(p,k, self.prefix),
EvaluationName = "EVAL {2} p{0} {1}".format(p,k, self.prefix),
MLModelId = self.model['MLModelId'],
EvaluationDataSourceId= self.ds_evaluation['DataSourceId']
)
我们使用create_sql(p)和create_schema(p)在创建数据源时渲染SelectSqlQuery和Data.Schema字段。模型创建函数使用两个尚未初始化的类项:sgd_parameters和recipe。数据源创建函数返回 Amazon ML 的create_data_source_from_redshift函数的响应。我们将响应保存在ds_training和ds_evaluation中,并使用这些项在模型和评估创建函数中传递适当的DataSourceId。
运行所有不同评估的全局代码如下:
# Initialize the object
nl = NonLinear(max_p, n_crossval, prefix)
# Run all the datasources, models and evaluations creation
nl.run_all_trials()
# Wait until the evaluations are finished and get the results
nl.get_results()
# Export the results to a csv file
nl.to_csv(filename)
# Free the resources
nl.delete_resources()
这些函数由以下定义:
import pandas as pd
import boto3
import json
import csv
class NonLinear():
def __init__(self, max_p, n_crossval, prefix):
self.trials = []
self.max_p = max_p
self.n_crossval = n_crossval
self.prefix = prefix
self.client = boto3.client('machinelearning')
self.sgd_parameters = {
"sgd.shuffleType": "auto",
"sgd.l2RegularizationAmount": "1.0E-06",
"sgd.maxPasses": "100"
}
self.recipe = {
"groups" : {},
"assignments" : { },
"outputs": ["ALL_INPUTS"]
# "outputs": ["quantile_bin(ALL_NUMERIC,200)"]
}
def run_all_trials(self):
for p in range(1,self.max_p+1):
for k in range(self.n_crossval):
self.trials.append( self.run_trial(p,k) )
def run_trial(self, p, k ):
self.ds_training = self.create_datasource(p, k, 'training')
self.ds_evaluation = self.create_datasource(p, k, 'evaluation')
self.model = self.create_model(p,k)
self.evaluation = self.create_evaluation(p,k)
return {
"p": p,
"k": k,
"ds_training_id": self.ds_training['DataSourceId'],
"ds_evaluation_id": self.ds_evaluation['DataSourceId'],
"model_id": self.model['MLModelId'],
"evaluation_id": self.evaluation['EvaluationId'],
"rmse": None
}
def get_results(self):
results = []
for trial in self.trials:
waiter = self.client.get_waiter('evaluation_available')
print("Waiting on evaluation {0} to finish ".format( trial['evaluation_id'] ) )
waiter.wait(FilterVariable='DataSourceId', EQ=trial['ds_evaluation_id'] )
response = self.client.get_evaluation( EvaluationId=trial['evaluation_id'] )
rmse = float(response['PerformanceMetrics']['Properties']['RegressionRMSE'])
trial["rmse"] = rmse
results.append(trial)
print("Evaluation score {0}".format(rmse))
self.trials = results
def delete_resources(self):
# Now delete the resources
print("Deleting datasources and model")
for trial in self.trials:
response = self.client.delete_data_source(
DataSourceId = trial['ds_training_id']
)
response = self.client.delete_data_source(
DataSourceId = trial['ds_evaluation_id']
)
response = self.client.delete_ml_model(
MLModelId = trial['model_id']
)
def to_csv(self, filename):
print("exporting to csv {0}".format(filename))
keys = self.trials[0].keys()
with open(filename, 'w') as output_file:
dict_writer = csv.DictWriter(output_file, keys)
dict_writer.writeheader()
dict_writer.writerows(self.trials)
整个代码在 GitHub 上可用,地址为github.com/alexperrier/packt-aml。
解释结果
下面的图表显示了五次交叉验证和不同多项式度(1 到 5)获得的不同 RMSE:
我们看到,对于三次和四次多项式,最佳拟合效果最好。最后,我们基于多项式回归模型的模型整体 RMSE 与使用分位数分箱获得的 RMSE 相比并不好。多项式回归的最佳 RMSE 值约为 0.85,而分位数分箱的 RMSE 发现约为 0.15。分位数分箱,如 Amazon ML 所做的那样,比多项式回归好得多。
摘要
在本章中,我们看到了如何将 Redshift 用作 Amazon ML 的数据源。尽管 RDS 也可以用来创建数据源,但与 Amazon ML 相比,Redshift 更容易使用,因为所有访问配置都由 AWS 向导处理。
我们展示了如何使用 Redshift 上的简单 SQL 查询进行特征工程,并在高度非线性数据集上实现多项式回归方法。我们还展示了如何生成所需的 SQL 查询、模式和配方以执行蒙特卡洛交叉验证。
在下一章中,我们将基于我们的 Redshift 集成,并开始使用 AWS Kinesis 服务进行数据流。