如何使用 SageMaker Estimator 进行模型训练和保存

mon*_*mon 34 amazon-web-services amazon-sagemaker

有关如何使用 SageMaker 估算器的文档分散在各处,有时甚至过时、不正确。是否有一站式位置可以全面介绍如何使用 SageMaker SDK Estimator 训练和保存模型?

mon*_*mon 111

回答

\n

AWS 中没有任何此类资源可以全面介绍如何使用 SageMaker SDK Estimator 来训练和保存模型。

\n

替代概览图

\n

我提供了一个图表和简要说明,以概述 SageMaker Estimator 如何运行训练。

\n
    \n
  1. SageMaker 为训练作业设置一个 docker 容器,其中:

    \n\n
  2. \n
\n
/opt/ml\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 input\n\xe2\x94\x82   \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 config\n\xe2\x94\x82   \xe2\x94\x82   \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 hyperparameters.json  <--- From Estimator hyperparameter arg\n\xe2\x94\x82   \xe2\x94\x82   \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 resourceConfig.json\n\xe2\x94\x82   \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 data\n\xe2\x94\x82       \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 <channel_name>        <--- From Estimator fit method inputs arg\n\xe2\x94\x82           \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 <input data>\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 code\n\xe2\x94\x82   \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 <code files>              <--- From Estimator src_dir arg\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 model\n\xe2\x94\x82   \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 <model files>             <--- Location to save the trained model artifacts\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 output\n    \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 failure                   <--- Training job failure logs\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. SageMaker Estimatorfit(inputs)方法执行训练脚本。估计器hyperparametersfit方法inputs作为其命令行参数提供。

    \n
  2. \n
  3. /opt/ml/model训练脚本会在训练完成后保存模型工件。

    \n
  4. \n
  5. SageMaker 将工件归档/opt/ml/modelmodel.tar.gz并保存到output_pathEstimator 参数指定的 S3 位置。

    \n
  6. \n
  7. 您可以设置 Estimatormetric_definitions参数以从训练日志中提取模型指标。然后,您可以在 SageMaker 控制台指标中监控训练进度。

    \n
  8. \n
\n

在此输入图像描述

\n

我认为 AWS 需要停止大量生产冗长、冗余、冗长、分散和过时的文档。AWS 需要理解一图胜千言

\n

在上下文中绘制图表并将文档各个部分组合在一起,以实现明确的目标。

\n
\n

问题

\n

AWS文档充其量是无法使用的。他们必须停止将文档任务交给技术作家。

\n

AWS 文档需要认真的重新设计和重组。仅仅为了了解如何训练和保存模型,我们就必须查看数十个分散的、支离破碎的、冗长的、冗余的文档,这些文档通常是过时的、不完整的,有时甚至是不正确的。

\n

为什么我认为 GCP 比 AWS 更好,对此进行了很好的总结:

\n
\n

并不是AWS比GCP难用,而是它不必要地难;基础设施原语杂乱无序,相互之间缺乏凝聚力。

\n有挑战是好的,令人困惑的混乱则不然,AWS 的问题是你的大部分工作时间将花在理清他们的文档并筛选功能和产品上以找到你想要的东西,而不是专注于酷有趣的东西挑战。

\n
\n

特别是 SageMaker 团队不断更改实施而不更新文档。它的推出也不一致,例如,SDK 版本 2 在 SageMaker Studio 中推出,使得 Github 中的 AWS 示例不兼容,而没有宣布。而 SageMaker 实例仍然具有 SDK 1,因此代码可以在 Instance 中运行,但不能在 Studio 中运行。

\n

令人难以置信的是,我们必须浏览这些杂乱无章的冗余页面,并且无法期望其准确性,才能弄清楚如何使用 SageMaker SDK Estimator 进行训练。

\n

模型训练文档

\n\n

本文档概述了 20,000 英尺的 SageMaker 如何训练,但没有提供任何具体操作的线索。

\n\n

本文档概述了 SageMaker 培训的情况。但是,这不是最新的,因为它基于已过时的SageMaker 容器

\n
\n

警告:此包已被弃用。请使用 SageMaker Training Toolkit 进行模型训练,并使用 SageMaker Inference Toolkit 进行模型服务。

\n
\n\n

本文档列出了培训步骤。

\n
\n

Amazon SageMaker Python SDK 提供框架估计器和通用估计器来训练您的模型,同时编排机器学习 (ML) 生命周期,访问 SageMaker 功能进行训练和 AWS 基础设施

