原文:
annas-archive.org/md5/9ea32aa91e5ec8d38bb57552873cedb5译者:飞龙
第四部分:生产环境中的模型管理
在本节中,您将学习如何在多种配置下部署机器学习模型,包括使用 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、pandas、numpy 等)。
本书中的代码示例可以在 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),但我们将按如下方式继续:
-
让我们从我们在第四章中训练的线性学习者模型的工件开始,如以下代码片段所示:
$ 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 -
我们加载符号文件,该文件包含模型的JavaScript 对象表示法(JSON)定义,如下所示:
import json sym_json = json.load(open('mx-mod-symbol.json')) sym_json_string = json.dumps(sym_json) -
我们使用这个 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')) -
现在,我们可以轻松地绘制模型,如下所示:
mx.viz.plot_network( net(mx.sym.var('data'))[0], node_attrs={'shape':'oval','fixedsize':'false'})这将生成以下输出:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_11_1.jpg
图 11.1 – 线性学习者模型
-
然后,我们加载在训练过程中学到的模型参数,如下所示:
net.load_parameters('mx-mod-0000.params', allow_missing=True) net.collect_params().initialize() -
我们定义一个存储在 MXNet
NDArray(https://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]) -
最后,我们将其通过模型并读取输出,如下所示:
response = net(test_sample) print(response)该房子的预测价格为美元(USD)30,173,如下图所示:
array([[30.173424]], dtype=float32)
该技术应该适用于所有基于 MXNet 的算法。现在,让我们看看计算机视觉(CV)的内置算法。
检查和导出内置的计算机视觉(CV)模型
这三个内置的计算机视觉算法也基于 Apache MXNet。过程完全相同,如此处所述:
-
以下是我们在第五章中训练的图像分类模型的工件,训练计算机视觉模型:
$ tar xvfz model.tar.gz x image-classification-0010.params x model-shapes.json x image-classification-symbol.json -
加载模型及其参数,如下所示:
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() -
输入形状是一个 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 模型是通过joblib(https://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 训练了一个情感分析模型,然后按以下步骤进行:
-
我们从 S3 复制模型工件并进行解压,像这样:
$ tar xvfz model.tar.gz training_args.bin config.json pytorch_model.bin -
在 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) -
接下来,我们获取与模型关联的分词器,如下所示:
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained( 'distilbert-base-uncased') -
我们写一个简短的函数,将
softmax应用于模型输出层返回的激活值,如下所示:import torch def probs(logits): softmax = torch.nn.Softmax(dim=1) pred = softmax(logits).detach().numpy() return pred -
最后,我们定义一个示例并使用我们的模型进行预测,如下所示:
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 模型时,这非常有用。在前一节中,我们看到了模型工件的样子,以及如何使用它们来打包模型。我们将按照以下步骤进行:
-
从我们使用
save_model()在本地训练并保存的 XGBoost 模型开始,我们首先通过运行以下代码创建一个模型工件:$ tar cvfz model-xgb.tar.gz xgboost-model -
在 Jupyter 笔记本中,我们将模型工件上传到我们的默认存储桶,如下所示:
import sagemaker sess = sagemaker.Session() prefix = 'export-xgboost' model_path = sess.upload_data( path=model-xgb.tar.gz', key_prefix=prefix) -
然后,我们创建一个
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()) -
推断脚本非常简单。它只需要包含一个加载模型的函数,就像我们在 第七章 中讨论部署框架模型时所解释的那样。代码在以下片段中说明:
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 -
回到笔记本,然后我们像往常一样部署和预测,如下所示:
xgb_predictor = xgb_model.deploy(. . .) xgb_predictor.predict(. . .)
现在,让我们用 TensorFlow 模型做同样的事情。
导入和部署 TensorFlow 模型
这个过程非常类似,我们将在下面看到:
-
我们首先使用
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 -
然后,我们按如下方式将工件上传到 S3:
import sagemaker sess = sagemaker.Session() prefix = 'byo-tf' model_path = sess.upload_data( path='model.tar.gz', key_prefix=prefix) -
接下来,我们从工件创建一个 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()) -
然后,由于TensorFlow的深度学习容器(DLC),我们像往常一样部署和预测。
现在,让我们做一个最终的示例,在这个示例中,我们导入并部署一个 Hugging Face 模型,使用 PyTorch 的 DLC 和一个用于模型加载及自定义处理的推理脚本。
使用 PyTorch 导入并部署 Hugging Face 模型
我们将重用 Hugging Face 的示例,并首先关注推理脚本。它包含四个函数:模型加载、预处理、预测和后处理。我们将按以下步骤进行:
-
模型加载函数使用我们导出模型时所用的相同代码。唯一的不同是我们从
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 -
预处理和后处理函数很简单。它们只检查正确的内容和接受的类型。您可以在以下代码片段中看到它们:
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) -
最后,预测函数对输入数据进行分词,进行预测,并返回最可能的类别名称,如下所示:
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]
现在我们的推理脚本已经准备好,接下来我们进入笔记本,导入模型并进行部署,如下所示:
-
我们创建一个
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') -
我们使用
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)如预期,输出结果为
positive和negative。
本节关于导入和部署模型的内容到此结束。接下来,让我们学习如何调用已经部署的端点。
调用现有端点
当你想要与一个实时端点交互,但没有访问预测器的权限时,这非常有用。我们只需要知道端点的名称,按以下步骤进行:
-
为我们之前部署的端点构建一个
TensorFlowPredictor预测器。该对象是框架特定的。代码如下所示:from sagemaker.tensorflow.model import TensorFlowPredictor another_predictor = TensorFlowPredictor( endpoint_name=tf_endpoint_name, serializer=sagemaker.serializers.JSONSerializer() ) -
然后,像往常一样进行预测,如下所示:
another_predictor.predict(…)
现在,让我们学习如何更新端点。
更新现有端点
update_endpoint() API 允许你以非破坏性的方式更新端点的配置。端点仍然在服务中,你可以继续使用它进行预测。
让我们在 TensorFlow 端点上尝试这个,如下所示:
-
我们将实例数量设置为
2并更新端点,如下所示:another_predictor.update_endpoint( initial_instance_count=2, instance_type='ml.t2.medium') -
端点会立即更新,如下图所示。https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_11_2.jpg
图 11.2 – 端点正在更新
-
一旦更新完成,端点将由两个实例支持,如下图所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_11_3.jpg
图 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部署端点是一个四步操作,概述如下:
-
使用
create_model()API 创建一个或多个模型。或者,我们可以使用已经训练或通过 SageMaker SDK 导入的现有模型。为了简洁起见,我们将在此进行演示。 -
定义一个或多个生产变体,列出每个模型的基础设施需求。
-
创建
create_endpoint_config()API,传递之前定义的生产变体,并为每个变体分配一个权重。 -
使用
create_endpoint()API 创建端点。
让我们利用这些 API,部署一个运行我们在波士顿住房数据集上训练的 XGBoost 模型的端点,该端点包含两个变体,如下所示:
-
我们定义了两个变体;它们都由一个单一实例支持。然而,它们将分别接收九成和一成的请求——也就是说,“变体权重/权重总和”。如果我们想要在生产环境中引入一个新模型并确保它正常工作,再向其发送流量时,可以使用这种设置。代码如以下片段所示:
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'}] -
我们通过传递这两个变体并设置可选标签来创建端点配置,如下所示:
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()boto3API 描述特定配置。 -
我们基于此配置创建一个端点:
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()boto3API 描述特定端点。 -
创建
boto3等待器是一种便捷的方式,用于等待端点投入使用。你可以在这里看到创建过程:waiter = sm.get_waiter('endpoint_in_service') waiter.wait(EndpointName=endpoint_name) -
几分钟后,端点已投入使用。如以下截图所示,现在它使用了两个生产变体:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_11_4.jpg
图 11.4 – 查看生产变体
-
然后,我们调用端点,如以下代码片段所示。默认情况下,预测请求会根据变体的权重转发:
smrt = boto3.Session().client( service_name='runtime.sagemaker') response = smrt.invoke_endpoint( EndpointName=endpoint_name, ContentType='text/csv', Body=test_sample) -
我们还可以选择接收预测请求的变体。这对于 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]' -
我们还可以更新权重——例如,赋予两个变体相等的权重,使它们接收到相同的流量份额——如下所示:
response = sm.update_endpoint_weights_and_capacities( EndpointName=endpoint_name, DesiredWeightsAndCapacities=[ { 'VariantName': 'variant-1', 'DesiredWeight': 5}, { 'VariantName': 'variant-2', 'DesiredWeight': 5}]) -
我们可以完全移除一个变体,并将所有流量发送到剩下的变体。这里,端点在整个过程中保持服务状态,且没有流量丢失。代码示例如下:
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) -
最后,我们通过删除端点和两个端点配置来进行清理,如下所示:
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 脚本,在第七章,使用内建框架扩展机器学习服务中进行训练。让我们开始,如下所示:
-
按照惯例配置估算器,运行以下代码:
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}) -
让我们在批处理模式下预测训练集。我们去除目标值,将数据集保存为逗号分隔值(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') -
创建转换器对象并启动批处理,如下所示:
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) -
在训练日志中,我们可以看到 SageMaker 创建了一个临时端点并用它来预测数据。对于大规模任务,我们可以通过对样本进行小批量预测(使用
strategy参数)来优化吞吐量,增加预测并发性(max_concurrent_transforms),以及增大最大负载大小(max_payload)。 -
一旦作业完成,预测结果将在 S3 中可用,如下所示:
print(sk_transformer.output_path) s3://sagemaker-us-east-1-123456789012/sagemaker-scikit-learn-2020-06-12-08-28-30-978 -
使用 AWS CLI,我们可以通过运行以下代码轻松地获取这些预测:
%%bash -s "$sk_transformer.output_path" aws s3 cp $1/data.csv.out . head -1 data.csv.out [[29.73828574177013], [24.920634119498292], … -
就像训练一样,转换器使用的基础设施在作业完成后立即关闭,因此无需进行清理。
在接下来的部分中,我们将讨论推理管道,以及如何使用它们部署一系列相关模型。
在推理管道上部署模型
现实中的机器学习场景通常涉及多个模型;例如,您可能需要对传入的数据执行预处理步骤,或者使用主成分分析(PCA)算法降低其维度。
当然,你可以将每个模型部署到独立的端点。然而,必须编写编排代码来按顺序将预测请求传递给每个模型。增加多个端点也会带来额外的成本。
相反,推理管道 允许你在同一个端点上部署最多五个模型,或者用于批量转换,并自动处理预测顺序。
假设我们想先运行 PCA,然后再运行 Linear Learner。构建推理管道将如下所示:
-
在输入数据集上训练 PCA 模型。
-
使用 PCA 处理训练集和验证集,并将结果存储在 S3 中。批量转换是实现这一目标的一个好方法。
-
使用 PCA 处理后的数据集作为输入训练 Linear Learner 模型。
-
使用
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 ) -
按照常规方式创建端点配置和端点。我们还可以使用批量转换器来使用管道。
你可以在 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,但不能混合使用。
-
如果端点附加了监控计划,你无法删除该端点。你必须先删除监控计划,然后才能删除端点。
既然知道了这一点,我们来捕获一些数据吧!开始:
-
训练像往常一样进行。你可以在 GitHub 仓库中找到代码。
-
我们为 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)) -
一旦端点投入使用,我们就会发送数据进行预测。大约一两分钟内,我们可以在 S3 中看到捕获的数据,然后将其复制到本地,如下所示:
%%bash -s "$capture_path" aws s3 ls --recursive $1 aws s3 cp --recursive $1 . -
打开其中一个文件,我们可以看到样本和预测,如下所示:
{"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对象。让我们开始,如下所示:
-
基准只能建立在 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 规范,否则作业将以难以理解的方式失败。特别是,列名中不允许使用点号。我们这里没有这个问题,但请记住这一点。
-
定义基础设施需求、训练集的位置及其格式如下:
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)) -
正如您猜测的那样,这是作为 SageMaker 处理作业运行的,您可以在
/aws/sagemaker/ProcessingJobs前缀中找到其日志。其输出位置有两个 JSON 文档:
statistics.json和constraints.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"]) -
如下截图所示,
constraints文件提供了每个特征的推断类型、数据集的完整性以及是否包含负值:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_11_5.jpg图 11.5 – 查看推断模式
-
statistics文件添加了基本统计信息,如下截图所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_11_6.jpg
图 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 等等。让我们模拟这种情况,看看这对预测质量有多大影响,如下所示:
-
从有效样本开始,我们得到了正确的预测,如此所示:
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']] -
现在,让我们将第一个特征乘以 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']] -
让我们尝试通过运行以下代码否定最后一个特征:
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 小时才能看到结果,请不要担心;作业执行是由后端负载均衡的,短暂的延迟是很常见的:
-
我们可以在 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>] -
违规报告以 JSON 文件的形式存储在 S3 中。我们可以读取它,并使用
pandas显示其内容,具体如下:violations = ll_monitor.latest_monitoring_constraint_violations() violations = pd.io.json.json_normalize( violations.body_dict["violations"]) violations这将打印出上次监控作业检测到的违规情况,如下图所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_11_7.jpg
图 11.7 – 查看违规情况
-
当然,我们也可以在 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找到更多信息。 -
完成后,别忘了删除监控计划和端点本身,具体如下:
response = ll_monitor.delete_monitoring_schedule() ll_predictor.delete_endpoint()
如您所见,SageMaker 模型监控帮助您捕获进来的数据和预测,这是一个对模型测试非常有用的功能。此外,您还可以使用内置容器或您自己的容器执行数据质量分析。
在接下来的部分,我们将不再关注端点,而是学习如何将模型部署到容器服务。
将模型部署到容器服务
之前,我们看到如何在 S3 中获取模型工件并如何从中提取实际的模型。了解这一点后,将其部署到容器服务上就变得相当简单,比如Amazon Elastic Container Service(ECS)、Amazon Elastic Kubernetes Service(EKS)或Amazon Fargate。
也许公司政策要求所有内容都部署在容器中,也许您就是喜欢容器,或者两者兼而有之!无论什么原因,您都完全可以做到。这里没有特定于 SageMaker 的内容,AWS 对这些服务的文档会告诉您所有需要了解的内容。
一个高层次的示例流程可能是这样的:
-
在 SageMaker 上训练一个模型。
-
当训练完成时,获取工件并提取模型。
-
将模型推送到 Git 仓库。
-
编写任务定义(适用于 ECS 和 Fargate)或 Pod 定义(适用于 EKS)。它可以使用内置容器之一,也可以使用您自己的容器。然后,它可以运行模型服务器或您自己的代码,从 Git 仓库克隆模型,加载它并提供预测服务。
-
使用此定义,在您的集群上运行容器。
让我们将其应用到 Amazon Fargate。
在 SageMaker 上训练并在 Amazon Fargate 上部署
Amazon Fargate让您在完全托管的基础设施上运行容器(aws.amazon.com/fargate)。无需创建和管理集群,这使得它非常适合那些不希望涉及基础设施细节的用户。然而,请注意,在撰写本文时,Fargate 尚不支持图形处理单元(GPU)容器。
准备模型
我们通过以下步骤来准备模型:
-
首先,我们在 Fashion-MNIST 数据集上训练一个 TensorFlow 模型。一切照常进行。
-
我们找到 S3 中模型工件的位置,并将其设置为环境变量,如下所示:
%env model_data {tf_estimator.model_data} -
我们从 S3 下载工件并将其提取到本地目录,如下所示:
%%sh aws s3 cp ${model_data} . mkdir test-models tar xvfz model.tar.gz -C test-models -
我们打开终端并将模型提交到公共 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 做同样的事情。我们按以下步骤进行:
-
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 -
我们使用它来“创建”一个 Fargate 集群。实际上,这并不涉及创建任何基础设施;我们只是在定义一个集群名称,用于运行任务。请确保您有
ecs:CreateCluster权限。如果没有,请在继续之前添加它。代码如下所示:%%sh aws ecs create-cluster --cluster-name fargate-demo ecs-cli configure --cluster fargate-demo --region eu-west-1 -
我们在CloudWatch中创建一个日志组,容器将在其中写入输出。我们只需要做一次。以下是实现此操作的代码:
%%sh aws logs create-log-group --log-group-name awslogs-tf-ecs -
我们需要一个
8500端口用于 Google 远程过程调用(8501端口用于表述性状态转移(REST)API)。如果您还没有,您可以在弹性计算云(EC2)控制台轻松创建一个。这里,我在我的默认虚拟私有云(VPC)中创建了一个。它看起来像这样:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_11_8.jpg
图 11.8 – 查看安全组
定义任务
现在,我们需要编写一个JSON文件,其中包含任务定义:要使用的容器镜像、入口点以及其系统和网络属性。我们开始吧,如下所示:
-
首先,我们定义任务允许消耗的中央处理单元(CPU)和内存的数量。与 ECS 和 EKS 不同,Fargate 只允许一组有限的值,详情请见
docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html。我们将选择 4 个 虚拟 CPU(vCPU)和 8 GB 随机存取内存(RAM),如下面的代码片段所示:{ "requiresCompatibilities": ["FARGATE"], "family": "inference-fargate-tf-230", "memory": "8192", "cpu": "4096", -
接下来,我们定义一个容器,加载我们的模型并进行预测。我们将使用 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, -
它的入口点创建一个目录,克隆我们推送模型的代码库,并启动 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"], -
因此,我们映射了两个 TensorFlow Serving 端口,如下所示:
"portMappings": [ { "hostPort": 8500, "protocol": "tcp", "containerPort": 8500 }, { "hostPort": 8501, "protocol": "tcp", "containerPort": 8501 } ], -
我们定义了日志配置,指向我们之前创建的 CloudWatch 日志组,如下所示:
"logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "awslogs-tf-ecs", "awslogs-region": "eu-west-1", "awslogs-stream-prefix": "inference" } } }], -
我们设置容器的网络模式,如下代码片段所示。
awsvpc是最灵活的选项,它将允许我们的容器公开访问,具体说明请见docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html。它将在我们选择的子网中创建一个弹性网络接口:"networkMode": "awsvpc" -
最后,我们为任务定义一个 IAM 角色。如果这是你第一次使用 ECS,你应该在 IAM 控制台中创建这个角色。你可以在
docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html找到相关说明。代码如下所示:"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole" }
运行任务
我们现在准备使用之前创建的安全组和默认 VPC 中的一个子网来运行任务。具体操作如下:
-
我们使用
run-taskAPI 启动任务,传递任务定义的家族名称(不是文件名!)。请注意版本号,因为每次你注册新版本的任务定义时,它会自动增加,所以请确保使用的是最新版本。代码如下所示:%%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}" -
几秒钟后,我们可以看到我们的预测容器正在运行(显示任务标识符(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 -
使用容器的公共互联网协议(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] -
当我们完成任务后,我们使用
run-taskAPI 停止任务并删除集群,如下代码片段所示。当然,你也可以使用 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 CloudFormation、AWS 云开发工具包(AWS CDK)和 Amazon SageMaker Pipelines。
第十二章:自动化机器学习工作流
在上一章中,你学习了如何使用 boto3 SDK 在不同配置中部署机器学习模型。我们在 Jupyter Notebooks 中使用了它们的 API——这也是快速实验和迭代的首选方式。
然而,将笔记本用于生产任务并不是一个好主意。即使你的代码经过了仔细测试,监控、日志记录、创建其他 AWS 资源、处理错误、回滚等问题怎么办?做到这一切正确将需要大量额外的工作和代码,这可能导致更多的错误。需要一种更工业化的方法。
在本章中,你将首先学习如何通过AWS CloudFormation 和 AWS Cloud Development Kit(CDK)配置 SageMaker 资源——这两个 AWS 服务专为实现可重复性、可预测性和可靠性而构建。你将看到如何在应用更改之前预览它们,以避免不可控和潜在的破坏性操作。
然后,你将学习如何使用另外两个服务——AWS Step Functions 和 Amazon 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 Interface(CLI)(aws.amazon.com/cli/)。
你需要一个可用的 pandas、numpy 等。
本书中包含的代码示例可以在 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 存储桶)的JSON 或 YAML 文本文件。几乎所有 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 资源(一个模型,一个端点配置和一个端点)及其参数:
-
创建一个名为
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 -
在
Resources部分,我们定义一个模型资源,使用Ref内置函数来引用适当的输入参数:Resources: Model: Type: "AWS::SageMaker::Model" Properties: Containers: - Image: !Ref ContainerImage ModelDataUrl: !Ref ModelDataUrl ExecutionRoleArn: !Ref RoleArn ModelName: !Ref ModelName -
然后,我们定义一个端点配置资源。我们使用
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 -
最后,我们定义一个端点资源。同样,我们使用
GetAtt来获取端点配置的名称:Endpoint: Type: "AWS::SageMaker::Endpoint" Properties: EndpointConfigName: !GetAtt EndpointConfig.EndpointConfigName -
在
Outputs部分,我们返回端点的 CloudFormation 标识符以及其名称:Outputs: EndpointId: Value: !Ref Endpoint EndpointName: Value: !GetAtt Endpoint.EndpointName
现在模板已经完成(endpoint-one-model.yml),我们可以创建堆栈。
注意
请确保您的 IAM 角色有权限调用 CloudFormation API。如果没有,请将 AWSCloudFormationFullAccess 管理策略添加到该角色中。
将模型部署到实时端点
让我们使用 boto3 API 创建一个堆栈,部署一个 TensorFlow 模型。我们将重用一个用 Keras 在 Fashion MNIST 上训练的模型:
注意
由于我们的模板完全不依赖于区域,您可以选择任何您想要的区域。只需确保您已经在该区域训练了模型,并且正在使用适当的容器镜像。
-
我们将需要
boto3客户端来访问 SageMaker 和 CloudFormation:import boto3 sm = boto3.client('sagemaker') cf = boto3.client('cloudformation') -
我们描述了训练任务以查找其工件的位置,以及其执行角色:
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'] -
我们设置了用于部署的容器。在某些情况下,这是不必要的,因为相同的容器既用于训练也用于部署。对于 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' -
然后,我们读取模板,创建一个新堆栈,并传递所需的参数:
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 } ] ) -
跳转到 CloudFormation 控制台,我们看到堆栈正在创建,如下图所示。请注意,资源是按正确的顺序创建的:模型、端点配置和端点:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_1.jpg
图 12.1 – 查看堆栈创建
正如我们预期的那样,我们也可以在 SageMaker Studio 中看到端点,如下图所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_2.jpg
图 12.2 – 查看端点创建
-
一旦堆栈创建完成,我们可以使用其输出查找端点的名称:
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
-
我们可以像往常一样测试端点。然后,我们可以删除堆栈及其资源:
cf.delete_stack(StackName=stack_name)
然而,让我们暂时不要删除堆栈。相反,我们将使用更改集来更新它。
使用更改集修改堆栈
在这里,我们将更新支撑端点的实例数量:
-
我们使用相同的模板和参数创建一个新的更改集,唯一不同的是
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 } ] ) -
我们可以在 CloudFormation 控制台看到更改集的详细信息,如下图所示。我们也可以使用
describe_change_set()API:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_3.jpg图 12.3 – 查看更改集
这告诉我们端点配置和端点需要被修改,甚至可能被替换。正如我们从第十一章《部署机器学习模型》所知,一个新的端点将会创建,并以非破坏性方式应用于现有的端点。
注意
在使用 CloudFormation 时,了解资源的 替换策略至关重要。每种资源类型的文档中都有详细信息。
-
通过点击
execute_change_set()API。正如预期的那样,端点立即更新,如下图所示:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_4.jpg图 12.4 – 更新端点
-
更新完成后,我们可以在 CloudFormation 控制台中看到事件序列,如下图所示。已创建并应用了新的端点配置,之前的端点配置已被删除:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_5.jpg
图 12.5 – 更新堆栈
-
我们可以检查端点现在由两个实例支持:
r = sm.describe_endpoint(EndpointName=endpoint_name) print r(['ProductionVariants'][0] ['CurrentInstanceCount'])这会打印出支持生产变种的实例数量:
2
让我们继续处理变更集,并向端点添加第二个生产变种。
向端点添加第二个生产变种
我们的初始模板只定义了一个生产变种。我们将更新它并添加另一个变种(endpoint-two-models.yml):
-
在
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 -
我们在
Resources部分做同样的事情:Model2: Type: "AWS::SageMaker::Model" Properties: Containers: - Image: !Ref ContainerImage ModelDataUrl: !Ref ModelDataUrl2 ExecutionRoleArn: !Ref RoleArn ModelName: !Ref ModelName2 -
返回我们的笔记本,我们获取到另一个训练任务的信息。然后我们创建一个变更集,读取更新后的模板并传递所有必需的参数:
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 } ] ) -
查看 CloudFormation 控制台,我们可以看到由变更集引起的变化。创建一个新模型并修改端点配置和端点:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_6.jpg
图 12.6 – 查看变更集
-
我们执行变更集。完成后,我们看到端点现在支持两个生产变种。注意,实例数量恢复到初始值,因为我们在更新的模板中将其定义为
1:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_7.jpg
图 12.7 – 查看生产变种
新的生产变种的权重为 0,因此不会用于预测。让我们看看如何使用 金丝雀部署逐步引入它。
实现金丝雀部署
金丝雀部署是一种流行的渐进式应用部署技术(martinfowler.com/bliki/CanaryRelease.html),也可用于机器学习模型。
简单来说,我们将通过一系列堆栈更新,逐步增加第二个生产变体的权重,每次增加 10%,直到它完全替代第一个生产变体。我们还将创建一个 CloudWatch 警报,监控第二个生产变体的延迟——如果警报被触发,变更集将被回滚:
-
我们创建了一个 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' ) -
我们找到警报的 ARN:
response = cw.describe_alarms(AlarmNames=[alarm_name]) for a in response['MetricAlarms']: if a['AlarmName'] == alarm_name: alarm_arn = a['AlarmArn'] -
然后,我们循环遍历权重并更新堆栈。在这里不需要更改集,因为我们清楚地知道从资源的角度会发生什么。我们将 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)。
现在,我们只需要坐下来,喝杯茶,享受模型正在安全、自动地部署的事实。由于端点更新是无缝的,客户端应用程序不会察觉任何变化。
几个小时后,部署完成。下一张截图显示了两种变体随时间的调用情况。正如我们所见,流量逐渐从第一个变体转移到第二个变体:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_8.jpg
图 12.8 – 监控金丝雀部署
延迟保持在我们 500 毫秒的限制之下,警报没有被触发,正如下一张截图所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_9.jpg
图 12.9 – 查看 CloudWatch 警报
这个示例可以作为你自己部署的起点。例如,你可以添加一个监控4xx或5xx HTTP 错误的警报。你还可以直接监控受预测延迟和准确性影响的业务指标,如点击率、转化率等。一个有用的补充是添加警报通知(电子邮件、短信,甚至 Lambda 函数),以便在模型部署失败时触发下游操作。可能性是无限的!
完成后,别忘了删除堆栈,可以在 CloudFormation 控制台中删除,也可以使用delete_stack()API。这样会自动清理堆栈创建的所有 AWS 资源。
蓝绿部署是另一种流行的技术。让我们看看如何在 SageMaker 上实现它。
实现蓝绿部署
蓝绿部署需要两个生产环境(martinfowler.com/bliki/BlueGreenDeployment.html):
-
运行版本
n的实时生产环境(blue) -
运行版本
n+1的这个环境的副本(green)
我们来看两个可能的场景,这些场景可以通过我们为金丝雀发布所使用的相同 API 来实现。
使用单一端点实现蓝绿部署
从一个运行当前版本模型的现有端点开始,我们将执行以下步骤:
-
创建一个包含两个生产版本的新端点配置:一个用于当前模型,一个用于新模型。初始权重分别设置为
1和0。 -
将其应用到端点。
-
在新生产版本上运行测试,使用
invoke_endpoint()中的TargetVariant参数显式选择它。 -
当测试通过后,更新权重为
0和1。这将无缝地将流量切换到新模型。如果出现问题,可以将权重还原为1和0。 -
部署完成后,更新端点以删除第一个生产版本。
这是一个简单而可靠的解决方案。然而,更新端点需要几分钟时间,这使得整个过程不如预期那样快速。让我们看看如何通过使用两个端点来解决这个问题。
使用两个端点实现蓝绿部署
从一个运行当前版本模型的现有端点开始,我们将实施以下步骤:
-
创建一个运行新版本模型的第二个端点。
-
在这个新端点上运行测试。
-
当测试通过后,将所有流量切换到新端点。可以通过不同方式实现此操作;例如,更新业务应用中的参数,或更新私有 DNS 条目。如果出现问题,可以恢复到先前的设置。
-
部署完成后,删除旧的端点。
这个设置稍微复杂一些,但它让你能够即时从一个模型版本切换到下一个,无论是部署还是回滚。
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,你也可以使用JavaScript、TypeScript、Java和**.NET**。API 文档可以在docs.aws.amazon.com/cdk/api/latest/python/找到:
-
首先,我们创建一个名为
endpoint的 Python 应用程序:$ mkdir cdk $ cd cdk $ cdk init --language python --app endpoint -
这会自动创建一个虚拟环境,我们需要激活它:
$ source .venv/bin/activate -
这还会为我们的 CDK 代码创建一个默认的
app.py文件,一个用于应用配置的cdk.json文件,以及一个用于安装依赖的requirements.txt文件。相反,我们将使用 GitHub 仓库中现有的文件: -
在
requirements.txt文件中,我们安装 CDK 的 S3 和 SageMaker 包。每个服务需要不同的包。例如,我们需要为 S3 添加aws_cdk.aws_s3:-e . aws_cdk.aws_s3 aws_cdk.aws_sagemaker -
然后,我们像往常一样安装依赖:
$ pip install -r requirements.txt -
在
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 } }这是将值传递给应用程序的首选方法。你应该使用版本控制来管理此文件,以便追踪堆栈的构建过程。
-
我们可以使用
cdk context命令查看我们应用程序的上下文:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_10.jpg
图 12.10 – 查看 CDK 上下文
现在,我们需要编写实际的应用程序。
编写 CDK 应用程序
所有代码都放在app.py文件中,我们将在接下来的步骤中实现:
-
我们导入所需的包:
import time from aws_cdk import ( aws_sagemaker as sagemaker, core ) -
我们扩展
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) -
我们添加一个
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 ) -
我们添加一个
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 ) -
我们添加一个
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 ) -
最后,我们实例化应用程序:
app = core.App() SagemakerEndpoint( app, "SagemakerEndpoint", env={'region': 'eu-west-1'} ) app.synth()
我们的代码完成了!
部署 CDK 应用程序
我们现在可以部署端点:
-
我们可以列出可用的堆栈:
$ cdk list SagemakerEndpointEU -
我们还可以看到实际的 CloudFormation 模板。它应该与我们在前一节中编写的模板非常相似:
$ cdk synth SagemakerEndpointEU -
部署堆栈同样简单,如下图所示:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_11.jpg
图 12.11 – 部署端点
-
查看 CloudFormation,我们看到堆栈是使用变更集创建的。几分钟后,端点已投入使用。
-
编辑
app.py时,我们将初始实例数设置为2。然后我们要求 CDK 部署堆栈,但不执行变更集,如下图所示:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_12.jpg图 12.12 – 创建变更集
-
如果我们对变更集感到满意,可以在 CloudFormation 控制台中执行它,或者再次运行之前的命令,去掉
--no-execute。如预期所示,且如下图所示,端点已更新:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_13.jpg图 12.13 – 更新端点
-
当我们完成时,可以销毁堆栈:
$ 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 DynamoDB、Amazon EMR、AWS 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:
-
从 IAM 控制台开始 (
console.aws.amazon.com/iam/home#/roles),我们点击 创建角色。 -
我们选择 AWS 服务 和 Step Functions。
-
我们点击接下来的页面,直到可以输入角色名称。我们将其命名为
StepFunctionsWorkflowExecutionRole,然后点击 创建角色。 -
选择该角色后,我们点击它的 权限 标签页,再点击 添加内联策略。
-
选择 JSON 标签,我们用
Chapter12/step_functions/service-role-policy.json文件的内容替换空的策略,然后点击 查看策略。 -
我们将策略命名为
StepFunctionsWorkflowExecutionPolicy,然后点击 创建策略。 -
我们记下角色的 ARN,然后关闭 IAM 控制台。
设置现在已经完成。现在,让我们创建一个工作流。
实现我们的第一个工作流
在这个工作流中,我们将按以下步骤顺序进行:训练模型、创建模型、使用模型进行批量转换、创建端点配置,并将模型部署到端点:
-
我们将训练集上传到 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") -
我们像往常一样配置估算器:
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 } ) -
我们还定义了用于批量转换的 transformer:
sk_transformer = sk.transformer( instance_count=1, instance_type='ml.m5.large') -
我们导入工作流所需的 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 -
我们定义工作流的输入。我们将传递给它一个训练任务名称、一个模型名称和一个端点名称:
execution_input = ExecutionInput(schema={ 'JobName': str, 'ModelName': str, 'EndpointName': str} ) -
工作流的第一步是训练步骤。我们将其传递估算器、数据集在 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'] ) -
下一步是模型创建步骤。我们将其传递已在前一步中训练的模型位置和模型名称:
model_step = ModelStep( 'Create the model in SageMaker', model=training_step.get_expected_model(), model_name=execution_input['ModelName'] ) -
下一步是在测试数据集上运行批量转换。我们传递
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' ) -
下一步是创建端点配置:
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' ) -
最后一步是创建端点:
endpoint_step = EndpointStep( "Create an endpoint hosting the model", endpoint_name=execution_input['EndpointName'], endpoint_config_name=execution_input['ModelName'] ) -
现在,所有步骤都已经定义,我们按顺序将它们链起来:
workflow_definition = Chain([ training_step, model_step, transform_step, endpoint_config_step, endpoint_step ]) -
我们现在构建我们的工作流,使用工作流定义和输入定义:
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 ) -
我们可以可视化状态机,这是检查我们是否按预期构建它的一个简便方法,如下图所示:
workflow.render_graph(portrait=True)https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_14.jpg
图 12.14 – 查看状态机
-
我们创建工作流:
workflow.create() -
它可以在 Step Functions 控制台中查看,如下图所示。我们可以看到它的图形表示以及基于 Amazon States Language 的 JSON 定义。如果需要,我们也可以编辑工作流:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_15.jpg
图 12.15 – 在控制台中查看状态机
-
我们运行工作流:
execution = workflow.execute( inputs={ 'JobName': 'sklearn-boston-housing-{}' .format(timestamp), 'ModelName': 'sklearn-boston-housing-{}' .format(timestamp), 'EndpointName': 'sklearn-boston-housing-{}' .format(timestamp) } ) -
我们可以通过
render_progress()和list_events()API 跟踪其进度。我们也可以在控制台中看到它,如下图所示。请注意,我们还可以看到每个步骤的输入和输出,这对故障排除问题非常有帮助:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_16.jpg图 12.16 – 运行状态机
-
当工作流完成时,您可以像往常一样测试端点。完成后别忘了在 SageMaker 控制台删除它。
这个例子展示了使用这个 SDK 构建 SageMaker 工作流是多么简单。不过,我们可以通过让批量转换和端点创建并行运行来进一步改进它。
向工作流添加并行执行
下一张截图展示了我们将要构建的工作流。这些步骤本身完全相同,我们只是修改了它们的连接方式:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_17.jpg
图 12.17 – 查看并行状态机
我们将按照以下步骤开始:
-
我们的工作流有两个分支—一个用于批量转换,另一个用于端点:
batch_branch = Chain([ transform_step ]) endpoint_branch = Chain([ endpoint_config_step, endpoint_step ]) -
我们创建一个
Parallel步骤,以便允许这两个分支并行执行:parallel_step = Parallel('Parallel execution') parallel_step.add_branch(batch_branch) parallel_step.add_branch(endpoint_branch) -
我们将所有内容整合在一起:
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 查找更多信息:
-
我们首先为角色定义一个 信任策略,允许 Lambda 服务假设该角色:
{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" }] } -
我们创建一个角色并附加信任策略:
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'] -
我们定义一个列出允许 API 的策略:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sagemaker:DescribeEndpoint", "Resource": "*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "*" } ] } -
我们创建策略并将其添加到角色中:
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 控制台中的代码:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_18.jpg
图 12.18 – 我们的 Lambda 函数
让我们部署这个函数:
-
我们为 Lambda 函数创建一个部署包并将其上传到 S3:
$ zip -9 lambda.zip lambda.py $ aws s3 cp lambda.zip s3://my-bucket -
我们创建了一个超时为 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 ) -
现在 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 ]) -
再次运行工作流,我们在下面的截图中看到,新的步骤接收端点 ARN 作为输入,并等待端点处于服务状态:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_19.jpg
图 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 模板一样,你可以(并且应该)在工作流中定义参数。这样可以更方便地在其他项目中重用它们。参数可以是字符串、整数和浮点数,并可以设置一个可选的默认值。
-
我们为 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) -
我们还为输入数据的位置、模型名称以及在模型注册表中设置的模型状态创建了参数(稍后会详细介绍)。
input_data = ParameterString(name='InputData') model_name = ParameterString(name='ModelName') model_approval_status = ParameterString( name='ModelApprovalStatus', default_value='PendingManualApproval')
现在,让我们定义数据处理步骤。
使用 SageMaker Processing 处理数据集
我们重用了在 第六章 中编写的处理脚本(preprocessing.py)。
-
我们创建一个
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) -
然后,我们定义数据处理步骤。请记住,它会创建两个输出:一个是 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)。
-
我们首先为特征组定义一个名称:
feature_group_name = 'amazon-reviews-feature-group-' + strftime('%d-%H-%M-%S', gmtime()) -
然后,我们定义了一个处理步骤,将数据输入设置为第一个处理作业的输出。为了演示步骤链式处理,我们定义了一个输出,指向由脚本保存的文件,该文件包含特征组的名称:
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,]
)
现在,让我们继续进行训练步骤。
训练模型
没有意外:
-
我们为这个任务定义了一个
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') -
我们接着定义训练步骤,将训练集和验证集数据集作为输入:
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 模型并在模型注册表中注册它。
-
我们创建模型,传递训练容器和模型工件的位置:
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) -
然后,我们将模型注册到模型注册表中,传递允许的实例类型列表以供部署,以及审批状态。我们将其关联到一个模型包组,该组将保存此模型以及我们未来训练的更多版本:
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)
一切准备就绪。让我们运行管道吧!
运行管道
只需一行代码即可启动管道执行:
-
我们为数据位置和模型名称参数分配值(其他参数使用默认值):
execution = pipeline.start( parameters=dict( InputData=input_data_uri, ModelName='blazing-text-amazon-reviews') ) -
在 SageMaker Studio 中,我们进入 SageMaker 资源 / 管道,并看到管道正在执行,如下图所示:https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_20.jpg
图 12.20 – 执行管道
一个半小时后,管道完成,如下图所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_21.jpg
图 12.21 – 可视化管道
-
最后,对于管道中的每个步骤,我们都可以看到所有工件的血缘关系:
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))例如,训练步骤的输出在下图中展示。我们可以清楚地看到使用了哪些数据集和容器来训练模型:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_22.jpg
图 12.22 – 查看训练步骤的血缘关系
让我们看看如何部署这个模型。
从模型注册表部署模型
进入 SageMaker 资源 / 模型注册表,我们还可以看到该模型已经在模型注册表中注册,如下图所示。如果我们训练了更多版本的模型,它们也会出现在这里:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/lrn-amz-sgmk-2e/img/B17705_12_23.jpg
图 12.23 – 在模型注册表中查看模型
由于它的状态是 Pending,因此暂时无法部署。我们需要将其更改为 Approved,以便允许部署。这是一种安全的方式,可以确保只有经过所有适当测试的优质模型被部署。
我们右键点击模型并选择 Approved。我们还注意到模型 ARN,它在 设置 标签中是可见的。
现在,我们可以部署并测试模型:
-
在我们的 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) -
我们像往常一样调用
deploy():model.deploy( initial_instance_count = 1, instance_type = 'ml.t2.medium', endpoint_name='blazing-text-on-amazon-reviews') -
我们创建一个
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]}, -
一旦完成,我们可以删除终端节点。
注意
为了完全清理,你还应该删除管道、特征存储和模型包组。你可以在 GitHub 仓库中找到清理的笔记本。
如你所见,SageMaker Pipelines 为你提供了强大且高效的工具,帮助你构建、运行和跟踪端到端的机器学习工作流。这些工具在 SageMaker Studio 中得到了很好的集成,应该能帮助你提高生产力,并更快地将高质量模型投入生产。
总结
在这一章中,你首先学习了如何使用 AWS CloudFormation 部署和更新终端节点。你还看到了它如何用于实现金丝雀部署和蓝绿部署。
然后,你了解了 AWS CDK,这是一个专门为使用多种编程语言轻松生成和部署 CloudFormation 模板而构建的 SDK。
最后,你通过 AWS Step Functions 和 Amazon SageMaker Pipelines 构建了完整的端到端机器学习工作流。
在下一章也是最后一章中,你将了解更多关于 SageMaker 的功能,帮助你优化预测的成本和性能。
11万+

被折叠的 条评论
为什么被折叠?



