29、自动化机器学习工作流:从AWS CDK到Step Functions

自动化机器学习工作流:从AWS CDK到Step Functions

1. 使用AWS CDK自动化

AWS CDK是一个多语言SDK,允许你通过编写代码来定义AWS基础设施(https://github.com/aws/aws-cdk )。借助CDK CLI,你可以在底层使用CloudFormation来配置这些基础设施。

1.1 安装CDK

CDK原生使用Node.js实现,因此请确保你的机器上安装了npm工具(https://www.npmjs.com/get-npm )。安装CDK非常简单,只需执行以下命令:

$ npm i -g aws-cdk
$ cdk --version
1.114.0 (build 7e41b6b)

1.2 创建CDK应用程序

我们将部署与使用CloudFormation部署的相同模型。这里使用Python,当然你也可以使用JavaScript、TypeScript、Java和.NET。API文档可在https://docs.aws.amazon.com/cdk/api/latest/python/ 查看。具体步骤如下:
1. 创建一个名为endpoint的Python应用程序:

$ mkdir cdk
$ cd cdk
$ cdk init --language python --app endpoint
  1. 这将自动创建一个虚拟环境,我们需要激活它:
$ source .venv/bin/activate
  1. 同时会创建一个默认的app.py文件用于编写CDK代码,一个cdk.json文件用于应用程序配置,以及一个requirements.txt文件用于安装依赖项。我们将使用GitHub仓库中的文件。
  2. 在requirements.txt文件中,安装S3和SageMaker的CDK包。每个服务需要不同的包,例如,为S3添加aws_cdk.aws_s3:
-e .
aws_cdk.aws_s3
aws_cdk.aws_sagemaker
  1. 像往常一样安装依赖项:
$ pip install -r requirements.txt
  1. 在cdk.json文件中,存储应用程序上下文,即应用程序可以读取的键值对,用于配置(https://docs.aws.amazon.com/cdk/latest/guide/context.html ):
{
  "app": "python3 app.py",
  "context": {
    "role_arn": "arn:aws:iam::123456789012:role/Sagemaker-fullaccess",
    "model_name": "tf2-fmnist",
    "epc_name": "tf2-fmnist-epc",
    "ep_name": "tf2-fmnist-ep",
    "image": "763104351884.dkr.ecr.us-east-1.amazonaws.com/tensorflow-inference:2.1-cpu",
    "model_data_url": "s3://sagemaker-us-east-1-123456789012/keras2-fashion-mnist/output/tensorflow-training-2020-06-08-07-46-04-367/output/model.tar.gz",
    "instance_type": "ml.t2.xlarge",
    "instance_count": 1
  }
}

这是向应用程序传递值的首选方式。你应该使用版本控制来管理此文件,以便跟踪堆栈的构建方式。
7. 可以使用 cdk context 命令查看应用程序的上下文。

1.3 编写CDK应用程序

所有代码都放在app.py文件中,具体实现步骤如下:
1. 导入所需的包:

import time
from aws_cdk import (
    aws_sagemaker as sagemaker,
    core
)
  1. 扩展 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)
  1. 添加一个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
)
  1. 添加一个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
)
  1. 添加一个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
)
  1. 最后,实例化应用程序:
app = core.App()
SagemakerEndpoint(
    app, 
    "SagemakerEndpoint", 
    env={'region': 'eu-west-1'}
)
app.synth()

1.4 部署CDK应用程序

现在可以部署端点了,具体步骤如下:
1. 列出可用的堆栈:

$ cdk list
SagemakerEndpointEU
  1. 查看实际的CloudFormation模板,它应该与上一节中编写的模板非常相似:
$ cdk synth SagemakerEndpointEU
  1. 部署堆栈同样简单。
  2. 查看CloudFormation,会发现堆栈是使用变更集创建的。几分钟后,端点即可投入使用。
  3. 编辑app.py,将初始实例计数设置为2。然后要求CDK部署堆栈,但不执行变更集。
  4. 如果你对变更集满意,可以在CloudFormation控制台中执行它,或者再次运行之前的命令,但不使用 --no-execute 。如预期的那样,端点将被更新。
  5. 完成后,可以销毁堆栈:
$ cdk destroy SagemakerEndpointEU

1.5 工作流示意图

graph LR
    A[安装CDK] --> B[创建CDK应用程序]
    B --> C[编写CDK应用程序]
    C --> D[部署CDK应用程序]

2. 使用AWS Step Functions构建端到端工作流