\n
\n\n
\n

要使用 SageMaker Python SDK 训练模型,您:

\n
    \n
  • 准备训练脚本
  • \n
  • 创建一个估算器
  • \n
  • 调用估计器的拟合方法
  • \n
\n
\n

最后本文档给出了具体的步骤和思路。然而,仍然缺少有关环境变量、SageMaker docker 容器中的目录结构**、用于上传代码、放置数据的 S3、保存训练模型的 S3 等的全面详细信息。

\n\n

本文档重点介绍 TensorFlow Estimator 实现步骤。在 MNIST 上训练Tensorflow 模型示例来跟随实际的实现。

\n

用于传递参数和数据位置的文档

\n\n
\n

本部分介绍 SageMaker 如何使训练信息(例如训练数据、超参数和其他配置信息)可供 Docker 容器使用。

\n
\n

本文档最终给出了如何传递参数和数据的想法,但同样并不全面。

\n\n

本文档被标记为已弃用,但却是解释 SageMaker 环境变量的唯一文档。

\n
\n

重要的环境变量

\n
    \n
  • SM_MODEL_DIR
  • \n
  • SM_CHANNELS
  • \n
  • SM_CHANNEL_{频道名称}
  • \n
  • SM_HPS
  • \n
  • SM_HP_{hyperparameter_name}
  • \n
  • SM_CURRENT_HOST
  • \n
  • SM_HOSTS
  • \n
  • SM_NUM_GPUS
  • \n
\n

SageMaker 容器提供的环境变量列表

\n
    \n
  • SM_NUM_CPUS
  • \n
  • SM_LOG_LEVEL
  • \n
  • SM_NETWORK_INTERFACE_NAME
  • \n
  • SM_USER_ARGS
  • \n
  • SM_INPUT_DIR
  • \n
  • SM_INPUT_CONFIG_DIR
  • \n
  • SM_OUTPUT_DATA_DIR
  • \n
  • SM_RESOURCE_CONFIG
  • \n
  • SM_INPUT_DATA_CONFIG
  • \n
  • SM_TRAINING_ENV
  • \n
\n
\n

SageMaker Docker 容器目录结构的文档

\n\n
/opt/ml\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 input\n\xe2\x94\x82   \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 config\n\xe2\x94\x82   \xe2\x94\x82   \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 hyperparameters.json\n\xe2\x94\x82   \xe2\x94\x82   \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 resourceConfig.json\n\xe2\x94\x82   \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 data\n\xe2\x94\x82       \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 <channel_name>\n\xe2\x94\x82           \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 <input data>\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 model\n\xe2\x94\x82   \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 <model files>\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 output\n    \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 failure\n
Run Code Online (Sandbox Code Playgroud)\n

本文档解释了目录结构和每个目录的用途。

\n
\n

输入

\n
    \n
  • /opt/ml/input/config 包含控制程序运行方式的信息。hyperparameters.json 是超参数名称到值的 JSON 格式字典。这些值始终是字符串,因此您可能需要转换它们。resourcesConfig.json 是一个 JSON 格式的文件,描述用于分布式训练的网络布局。由于 scikit-learn 不支持分布式训练,因此我们在这里忽略它。
  • \n
  • /opt/ml/input/data/<channel_name>/(对于文件模式)包含该通道的输入数据。通道是根据对 CreateTrainingJob 的调用创建的,但通道与算法期望的匹配通常很重要。每个通道的文件将从 S3 复制到此目录,保留 S3 密钥结构指示的树结构。
  • \n
  • /opt/ml/input/data/<channel_name>_<epoch_number> (对于管道模式)是给定纪元的管道。纪元从零开始,每次阅读时都会增加一。您可以运行的纪元数量没有限制,但在读取下一个纪元之前必须关闭每个管道。
  • \n
\n

输出

\n
    \n
  • /opt/ml/model/ 是编写算法生成的模型的目录。您的模型可以是您想要的任何格式。它可以是单个文件或整个目录树。SageMaker 会将此目录中的所有文件打包到压缩的 tar 存档文件中。该文件将在DescribeTrainingJob 结果中返回的S3 位置可用。
  • \n
  • /opt/ml/output 是一个目录,算法可以在其中写入描述作业失败原因的文件失败。该文件的内容将在DescribeTrainingJob 结果的FailureReason 字段中返回。对于成功的作业,没有理由写入此文件,因为它将被忽略。
  • \n
