Best Practices und Entwurfsmuster zum Erstellen von Arbeitsabläufen für maschinelles Lernen mit Amazon SageMaker Pipelines | Amazon Web Services

Best Practices und Entwurfsmuster zum Erstellen von Arbeitsabläufen für maschinelles Lernen mit Amazon SageMaker Pipelines | Amazon Web Services

Amazon SageMaker-Pipelines ist ein vollständig verwalteter AWS-Service zum Erstellen und Orchestrieren von Arbeitsabläufen für maschinelles Lernen (ML). SageMaker Pipelines bietet ML-Anwendungsentwicklern die Möglichkeit, verschiedene Schritte des ML-Workflows zu orchestrieren, einschließlich Datenladen, Datentransformation, Schulung, Optimierung und Bereitstellung. Sie können SageMaker Pipelines verwenden, um ML-Jobs in SageMaker zu orchestrieren Integration in das größere AWS-Ökosystem ermöglicht Ihnen auch die Verwendung von Ressourcen wie AWS Lambda Funktionen, Amazon EMR Arbeitsplätze und mehr. Dadurch können Sie eine individuelle und reproduzierbare Pipeline für spezifische Anforderungen in Ihren ML-Workflows aufbauen.

In diesem Beitrag stellen wir einige Best Practices vor, um den Wert von SageMaker Pipelines zu maximieren und die Entwicklungserfahrung nahtlos zu gestalten. Wir diskutieren auch einige gängige Entwurfsszenarien und -muster beim Aufbau von SageMaker-Pipelines und stellen Beispiele für deren Bewältigung bereit.

Best Practices für SageMaker Pipelines

In diesem Abschnitt besprechen wir einige Best Practices, die beim Entwerfen von Workflows mit SageMaker Pipelines befolgt werden können. Ihre Einführung kann den Entwicklungsprozess verbessern und das Betriebsmanagement von SageMaker Pipelines rationalisieren.

Verwenden Sie Pipeline Session zum verzögerten Laden der Pipeline

Pipeline-Sitzung ermöglicht die verzögerte Initialisierung von Pipeline-Ressourcen (die Jobs werden erst zur Pipeline-Laufzeit gestartet). Der PipelineSession Kontext erbt die SageMaker-Sitzung und implementiert praktische Methoden für die Interaktion mit anderen SageMaker-Entitäten und -Ressourcen, z. B. Trainingsjobs, Endpunkten und Eingabedatensätzen Amazon Simple Storage-Service (Amazon S3) und so weiter. Beim Definieren von SageMaker-Pipelines sollten Sie verwenden PipelineSession über die reguläre SageMaker-Sitzung:

from sagemaker.workflow.pipeline_context import PipelineSession
from sagemaker.sklearn.processing import SKLearnProcessor
role = sagemaker.get_execution_role()
pipeline_session = PipelineSession()
sklearn_processor = SKLearnProcessor( framework_version=’0.20.0’, instance_type=’ml.m5.xlarge’, instance_count=1, base_job_name="sklearn-abalone-process", role=role, sagemaker_session=pipeline_session,
)

Führen Sie Pipelines im lokalen Modus aus, um kostengünstige und schnelle Iterationen während der Entwicklung zu ermöglichen

Sie können eine ausführen Pipeline im lokalen Modus Verwendung der LocalPipelineSession Kontext. In diesem Modus werden die Pipeline und die Jobs lokal unter Verwendung von Ressourcen auf dem lokalen Computer ausgeführt, statt der von SageMaker verwalteten Ressourcen. Der lokale Modus bietet eine kostengünstige Möglichkeit, den Pipeline-Code mit einer kleineren Teilmenge von Daten zu iterieren. Nachdem die Pipeline lokal getestet wurde, kann sie für die Ausführung skaliert werden Pipeline-Sitzung Kontext.

from sagemaker.sklearn.processing import SKLearnProcessor
from sagemaker.workflow.pipeline_context import LocalPipelineSession
local_pipeline_session = LocalPipelineSession()
role = sagemaker.get_execution_role()
sklearn_processor = SKLearnProcessor( framework_version=’0.20.0’, instance_type=’ml.m5.xlarge, instance_count=1, base_job_name="sklearn-abalone-process", role=role, sagemaker_session=local_pipeline_session,
)