AWS Step Functions允许你基于状态机定义和运行工作流(https://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(https://github.com/aws/aws-step-functions-data-science-sdk-python )。

2.1 设置权限

首先,请确保你的用户或笔记本实例的IAM角色具有调用Step Functions API的权限。如果没有,请将 AWSStepFunctionsFullAccess 托管策略添加到该角色。

然后,需要为Step Functions创建一个服务角色,允许它代表我们调用AWS API,具体步骤如下:
1. 从IAM控制台(https://console.aws.amazon.com/iam/home#/roles )开始,点击“Create role”。
2. 选择AWS服务和Step Functions。
3. 点击后续屏幕,直到可以输入角色名称。将其命名为 StepFunctionsWorkflowExecutionRole ,然后点击“Create role”。
4. 选择此角色,点击其“Permission”选项卡,然后点击“Add inline policy”。
5. 选择JSON选项卡,将空策略替换为 Chapter12/step_functions/service-role-policy.json 文件的内容,然后点击“Review policy”。
6. 将策略命名为 StepFunctionsWorkflowExecutionPolicy ,然后点击“Create policy”。
7. 记录角色的ARN,然后关闭IAM控制台。

2.2 实现第一个工作流

在这个工作流中,我们将按以下步骤顺序进行:训练模型、创建模型、使用模型进行批量转换、创建端点配置,并将模型部署到端点。具体步骤如下:
1. 将训练集和去除目标属性的测试集上传到S3,后者用于批量转换:

import sagemaker
import pandas as pd
sess = sagemaker.Session()
bucket = sess.default_bucket()   
prefix = 'sklearn-boston-housing-stepfunc'
training_data = sess.upload_data(
    path='housing.csv', 
    key_prefix=prefix + "/training")
data = pd.read_csv('housing.csv')
data.drop(['medv'], axis=1, inplace=True)
data.to_csv('test.csv', index=False, header=False)
batch_data = sess.upload_data(
    path='test.csv', 
    key_prefix=prefix + "/batch")
  1. 像往常一样配置估算器:
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
    }
)
  1. 定义用于批量转换的转换器:
sk_transformer = sk.transformer(
    instance_count=1,
    instance_type='ml.m5.large')
  1. 导入工作流所需的Step Functions对象。API文档可在https://aws-step-functions-data-science-sdk.readthedocs.io/en/latest/ 查看:
import stepfunctions
from stepfunctions import steps
from stepfunctions.steps import TrainingStep, ModelStep, TransformStep
from stepfunctions.inputs import ExecutionInput
from stepfunctions.workflow import Workflow
  1. 定义工作流的输入,传递训练作业名称、模型名称和端点名称:
execution_input = ExecutionInput(schema={
    'JobName': str,
    'ModelName': str,
    'EndpointName': str}
)
  1. 工作流的第一步是训练步骤,传递估算器、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']
)
  1. 下一步是模型创建步骤,传递上一步训练的模型的位置和模型名称:
model_step = ModelStep(
    'Create the model in SageMaker',
    model=training_step.get_expected_model(),
    model_name=execution_input['ModelName']
)
  1. 接下来是对测试数据集进行批量转换的步骤,传递转换器对象、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'
)
  1. 下一步是创建端点配置:
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'
)
  1. 最后一步是创建端点:
endpoint_step = EndpointStep(
    "Create an endpoint hosting the model",
    endpoint_name=execution_input['EndpointName'],
    endpoint_config_name=execution_input['ModelName']
)
  1. 定义好所有步骤后,按顺序将它们链接起来:
workflow_definition = Chain([
    training_step,
    model_step,
    transform_step,
    endpoint_config_step,
    endpoint_step
])
  1. 使用工作流定义和输入定义构建工作流:
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
)
  1. 可视化状态机,这是检查是否按预期构建的简单方法:
workflow.render_graph(portrait=True)
  1. 创建工作流:
workflow.create()
  1. 在Step Functions控制台中可以看到它,既可以看到其图形表示,也可以看到基于Amazon States Language的JSON定义。如有需要,还可以编辑工作流。
  2. 运行工作流:
execution = workflow.execute(
    inputs={
        'JobName': 'sklearn-boston-housing-{}'.format(timestamp),
        'ModelName': 'sklearn-boston-housing-{}'.format(timestamp),
        'EndpointName': 'sklearn-boston-housing-{}'.format(timestamp)
    }
)
  1. 可以使用 render_progress() list_events() API跟踪其进度,也可以在控制台中查看。注意,还可以看到每个步骤的输入和输出,这是排查问题的好方法。
  2. 工作流完成后,可以像往常一样测试端点。完成后,别忘了在SageMaker控制台中删除它。