\n
\n

但是,这不是最新的,因为它基于已过时的SageMaker 容器

\n

模型保存文档

\n

关于训练模型的保存位置和格式的信息根本缺失。训练脚本需要将模型保存在下面/opt/ml/model,其格式和子目录结构取决于TensorFlow、Pytorch等框架。这是因为 SageMaker 部署使用依赖于框架的模型服务,例如。用于 TensorFlow 框架的 TensorFlow 服务。

\n

这没有明确记录并造成混乱。开发者需要指定使用哪种格式以及保存在哪个子目录下。

\n

要使用 TensorFlow Estimator 训练和部署:

\n\n
\n

因为我们\xe2\x80\x99使用TensorFlow Serving进行部署,所以我们的训练脚本以TensorFlow\xe2\x80\x99s SavedModel格式保存模型

\n
\n\n
    # Save the model\n    # A version number is needed for the serving container\n    # to load the model\n    version = "00000000"\n    ckpt_dir = os.path.join(args.model_dir, version)\n    if not os.path.exists(ckpt_dir):\n        os.makedirs(ckpt_dir)\n    model.save(ckpt_dir)\n
Run Code Online (Sandbox Code Playgroud)\n

该代码正在保存模型,/opt/ml/model/00000000因为这是用于 TensorFlow 服务的。

\n\n
\n

保存路径遵循 TensorFlow Serving 使用的约定,其中最后一个路径组件(此处为 1/)是模型的版本号 - 它允许 Tensorflow Serving 等工具推断相对新鲜度。

\n
\n\n
\n

要将经过训练的模型加载到 TensorFlow Serving 中,我们首先需要将其保存为 SavedModel 格式。这将在明确定义的目录层次结构中创建一个 protobuf 文件,并将包含版本号。TensorFlow Serving 允许我们在发出推理请求时选择要使用的模型版本或“可服务”版本。每个版本都会导出到给定路径下的不同子目录。

\n
\n

API文档

\n

基本上,SageMaker SDK Estimator 为训练部分实现了CreateTrainingJob API。因此,更好地理解它是如何设计的以及需要定义哪些参数。否则,使用估算器就像在黑暗中行走一样。

\n
\n

例子

\n

Jupyter笔记本

\n
import sagemaker\nfrom sagemaker import get_execution_role\n\nsagemaker_session = sagemaker.Session()\nrole = get_execution_role()\nbucket = sagemaker_session.default_bucket()\n\nmetric_definitions = [\n    {"Name": "train:loss", "Regex": ".*loss: ([0-9\\\\.]+) - accuracy: [0-9\\\\.]+.*"},\n    {"Name": "train:accuracy", "Regex": ".*loss: [0-9\\\\.]+ - accuracy: ([0-9\\\\.]+).*"},\n    {\n        "Name": "validation:accuracy",\n        "Regex": ".*step - loss: [0-9\\\\.]+ - accuracy: [0-9\\\\.]+ - val_loss: [0-9\\\\.]+ - val_accuracy: ([0-9\\\\.]+).*",\n    },\n    {\n        "Name": "validation:loss",\n        "Regex": ".*step - loss: [0-9\\\\.]+ - accuracy: [0-9\\\\.]+ - val_loss: ([0-9\\\\.]+) - val_accuracy: [0-9\\\\.]+.*",\n    },\n    {\n        "Name": "sec/sample",\n        "Regex": ".* - \\d+s (\\d+)[mu]s/sample - loss: [0-9\\\\.]+ - accuracy: [0-9\\\\.]+ - val_loss: [0-9\\\\.]+ - val_accuracy: [0-9\\\\.]+",\n    },\n]\n\nimport uuid\n\ncheckpoint_s3_prefix = "checkpoints/{}".format(str(uuid.uuid4()))\ncheckpoint_s3_uri = "s3://{}/{}/".format(bucket, checkpoint_s3_prefix)\n\nfrom sagemaker.tensorflow import TensorFlow\n\n# --------------------------------------------------------------------------------\n# \'trainingJobName\' msut satisfy regular expression pattern: ^[a-zA-Z0-9](-*[a-zA-Z0-9]){0,62}\n# --------------------------------------------------------------------------------\nbase_job_name = "fashion-mnist"\nhyperparameters = {\n    "epochs": 2, \n    "batch-size": 64\n}\nestimator = TensorFlow(\n    entry_point="fashion_mnist.py",\n    source_dir="src",\n    metric_definitions=metric_definitions,\n    hyperparameters=hyperparameters,\n    role=role,\n    input_mode=\'File\',\n    framework_version="2.3.1",\n    py_version="py37",\n    instance_count=1,\n    instance_type="ml.m5.xlarge",\n    base_job_name=base_job_name,\n    checkpoint_s3_uri=checkpoint_s3_uri,\n    model_dir=False\n)\nestimator.fit()\n
Run Code Online (Sandbox Code Playgroud)\n