Verwalten Sie eine SageMaker-Pipeline durch Versionierung

Die Versionierung von Artefakten und Pipelinedefinitionen ist eine häufige Anforderung im Entwicklungslebenszyklus. Sie können mehrere Versionen der Pipeline erstellen, indem Sie Pipeline-Objekte mit einem eindeutigen Präfix oder Suffix benennen. Am häufigsten wird ein Zeitstempel verwendet, wie im folgenden Code gezeigt:

from sagemaker.workflow.pipeline_context import PipelineSession
import time current_time = time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime())
pipeline_name = "pipeline_" + current_time
pipeline_session = PipelineSession()
pipeline = Pipeline( name=pipeline_name, steps=[step_process, step_train, step_eval, step_cond], sagemaker_session=pipeline_session,
)

Organisieren und verfolgen Sie SageMaker-Pipeline-Läufe durch die Integration mit SageMaker Experiments

SageMaker Pipelines können problemlos integriert werden SageMaker-Experimente zum Organisieren u Verfolgung von Pipeline-Ausführungen. Dies wird durch Spezifizierung erreicht PipelineExperimentConfig zum Zeitpunkt der Erstellung eines Pipeline-Objekt. Mit diesem Konfigurationsobjekt können Sie einen Experimentnamen und einen Testnamen angeben. Die Ausführungsdetails einer SageMaker-Pipeline werden unter dem angegebenen Experiment und Test organisiert. Wenn Sie keinen Experimentnamen explizit angeben, wird ein Pipelinename als Experimentname verwendet. Wenn Sie nicht explizit einen Testnamen angeben, wird entsprechend eine Pipeline-Ausführungs-ID für den Test- oder Ausführungsgruppennamen verwendet. Siehe den folgenden Code:

Pipeline( name="MyPipeline", parameters=[...], pipeline_experiment_config=PipelineExperimentConfig( experiment_name = ExecutionVariables.PIPELINE_NAME, trial_name = ExecutionVariables.PIPELINE_EXECUTION_ID ), steps=[...]
)

Führen Sie SageMaker-Pipelines sicher in einer privaten VPC aus

Um die ML-Workloads zu sichern, empfiehlt es sich, die von SageMaker Pipelines orchestrierten Jobs in einer sicheren Netzwerkkonfiguration innerhalb einer privaten VPC, privaten Subnetzen und Sicherheitsgruppen bereitzustellen. Um die Nutzung dieser sicheren Umgebung sicherzustellen und durchzusetzen, können Sie Folgendes implementieren AWS Identity and Access Management and (IAM)-Richtlinie für die SageMaker-Ausführungsrolle (Dies ist die Rolle, die die Pipeline während ihres Laufs übernimmt). Sie können auch die Richtlinie hinzufügen, um die von SageMaker Pipelines orchestrierten Jobs im Netzwerkisolationsmodus auszuführen.

# IAM Policy to enforce execution within a private VPC { "Action": [ "sagemaker:CreateProcessingJob", "sagemaker:CreateTrainingJob", "sagemaker:CreateModel" ], "Resource": "*", "Effect": "Deny", "Condition": { "Null": { "sagemaker:VpcSubnets": "true" } }
} # IAM Policy to enforce execution in network isolation mode
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Action": [ "sagemaker:Create*" ], "Resource": "*", "Condition": { "StringNotEqualsIfExists": { "sagemaker:NetworkIsolation": "true" } } } ]
}

Ein Beispiel für die Pipeline-Implementierung mit diesen Sicherheitskontrollen finden Sie unter Orchestrieren Sie Jobs, Modellregistrierung und kontinuierliche Bereitstellung mit Amazon SageMaker in einer sicheren Umgebung.

Überwachen Sie die Kosten von Pipelineläufen mithilfe von Tags

Die Nutzung der SageMaker-Pipelines allein ist kostenlos; Sie zahlen für die Rechen- und Speicherressourcen, die Sie im Rahmen der einzelnen Pipeline-Schritte wie Verarbeitung, Training und Batch-Inferenz bereitstellen. Um die Kosten pro Pipeline-Durchlauf zu aggregieren, können Sie Folgendes einbeziehen: Tags in jedem Pipelineschritt, der eine Ressource erstellt. Auf diese Tags kann dann im Kosten-Explorer verwiesen werden, um die gesamten Pipeline-Laufkosten zu filtern und zu aggregieren, wie im folgenden Beispiel gezeigt:

sklearn_processor = SKLearnProcessor( framework_version=’0.20.0’, instance_type=’ml.m5.xlarge, instance_count=1, base_job_name="sklearn-abalone-process", role=role, tags=[{'Key':'pipeline-cost-tag', 'Value':'<<tag_parameter>>'}]
) step_process = ProcessingStep( name="AbaloneProcess", processor=sklearn_processor, ...
)

Im Kosten-Explorer können Sie nun die Kosten gefiltert nach folgendem Tag abrufen:

response = client.get_cost_and_usage( TimePeriod={ 'Start': '2023-07-01', 'End': '2023-07-15' }, Metrics=['BLENDED_COST','USAGE_QUANTITY','UNBLENDED_COST'], Granularity='MONTHLY', Filter={ 'Dimensions': { 'Key':'USAGE_TYPE', 'Values': [ ‘SageMaker:Pipeline’ ] }, 'Tags': { 'Key': 'keyName', 'Values': [ 'keyValue', ] } }
)

Entwurfsmuster für einige gängige Szenarien

In diesem Abschnitt besprechen wir Entwurfsmuster für einige häufige Anwendungsfälle mit SageMaker Pipelines.

Führen Sie eine einfache Python-Funktion mit einem Lambda-Schritt aus

Python-Funktionen sind in ML-Workflows allgegenwärtig; Sie werden in der Vorverarbeitung, Nachverarbeitung, Auswertung und mehr verwendet. Lambda ist ein serverloser Rechendienst, mit dem Sie Code ausführen können, ohne Server bereitzustellen oder zu verwalten. Mit Lambda können Sie Code in Ihrer bevorzugten Sprache ausführen, die Python enthält. Damit können Sie benutzerdefinierten Python-Code als Teil Ihrer Pipeline ausführen. Ein Lambda-Schritt ermöglicht Ihnen die Ausführung von Lambda-Funktionen als Teil Ihrer SageMaker-Pipeline. Beginnen Sie mit dem folgenden Code:

%%writefile lambdafunc.py import json def lambda_handler(event, context): str1 = event["str1"] str2 = event["str2"] str3 = str1 + str2 return { "str3": str3 }

Erstellen Sie die Lambda-Funktion mit der Der Lambda-Helfer des SageMaker Python SDK:

from sagemaker.lambda_helper import Lambda def create_lambda(function_name, script, handler): response = Lambda( function_name=function_name, execution_role_arn=role, script= script, handler=handler, timeout=600, memory_size=10240, ).upsert() function_arn = response['FunctionArn'] return function_arn fn_arn = create_Lambda("func", "lambdafunc.py", handler = "lambdafunc.lambda_handler")

Rufen Sie den Lambda-Schritt auf:

from sagemaker.lambda_helper import Lambda
from sagemaker.workflow.lambda_step import ( LambdaStep, LambdaOutput, LambdaOutputTypeEnum
) str3 = LambdaOutput(output_name="str3", output_type=LambdaOutputTypeEnum.String) # Lambda Step
step_lambda1 = LambdaStep( name="LambdaStep1", lambda_func=Lambda( function_arn=fn_arn ), inputs={ "str1": "Hello", "str2": " World" }, outputs=[str3],
)

Übergeben Sie Daten zwischen Schritten

Eingabedaten für einen Pipeline-Schritt sind entweder ein zugänglicher Datenspeicherort oder Daten, die von einem der vorherigen Schritte in der Pipeline generiert wurden. Sie können diese Informationen als angeben ProcessingInput Parameter. Schauen wir uns einige Szenarien an, wie Sie ProcessingInput verwenden können.

Szenario 1: Übergeben Sie die Ausgabe (primitive Datentypen) eines Lambda-Schritts an einen Verarbeitungsschritt

Primitive Datentypen beziehen sich auf skalare Datentypen wie String, Integer, Boolean und Float.

Der folgende Codeausschnitt definiert eine Lambda-Funktion, die ein Wörterbuch von Variablen mit primitiven Datentypen zurückgibt. Ihr Lambda-Funktionscode gibt einen JSON-Code mit Schlüssel-Wert-Paaren zurück, wenn er vom Lambda-Schritt innerhalb der SageMaker-Pipeline aufgerufen wird.