2.3 工作流步骤表格

步骤 描述
训练步骤 使用估算器在指定数据集上进行训练
模型创建步骤 根据训练结果创建模型
批量转换步骤 对测试数据集进行批量转换
端点配置步骤 创建端点配置
端点创建步骤 将模型部署到端点

2.4 工作流示意图

graph LR
    A[设置权限] --> B[实现工作流]
    B --> C[训练模型]
    C --> D[创建模型]
    D --> E[批量转换]
    E --> F[创建端点配置]
    F --> G[部署到端点]

3. 为工作流添加并行执行

接下来要构建的工作流,其步骤与之前相同,只是修改了步骤的链接方式。

3.1 分支定义

工作流有两个分支,一个用于批量转换,一个用于端点:

batch_branch = Chain([
    transform_step
])
endpoint_branch = Chain([
    endpoint_config_step,
    endpoint_step
]) 

3.2 创建并行步骤

创建一个并行步骤,以允许这两个分支并行执行:

parallel_step = Parallel('Parallel execution')
parallel_step.add_branch(batch_branch)
parallel_step.add_branch(endpoint_branch)

3.3 整合工作流

将所有步骤整合在一起:

workflow_definition = Chain([
    training_step,
    model_step,
    parallel_step
])

现在可以像之前的示例一样创建和运行这个工作流。查看Step Functions控制台,会发现工作流确实会并行运行这两个分支。不过存在一个小问题,端点创建步骤显示已完成,但端点仍在创建中。在SageMaker控制台中可以看到端点状态为“Creating”,如果客户端应用程序在工作流完成后立即尝试调用端点,可能会出现问题。

3.4 并行工作流示意图

graph LR
    A[训练步骤] --> B[模型创建步骤]
    B --> C{并行步骤}
    C --> D[批量转换分支]
    C --> E[端点创建分支]

4. 向工作流添加Lambda函数

AWS Lambda是无服务器架构的核心,你可以编写和部署在完全托管的基础设施上运行的短函数。这些函数可以由各种AWS事件触发,也可以按需调用。

4.1 设置权限

创建Lambda函数的前提是创建一个执行角色,即一个IAM角色,该角色授予函数调用其他AWS服务的权限。这里只需要 DescribeEndpoint API的权限以及在CloudWatch中创建日志的权限。使用boto3 API进行操作,具体步骤如下:
1. 定义角色的信任策略,允许Lambda服务承担该角色:

{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {
            "Service": "lambda.amazonaws.com"
        },
        "Action": "sts:AssumeRole"
    }]
}
  1. 创建角色并附加信任策略:
import boto3
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']
  1. 定义允许的API列表的策略:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sagemaker:DescribeEndpoint",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
}
  1. 创建策略并将其添加到角色:
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
)

4.2 编写Lambda函数

编写一个短的Lambda函数,它接收一个JSON事件作为输入,该事件存储了由 EndpointStep 步骤创建的端点的ARN。函数从ARN中提取端点名称,创建一个boto3等待器,并等待端点投入使用。

# lambda.py
import boto3

def lambda_handler(event, context):
    endpoint_arn = event['EndpointArn']
    endpoint_name = endpoint_arn.split('/')[-1]
    sagemaker_client = boto3.client('sagemaker')
    waiter = sagemaker_client.get_waiter('endpoint_in_service')
    waiter.wait(EndpointName=endpoint_name)
    return {
        'statusCode': 200,
        'body': f'Endpoint {endpoint_name} is in service.'
    }

4.3 部署Lambda函数

  1. 创建Lambda函数的部署包并上传到S3:
$ zip -9 lambda.zip lambda.py
$ aws s3 cp lambda.zip s3://my-bucket
  1. 创建函数,设置超时时间为15分钟(Lambda函数的最长运行时间):
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
)

4.4 将Lambda函数添加到工作流

定义一个Lambda步骤并将其添加到端点分支,其有效负载是从 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作为输入,并等待端点投入使用。

4.5 包含Lambda函数的工作流示意图

graph LR
    A[训练步骤] --> B[模型创建步骤]
    B --> C{并行步骤}
    C --> D[批量转换分支]
    C --> E[端点创建分支]
    E --> F[等待端点就绪(Lambda)]

通过以上步骤,我们可以看到如何利用AWS CDK和Step Functions实现机器学习工作流的自动化,并且可以根据需求灵活调整工作流的执行方式,如添加并行执行和使用Lambda函数进行额外的控制和等待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值