Fashion_mnist.py

\n
import os\nimport argparse\nimport json\nimport multiprocessing\n\nimport numpy as np\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Dense, Dropout, Flatten, BatchNormalization\nfrom tensorflow.keras.layers import Conv2D, MaxPooling2D\nfrom tensorflow.keras.layers.experimental.preprocessing import Normalization\nfrom tensorflow.keras import backend as K\n\nprint("TensorFlow version: {}".format(tf.__version__))\nprint("Eager execution is: {}".format(tf.executing_eagerly()))\nprint("Keras version: {}".format(tf.keras.__version__))\n\n\nimage_width = 28\nimage_height = 28\n\n\ndef load_data():\n    fashion_mnist = tf.keras.datasets.fashion_mnist\n    (x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()\n\n    number_of_classes = len(set(y_train))\n    print("number_of_classes", number_of_classes)\n\n    x_train = x_train / 255.0\n    x_test = x_test / 255.0\n    x_full = np.concatenate((x_train, x_test), axis=0)\n    print(x_full.shape)\n\n    print(type(x_train))\n    print(x_train.shape)\n    print(x_train.dtype)\n    print(y_train.shape)\n    print(y_train.dtype)\n\n    # ## Train\n    # * C: Convolution layer\n    # * P: Pooling layer\n    # * B: Batch normalization layer\n    # * F: Fully connected layer\n    # * O: Output fully connected softmax layer\n\n    # Reshape data based on channels first / channels last strategy.\n    # This is dependent on whether you use TF, Theano or CNTK as backend.\n    # Source: https://github.com/keras-team/keras/blob/master/examples/mnist_cnn.py\n    if K.image_data_format() == \'channels_first\':\n        x = x_train.reshape(x_train.shape[0], 1, image_width, image_height)\n        x_test = x_test.reshape(x_test.shape[0], 1, image_width, image_height)\n        input_shape = (1, image_width, image_height)\n    else:\n        x_train = x_train.reshape(x_train.shape[0], image_width, image_height, 1)\n        x_test = x_test.reshape(x_test.shape[0], image_width, image_height, 1)\n        input_shape = (image_width, image_height, 1)\n\n    return x_train, y_train, x_test, y_test, input_shape, number_of_classes\n\n# tensorboard --logdir=/full_path_to_your_logs\n\nvalidation_split = 0.2\nverbosity = 1\nuse_multiprocessing = True\nworkers = multiprocessing.cpu_count()\n\n\ndef train(model, x, y, args):\n    # SavedModel Output\n    tensorflow_saved_model_path = os.path.join(args.model_dir, "tensorflow/saved_model/0")\n    os.makedirs(tensorflow_saved_model_path, exist_ok=True)\n\n    # Tensorboard Logs\n    tensorboard_logs_path = os.path.join(args.model_dir, "tensorboard/")\n    os.makedirs(tensorboard_logs_path, exist_ok=True)\n\n    tensorboard_callback = tf.keras.callbacks.TensorBoard(\n        log_dir=tensorboard_logs_path,\n        write_graph=True,\n        write_images=True,\n        histogram_freq=1,  # How often to log histogram visualizations\n        embeddings_freq=1,  # How often to log embedding visualizations\n        update_freq="epoch",\n    )  # How often to write logs (default: once per epoch)\n\n    model.compile(\n        optimizer=\'adam\',\n        loss=tf.keras.losses.sparse_categorical_crossentropy,\n        metrics=[\'accuracy\']\n    )\n    history = model.fit(\n        x,\n        y,\n        shuffle=True,\n        batch_size=args.batch_size,\n        epochs=args.epochs,\n        validation_split=validation_split,\n        use_multiprocessing=use_multiprocessing,\n        workers=workers,\n        verbose=verbosity,\n        callbacks=[\n            tensorboard_callback\n        ]\n    )\n    return history\n\n\ndef create_model(input_shape, number_of_classes):\n    model = Sequential([\n        Conv2D(\n            name="conv01",\n            filters=32,\n            kernel_size=(3, 3),\n            strides=(1, 1),\n            padding="same",\n            activation=\'relu\',\n            input_shape=input_shape\n        ),\n        MaxPooling2D(\n            name="pool01",\n            pool_size=(2, 2)\n        ),\n        Flatten(),  # 3D shape to 1D.\n        BatchNormalization(\n            name="batch_before_full01"\n        ),\n        Dense(\n            name="full01",\n            units=300,\n            activation="relu"\n        ),  # Fully connected layer\n        Dense(\n            name="output_softmax",\n            units=number_of_classes,\n            activation="softmax"\n        )\n    ])\n    return model\n\n\ndef save_model(model, args):\n    # Save the model\n    # A version number is needed for the serving container\n    # to load the model\n    version = "00000000"\n    model_save_dir = os.path.join(args.model_dir, version)\n    if not os.path.exists(model_save_dir):\n        os.makedirs(model_save_dir)\n    print(f"saving model at {model_save_dir}")\n    model.save(model_save_dir)\n\n\ndef parse_args():\n    # --------------------------------------------------------------------------------\n    # https://docs.python.org/dev/library/argparse.html#dest\n    # --------------------------------------------------------------------------------\n    parser = argparse.ArgumentParser()\n\n    # --------------------------------------------------------------------------------\n    # hyperparameters Estimator argument are passed as command-line arguments to the script.\n    # --------------------------------------------------------------------------------\n    parser.add_argument(\'--epochs\', type=int, default=10)\n    parser.add_argument(\'--batch-size\', type=int, default=64)\n\n    # /opt/ml/model\n    # sagemaker.tensorflow.estimator.TensorFlow override \'model_dir\'.\n    # See https://sagemaker.readthedocs.io/en/stable/frameworks/tensorflow/\\\n    # sagemaker.tensorflow.html#sagemaker.tensorflow.estimator.TensorFlow\n    parser.add_argument(\'--model_dir\', type=str, default=os.environ[\'SM_MODEL_DIR\'])\n\n    # /opt/ml/output\n    parser.add_argument("--output_dir", type=str, default=os.environ["SM_OUTPUT_DIR"])\n\n    args = parser.parse_args()\n    return args\n\n\nif __name__ == "__main__":\n    args = parse_args()\n    print("---------- key/value args")\n    for key, value in vars(args).items():\n        print(f"{key}:{value}")\n\n    x_train, y_train, x_test, y_test, input_shape, number_of_classes = load_data()\n    model = create_model(input_shape, number_of_classes)\n\n    history = train(model=model, x=x_train, y=y_train, args=args)\n    print(history)\n    \n    save_model(model, args)\n    results = model.evaluate(x_test, y_test, batch_size=100)\n    print("test loss, test accuracy:", results)\n
Run Code Online (Sandbox Code Playgroud)\n

SageMaker 控制台

\n

在此输入图像描述

\n

笔记本输出

\n
2021-09-03 03:02:04 Starting - Starting the training job...\n2021-09-03 03:02:16 Starting - Launching requested ML instancesProfilerReport-1630638122: InProgress\n......\n2021-09-03 03:03:17 Starting - Preparing the instances for training.........\n2021-09-03 03:04:59 Downloading - Downloading input data\n2021-09-03 03:04:59 Training - Downloading the training image...\n2021-09-03 03:05:23 Training - Training image download completed. Training in progress.2021-09-03 03:05:23.966037: W tensorflow/core/profiler/internal/smprofiler_timeline.cc:460] Initializing the SageMaker Profiler.\n2021-09-03 03:05:23.969704: W tensorfl

  • 老兄,这真是一个很棒的回复~你在阅读了无数文档后设法解开了我无法理解的工作流程 (9认同)
  • 这是我见过的最好的回应之一。绝对可以做标记以供将来参考。太感谢了! (5认同)
  • 在挖掘 SageMaker 文档之前看到这个*精彩的*答案可以轻松地节省我很多时间。谢谢。 (3认同)
  • 这么棒的答案!!列出环境变量的另一个页面:https://github.com/aws/sagemaker-training-toolkit/blob/master/ENVIRONMENT_VARIABLES.md (2认同)