def handler(event, context): ... return { "output1": "string_value", "output2": 1, "output3": True, "output4": 2.0, }

In der Pipeline-Definition können Sie dann SageMaker-Pipeline-Parameter definieren, die einen bestimmten Datentyp haben, und die Variable auf die Ausgabe der Lambda-Funktion festlegen:

from sagemaker.workflow.lambda_step import ( LambdaStep, LambdaOutput, LambdaOutputTypeEnum
)
from sagemaker.workflow.pipeline_context import PipelineSession
from sagemaker.sklearn.processing import SKLearnProcessor role = sagemaker.get_execution_role()
pipeline_session = PipelineSession() # 1. Define the output params of the Lambda Step str_outputParam = LambdaOutput(output_name="output1", output_type=LambdaOutputTypeEnum.String)
int_outputParam = LambdaOutput(output_name"output2", output_type=LambdaOutputTypeEnum.Integer)
bool_outputParam = LambdaOutput(output_name"output3", output_type=LambdaOutputTypeEnum.Boolean)
float_outputParam = LambdaOutput(output_name"output4", output_type=LambdaOutputTypeEnum.Float) # 2. Lambda step invoking the lambda function and returns the Output step_lambda = LambdaStep( name="MyLambdaStep", lambda_func=Lambda( function_arn="arn:aws:lambda:us-west-2:123456789012:function:sagemaker_test_lambda", session=PipelineSession(), ), inputs={"arg1": "foo", "arg2": "foo1"}, outputs=[ str_outputParam, int_outputParam, bool_outputParam, float_outputParam ],
) # 3. Extract the output of the Lambda str_outputParam = step_lambda.properties.Outputs["output1"] # 4. Use it in a subsequent step. For ex. Processing step sklearn_processor = SKLearnProcessor( framework_version="0.23-1", instance_type="ml.m5.xlarge", instance_count=1, sagemaker_session=pipeline_session, role=role
) processor_args = sklearn_processor.run( code="code/preprocess.py", #python script to run arguments=["--input-args", str_outputParam]
) step_process = ProcessingStep( name="processstep1", step_args=processor_args,
)

Szenario 2: Übergeben Sie die Ausgabe (nicht-primitive Datentypen) eines Lambda-Schritts an einen Verarbeitungsschritt

Nicht-primitive Datentypen beziehen sich auf nicht-skalare Datentypen (z. B. NamedTuple). Es kann vorkommen, dass Sie einen nicht-primitiven Datentyp von einer Lambda-Funktion zurückgeben müssen. Dazu müssen Sie Ihren nicht-primitiven Datentyp in einen String konvertieren:

# Lambda function code returning a non primitive data type from collections import namedtuple def lambda_handler(event, context): Outputs = namedtuple("Outputs", "sample_output") named_tuple = Outputs( [ {'output1': 1, 'output2': 2}, {'output3': 'foo', 'output4': 'foo1'} ] )
return{ "named_tuple_string": str(named_tuple)
}

#Pipeline step that uses the Lambda output as a “Parameter Input” output_ref = step_lambda.properties.Outputs["named_tuple_string"]

Anschließend können Sie diese Zeichenfolge als Eingabe für einen nachfolgenden Schritt in der Pipeline verwenden. Um das benannte Tupel im Code zu verwenden, verwenden Sie eval() So analysieren Sie den Python-Ausdruck in der Zeichenfolge:

# Decipher the string in your processing logic code import argparse
from collections import namedtuple Outputs = namedtuple("Outputs", "sample_output") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--named_tuple_string", type=str, required=True) args = parser.parse_args() #use eval to obtain the named tuple from the string named_tuple = eval(args.named_tuple_string)

Szenario 3: Übergeben Sie die Ausgabe eines Schritts über eine Eigenschaftendatei

Sie können die Ausgabe eines Verarbeitungsschritts auch in einem speichern Eigenschaft JSON-Datei für den nachgelagerten Verbrauch in a ConditionStep oder ein anderes ProcessingStep. Sie können die JSONGet-Funktion a abfragen Eigenschaftsdatei. Siehe folgenden Code:

# 1. Define a Processor with a ProcessingOutput
sklearn_processor = SKLearnProcessor( framework_version="0.23-1", instance_type="ml.m5.xlarge", instance_count=1, base_job_name="sklearn-abalone-preprocess", sagemaker_session=session, role=sagemaker.get_execution_role(),
) step_args = sklearn_processor.run( outputs=[ ProcessingOutput( output_name="hyperparam", source="/opt/ml/processing/evaluation" ), ], code="./local/preprocess.py", arguments=["--input-data", "s3://my-input"],
) # 2. Define a PropertyFile where the output_name matches that with the one used in the Processor

hyperparam_report = PropertyFile( name="AbaloneHyperparamReport", output_name="hyperparam", path="hyperparam.json",
)

Nehmen wir an, der Inhalt der Eigenschaftendatei sei wie folgt:

{ "hyperparam": { "eta": { "value": 0.6 } }
}

In diesem Fall kann er mit der JsonGet-Funktion nach einem bestimmten Wert abgefragt und in nachfolgenden Schritten verwendet werden:

# 3. Query the property file
eta = JsonGet( step_name=step_process.name, property_file=hyperparam_report, json_path="hyperparam.eta.value",
)

Parametrisieren Sie eine Variable in der Pipeline-Definition

Oft ist es wünschenswert, Variablen so zu parametrisieren, dass sie zur Laufzeit verwendet werden können, beispielsweise um einen S3-URI zu erstellen. Sie können einen String so parametrisieren, dass er zur Laufzeit ausgewertet wird Join Funktion. Der folgende Codeausschnitt zeigt, wie die Variable mithilfe von definiert wird Join Funktion und verwenden Sie diese, um den Ausgabeort in einem Verarbeitungsschritt festzulegen:

# define the variable to store the s3 URI
s3_location = Join( on="/", values=[ "s3:/", ParameterString( name="MyBucket", default_value="" ), "training", ExecutionVariables.PIPELINE_EXECUTION_ID ]
) # define the processing step
sklearn_processor = SKLearnProcessor( framework_version="1.2-1", instance_type="ml.m5.xlarge", instance_count=processing_instance_count, base_job_name=f"{base_job_prefix}/sklearn-abalone-preprocess", sagemaker_session=pipeline_session, role=role,
) # use the s3uri as the output location in processing step
processor_run_args = sklearn_processor.run( outputs=[ ProcessingOutput( output_name="train", source="/opt/ml/processing/train", destination=s3_location, ), ], code="code/preprocess.py"
) step_process = ProcessingStep( name="PreprocessingJob”, step_args=processor_run_args,
)

Führen Sie parallelen Code über eine Iterable aus

Einige ML-Workflows führen Code in parallelen for-Schleifen über einen statischen Satz von Elementen aus (eine wiederholbar). Dabei kann es sich entweder um denselben Code handeln, der für unterschiedliche Daten ausgeführt wird, oder um einen anderen Codeabschnitt, der für jedes Element ausgeführt werden muss. Wenn Sie beispielsweise eine sehr große Anzahl von Zeilen in einer Datei haben und die Verarbeitungszeit beschleunigen möchten, können Sie auf das erstere Muster zurückgreifen. Wenn Sie unterschiedliche Transformationen für bestimmte Untergruppen in den Daten durchführen möchten, müssen Sie möglicherweise für jede Untergruppe in den Daten einen anderen Code ausführen. Die folgenden zwei Szenarien veranschaulichen, wie Sie SageMaker-Pipelines für diesen Zweck entwerfen können.

Szenario 1: Implementieren Sie eine Verarbeitungslogik für verschiedene Datenteile

Sie können einen Verarbeitungsjob mit mehreren Instanzen ausführen (durch Einstellung). instance_count auf einen Wert größer als 1). Dadurch werden die Eingabedaten von Amazon S3 auf alle Verarbeitungsinstanzen verteilt. Sie können dann ein Skript (process.py) verwenden, um einen bestimmten Teil der Daten basierend auf der Instanznummer und dem entsprechenden Element in der Liste der Elemente zu bearbeiten. Die Programmierlogik in „process.py“ kann so geschrieben werden, dass abhängig von der Liste der verarbeiteten Elemente ein anderes Modul oder ein anderer Code ausgeführt wird. Das folgende Beispiel definiert einen Prozessor, der in einem ProcessingStep verwendet werden kann:

sklearn_processor = FrameworkProcessor( estimator_cls=sagemaker.sklearn.estimator.SKLearn, framework_version="0.23-1", instance_type='ml.m5.4xlarge', instance_count=4, #number of parallel executions / instances base_job_name="parallel-step", sagemaker_session=session, role=role,
) step_args = sklearn_processor.run( code='process.py', arguments=[ "--items", list_of_items, #data structure containing a list of items inputs=[ ProcessingInput(source="s3://sagemaker-us-east-1-xxxxxxxxxxxx/abalone/abalone-dataset.csv", destination="/opt/ml/processing/input" ) ], ]
)

Szenario 2: Führen Sie eine Folge von Schritten aus

Wenn Sie eine Abfolge von Schritten haben, die parallel ausgeführt werden müssen, können Sie jede Abfolge als unabhängige SageMaker-Pipeline definieren. Die Ausführung dieser SageMaker-Pipelines kann dann von einer Lambda-Funktion ausgelöst werden, die Teil einer ist LambdaStep in der übergeordneten Pipeline. Der folgende Codeabschnitt veranschaulicht das Szenario, in dem zwei verschiedene SageMaker-Pipeline-Ausführungen ausgelöst werden:

import boto3
def lambda_handler(event, context): items = [1, 2] #sagemaker client sm_client = boto3.client("sagemaker") #name of the pipeline that needs to be triggered. #if there are multiple, you can fetch available pipelines using boto3 api #and trigger the appropriate one based on your logic. pipeline_name = 'child-pipeline-1' #trigger pipeline for every item response_ppl = sm_client.start_pipeline_execution( PipelineName=pipeline_name, PipelineExecutionDisplayName=pipeline_name+'-item-%d' %(s), ) pipeline_name = 'child-pipeline-2' response_ppl = sm_client.start_pipeline_execution( PipelineName=pipeline_name, PipelineExecutionDisplayName=pipeline_name+'-item-%d' %(s), )
return

Zusammenfassung

In diesem Beitrag haben wir einige Best Practices für die effiziente Nutzung und Wartung von SageMaker-Pipelines besprochen. Wir haben auch bestimmte Muster bereitgestellt, die Sie beim Entwerfen von Workflows mit SageMaker Pipelines übernehmen können, unabhängig davon, ob Sie neue Pipelines erstellen oder ML-Workflows von anderen Orchestrierungstools migrieren. Informationen zu den ersten Schritten mit SageMaker Pipelines für die ML-Workflow-Orchestrierung finden Sie im Codebeispiele auf GitHub und Amazon SageMaker-Modellerstellungspipelines.


Über die Autoren

Best Practices und Entwurfsmuster zum Erstellen von Arbeitsabläufen für maschinelles Lernen mit Amazon SageMaker Pipelines | Amazon Web Services PlatoBlockchain Data Intelligence. Vertikale Suche. Ai.Pinak Panigrahi arbeitet mit Kunden zusammen, um auf maschinellem Lernen basierende Lösungen zu entwickeln, um strategische Geschäftsprobleme auf AWS zu lösen. Wenn er sich nicht gerade mit maschinellem Lernen beschäftigt, kann man ihn beim Wandern, beim Lesen eines Buches oder beim Sportschauen antreffen.

Best Practices und Entwurfsmuster zum Erstellen von Arbeitsabläufen für maschinelles Lernen mit Amazon SageMaker Pipelines | Amazon Web Services PlatoBlockchain Data Intelligence. Vertikale Suche. Ai.Meenakshisundaram Thandavarayan arbeitet für AWS als AI/ML-Spezialist. Er hat eine Leidenschaft dafür, menschenzentrierte Daten- und Analyseerlebnisse zu entwerfen, zu erstellen und zu fördern. Meena konzentriert sich auf die Entwicklung nachhaltiger Systeme, die messbare Wettbewerbsvorteile für strategische Kunden von AWS bieten. Meena ist eine Konnektorin, Design-Denkerin und bestrebt, Unternehmen durch Innovation, Inkubation und Demokratisierung zu neuen Arbeitsweisen zu führen.

Zeitstempel:

Mehr von AWS Maschinelles Lernen