生产就绪的应用深度学习指南-二-

196 阅读1小时+

生产就绪的应用深度学习指南(二)

原文:zh.annas-archive.org/md5/e14f18bbb2088c66db1babf46e8b6eee

译者:飞龙

协议:CC BY-NC-SA 4.0

第四章:实验跟踪,模型管理和数据集版本控制

在本章中,我们将介绍一组用于实验跟踪、模型管理和数据集版本控制的实用工具,这些工具可以帮助您有效管理深度学习(DL)项目。本章讨论的工具可以帮助我们跟踪许多实验并更高效地解释结果,从而自然而然地减少运营成本并加速开发周期。通过本章,您将能够亲自体验最流行的工具,并能够为您的项目选择适当的工具集。

在本章中,我们将涵盖以下主要内容:

  • DL 项目跟踪概述

  • 使用 Weights & Biases 进行 DL 项目跟踪

  • 使用 MLflow 和 DVC 进行 DL 项目跟踪

  • 数据集版本控制 – 超越 Weights & Biases, MLflow 和 DVC

技术要求

您可以从本书的 GitHub 仓库下载本章的补充材料,网址为github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_4

DL 项目跟踪概述

训练 DL 模型是一个消耗大量时间和资源的迭代过程。因此,跟踪所有实验并始终有条理地组织它们可以防止我们浪费时间在无需重复训练相似模型的操作上。换句话说,有关所有模型架构及其超参数集合以及实验过程中使用的数据版本的详细记录可以帮助我们从实验中得出正确的结论,这自然而然地有助于项目的成功。

DL 项目跟踪的组成部分

DL 项目跟踪的基本组成部分包括实验跟踪,模型管理和数据集版本控制。让我们详细看看每个组件。

实验跟踪

实验跟踪背后的概念很简单:存储每个实验的描述和动机,以便我们不为了相同目的再次运行一组实验。总体而言,有效的实验跟踪将节省我们运营成本,并允许我们从最小的实验结果集中得出正确的结论。有效实验跟踪的基本方法之一是为每个实验添加唯一标识符。我们需要跟踪每个实验的信息包括项目依赖关系、模型架构定义、使用的参数和评估指标。实验跟踪还包括实时可视化进行中的实验,并能够直观地比较一组实验。例如,如果我们可以检查模型训练过程中每个时期的训练和验证损失,我们可以更快地识别过拟合,从而节省一些资源。此外,通过比较两个实验之间的结果和一组变化,我们可以理解这些变化如何影响模型性能。

模型管理

模型管理不仅仅涵盖实验跟踪,还涵盖了模型的整个生命周期:数据集信息、工件(从训练模型生成的任何数据)、模型的实施、评估指标以及管道信息(如开发、测试、暂存和生产)。模型管理使我们能够快速选择感兴趣的模型,并有效地设置模型可用的环境。

数据集版本控制

DL 项目跟踪的最后一个组成部分是数据集版本控制。在许多项目中,数据集会随时间改变。这些变化可能来自数据模式(数据组织的蓝图)、文件位置,甚至是应用于数据集的筛选器,从而操作底层数据的含义。工业中发现的许多数据集结构复杂,并且通常以多种数据格式存储在多个位置。因此,变化可能比预期的更加显著和难以跟踪。因此,在记录这些变化方面至关重要,以在整个项目中复制一致的结果。

数据集跟踪可以总结如下:将存储为工件的一组数据,在基础数据修改时应成为工件的新版本。话虽如此,每个工件应具有元数据,其中包含关于数据集的重要信息:创建时间、创建者以及与上一版本的差异。

例如,应该如下制定具有数据集版本控制的数据集。数据集应在其名称中具有时间戳:

dataset_<timestamp>
> metadata.json
> img1.png
> img2.png
> img3.png

正如前面所述,元数据应包含关于数据集的关键信息:

{
   "created_by": "Adam"
   "created_on": "2022-01-01"
   "labelled_by": "Bob"
   "number_of_samples": 3
}

请注意,由元数据跟踪的信息集可能因项目而异。

DL 项目跟踪工具

DL 跟踪可以通过多种方式实现,从简单的文本文件中的注释、电子表格,到在 GitHub 或专用网页上保存信息,再到自建平台和外部工具。模型和数据工件可以按原样存储,也可以应用更复杂的方法以避免冗余并提高效率。

DL 项目跟踪领域正在快速增长,并不断引入新工具。因此,为底层项目选择合适的工具并不容易。我们必须考虑业务和技术限制。尽管定价模型是基本的,但其他限制可能由现有的开发设置引入;集成现有工具应该很容易,基础设施必须易于维护。还要考虑 MLOps 团队的工程能力。话虽如此,在选择项目工具时,以下列表将是一个很好的起点。

  • TensorBoard (www.tensorflow.org/tensorboard):

    • TensorFlow 团队开发的一款开源可视化工具。

    • 用于跟踪和可视化实验结果的标准工具。

  • Weights & Biases (wandb.ai):

    • 一款云端服务,提供有效的交互式仪表板,用于可视化和组织实验结果。

    • 服务器可以在本地运行,也可以托管在私有云中。

    • 提供名为 Sweeps 的自动化超参数调整功能。

    • 个人项目免费。定价基于跟踪小时数和存储空间。

  • Neptune (neptune.ai):

    • 用于监视和存储机器学习(ML)实验结果的在线工具。

    • 可轻松集成其他 ML 工具。

    • 以其强大的仪表板而闻名,实时总结实验结果。

  • MLflow (mlflow.org):

    • 一个提供端到端 ML 生命周期管理的开源平台。

    • 它支持基于 Python 和 R 的系统。通常与数据版本控制DVC)结合使用。

  • SageMaker Studio (aws.amazon.com/sagemaker/studio/):

    • 一个基于 Web 的可视化界面,用于管理与 SageMaker 设置的 ML 实验。

    • 该工具允许用户通过提供与 AWS 的其他有用功能简单集成,高效地构建、训练和部署模型。

  • Kubeflow (www.kubeflow.org):

    • 由 Google 设计的开源平台,用于端到端 ML 编排和管理。

    • 也设计用于将 ML 系统高效地部署到各种开发和生产环境中。

  • Valohai (valohai.com):

    • 一款专为自动化机器编排、版本控制和数据管道管理而设计的 DL 管理平台。

    • 它并非免费软件,而是专为企业设计的

    • 它因技术不限和响应迅速的支持团队而日益受欢迎

在各种工具中,我们将介绍两种最常用的设置:Weights & Biases 和 MLflow 结合 DVC。

需记住的事项

a. DL 跟踪的基本组件包括实验跟踪、模型管理和数据集版本控制。近期的 DL 跟踪工具通常具有用户友好的仪表板,总结实验结果。

b. 该领域正在快速发展,并且有许多具有不同优势的工具。选择合适的工具需要理解业务和技术约束。

首先,让我们看看使用 Weights & Biases (W&B) 进行 DL 项目跟踪。

使用 Weights & Biases 进行 DL 项目跟踪

W&B 是一个实验管理平台,为模型和数据提供版本控制。

W&B 提供了一个交互式仪表板,可以嵌入到 Jupyter 笔记本中或用作独立的网页。简单的 Python API 也为简单集成打开了可能性。此外,其功能侧重于简化 DL 实验管理:记录和监控模型和数据版本、超参数值、评估指标、工件和其他相关信息。

W&B 的另一个有趣功能是其内置的超参数搜索功能称为 Sweeps (docs.wandb.ai/guides/sweeps)。可以使用 Python API 轻松设置 Sweeps,并在 W&B 网页上交互比较结果和模型。

最后,W&B 会自动为您创建报告,直观地总结和组织一组实验(docs.wandb.ai/guides/reports)。

总体而言,W&B 的关键功能可以总结如下:

  • 实验跟踪和管理

  • 工件管理

  • 模型评估

  • 模型优化

  • 协同分析

W&B 是一种订阅服务,但个人账户是免费的。

设置 W&B

W&B 拥有 Python API,为多种 DL 框架(包括 TensorFlow 和 PyTorch)提供简单的集成方法。记录的信息,如项目、团队和运行列表,可在线管理和查看,或者在自托管服务器上进行管理。

设置 W&B 的第一步是安装 Python API 并登录 W&B 服务器。您必须预先通过 wandb.ai 创建一个账户:

pip install wandb
wandb login

在您的 Python 代码中,您可以通过以下代码注册一个称为 run-1 的单个实验:

import wandb
run_1 = wandb.init(project="example-DL-Book", name="run-1") 

更具体地说,wandb.init 函数在名为 example-DL-Book 的项目中创建了一个名为 run_1 的新 wandb.Run 实例。如果未提供名称,W&B 将为您生成一个随机的双字名称。如果项目名称为空,W&B 将将您的运行放入 Uncategorized 项目中。wandb.init 的所有参数列在 docs.wandb.ai/ref/python/init,但我们想介绍您最常与之交互的参数:

  • id 为您的运行设置一个唯一的 ID

  • resume 允许您在不创建新运行的情况下恢复实验

  • job_type 允许您将运行分配给特定类型,例如训练、测试、验证、探索或任何其他可以用于分组运行的名称

  • tags 提供了额外的灵活性,用于组织您的运行

当触发 wandb.init 函数时,关于运行的信息将开始出现在 W&B 仪表板上。您可以在 W&B 网页或直接在 Jupyter 笔记本环境中监视仪表板,如下面的截图所示:

Figure 4.1 – Jupyter 笔记本环境中的 W&B 仪表板

图 4.1 – Jupyter 笔记本环境中的 W&B 仪表板

创建运行后,您可以开始记录信息;wandb.log 函数允许您记录任何您想要的数据。例如,您可以通过在训练循环中添加 wandb.log({"custom_loss": custom_loss}) 来记录损失。同样,您可以记录验证损失和任何其他您想要跟踪的详细信息。

有趣的是,W&B 通过为 DL 模型提供内置的日志记录功能,使这个过程变得更加简单。在撰写本文时,您可以找到大多数框架的集成,包括 Keras、PyTorch、PyTorch Lightning、TensorFlow、fast.ai、scikit-learn、SageMaker、Kubeflow、Docker、Databricks 和 Ray Tune(有关详细信息,请参见 docs.wandb.ai/guides/integrations)。

wandb.config 是跟踪模型超参数的绝佳位置。对于来自实验的任何工件,您可以使用 wandb.log_artifact 方法(有关更多详细信息,请参见 docs.wandb.ai/guides/artifacts)。在记录工件时,您需要定义文件路径,然后指定您的工件的名称和类型,如以下代码片段所示:

wandb.log_artifact(file_path, name='new_artifact', type='my_dataset')

然后,您可以重复使用已存储的工件,如下所示:

run = wandb.init(project="example-DL-Book")
artifact = run.use_artifact('example-DL-Book/new_artifact:v0', type='my_dataset')
artifact_dir = artifact.download()

到目前为止,您已经学会了如何为您的项目设置 wandb 并在整个训练过程中单独记录您选择的指标和工件。有趣的是,wandb 为许多深度学习框架提供了自动日志记录。在本章中,我们将更详细地了解如何将 W&B 集成到 Keras 和 PyTorch Lightning (PL) 中。

将 W&B 集成到 Keras 项目中

在 Keras 的情况下,可以通过 WandbCallback 类实现集成。完整版本可以在本书的 GitHub 存储库中找到:

import wandb
from wandb.keras import WandbCallback
from tensorflow import keras
from tensorflow.keras import layers
wandb.init(project="example-DL-Book", name="run-1")
wandb.config = {
   "learning_rate": 0.001,
   "epochs": 50,
   "batch_size": 128
}
model = keras.Sequential()
logging_callback = WandbCallback(log_evaluation=True)
model.fit(
   x=x_train, y=y_train,
   epochs=wandb.config['epochs'],
   batch_size=wandb.config['batch_size'], 
   verbose='auto', 
   validation_data=(x_valid, y_valid),
   callbacks=[logging_callback])

如前一节所述,关于模型的关键信息被记录并在 W&B 仪表板上可用。您可以监视损失、评估指标和超参数。图 4.2 展示了由前述代码自动生成的示例图:

Figure 4.2 – Sample plots generated by W&B from logged metrics

Figure 4.2 – Sample plots generated by W&B from logged metrics

图 4.2 – W&B 根据记录的指标生成的示例图

将 W&B 集成到 PL 项目中类似于将其集成到 Keras 项目中。

将 W&B 集成到 PyTorch Lightning 项目中

对于基于 PL 的项目,W&B 提供了自定义记录器,并隐藏了大部分样板代码。您只需要实例化 WandbLogger 类,并通过 logger 参数将其传递给 Trainer 实例:

import pytorch_lightning as pl
from pytorch_lightning.loggers import WandbLogger
wandb_logger = WandbLogger(project="example-DL-Book")
trainer = Trainer(logger=wandb_logger)
class LitModule(LightningModule):
   def __init__(self, *args, **kwarg):
       self.save_hyperparameters()
   def training_step(self, batch, batch_idx):
       self.log("train/loss", loss)

关于集成的详细说明可在 pytorch-lightning.readthedocs.io/en/stable/extensions/generated/pytorch_lightning.loggers.WandbLogger.html 找到。

需记住的事项

a. W&B 是一个实验管理平台,有助于跟踪模型和数据的不同版本。它还支持存储配置、超参数、数据和模型工件,并提供实时实验跟踪。

b. W&B 安装简便。它为多个 DL 框架提供了内置的集成功能,包括 TensorFlow 和 PyTorch。

c. 可以使用 W&B 进行超参数调优/模型优化。

虽然 W&B 在 DL 项目跟踪领域占据主导地位,但 MLflow 和 DVC 的组合是另一种流行的 DL 项目设置。

使用 MLflow 和 DVC 进行 DL 项目跟踪

MLflow 是一个流行的框架,支持跟踪技术依赖项、模型参数、指标和工件。MLflow 的关键组件如下:

  • 跟踪:每次模型运行时跟踪结果变化

  • 项目:它以可重现的方式打包模型代码

  • 模型:它为未来便捷的部署组织模型工件

  • 模型注册表:它管理 MLflow 模型的完整生命周期

  • 插件:由于提供了灵活的插件,可以轻松集成其他 DL 框架

正如您可能已经注意到的那样,W&B 和 MLflow 之间存在一些相似之处。然而,在 MLflow 的情况下,每个实验都与一组 Git 提交关联。Git 并不会阻止我们保存数据集,但是在数据集较大时会显示出许多限制,即使使用了专为大文件构建的扩展(Git LFS)。因此,MLflow 通常与 DVC 结合使用,DVC 是一个解决 Git 限制的开源版本控制系统。

设置 MLflow

可以使用 pip 安装 MLflow:

pip install mlflow

类似于 W&B,MLflow 还提供了一个 Python API,允许您跟踪超参数(log_param)、评估指标(log_metric)和工件(log_artifacts):

import os
import mlflow
from mlflow import log_metric, log_param, log_artifacts
log_param("epochs", 30)
log_metric("custom", 0.6)
log_metric("custom", 0.75) # metrics can be updated
if not os.path.exists("artifact_dir"):
   os.makedirs("artifact_dir")
with open("artifact_dir/test.txt", "w") as f:
   f.write("simple example")
log_artifacts("artifact_dir")

实验定义可以通过以下代码初始化并标记:

exp_id = mlflow.create_experiment("DLBookModel_1")
exp = mlflow.get_experiment(exp_id)
with mlflow.start_run(experiment_id=exp.experiment_id, run_name='run_1') as run:
   # logging starts here
   mlflow.set_tag('model_name', 'model1_dev')

MLflow 提供了一系列教程,介绍了其 API:www.mlflow.org/docs/latest/tutorials-and-examples/tutorial.html

现在您已经熟悉了 MLflow 的基本用法,我们将描述如何将其集成到 Keras 和 PL 项目中。

将 MLflow 集成到 Keras 项目中

首先,让我们看一下 Keras 的集成。通过 log_model 函数可以记录 Keras 模型的详细信息:

history = keras_model.fit(...)
mlflow.keras.log_model(keras_model, model_dir)

mlflow.kerasmlflow.tensorflow 模块提供了一组 API,用于记录关于 Keras 和 TensorFlow 模型的各种信息。有关更多详细信息,请查看 www.mlflow.org/docs/latest/python_api/index.html

将 MLflow 集成到 PyTorch Lightning 项目中

类似于 W&B 支持 PL 项目的方式,MLflow 也提供了一个 MLFlowLogger 类。这可以传递给 Trainer 实例,用于在 MLflow 中记录模型详细信息:

import pytorch_lightning as pl 
from pytorch_lightning import Trainer
from pytorch_lightning.loggers import MLFlowLogger
mlf_logger = MLFlowLogger(experiment_name="example-DL-Book ", tracking_uri="file:./ml-runs")
trainer = Trainer(logger=mlf_logger)
class DLBookModel(pl.LightningModule):
   def __init__(self):
       super(DLBookModel, self).__init__()
       ...
   def training_step(self, batch, batch_nb):
       loss = self.log("train_loss", loss, on_epoch=True)

在上述代码中,我们传递了一个 MLFlowLogger 实例来替换 PL 的默认记录器。tracking_uri 参数控制记录的数据去向。

关于 PyTorch 集成的其他详细信息可以在官方网站找到:pytorch-lightning.readthedocs.io/en/stable/api/pytorch_lightning.loggers.mlflow.html

使用 DVC 设置 MLflow

要使用 DVC 管理大型数据集,需要使用诸如 pipcondabrew(适用于 macOS 用户)之类的包管理器安装它:

pip install dvc

所有的安装选项可以在 dvc.org/doc/install 找到。

使用 DVC 管理数据集需要按特定顺序执行一组命令:

  1. 第一步是使用 DVC 设置一个 Git 仓库:

    git init
    dvc init
    git commit -m 'initialize repo'
    
  2. 现在,我们需要配置 DVC 的远程存储:

    dvc remote add -d myremote /tmp/dvc-storage
    git commit .dvc/config -m "Added local remote storage"
    
  3. 让我们创建一个样本数据目录,并填充一些示例数据:

    mkdir data
    cp example_data.csv data/
    
  4. 在这个阶段,我们已经准备好开始跟踪数据集了。我们只需要将文件添加到 DVC 中。此操作将创建一个额外的文件 example_data.csv.dvc。此外,example_data.csv 文件会自动添加到 .gitignore 中,使得 Git 不再跟踪原始文件:

    dvc add data/example_data.csv
    
  5. 接下来,您需要提交并上传 example_data.csv.dvc.gitignore 文件。我们将我们的第一个数据集标记为 v1

    git add data/.gitignore data/example_data.csv.dvc
    git commit -m 'data tracking'
    git tag -a 'v1' -m 'test_data'
    dvc push
    
  6. 使用 dvc push 命令后,我们的数据将在远程存储中可用。这意味着我们可以删除本地版本。要恢复 example_data.csv,只需简单地调用 dvc pull

    dvc pull data/example_data.csv.dvc
    
  7. 当修改 example_data.csv 后,我们需要再次添加并推送以更新远程存储中的版本。我们将修改后的数据集标记为 v2

    dvc add data/example_data.csv
    git add data/example_data.csv.dvc
    git commit -m 'data modification description'
    git tag -a 'v2' -m 'modified test_data'
    dvc push
    

执行这些命令后,您将通过 Git 和 DVC 跟踪同一数据集的两个版本:v1v2

接下来,让我们看看如何将 MLflow 与 DVC 结合使用:

import mlflow
import dvc.api
import pandas as pd
data_path='data/example_data.csv'
repo='/Users/BookDL_demo/'
version='v2'
data_url=dvc.api.get_url(path=path, repo=repo, rev=version)
# this will fetch the right version of our data file
data = pd.read_csv(data_url)
# log important information using mlflow
mlflow.start_run()
mlflow.log_param("data_url", data_url)
mlflow.log_artifact(...)

在前面的代码片段中,使用了mlflow.log_artifact来保存有关实验特定列的信息。

总体而言,我们可以通过 MLflow 运行多个实验,使用 DVC 跟踪不同版本的数据集。与 W&B 类似,MLflow 还提供一个网页,我们可以在其中比较我们的实验。您只需在终端中输入以下命令即可:

mlflow ui 

这个命令将启动一个托管在127.0.0.1:5000上的 Web 服务器,以下屏幕截图显示了 MLflow 仪表板:

![图 4.3 – MLflow 仪表板;新运行将显示在页面底部

![图 4.3 – MLflow 仪表板;新运行将显示在页面底部

图 4.3 – MLflow 仪表板;新运行将显示在页面底部

记住的事情

a. MLflow 可以跟踪依赖关系、模型参数、指标和工件。通常与 DVC 结合使用,以实现高效的数据集版本控制。

b. MLflow 可以轻松集成到包括 Keras、TensorFlow 和 PyTorch 在内的 DL 框架中。

c. MLflow 提供交互式可视化,可以同时分析多个实验。

到目前为止,我们已经学习了如何在 W&B、MLflow 和 DVC 中管理 DL 项目。在下一节中,我们将介绍用于数据集版本控制的流行工具。

数据集版本控制 – 超越 Weights & Biases、MLflow 和 DVC

在本章中,我们看到了如何通过 DL 项目跟踪工具管理数据集。在 W&B 的情况下,我们可以使用工件,而在 MLflow 和 DVC 的情况下,DVC 运行在 Git 存储库之上,以跟踪数据集的不同版本,从而解决了 Git 的限制。

是否还有其他对数据集版本控制有用的方法和/或工具?简单的答案是肯定的,但更精确的答案取决于具体的情况。为了做出正确的选择,您必须考虑多个方面,包括成本、易用性和集成难度。在本节中,我们将提及一些我们认为值得探索的工具,如果数据集版本控制是项目的关键组成部分:

  • Neptune (docs.neptune.ai) 是一个用于 MLOps 的元数据存储。Neptune 工件允许在本地或云中存储的数据集上进行版本控制。

  • Delta Lake (delta.io) 是一个在数据湖顶层运行的开源存储抽象。Delta Lake 与 Apache Spark API 配合使用,并使用分布式处理来提高吞吐量和效率。

记住的事情

a. 市场上有许多数据版本控制工具。要选择合适的工具,您必须考虑多个方面,包括成本、易用性和集成难度。

b. 诸如 W&B、MLflow、DVC、Neptune 和 Delta Lake 的工具可以帮助您进行数据集版本控制。

至此,我们介绍了数据集版本控制的流行工具。适合的工具因项目而异。因此,在将任何工具集成到您的项目之前,您必须评估每个工具的利弊。

摘要

由于深度学习项目涉及多次模型训练和评估迭代,有效管理实验、模型和数据集可以帮助团队更快地实现目标。在本章中,我们探讨了两种最流行的深度学习项目跟踪设置:W&B 和与 DVC 集成的 MLflow。这两种设置都内置支持 Keras 和 PyTorch,这两个最流行的深度学习框架。我们还花了一些时间描述了更加强调数据集版本控制的工具:Neptune 和 Delta Lake。请记住,您必须仔细评估每个工具,以选择适合您项目的正确工具。

目前为止,您已经熟悉了构建概念验证和训练必要深度学习模型的框架和流程。从下一章开始,我们将讨论如何通过将深度学习管道的各个组件移至云端来实现规模化。

第二部分:构建一个功能齐全的产品

下一阶段是将概念验证迁移到现有基础设施。在整个过程中,数据处理逻辑和模型的初始版本通常会使用不同的工具和服务重新实现,目的是提高吞吐量和提高效率。在本书中,我们专注于 AWS,这是处理大量数据和昂贵计算的最流行的网络服务。

本部分包括以下章节:

  • 第五章云中的数据准备

  • 第六章高效模型训练

  • 第七章揭秘深度学习模型的秘密

第五章:云中的数据准备

在本章中,我们将学习如何通过利用各种 AWS 云服务在云中设置数据准备。考虑到在数据准备中的抽取、转换和加载(ETL)操作的重要性,我们将深入研究如何以成本效益的方式设置和调度 ETL 作业。我们将涵盖四种不同的设置:在单节点 EC2 实例和 EMR 集群上运行 ETL,然后利用 Glue 和 SageMaker 进行 ETL 作业。本章还将介绍 Apache Spark,这是最流行的 ETL 框架。通过完成本章,您将能够利用所提供的不同设置的优势,并为项目选择合适的工具集。

本章将涵盖以下主要主题:

  • 云中的数据处理

  • Apache Spark 简介

  • 设置单节点 EC2 实例用于 ETL

  • 设置用于 ETL 的 EMR 集群

  • 创建用于 ETL 的 Glue 作业

  • 利用 SageMaker 进行 ETL

技术要求

您可以从本书的 GitHub 存储库下载本章的补充材料:github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_5

云中的数据处理

深度学习(DL)项目的成功取决于数据的质量和数量。因此,用于数据准备的系统必须稳定且可扩展,以有效地处理 TB 和 PB 级数据。这通常需要不止一台机器;必须设置一组运行强大的 ETL 引擎的机器集群,以便存储和处理大量数据。

首先,我们想介绍 ETL,即云中数据处理的核心概念。接下来,我们将概述用于数据处理的分布式系统设置。

ETL 简介

在整个 ETL 过程中,数据将从一个或多个来源收集,根据需要转换为不同的形式,并保存在数据存储中。 简而言之,ETL 本身涵盖了整个数据处理管道。ETL 在整个过程中与三种不同类型的数据交互:结构化非结构化半结构化。结构化数据表示具有模式的数据集(例如表),非结构化数据没有明确定义的显式模式(例如文本、图像或 PDF 文件),而半结构化数据在数据本身内部具有部分结构(例如 HTML 或电子邮件)。

流行的 ETL 框架包括Apache Hadoop (hadoop.apache.org),Presto (prestodb.io),Apache Flink (flink.apache.org)和Apache Spark (spark.apache.org)。Hadoop 是最早利用分布式处理优势的数据处理引擎之一。Presto 专门用于处理 SQL 中的数据,而 Apache Flink 则专注于处理流数据。在这四个框架中,Apache Spark 是最流行的工具,因为它可以处理各种数据类型。Apache Spark 利用内存数据处理来增加吞吐量,并提供比 Hadoop 更可扩展的数据处理解决方案。此外,它可以轻松集成其他 ML 和 DL 工具。因此,我们在本书中主要关注 Spark。

数据处理系统架构

为数据处理系统设置环境并不是一项简单的任务,因为它涉及定期获取高端机器、正确链接各种数据处理软件,并确保在发生故障时不丢失数据。因此,许多公司利用云服务,这是一种通过互联网按需提供的各种软件服务。虽然许多公司提供各种云服务,但亚马逊云服务 (AWS)以其稳定且易于使用的服务脱颖而出。

为了让您对现实生活中的数据处理系统有一个更广泛的了解,让我们看一个基于 AWS 服务的样例系统架构。这个系统的核心组件是开源的Apache Spark,执行主要的 ETL 逻辑。一个典型的系统还包括用于调度个别作业、存储数据和可视化处理后数据的组件:

图 5.1 – 数据处理管道的通用架构还包括可视化和实验平台

图 5.1 – 数据处理管道的通用架构,同时包括可视化和实验平台

让我们逐个看看这些组件:

  • 数据存储:数据存储负责保存数据和相关的元数据:

    • Hadoop 分布式文件系统 (HDFS):开源的 HDFS 是一个可按需扩展的分布式文件系统 (hadoop.apache.org)。HDFS 一直是数据存储的传统选择,因为 Apache Spark 和 Apache Hadoop 在 HDFS 上表现最佳。

    • Amazon Simple Storage Service (S3): 这是 AWS 提供的数据存储服务aws.amazon.com/s3)。S3 使用对象和存储桶的概念,其中对象指单个文件,存储桶指对象的容器。对于每个项目或子模块,您可以创建一个存储桶,并为读写操作配置不同的权限。存储桶还可以对数据应用版本控制,跟踪更改记录。

  • ml,成本大约比其他 EC2 实例高 30%到 40%(aws.amazon.com/sagemaker/pricing)。在利用 SageMaker 进行 ETL部分,我们将描述如何在 EC2 实例上设置 SageMaker 进行 ETL 流程。

考虑到需要处理的数据量,正确选择的 ETL 服务以及适当的数据存储选择可以显著提高管道的效率。需要考虑的关键因素包括数据源、数据量、可用的硬件资源和可伸缩性等。

  • Scheduling: 经常需要定期运行 ETL 作业(例如每天、每周或每月),因此需要调度器:

    • AWS Lambda functions: Lambda 函数(aws.amazon.com/lambda)旨在在 EMR 上运行作业,无需提供或管理基础设施。执行时间可以动态配置;作业可以立即运行,也可以计划在不同时间运行。AWS Lambda 函数以无服务器方式运行代码,因此无需维护。如果执行期间出现错误,EMR 集群将自动关闭。

    • Airflow: 调度器在自动化 ETL 过程中发挥重要作用。Airflow(airflow.apache.org是数据工程师使用的最流行的调度框架之一。Airflow 的有向无环图DAG)可用于定期调度管道。Airflow 比 AWS Lambda 函数更常见,用于定期运行 Spark 作业,因为 Airflow 在前面的执行失败时可以轻松地回填数据。

  • Build: Build 是将代码包部署到 AWS 计算资源(如 EMR 或 EC2)或根据预定义规范设置一组 AWS 服务的过程:

    • CloudFormation: CloudFormation 模板(aws.amazon.com/cloudformation帮助以代码形式配置云基础设施。CloudFormation 通常用于执行特定任务,比如创建 EMR 集群、准备具体规格的 S3 存储桶或终止正在运行的 EMR 集群。它有助于标准化重复性任务。

    • Jenkins:Jenkins (www.jenkins.io) 构建用 Java 和 Scala 编写的可执行文件。我们使用 Jenkins 构建 Spark 流水线工件(例如.jar 文件)并部署到 EMR 节点。Jenkins 还利用 CloudFormation 模板以标准化方式执行任务。

  • 数据库(Database):数据存储与数据库的关键区别在于数据库用于存储结构化数据。在这里,我们将讨论两种流行的数据库类型:关系数据库键-值存储数据库。我们将描述它们的区别,并解释适当的使用案例。

    • 关系数据库(Relational databases)关系数据库以表格格式存储带有模式的结构化数据。以结构化方式存储数据的主要优点来自于数据管理;存储的数据值受到严格控制,保持值的一致格式。这使得数据库能够在存储和检索特定数据集时进行额外的优化。ETL 作业通常从一个或多个数据存储服务中读取数据,处理数据,并将处理后的数据存储在关系数据库中,例如MySQL (www.mysql.com) 和 PostgreSQL (www.postgresql.org)。AWS 还提供关系数据库服务,例如Amazon RDS (aws.amazon.com/rds)。

    • 键-值存储数据库(Key-value storage databases):与传统的关系数据库不同,这些是专为高频率读写操作优化的数据库。这些数据库以独特的键-值对方式存储数据。一般来说,数据由一组键和一组值组成,每个键持有各自的属性。许多数据库支持模式,但它们的主要优势在于它们也支持非结构化数据。换句话说,您可以存储任何数据,即使每个数据具有不同的结构。这类数据库的流行例子包括Cassandra (cassandra.apache.org) 和 MongoDB (www.mongodb.com)。有趣的是,AWS 提供了一个称为DynamoDB的键-值存储数据库服务 (aws.amazon.com/dynamodb)。

  • 元数据存储库(Metastore):在某些情况下,最初收集和存储的数据集可能缺少关于自身的任何信息:例如,可能缺少列类型或关于数据源的详细信息。当工程师们管理和处理数据时,这些信息通常对他们有所帮助。因此,工程师们引入了元数据存储库的概念。这是一个存储元数据的仓库。存储为表格的元数据提供了数据指向的位置、模式以及更新历史。

在 AWS 的情况下,Glue Data Catalog 充当元数据存储库的角色,为 S3 提供内置支持。而 Hive (hive.apache.org) 则是一个针对 HDFS 的开源元数据存储库。Hive 的主要优势来自于数据查询、汇总和分析,这些功能天然支持基于类 SQL 语言的交互。

  • 应用程序编程接口 (API) 服务API 端点允许数据科学家和工程师有效地与数据进行交互。例如,可以设置 API 端点以便轻松访问存储在 S3 存储桶中的数据。许多框架专为 API 服务而设计。例如,Flask API (flask.palletsprojects.com) 和 Django (www.djangoproject.com) 框架基于 Python,而 Play 框架 (www.playframework.com) 则经常用于 Scala 项目。

  • 实验平台:在生产中评估系统性能通常通过一种称为 A/B 测试的流行用户体验研究方法来实现。通过部署系统的两个不同版本并比较用户体验,A/B 测试使我们能够了解最近的更改是否对系统产生了积极影响。一般来说,设置 A/B 测试涉及两个组成部分:

    • Rest APIRest API 在处理带有不同参数的请求和返回经过处理的数据方面提供了更大的灵活性。因此,通常会设置一个 Rest API 服务,从数据库或数据存储中聚合必要的数据以供分析目的,并以 JSON 格式提供数据给 A/B 实验平台。

    • A/B 实验平台:数据科学家通常使用一个带有图形用户界面 (GUI) 的应用程序安排各种 A/B 测试实验,并直观地可视化聚合数据进行分析。GrowthBook (www.growthbook.io) 是这类平台的开源示例。

  • 数据可视化工具: 公司内的几个团队和组别(例如市场营销、销售和高管团队)可以从直观地可视化数据中受益。数据可视化工具通常支持创建自定义仪表板,有助于数据分析过程。Tableau (www.tableau.com) 是项目领导者中广受欢迎的工具,但它是专有软件。另一方面,Apache Superset (superset.apache.org) 是一款开源数据可视化工具,支持大多数标准数据库。如果担心管理成本,可以配置 Apache Superset 来读取和绘制使用无服务器数据库(例如 AWS Athena (aws.amazon.com/athena))存储的数据的可视化图表。

  • 身份访问管理 (IAM): IAM 是一种权限系统,用于管理对 AWS 资源的访问。通过 IAM,可以控制用户可以访问的一组资源以及他们可以对提供的资源执行的一组操作。有关 IAM 的更多详细信息,请访问 aws.amazon.com/iam

记住的事情

a. 在整个 ETL 过程中,数据将从一个或多个源收集,根据需要转换为不同的形式,并保存到数据存储或数据库中。

b. Apache Spark 是一款开源的 ETL 引擎,广泛用于处理各种类型的大量数据:结构化、非结构化和半结构化数据。

c. 为数据处理作业设置的典型系统包括各种组件,包括数据存储、数据库、ETL 引擎、数据可视化工具和实验平台。

d. ETL 引擎可以在多种设置中运行 - 单机器、集群、完全托管的云端 ETL 服务以及为深度学习项目设计的端到端服务。

在接下来的部分,我们将介绍 Apache Spark 的关键编程概念,这是最流行的 ETL 工具。

Apache Spark 简介

Apache Spark 是一个开源的数据分析引擎,用于数据处理。最流行的用例是 ETL。作为 Spark 的介绍,我们将涵盖围绕 Spark 的关键概念以及一些常见的 Spark 操作。具体来说,我们将从介绍弹性分布式数据集RDDs)和 DataFrames 开始。然后,我们将讨论用于 ETL 任务的 Spark 基础知识:如何从数据存储加载数据集合,应用各种转换,并存储处理后的数据。Spark 应用可以使用多种编程语言实现:Scala、Java、Python 和 R。在本书中,我们将使用 Python,以便与其他实现保持一致。本节中的代码片段可以在本书的 GitHub 代码库中找到:github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_5/spark。我们在示例中使用的数据集包括 Google Scholar 和我们在 第二章 深度学习项目的数据准备 中爬取的 COVID 数据集,以及由纽约时报提供的另一个 COVID 数据集(github.com/nytimes/covid-19-data)。我们将最后一个数据集称为 NY Times COVID。

弹性分布式数据集和 DataFrames

Spark 的独特优势来自于 RDDs,即不可变的分布式数据对象集合。通过利用 RDDs,Spark 能够高效处理利用并行性的数据。Spark 内置的操作基于 RDDs 的并行处理有助于数据处理,即使其中一个或多个处理器失败。当触发 Spark 作业时,输入数据的 RDD 表示会被分割成多个分区,并分发到每个节点进行转换,从而最大化吞吐量。

类似于 pandas 的 DataFrames,Spark 也有 DataFrames 的概念,它们表示关系数据库中的表,具有命名列。DataFrame 也是一个 RDD,因此我们在下一节描述的操作也可以应用于它们。DataFrame 可以从结构化为表格的数据创建,例如 CSV 数据、Hive 中的表或现有的 RDDs。DataFrame 包含 RDD 不提供的模式。因此,RDD 用于非结构化和半结构化数据,而 DataFrame 用于结构化数据。

在 RDDs 和 DataFrames 之间转换

任何 Spark 操作的第一步是创建一个 SparkSession 对象。具体来说,使用 pyspark.sql 中的 SparkSession 模块创建 SparkSession 对象。如下所示,使用该模块中的 getOrCreate 函数创建会话对象。SparkSession 对象是 Spark 应用程序的入口点。它提供了在不同上下文(如 Spark 上下文、Hive 上下文和 SQL 上下文)下与 Spark 应用程序交互的方式:

from pyspark.sql import SparkSession
spark_session = SparkSession.builder\
        .appName("covid_analysis")\
        .getOrCreate()

将 RDD 转换为 DataFrame 很简单。鉴于 RDD 没有任何模式,因此可以如下创建一个没有模式的 DataFrame:

# convert to df without schema
df_ri_freq = rdd_ri_freq.toDF() 

要将 RDD 转换为具有模式的 DataFrame,您需要使用pyspark.sql.types模块中的StructType类。一旦使用StructType方法创建了模式,就可以使用 Spark 会话对象的createDataFrame方法将 RDD 转换为 DataFrame:

from pyspark.sql.types import StructType, StructField, StringType, IntegerType
# rdd for research interest frequency data
rdd_ri_freq = ... 
# convert to df with schema
schema = StructType(
          [StructField("ri", StringType(), False), 
           StructField("frequency", IntegerType(), False)])
df = spark.createDataFrame(rdd_ri_freq, schema)

现在我们已经学会如何在 Python 中设置 Spark 环境,让我们学习如何将数据集加载为 RDD 或 DataFrame。

加载数据

Spark 可以加载存储在各种数据存储中的不同格式的数据。加载存储在 CSV 格式中的数据是 Spark 的基本操作。可以使用spark_session.read.csv函数轻松实现这一点。它将本地或云端(如 S3 桶中)的 CSV 文件读取为 DataFrame。在下面的代码片段中,我们正在加载存储在 S3 中的 Google Scholar 数据。可以使用header选项指示 CSV 文件包含标题行:

# datasets location
google_scholar_dataset_path = "s3a://my-bucket/dataset/dataset_csv/dataset-google-scholar/output.csv"
# load google scholar dataset
df_gs = spark_session. \
        .read \
        .option("header", True) \
        .csv(google_scholar_dataset_path)

下图显示了df_gs.show(n=3)的结果。show函数打印了前n行以及列标题:

图 5.2 – 通过加载 CSV 文件创建的样本 DataFrame

图 5.2 – 通过加载 CSV 文件创建的样本 DataFrame

同样地,可以使用SparkSession模块的read.json函数读取数据存储中的 JSON 文件:

# loada json file
json_file_path="s3a://my-bucket/json/cities.json"
df = spark_session.read.json(json_file_path)

在下一节中,我们将学习如何使用 Spark 操作处理加载的数据。

使用 Spark 操作处理数据

Spark 提供了一组操作,可以将 RDD 转换为不同结构的 RDD。实现 Spark 应用程序是在 RDD 上链接一组 Spark 操作以将数据转换为目标格式的过程。在本节中,我们将讨论最常用的操作 – 即filtermapflatMapreduceByKeytakegroupByjoin

过滤器

在大多数情况下,通常首先应用过滤器以丢弃不必要的数据。对 DataFrame 应用filter方法可以帮助您从给定的 DataFrame 中选择感兴趣的行。在下面的代码片段中,我们使用这种方法仅保留research_interest不为None的行:

# research_interest cannot be None
df_gs_clean = df_gs.filter("research_interest != 'None'")

map

与其他编程语言中的map函数类似,Spark 中的map操作将给定函数应用于每个数据条目。在这里,我们使用map函数仅保留research_interest列:

# we are only interested in research_interest column
rdd_ri = df_gs_clean.rdd.map(lambda x: (x["research_interest"]))

flatMap

flatMap函数在对每个条目应用给定函数后展开 RDD,并返回新的 RDD。在本例中,flatMap操作使用##分隔符拆分每个数据条目,然后创建具有值为1的默认频率的research_interest对:

# raw research_interest data into pairs of research area and a frequency count
rdd_flattened_ri = rdd_ri.flatMap(lambda x: [(w.lower(), 1) for w in x.split('##')])

reduceByKey

reduceByKey基于其键对输入 RDD 进行分组。在这里,我们使用reduceByKey来对频率进行求和,以了解每个research_interest的出现次数:

# The pairs are grouped based on research area and the frequencies are summed up
rdd_ri_freq = rdd_flattened_ri.reduceByKey(add)

take

Spark 的基本操作之一是take。此函数用于从 RDD 中获取前n个元素:

# we are interested in the first 5 entries
rdd_ri_freq_5 = rdd_ri_freq.take(5)

分组操作

分组的概念是将 DataFrame 中相同的数据条目收集到组中,并对这些组执行聚合(例如平均值或求和)。

例如,让我们使用 Moderna COVID 数据集通过groupby操作获取每个司法管辖区(州)分配的平均剂量数。在这里,我们使用sort函数对州级平均剂量进行排序。toDFalias函数可以帮助为新 DataFrame 添加名称:

# calculate average number of 1st corona vaccine per jurisdiction (state)
df_avg_1 = df_covid.groupby("jurisdiction")\
  .agg(F.avg("_1st_dose_allocations")
  .alias("avg"))\
  .sort(F.col("avg").desc())\
  .toDF("state", "avg")

在应用groupby时,可以在单个命令中应用多个聚合(sumavg)。从聚合函数(如F.avgF.sum)创建的列可以使用alias重命名。在以下示例中,正在对 Moderna COVID 数据集执行聚合操作,以获取第一剂和第二剂的平均数和总数:

# At jurisdiction (state) level, calculate at average weekly 1st & 2nd dose vaccine distribution. Similarly calculate sum for 1st and 2nd dose
df_avg = df_covid.groupby(F.lower("jurisdiction").alias("state"))\
  .agg(F.avg("_1st_dose_allocations").alias("avg_1"), \
       F.avg("_2nd_dose_allocations").alias("avg_2"), \
       F.sum("_1st_dose_allocations").alias("sum_1"), \
       F.sum("_2nd_dose_allocations").alias("sum_2")
       ) \
  .sort(F.col("avg_1").desc())

使用groupby函数在州级别执行计算。该数据集总共包含 63 个州,包括某些实体(联邦机构)作为州。

join

join功能有助于组合来自两个 DataFrame 的行。

为了演示如何使用join,我们将 Moderna COVID 数据集与 NY Times COVID 数据集进行连接。在解释任何join操作之前,我们必须像之前处理 Moderna COVID 数据集一样,在 NY Times COVID 数据集上应用聚合。在以下代码片段中,正在应用groupby操作以州级别获取聚合(sum)值,代表总死亡人数和总病例数:

# at jurisdiction (state) level, calculate total number of deaths and total number of cases
df_cases = df_covid2 \
          .groupby(F.lower("state").alias("state")) \
          .agg(F.sum("deaths").alias("sum_deaths"), \
               F.sum("cases").alias("sum_cases"))

Figure 5.3 显示了df_cases.show(n=3)操作的结果,可视化处理后 DataFrame 的前三行:

![Figure 5.3 – 使用 df_inner.show(n=3)操作的输出结果![Figure 5.3 – 聚合结果的前三行 Figure 5.3 – 可视化处理后 DataFrame 的前三行结果现在我们准备演示两种类型的连接:equi-join 和左连接。#### Equi-join(内连接)Equi-join,也称为内连接,是 Spark 中默认的join操作。内连接用于在两个 DataFrame 之间基于共同列值进行连接。在最终的 DataFrame 中,键不匹配的行将被丢弃。在本例中,将应用 equi-join 到state列,作为 Moderna COVID 数据集和 NY Times COVID 数据集之间的共同列。第一步是使用alias为 DataFrame 创建别名。然后,在一个 DataFrame 上调用join函数,同时传递另一个 DataFrame 来定义列关系和连接类型:py# creating an alias for each DataFramedf_moderna = df_avg.alias("df_moderna")df_ny = df_cases.alias("df_ny")df_inner = df_moderna.join(df_ny, F.col("df_moderna.state") == F.col("df_ny.state"), 'inner')下面是df_inner.show(n=3)操作的输出:Figure 5.4 – 使用 df_inner.show(n=3)操作的输出结果

图 5.4 – 使用df_inner.show(n=3)操作的输出

现在,让我们看看另一种类型的连接,左连接。

左连接

左连接是另一种用于数据分析的join操作。左连接返回来自一个 DataFrame 的所有行,不管在另一个 DataFrame 上是否找到匹配项。当join表达式不匹配时,它会为缺失的条目分配null

左连接语法类似于等值连接。唯一的区别在于,在指定连接类型时,您需要使用left关键字而不是inner。左连接获取第一个 DataFrame(df_m)中提到的指定列(df_m.state)的所有值。然后,它试图在第二个提到的 DataFrame(df_ny)中的指定列(df_ny.state)上匹配条目。在本例中,如果某个特定状态在两个 DataFrame 中都出现,则join操作的输出将是该状态及来自两个 DataFrame 的值。如果某个特定状态仅在第一个 DataFrame(df_m)中可用,而不在第二个 DataFrame(df_ny)中,则它将添加该状态及第一个 DataFrame 的值,保留其他条目为null

# join results in all rows from the left table. Missing entries from the right table will result in "null"
df_left = df_moderna.join(df_ny, F.col("df_m.state") == F.col("df_ny.state"), 'left')

使用df_left.show(n=3)命令的输出如下所示:

图 5.5 – 使用df_inner.show(n=3)操作的输出

图 5.5 – 使用df_inner.show(n=3)操作的输出

尽管 Spark 提供了广泛的操作来涵盖不同的用例,但由于逻辑复杂性,构建自定义操作可能更有用。

使用用户定义函数处理数据

用户定义函数UDF是一种可重复使用的自定义函数,用于对 RDD 执行转换。UDF 函数可以在多个 DataFrame 上重复使用。在本节中,我们将提供一个完整的代码示例,用于使用 UDF 处理 Google Scholar 数据集。

首先,我们想介绍pyspark.sql.function模块,它允许您使用udf方法定义 UDF,并提供各种基于列的操作。pyspark.sql.function还包括用于聚合的函数,如用于计算平均值和总和的avgsum

import pyspark.sql.functions as F

在 Google Scholar 数据集中,data_scienceartificial_intelligencemachine_learning都指向相同的research_interest数据领域,并检查是否可以将任何数据归类为 AI。如果找到匹配项,则在新列中放置1值。否则,将分配0。UDF 的结果使用withColumn方法存储在名为is_artificial_intelligence的新列中。在以下代码片段中,@F.udf注解通知 Spark 该函数是一个 UDF。pyspark.sql.functions中的col方法经常用于将列作为 UDF 的参数传递。在这里,F.col("research_interest")已传递给 UDF is_ai方法,指示该 UDF 应操作的列:

# list of research_interests that are under same domain
lst_ai  = ["data_science", "artificial_intelligence",
           "machine_learning"]
@F.udf
def is_ai(research):
    """ return 1 if research in AI domain else 0"""
    try:
      # split the research interest with delimiter "##"  
      lst_research = [w.lower() for w in str(research).split("##")]
      for res in lst_research:
        # if present in AI domain
        if res in lst_ai:
          return 1
      # not present in AI domain
      return 0
    except:
      return -1
# create a new column "is_artificial_intelligence"
df_gs_new = df_gs.withColumn("is_artificial_intelligence",\ is_ai(F.col("research_interest")))

在处理原始数据后,我们希望将其存储在数据存储中,以便可以为其他目的重复使用。

导出数据

在本节中,我们将学习如何将 DataFrame 保存到 S3 存储桶中。对于 RDD,必须将其转换为 DataFrame 才能适当保存。

通常,数据分析师希望将聚合数据写入 CSV 文件以进行后续操作。要将 DataFrame 导出为 CSV 文件,必须使用 df.write.csv 函数。对于文本值,建议使用 option("quoteAll", True),这将用引号括起每个值。

在以下示例中,我们提供了一个 S3 路径来生成 S3 存储桶中的 CSV 文件。使用 coalesce(1) 以写入单个 CSV 文件而不是多个 CSV 文件:

S3_output_path = "s3a:\\my-bucket\output\vaccine_state_avg.csv"
# writing a DataFrame as a CSVfile
sample_data_frame.\
        .coalesce(1) \
        .write \
        .mode("overwrite") \
        .option("header", True) \
        .option("quoteAll",True) \
        .csv(s3_output_path)

如果要将 DataFrame 保存为 JSON 文件,可以使用 write.json

S3_output_path = "s3a:\\my-bucket\output\vaccine_state_avg.json"
# Writing a DataFrame as a json file
sample_data_frame \
        .write \
        .json(s3_output_path)

此时,您应该看到一个文件已存储在 S3 存储桶中。

需记住的事项

a. RDD 是一个不可变的分布式集合,被分割成多个分区并在集群的不同节点上计算。

b. Spark DataFrame 相当于关系数据库中的表,具有命名列。

c. Spark 提供了一系列操作,可以将一个 RDD 转换为具有不同结构的另一个 RDD。实现 Spark 应用程序是在 RDD 上链接一系列 Spark 操作,将数据转换为目标格式的过程。您可以使用 UDF 构建自定义的 Spark 操作。

在本节中,我们描述了 Apache Spark 的基础知识,这是最常用的 ETL 工具。从下一节开始,我们将讨论如何在云中为 ETL 设置 Spark 作业。首先,让我们看看如何在单个 EC2 实例上运行 ETL。

为 ETL 设置单节点 EC2 实例

EC2 实例可以具有各种 CPU/GPU、内存、存储和网络容量的组合。您可以在官方文档中找到 EC2 的可配置选项:aws.amazon.com/ec2/instance-types

创建 EC2 实例时,可以选择预定义的 Docker 镜像来运行各种项目。这些称为Amazon Machine ImagesAMIs)。例如,有一个安装了 TF 版本 2 用于 DL 项目的镜像,以及一个为通用 ML 项目设置了 Anaconda 的镜像,如下截图所示。有关完整的 AMI 列表,请参阅 docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html

图 5.6 – 选择 EC2 实例的 AMI

图 5.6 – 选择 EC2 实例的 AMI

AWS 提供了用于 DL 项目的Deep Learning AMIsDLAMIs),这些 AMIs 是专为 DL 项目创建的;这些镜像利用不同的 CPU 和 GPU 配置以及不同的计算架构(docs.aws.amazon.com/dlami/latest/devguide/options.html)。

如在 第一章 中提到的 深度学习驱动项目的有效规划,许多数据科学家利用 EC2 实例开发他们的算法,利用动态资源分配的灵活性。创建 EC2 实例并安装 Spark 的步骤如下:

  1. 创建一个 虚拟专用网络VPN),以限制访问 EC2 实例以增强安全性。

  2. 使用 EC2 密钥对创建一个 .pem 密钥。 .pem 文件用于在用户尝试从终端登录 EC2 实例时执行身份验证。

  3. 使用包含所需工具和包的 Docker 镜像创建 EC2 实例。

  4. 添加一个入站规则,允许从本地终端访问新实例。

  5. 使用 SSH 访问 EC2 实例,并使用在 第 2 步 中创建的 .pem 文件。

  6. 启动 Spark shell。

我们为每个步骤提供了详细的描述和屏幕截图,位于 github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_5/ec2

要记住的事情

a. 一个 EC2 实例可以具有不同的 CPU/GPU、内存、存储和网络容量组合。

b. 可以从 AWS Web 控制台上的预定义 Docker 镜像(AMI)中创建 EC2 实例。

接下来,我们将学习如何设置一个运行一组 Spark 工作节点的集群。

为 ETL 设置 EMR 集群

在 DL 的情况下,单个 EC2 实例的计算能力可能不足以进行模型训练或数据处理。因此,通常将一组 EC2 实例放在一起以增加吞吐量。AWS 为此提供了专门的服务:Amazon Elastic MapReduceEMR)。它是一个完全托管的集群平台,提供用于大数据框架(如 Apache Spark 和 Hadoop)的分布式系统。通常,为 ETL 设置的 EMR 集群从 AWS 存储(Amazon S3)读取数据,处理数据,然后将数据写回 AWS 存储。Spark 作业通常用于处理与 S3 交互的 ETL 逻辑。EMR 提供了一个名为 Workspace 的有趣功能,帮助开发人员组织笔记本,并与其他 EMR 用户共享以进行协作工作。

典型的 EMR 设置包括一个主节点和几个核心节点。在多节点集群中,必须至少有一个核心节点。主节点管理运行分布式应用程序(例如 Spark 或 Hadoop)的集群。核心节点由主节点管理,运行数据处理任务并将数据存储在数据存储中(例如 S3 或 HDFS)。

任务节点由主节点管理,是可选的。它们通过在计算过程中引入另一种并行性,提高了集群上运行的分布式应用程序的吞吐量。它们运行数据处理任务,但不将数据存储在数据存储中。

下面的屏幕截图显示了 EMR 集群创建页面。在整个表单中,我们需要提供集群名称、启动模式、EMR 版本、要在集群上运行的应用程序(例如用于数据处理的 Apache Spark 和笔记本的 Jupyter)以及 EC2 实例的规格。DL 的数据处理通常需要高计算能力的实例。在其他情况下,您可以构建具有增加内存限制的集群:

图 5.7 – EMR 集群创建

图 5.7 – EMR 集群创建

图 5.7 – EMR 集群创建

详细步骤如下:

  • 步骤 1:软件和步骤:在这里,您必须选择与软件相关的配置 – 即 EMR 版本和应用程序(Spark、JupyterHub 等)。

  • 步骤 2:硬件:在这里,您必须选择与硬件相关的配置 – 即实例类型、实例数量和 VPN 网络。

  • 步骤 3:通用集群设置:选择集群名称和用于操作日志的 S3 存储桶路径。

  • .pem 文件:

    • .pem 文件只在您想要登录到 EC2 主节点并在 Spark shell 上工作时才需要,就像在单个 EC2 实例的情况下一样。

完成这些步骤后,您需要等待几分钟,直到集群状态变为running。然后,您可以导航到 EMR 集群提供的端点以打开 Jupyter 笔记本。用户名为jovyan,密码为jupyter

我们的 GitHub 存储库提供了这一过程的逐步说明,以及屏幕截图(github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_5/emr)。

需记住的事项

a. EMR 是一个完全托管的集群平台,运行大数据 ETL 框架,如 Apache Spark。

b. 您可以通过 AWS Web 控制台创建具有各种 EC2 实例的 EMR 集群。

EMR 的缺点在于需要明确管理。一个组织通常有专门处理与 EMR 集群相关问题的开发人员小组。不幸的是,如果组织很小,这可能会很难做到。在接下来的部分中,我们将介绍 Glue,它不需要任何显式的集群管理。

创建用于 ETL 的 Glue 作业

AWS Glue(aws.amazon.com/glue)支持以无服务器方式进行数据处理。Glue 的计算资源由 AWS 管理,因此与专用集群(例如 EMR)相比,维护工作量较少。除了资源的最小维护工作外,Glue 还提供额外功能,如内置调度器和 Glue 数据目录,稍后将进行讨论。

首先,让我们学习如何使用 Glue 设置数据处理作业。在开始定义数据处理逻辑之前,您必须创建一个包含 S3 中数据架构的 Glue 数据目录。一旦为输入数据定义了 Glue 数据目录,您可以使用 Glue Python 编辑器定义数据处理逻辑的细节(图 5.8)。该编辑器为您的应用提供了一个基本设置,以减少设置 Glue 作业时的困难:docs.aws.amazon.com/glue/latest/dg/edit-script.html。在这个模板代码的基础上,您将读取 Glue 数据目录作为输入,对其进行处理,并存储处理后的输出。由于 Glue 数据目录与 Spark 高度集成,Glue 作业内的操作通常使用 Spark 完成:

图 5.8 – AWS Glue 作业脚本编辑器

图 5.8 – AWS Glue 作业脚本编辑器

在接下来的章节中,您将学习如何使用存储在 S3 存储桶中的 Google Scholar 数据集设置 Glue 作业。完整的实现可以在 github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_5/glue 找到。

创建 Glue 数据目录

首先,我们将创建一个 Glue 数据目录(见 图 5.9)。Glue 只能读取数据集,其中元数据存储在 Glue 数据目录中。数据目录由数据库组成,这些数据库是以表格形式存储的元数据集合。Glue 提供了一个称为 crawler 的功能,用于创建存储在数据存储中的数据文件的元数据(例如,一个 S3 存储桶):

图 5.9 – 设置爬虫的第一步

图 5.9 – 设置爬虫的第一步

上述截图显示了创建爬虫的第一步。每个步骤的详细信息可以在 docs.aws.amazon.com/glue/latest/dg/add-crawler.html 找到。

设置 Glue 上下文

如果您查看 AWS 为 Glue 提供的模板代码,您会发现已经导入了一些关键包。awsglue.utils 模块中的 getResolvedOptions 帮助利用在运行时传递给 Glue 脚本的参数:

from awsglue.utils import getResolvedOptions
args = getResolvedOptions(sys.argv, ['JOB_NAME'])

对于使用 Spark 的 Glue 作业,必须创建一个 Spark 上下文并将其传递给 GlueContext。可以从 Glue 上下文中访问 Spark 会话对象。可以通过传递 Glue 上下文对象来实例化使用 awsglue.job 模块的 Glue 作业:

from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job
# glue_job_google_scholar.py
# spark context
spark_context = SparkContext()
# glue context
glueContext = GlueContext(spark_context)
# spark
spark_session = glueContext.spark_session
# job
job = Job(glueContext)
# initialize job
job.init(args['JOB_NAME'], args)

接下来,我们将学习如何从 Glue 数据目录中读取数据。

读取数据

在本节中,您将学习如何在创建 Glue 表目录后,在 Glue 上下文中读取位于 S3 存储桶中的数据。

在 Glue 中,数据通过称为 DynamicFrame 的特定数据结构从转换到转换传递,它是 Apache Spark DataFrame 的扩展。DynamicFrame 具有自描述特性,不需要任何模式。与 Spark DataFrame 不同,DynamicFrame 的这一额外属性有助于容纳不符合固定模式的数据。可以从 awsglue.dynamicframe 导入所需的库。该包可以轻松地将 DynamicFrame 转换为 Spark DataFrame:

from awsglue.dynamicframe import DynamicFrame

在下面的示例中,我们正在创建一个名为 google_authors 的 Glue 数据目录表,存储在名为 google_scholar 的数据库中。一旦数据库可用,可以使用 glueContext.create_dynamic_frame.from_catalog 读取 google_scholar 数据库中的 google_authors 表,并将其加载为 Glue DynamicFrame:

# glue context
google_authors = glueContext.create_dynamic_frame.from_catalog(
           database="google_scholar",
           table_name="google_authors")

可以使用 toDF 方法将 Glue DynamicFrame 转换为 Spark DataFrame。此转换需要将 Spark 操作应用于数据:

# convert the glue DynamicFrame to Spark DataFrame
google_authors_df = google_authors.toDF()

现在,让我们定义数据处理逻辑。

定义数据处理逻辑

Glue DynamicFrame 可以执行的基本转换由 awsglue.transforms 模块提供。这些转换包括 joinfiltermap 等等(docs.aws.amazon.com/glue/latest/dg/built-in-transforms.html)。您可以类似于Apache Spark 简介部分中所介绍的方式来使用它们:

from awsglue.transforms import *

此外,如果 Glue DynamicFrame 已转换为 Spark DataFrame,则可以将使用 Spark 操作处理数据部分中描述的每个 Spark 操作应用于 Glue 数据。

写入数据

在本节中,我们将学习如何将 Glue DynamicFrame 中的数据写入 S3 存储桶。

给定一个 Glue DynamicFrame,您可以使用 Glue 上下文的 write_dynamic_frame.from_options 将数据存储在指定的 S3 路径中。您需要在作业结束时调用 commit 方法来执行各个操作:

# path for output file
path_s3_write= "s3://google-scholar-csv/write/"
# write to s3 as a CSV file with separator |
glueContext.write_dynamic_frame.from_options(
    frame = dynamic_frame_write,
    connection_type = "s3",
    connection_options = {
            "path": path_s3_write
                         },
    format = "csv",
    format_options={
            "quoteChar": -1,
            "separator": "|"
                   })
job.commit()

对于 Spark DataFrame,必须在存储数据之前将其转换为 DynamicFrame。DynamicFrame.fromDF 函数接受 Spark DataFrame 对象、Glue 上下文对象和新 DynamicFrame 的名称:

# create a DynamicFrame from a Spark DataFrame
dynamic_frame = DynamicFrame.fromDF(df_sort, glueContext, "dynamic_frame")

现在,您可以同时使用 Spark 操作和 Glue 转换来处理您的数据。

需要记住的事项

a. AWS Glue 是一个专为 ETL 操作设计的完全托管服务

b. AWS Glue 是一个无服务器架构,这意味着底层服务器将由 AWS 维护

c. AWS Glue 提供了一个带有 Python 样板代码的内置编辑器。在此编辑器中,您可以定义您的 ETL 逻辑,并利用 Spark。

作为 ETL 的最后设置,我们将看一下 SageMaker。

利用 SageMaker 进行 ETL

在本节中,我们将描述如何使用 SageMaker 设置 ETL 流程(下图显示了 SageMaker 的 Web 控制台)。SageMaker 的主要优势在于它是一个完全托管的基础设施,用于构建、训练和部署 ML 模型。缺点是它比 EMR 和 Glue 更昂贵。

SageMaker Studio 是面向 SageMaker 的基于 Web 的开发环境。SageMaker 的理念是提供一个集成的数据分析管道。使用 SageMaker Studio 可以完成 ML 管道的每个阶段:数据处理、算法设计、作业调度、实验管理、模型开发和训练、创建推断端点、检测数据漂移以及可视化模型性能。SageMaker Studio 的笔记本也可以连接到 EMR 进行计算,但有一些限制;只能使用部分限定的 Docker 镜像(如Data ScienceSparkMagic)(docs.aws.amazon.com/sagemaker/latest/dg/studio-notebooks-emr-cluster.html):

Figure 5.10 – The SageMaker web console

图 5.10 – SageMaker Web 控制台

SageMaker 提供各种预定义的开发环境作为 Docker 镜像。流行的环境包括已经安装了 PyTorch、TF 和 Anaconda 的 DL 项目环境。可以轻松地从 Web 开发环境将笔记本附加到任何这些镜像中,如下面的截图所示:

图 5.11 – 动态更新 SageMaker 笔记本的开发环境

图 5.11 – 动态更新 SageMaker 笔记本的开发环境

创建 ETL 作业的过程可以分解为四个步骤:

  1. 在 SageMaker Studio 内创建一个用户。

  2. 通过选择正确的 Docker 镜像在用户下创建一个笔记本。

  3. 定义数据处理逻辑。

  4. 安排一个作业。

在 SageMaker Web 控制台中,步骤 1步骤 2 只需一键即可完成。步骤 3 可以使用 Spark 设置。要安排作业(步骤 4),首先需要通过pip命令安装run-notebook命令行实用程序:

pip install https://github.com/aws-samples/sagemaker-run-notebook/releases/download/v0.20.0/sagemaker_run_notebook-0.20.0.tar.gz

在讨论安排笔记本的run-notebook命令之前,我们将简要介绍cron命令,它定义了一个调度的格式。如下图所示,使用六个数字表示时间戳。例如,45 22 ** 6*表示每周六晚上 10:45 的调度。*(星号)通配符表示相应单位的每个值:

图 5.12 – Cron 调度格式

图 5.12 – Cron 调度格式

run-notebook命令接受用cron表示的调度和一个笔记本。在以下示例中,notebook.ipynb已安排在 2021 年每天上午 8 点运行:

run-notebook schedule --at "cron(0 8 * * * 2021)" --name nightly notebook.ipynb

我们在 GitHub 仓库的每个步骤中都提供了一组截图:github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/blob/main/Chapter_5/sagemaker/sagemaker_studio.md

在接下来的章节中,我们将深入研究如何利用 SageMaker 笔记本运行数据处理作业。

创建 SageMaker 笔记本

笔记本实例是运行 Jupyter 笔记本应用程序的 ML 计算实例。SageMaker 将创建此实例以及相关资源。Jupyter 笔记本用于处理数据、训练模型、部署和验证模型。可以在几个步骤内创建笔记本实例。详细描述可在docs.aws.amazon.com/sagemaker/latest/dg/howitworks-create-ws.html找到:

  1. 转到 SageMaker Web 控制台:console.aws.amazon.com/sagemaker。请注意,您需要使用 AWS 凭据登录。

  2. 笔记本实例下,选择创建笔记本实例

  3. 在每个新笔记本上进行pip install tensorflow)。可以在github.com/aws-samples/amazon-sagemaker-notebook-instance-lifecycle-config-samples/tree/master/scripts找到各种示例:

图 5.13 – SageMaker 笔记本的生命周期配置脚本

图 5.13 – SageMaker 笔记本的生命周期配置脚本

虽然直接从 SageMaker 笔记本运行一组操作是一种选择,但 SageMaker 笔记本支持运行明确定义在笔记本外部的数据处理作业,以增加吞吐量和重用性。让我们看看如何从笔记本运行 Spark 作业。

通过 SageMaker 笔记本运行 Spark 作业

一旦笔记本准备就绪,您可以使用sagemaker.processing模块配置 Spark 作业,并使用一组计算资源执行它。SageMaker 提供了PySparkProcessor类,它提供了一个处理 Spark 作业的句柄(sagemaker.readthedocs.io/en/stable/amazon_sagemaker_processing.html#data-processing-with-spark)。其构造函数接受基本的设置详细信息,如作业的名称和 Python 版本。它接受三个参数 - framework_versionpy_versioncontainer_version,这些参数用于固定预构建的 Spark 容器以运行处理作业。可以注册并在image_uri参数上提供自定义映像。image_uri将覆盖framework_versionpy_versioncontainer_version参数:

From sagemaker.processing import PySparkProcessor, ProcessingInput
# ecr image URI
ecr_image_uri = '664544806723.dkr.ecr.eu-central-1.amazonaws.com/linear-learner:latest'
# create PySparkProcessor instance with initial job setup
spark_processor = PySparkProcessor(
    base_job_name="my-sparkjob", # job name
    framework_version="2.4", # tensorflow version
    py_version="py37", # python version
    container_version="1", # container version
    role="myiamrole", # IAM role
    instance_count=2, # ec2 instance count
    instance_type="ml.c5.xlarge", # ec2 instance type
    max_runtime_in_seconds=1200, # maximum run time
    image_uri=ecr_image_uri # ECR image
)

在上述代码中,使用了 PySparkProcessor 类来创建一个 Spark 实例。它接受 base_job_name(作业名称:my-sparkjob)、framework_version(TensorFlow 框架版本:2.0)、py_version(Python 版本:py37)、container_version(容器版本:1)、role(SageMaker 的 IAM 角色:myiamrole)、instance_count(EC2 实例数:2)、instance_type(EC2 实例类型:ml.c5.xlarge)、max_runtime_in_second(超时前的最大运行时间秒数:1200)和 image_url(Docker 镜像的 URL:ecr_image_uri)。

接下来,我们将讨论 PySparkProcessorrun 方法,它通过 Spark 启动提供的脚本:

# input s3 path
path_input = "s3://mybucket/input/"
# output s3 path
path_output = "s3://mybucket/output/"
# run method to execute job
spark_processor.run(
    submit_app="process.py", # processing python script
    arguments=['input', path_input, # input argument for script
               'output', path_output # input argument for script
              ])

在上述代码中,PySparkProcessorrun 方法执行给定的脚本和提供的参数。它接受 submit_app(用 Python 编写的数据处理作业)和参数。在此示例中,我们已经定义了输入数据的位置和输出应存储的位置。

通过 SageMaker 笔记本从自定义容器运行作业

在这一部分中,我们将讨论如何从自定义镜像运行数据处理作业。为此,SageMaker 提供了 sagemaker.processing 模块中的 Processor 类。在本例中,我们将使用 ProcessingInputProcessingOutput 类来分别创建输入和输出对象。这些对象将传递给 Processor 实例的 run 方法。run 方法执行数据处理作业:

# ecr image URI
ecr_image_uri = '664544806723.dkr.ecr.eu-central-1.amazonaws.com/linear-learner:latest'
# input data path
path_data = '/opt/ml/processing/input_data'
# output data path
path_data = '/opt/ml/processing/processed_data'
# s3 path for source
path_source = 's3://mybucket/input'
# s3 path for destination
path_dest = 's3://mybucket/output'
# create Processor instance
processor = Processor(image_uri=ecr_image_uri, # ECR image
               role='myiamrole', # IAM role
               instance_count=1, # instance count
               instance_type="ml.m5.xlarge" # instance type
           )
# calling "run" method of Processor instance
processor.run(inputs=[ProcessingInput(
                 source=path_source, # input source
                 destination=path_data # input destination)],
              outputs=[ProcessingOutput(
                 source=path_data, # output source
                 destination=path_dest # output destination)], ))

在上述代码中,首先创建了一个 Processor 实例。它接受 image_uri(ECR 镜像的 URL 路径:ecr_image_uri)、role(具有访问 ECR 镜像权限的 IAM 角色:myiamrole)、instance_count(EC2 实例计数:1)和 instance_type(EC2 实例类型:ml.m5.xlarge)。Processor 实例的 run 方法可以执行作业。它接受 inputs(作为 ProcessingInput 对象传递的输入数据)和 outputs(作为 ProcessingOutput 对象传递的输出数据)。虽然 Processor 提供了与 PySparkProcessor 类似的一组方法,但主要区别在于 run 函数接受的内容;PySparkProcessor 接受运行 Spark 操作的 Python 脚本,而 Processor 接受支持各种数据处理作业的 Docker 镜像。

对于那些愿意深入了解的人,我们建议阅读 构建自定义处理容器

记住的事情

a. SageMaker 是一个完全托管的基础设施,用于构建、训练和部署 ML 模型。

b. SageMaker 提供一组预定义的开发环境,用户可以根据需要动态更改。

c. SageMaker 笔记本支持通过 sagemaker.processing 模块定义笔记本之外的数据处理作业。

经过对 AWS 中四种最流行的 ETL 工具的介绍,让我们并排比较这四个选项。

比较 AWS 中的 ETL 解决方案

到目前为止,我们已经介绍了四种使用 AWS 设置 ETL 管道的不同方式。在本节中,我们将把这四种设置总结在一张表中(表 5.1)。比较点包括支持无服务器架构、内置调度器的可用性以及支持的 EC2 实例类型的多样性。

支持单节点 EC2 实例GlueEMRSageMaker
支持无服务器架构的可用性
内置用于开发人员协作的工作空间的可用性
多种 EC2 实例类型更多更少更多更多
内置调度器的可用性
内置作业监控 UI 的可用性
内置模型监控的可用性
提供从模型开发到部署的全托管服务
内置可视化器以分析处理过的数据的可用性
内置用于 ETL 逻辑开发的预定义环境的可用性

表 5.1 – 各种数据处理设置的比较 – 单节点 EC2 实例、Glue、EMR 和 SageMaker

正确的设置取决于技术和非技术因素,包括数据来源、数据量、MLOps 的可用性和成本。

需要记住的事情

a. 我们在本章描述的四种 ETL 设置具有独特的优势。

b. 在选择特定设置时,必须考虑各种因素:数据来源、数据量、MLOps 的可用性和成本。

总结

深度学习项目中的一个困难来自于数据量的大小。由于训练深度学习模型需要大量数据,因此数据处理步骤可能会占用大量资源。因此,在本章中,我们学习了如何利用最流行的云服务 AWS 高效处理数千兆字节和百万兆字节的数据。该系统包括调度器、数据存储、数据库、可视化以及用于运行 ETL 逻辑的数据处理工具。

我们额外花费了时间研究 ETL,因为它在数据处理中起着重要作用。我们介绍了 Spark,这是最流行的 ETL 工具,并描述了使用 AWS 设置 ETL 作业的四种不同方式。这四种设置包括使用单节点 EC2 实例、EMR 集群、Glue 和 SageMaker。每种设置都有独特的优势,正确的选择可能因情况而异。这是因为您需要考虑项目的技术和非技术方面。

类似于数据量对数据处理的影响,当训练模型时也会引入多个问题。在下一章中,您将学习如何使用分布式系统高效训练模型。

第六章:高效模型训练

与我们在前一章中扩展数据处理流水线的方式类似,我们可以通过分配更多计算资源来缩短训练深度学习DL)模型所需的时间。在本章中,我们将学习如何配置TensorFlowTF)和PyTorch的训练逻辑,以利用不同机器上多个 CPU 和 GPU 设备。首先,我们将学习 TF 和 PyTorch 如何支持分布式训练,无需任何外部工具。接下来,我们将描述如何利用 SageMaker,因为它专为从云端到端处理 DL 管道而构建。最后,我们将看看专为分布式训练开发的工具:Horovod、Ray 和 Kubeflow。

在本章中,我们将讨论以下主要主题:

  • 在单台机器上训练模型

  • 在集群上训练模型

  • 使用 SageMaker 训练模型

  • 使用 Horovod 训练模型

  • 使用 Ray 训练模型

  • 使用 Kubeflow 训练模型

技术要求

您可以从本书的 GitHub 存储库下载本章的补充材料:github.com/PacktPublis…

在单台机器上训练模型

第三章中所述,开发强大的深度学习模型,训练 DL 模型涉及从数据集中提取有意义的模式。当数据集大小较小且模型参数较少时,使用中央处理单元CPU)可能足以训练模型。然而,当使用更大的训练集并且模型包含更多神经元时,DL 模型表现出更好的性能。因此,使用图形处理单元GPU)进行训练已成为标准,因为您可以利用其在矩阵乘法中的大规模并行性。

在 TensorFlow 中利用多个设备进行训练

TF 提供了tf.distribute.Strategy模块,允许您使用多个 GPU 或 CPU 设备进行训练,只需非常简单的代码修改,详见分布式训练tf.distribute.Strategytf.keras.Model.fit完全兼容,以及自定义训练循环,如第三章中的在 TensorFlow 中实现和训练模型部分描述的那样,开发强大的深度学习模型。Keras 的各个组件,包括变量、层、模型、优化器、度量、摘要和检查点,均设计为支持各种tf.distribute.Strategy类,以尽可能简化转向分布式训练。让我们看看tf.distribute.Strategy模块如何使您能够快速修改为多设备上的单机代码集合:

import tensorflow as tf
mirrored_strategy = tf.distribute.MirroredStrategy()
# or 
# mirrored_strategy = tf.distribute.MirroredStrategy(devices=["/gpu:0", "/gpu:1", "/gpu:3"])
# if you want to use only specific devices 
with mirrored_strategy.scope():
    # define your model 
    # …
model.compile(... )
model.fit(... ) 

模型保存后,可以在有或无tf.distribute.Strategy作用域的情况下加载。为了在自定义训练循环中实现分布式训练,您可以参考示例:www.tensorflow.org/tutorials/distribute/custom_training。话虽如此,让我们来回顾一下最常用的策略。我们将涵盖最常见的方法,其中一些超出了单个实例的训练。它们将用于接下来的几节,涵盖在多台机器上进行训练:

  • 提供对tf.keras.Model.fit和自定义训练循环全面支持的策略:

    • MirroredStrategy: 在单台机器上使用多个 GPU 进行同步分布式训练

    • MultiWorkerMirroredStrategy: 在多台机器上进行同步分布式训练(可能使用每台机器上的多个 GPU)。此策略类需要使用已配置TF_CONFIG环境变量的 TF 集群(www.tensorflow.org/guide/distributed_training#TF_CONFIG)。

    • TPUStrategy: 在多个张量处理单元TPU)上进行训练。

  • 具有实验特性的策略(意味着类和方法仍处于开发阶段),适用于tf.keras.Model.fit和自定义训练循环:

    • ParameterServerStrategy: 模型参数在多个工作节点间共享(集群包括工作节点和参数服务器)。每次迭代后,工作节点读取和更新在参数服务器上创建的变量。

    • CentralStorageStrategy: 变量存储在中央存储中,并在每个 GPU 上复制。

  • 我们想要提及的最后一个策略是tf.distribute.OneDeviceStrategy(https://www.tensorflow.org/api_docs/python/tf/distribute/OneDeviceStrategy)。它在单个 GPU 设备上运行训练代码:

    strategy = tf.distribute.OneDeviceStrategy(device="/gpu:0")
    

在上述示例中,我们选择了第一个 GPU("/gpu:0")。

值得一提的是,可以使用tf.distribute.get_strategy函数获取当前的tf.distribute.Strategy对象。您可以使用此函数动态地为您的训练代码更改tf.distribute.Strategy对象,如下面的代码片段所示:

if tf.config.list_physical_devices('GPU'):
    strategy = tf.distribute.MirroredStrategy()
else:  # Use the Default Strategy
    strategy = tf.distribute.get_strategy()

在上述代码中,当 GPU 设备可用时,我们使用tf.distribute.MirroredStrategy,当 GPU 设备不可用时则回退到默认策略。接下来,让我们看一下 PyTorch 提供的功能。

在 PyTorch 中利用多个设备进行训练

要成功训练一个 PyTorch 模型,模型和输入张量需要配置到相同的设备上。如果要使用 GPU 设备,它们需要在训练之前显式地加载到目标 GPU 设备上,可以使用to(device=torch.device('cuda'))cuda()函数:

cpu = torch.device(cpu')
cuda = torch.device('cuda')     # Default CUDA device
cuda0 = torch.device('cuda:0')
x = torch.tensor([1., 2.], device=cuda0)
# x.device is device(type='cuda', index=0)
y = torch.tensor([1., 2.]).cuda()
# y.device is device(type='cuda', index=0)
# transfers a tensor from CPU to GPU 1
a = torch.tensor([1., 2.]).cuda()
# a.device are device(type='cuda', index=1)
# to function of a Tensor instance can be used to move the tensor to different devices
b = torch.tensor([1., 2.]).to(device=cuda)
# b.device are device(type='cuda', index=1)

前述示例展示了在使用 GPU 设备时需要注意的一些关键操作。这是官方 PyTorch 文档中介绍的一部分内容:pytorch.org/docs/stable…

然而,为了进行训练设置各个组件可能会很繁琐。因此,Trainergpus参数:

# Train using CPU
Trainer()
# Specify how many GPUs to use
Trainer(gpus=k)
# Specify which GPUs to use
Trainer(gpus=[0, 1])
# To use all available GPUs put -1 or '-1'
Trainer(gpus=-1)

在前述示例中,我们描述了单台机器上的各种训练设置:仅使用 CPU 设备进行训练,使用一组 GPU 设备进行训练以及使用所有 GPU 设备进行训练。

需要记住的事情

a. TF 和 PyTorch 都内置了使用 CPU 和 GPU 设备训练模型的支持。

b. 使用 TF 的tf.distribute.Strategy类可以控制训练过程。在单台机器上训练模型时,可以使用MirroredStrategyOneDeviceStrategy

c. 使用 GPU 设备训练 PyTorch 模型时,需要手动将模型和相关张量加载到同一 GPU 设备上。PL 通过Trainer类处理放置操作,隐藏了大部分样板代码。

在本节中,我们学习了如何在单台机器上利用多个设备。然而,随着单台机器计算能力的限制,已经有很多努力将集群用于训练。

在集群上训练模型

尽管在单台机器上使用多个 GPU 已经大大减少了训练时间,但有些模型仍然非常庞大,需要多天的时间进行训练。增加更多的 GPU 仍然是一种选择,但通常存在物理限制,阻止您充分利用多 GPU 设置的潜力:主板可能仅支持有限数量的 GPU 设备。

幸运的是,许多深度学习框架已经支持在分布式系统上训练模型。尽管在实际实施中存在一些细微差异,但大多数框架都采纳了模型并行数据并行的理念。如下图所示,模型并行将模型的组件分布到多台机器上,而数据并行则将训练集的样本分布到多个设备上:

图 6.1 – 模型并行和数据并行之间的区别

图 6.1 – 模型并行和数据并行之间的区别

在设置分布式系统进行模型训练时,有几个必须注意的细节。首先,集群中的机器需要稳定连接到互联网,因为它们通过网络进行通信。如果不能保证稳定性,集群必须有一种方法来恢复连接问题。理想情况下,分布式系统应该对可用的机器无感知,并且能够在不影响总体进度的情况下添加或删除机器。这样的功能将允许用户动态增加或减少机器数量,以最经济高效的方式进行模型训练。AWS 通过弹性 MapReduce (EMR) 和 弹性容器服务 (ECS) 提供上述功能。

接下来的两个部分,我们将更深入地研究模型并行 ism 和数据并行 ism。

模型并行 ism

在模型并行 ism 的情况下,分布式系统中的每台机器都负责模型的一部分,并管理分配组件的计算。当一个网络太大以至于无法放入单个 GPU 时,通常会考虑这种方法。然而,在实际情况下并不常见,因为 GPU 设备通常有足够的内存来容纳模型,并且设置起来非常复杂。在本节中,我们将描述模型并行 ism 的两种最基本的方法:模型分片 ism模型管道化

模型分片 ism

模型分片 ism 不过是将模型分割成多个计算子图,分布在多个设备上。让我们假设一个简单的基本单层深度神经网络 (DNN) 模型的简单场景(没有并行路径)。模型可以分成几个连续的子图,并且分片配置可以以图形方式表示如下。数据将从带有第一个子图的设备开始顺序流动。每个设备将将计算值传递给下一个子图的设备,直到到达所需的数据为止,设备将保持空闲状态。在此示例中,我们有四个子图:

图 6.2 – 模型分片示例分布图;每个箭头表示一个小批量

图 6.2 – 模型分片示例分布图;每个箭头表示一个小批量

如您所见,模型分片 ism 未充分利用计算资源;设备在等待另一个设备处理其子图。为了解决这个问题,提出了管道化方法。

模型管道化

在模型管道化的情况下,一个小批量被分割成微批次,并按照链式提供给系统,如下图所示:

图 6.3 – 模型管道逻辑图示;每个箭头表示一个小批量

图 6.3 – 模型管道逻辑图示;每个箭头表示一个小批量

然而,模型管道需要反向传播的修改版本。让我们看看如何在模型管道设置中实现单个前向和反向传播。在某些时候,每个设备不仅需要为其子图进行前向计算,还需要进行梯度计算。单个前向和反向传播可以如下实现:

图 6.4 – 模型管道中的单个前向和反向传播

图 6.4 – 模型管道中的单个前向和反向传播

在上图中,我们可以看到每个设备依次运行前向传播和反向传播,以相反的顺序传递计算值给下一个设备。将所有内容汇总在一起,我们得到下面的图表,总结了模型管道的逻辑:

图 6.5 – 基于模型管道的模型并行 ism

图 6.5 – 基于模型管道的模型并行 ism

为了进一步提高训练时间,每个设备都存储其先前计算的值,并在接下来的计算中利用这些值。

在 TensorFlow 中的模型并行 ism

下面的代码片段展示了如何在定义模型架构时将一组层分配给特定设备:

with tf.device('GPU:0'): 
    layer1 = layers.Dense(16, input_dim=8with tf.device('GPU:1'): 
    layer2 = layers.Dense(4, input_dim=16)

如果您想进一步探索 TensorFlow 中的模型并行 ism,我们推荐查看 Mesh TF 存储库(github.com/tensorflow/…

在 PyTorch 中的模型并行 ism

模型并行 ism 仅适用于 PyTorch,并尚未在 PL 中实现。尽管有许多方法可以使用 PyTorch 实现模型并行 ism,但最标准的方法是使用torch.distributed.rpc模块,该模块通过远程过程调用RPC)在机器之间进行通信。基于 RPC 的方法的三个主要特征是远程执行函数或网络(远程执行)、访问和引用远程数据对象(远程引用)以及扩展 PyTorch 跨机器边界的梯度更新功能(分布式梯度更新)。我们将详细信息委托给官方文档pytorch.org/docs/stable….

数据并行 ism

数据并行 ism 与模型并行 ism 不同,其目的是通过将数据集分片到集群中的机器来加速训练。每台机器都获得模型的副本,并与其分配的数据集计算梯度。然后,梯度被聚合,并且模型一次全局更新。

在 TensorFlow 中的数据并行 ism

可以通过利用tf.distribute.MultiWorkerMirroredStrategytf.distribute.ParameterServerStrategytf.distribute.CentralStorageStrategy在 TF 中实现数据并行 ism。

我们在 TensorFlow 中利用多个设备进行训练 部分介绍了这些策略,因为特定的 tf.distributed 策略也用于在单个机器内多设备上设置训练。

要使用这些策略,您需要设置一个 TF 集群,其中每台机器可以与其他机器通信。

通常,TF 集群使用 TF_CONFIG 环境变量定义。 TF_CONFIG 只是一个 JSON 字符串,通过定义两个组件来指定集群配置:clustertask。以下 Python 代码展示了如何从 Python 字典生成 TF_CONFIG.json 文件:

tf_config = {
    'cluster': {
        'worker': ['localhost:12345', 'localhost:23456']
    },
    'task': {'type': 'worker', 'index': 0}
}
js_tf = json.dumps(tf_config)
with open("tf_config.json", "w") as outfile:
    outfile.write(js_tf)

关于 TF_CONFIG 的字段和格式,请参阅 https://cloud.google.com/ai-platform/training/docs/distributed-training-details

正如在 TensorFlow 中利用多个设备进行训练 部分演示的,您需要将训练代码放在 tf.distribute.Strategy 范围内。在下面的示例中,我们将展示 tf.distribute.MultiWorkerMirroredStrategy 类的样例用法。

首先,您必须将您的模型实例放在 tf.distribute.MultiWorkerMirroredStrategy 的范围内,如下所示:

strategy = tf.distribute.MultiWorkerMirroredStrategy()
with strategy.scope():
    model = … 

接下来,您需要确保每台机器的 TF_CONFIG 环境变量已正确设置,并运行训练脚本,如下所示:

# On the first node
TF_CONFIG='{"cluster": {"worker": ['localhost:12345', 'localhost:23456']}, "task": {"index": 0, "type": "worker"}}' python training.py
# On the second node
TF_CONFIG='{"cluster": {"worker": ['localhost:12345', 'localhost:23456']}, "task": {"index": 1, "type": "worker"}}' python training.py

要正确保存您的模型,请查看官方文档:https://www.tensorflow.org/tutorials/distribute/multi_worker_with_keras

如果使用自定义训练循环,可以按照 https://www.tensorflow.org/tutorials/distribute/multi_worker_with_ctl 中的说明操作。

PyTorch 中的数据并行

与模型并行不同,数据并行在 PyTorch 和 PL 中都可用。在各种实现中,最标准的功能是 torch.nn.parallel.DistributedDataParallel(DDP)。本节中,我们将主要讨论 PL,因为其主要优势来自于使用数据并行的训练模型的简易性。

要使用数据并行训练模型,您需要修改训练代码以利用底层分布式系统,并使用 torch.distributed.run 模块在每台机器上生成一个进程(pytorch.org/docs/stable/distributed.html)。

下面的代码片段描述了您需要为 ddp 更改的内容。您只需为 Traineraccelerator 参数提供 ddp。当集群中有多台机器时,需要调整 num_nodes 参数:

# train on 8 GPUs (same machine)
trainer = Trainer(gpus=8, accelerator='ddp')
# train on 32 GPUs (4 nodes)
trainer = Trainer(gpus=8, accelerator='ddp', num_nodes=4)

一旦脚本设置完毕,您需要在每台机器上运行以下命令。请记住,MASTER_ADDRMASTER_PORT必须保持一致,因为每个处理器都会使用它们进行通信。另外,NODE_RANK表示机器的索引。换句话说,它必须对每台机器都不同,并且必须从零开始:

python -m torch.distributed.run
    --nnodes=2 # number of nodes you'd like to run with
    --master_addr <MASTER_ADDR>
    --master_port <MASTER_PORT>
    --node_rank <NODE_RANK>
    train.py (--arg1 ... train script args...)

根据官方文档,DDP 的工作原理如下:

  1. 每个节点上的每个 GPU 都会启动一个进程。

  2. 每个进程获取训练集的一个子集。

  3. 每个进程初始化模型。

  4. 每个进程都并行执行前向和后向传播。

  5. 梯度在所有进程之间同步和平均。

  6. 每个进程更新其拥有的模型的权重。

要记住的事情

a. TF 和 PyTorch 提供了使用模型并行 ism 和数据并行 ism 在多台机器上训练 DL 模型的选项。

b. 模型并行将模型分成多个组件,并将它们分布在多台机器上。在 TF 和 PyTorch 中设置模型并行 ism,可以使用Mesh TensorFlow库和torch.distributed.rpc包,分别。

c. 数据并行 ism 将模型复制到每台机器上,并分布小批量数据以进行训练。在 TF 中,可以使用MultiWorkerMirroredStrategyParameterServerStrategyCentralStorageStrategy实现数据并行 ism。PyTorch 中专门用于数据并行 ism 的主要包是torch.nn.parallel.DistributedDataParallel

在本节中,我们学习了如何实现模型训练,其中集群的生命周期得到明确管理。然而,一些工具还管理模型训练的集群。由于它们各自具有不同的优势,您应该理解其差异,以选择适合您开发的正确工具。

首先,我们将查看 SageMaker 的内置功能,以分布式方式训练 DL 模型。

使用 SageMaker 训练模型

正如在第五章在云中利用 SageMaker 进行 ETL部分中提到的,SageMaker 的动机是帮助工程师和研究人员专注于开发高质量的 DL 流水线,而无需担心基础设施管理。SageMaker 为您管理数据存储和计算资源,使您可以利用分布式系统轻松进行模型训练。此外,SageMaker 支持向模型流式传输数据以进行推断、超参数调整和跟踪实验和工件。

SageMaker Studio 是您定义模型逻辑的地方。SageMaker Studio 笔记本允许您快速探索可用数据并设置模型训练逻辑。当模型训练时间过长时,通过对基础架构配置进行少量修改,可以有效地实现扩展使用多个计算资源和找到最佳超参数集。此外,SageMaker 支持在分布式系统上进行超参数调整以利用并行性。

尽管 SageMaker 听起来像深度学习流水线的魔法钥匙,但也有其不足之处。首先是其成本。分配给 SageMaker 的实例比等效的 EC2 实例贵约 40%。其次,您可能会发现并非所有库都在笔记本中可用。换句话说,您可能需要额外的时间来构建和安装所需的库。

设置 SageMaker 的模型训练

现在,你应该能够启动一个笔记本并选择一个预定义的开发环境,因为我们在第五章利用 SageMaker 进行 ETL部分已经涵盖了这些内容。假设您已经处理了原始数据并将处理后的数据存储在数据存储中,我们将在本节中专注于模型训练。使用 SageMaker 进行模型训练可以总结为以下三个步骤:

  1. 如果存储中的处理数据尚未分割为训练、验证和测试集,则必须首先对其进行分割。

  2. 您需要定义模型训练逻辑并指定集群配置。

  3. 最后,您需要训练您的模型并将生成的结果保存回数据存储中。当训练完成时,分配的实例将自动终止。

使用 SageMaker 进行模型训练的关键是sagemaker.estimator.Estimator。它允许您配置训练设置,包括基础架构设置、要使用的 Docker 镜像类型和超参数。以下是您通常会配置的主要参数:

  • role (str): AWS IAM 角色

  • instance_count (int): 用于训练的 SageMaker EC2 实例数量

  • instance_type (str): 用于训练的 SageMaker EC2 实例类型

  • volume_size (int): 用于临时下载训练输入数据的 Amazon 弹性块存储EBS)卷的大小(以 GB 为单位)

  • output_path (str): 训练结果将存储在的 S3 对象

  • use_spot_instances (bool): 指定是否使用 SageMaker 管理的 AWS Spot 实例进行训练的标志

  • checkpoint_s3_uri (str): 训练期间将检查点存储在的 S3 URI

  • hyperparameters (dict): 包含初始超参数集的字典

  • entry_point (str): 运行的 Python 文件路径

  • dependencies (list[str]): 将加载到作业中的目录列表

只要您从 Amazon 弹性容器注册表 (ECR)中选择正确的容器,您就可以为 SageMaker 设置任何训练配置。还存在具有不同 CPU 和 GPU 设备配置的容器。您可以在 github.com/aws/deep-le… 中找到这些信息。

另外,还存在开源工具包的存储库,旨在帮助在 Amazon SageMaker 上进行 TF 和 PyTorch 模型训练。这些存储库还包含已经安装了必要库(如 TF、PyTorch 和其他构建 SageMaker images 所需的依赖项的 Docker 文件:

最后,我们想提一下,您可以在本地机器上构建和运行容器。如果需要,您还可以更新安装的库。如果进行任何修改,需要将修改后的容器上传到 Amazon ECR,然后才能在sagemaker.estimator.Estimator中使用它。

在接下来的两个部分中,我们将描述训练 TF 和 PyTorch 模型所需的一系列更改。

使用 SageMaker 训练 TensorFlow 模型

SageMaker 为 TF 提供了一个sagemaker.estimator.Estimator类:sagemaker.tensorflow.estimator.TensorFlow (sagemaker.readthedocs.io/en/stable/frameworks/tensorflow/sagemaker.tensorflow.html).

以下示例展示了您需要使用sagemaker.tensorflow.estimator.TensorFlow类编写的包装脚本,以在 SageMaker 上训练 TF 模型:

import sagemaker
from sagemaker.tensorflow import TensorFlow
# Initializes SageMaker session
sagemaker_session = sagemaker.Session()
bucket = 's3://dataset/'
tf_estimator = TensorFlow(entry_point='training_script.py', 
              source_dir='.',
              role=sagemaker.get_execution_role(),
              instance_count=1, 
              instance_type='ml.c5.18xlarge',
              framework_version=tf_version, 
              py_version='py3',
              script_mode=True,
              hyperparameters={'epochs': 30} )

请记住,在训练脚本(train_script.py)的ArgumentParser中,hyperparameters参数的每个键必须有相应的条目定义。在上述示例中,我们仅定义了 epochs ('epochs': 30)。

要启动训练,您需要调用fit函数。如果您的数据集在 S3 桶上,fit函数将如下所示:

tf_estimator.fit({'training': 's3://bucket/training',
                  'validation': 's3://bucket/validation'})   

前面的示例将在由 source_dir 提供的目录中运行 entry_point 参数指定的 training_script.py。实例的详细信息可以在 instance_countinstance_type 参数中找到。训练脚本将在 fit 函数中定义的训练和验证数据集上使用 tf_estimatorhyperparameters 进行运行。

使用 SageMaker 训练 PyTorch 模型

类似于 sagemaker.tensorflow.estimator.TensorFlow,存在 sagemaker.pytorch.PyTorch链接)。您可以按照《在 PyTorch 中实现和训练模型》一节中的描述设置 PyTorch(或 PL)模型的训练,并集成 sagemaker.pytorch.PyTorch,如下面的代码片段所示:

import sagemaker
from sagemaker.pytorch import PyTorch
# Initializes SageMaker session
sagemaker_session = sagemaker.Session()
bucket = 's3://dataset/'
pytorch_estimator = PyTorch(
                      entry_point='train.py',
                      source_dir='.',
                      role=sagemaker.get_execution_role(),
                      framework_version='1.10.0',
                      train_instance_count=1,
                      train_instance_type='ml.c5.18xlarge',
                      hyperparameters={'epochs': 6})
…
pytorch_estimator.fit({
                        'training': bucket+'/training',
                        'validation': bucket+'/validation'})   

使用 PyTorch 估算器的方法与前一节描述的 TF 估算器相同。

这就完成了使用 SageMaker 进行模型训练的基本用法。接下来,我们将学习如何在 SageMaker 中扩展训练作业。我们将讨论使用分布策略进行分布式训练。我们还将介绍如何通过使用具有更低延迟的其他数据存储服务来加快训练速度。

使用 SageMaker 以分布方式训练模型

在 SageMaker 中可以通过使用分布式数据并行库(链接)实现数据并行。

您所需做的就是在创建 sagemaker.estimator.Estimator 实例时启用 dataparallel,如下所示:

distribution = {"smdistributed": {"dataparallel": { "enabled": True}} 

下面的代码片段展示了使用 dataparallel 创建的 TF 估算器。详细信息可以在 docs.aws.amazon.com/en_jp/sagem… 找到:

tf_estimator = TensorFlow(
                 entry_point='training_script.py', 
                 source_dir='.',
                 role=sagemaker.get_execution_role(),
                 instance_count=4, 
                 instance_type='ml.c5.18xlarge',
                 framework_version=tf_version, 
                 py_version='py3',
                 script_mode=True,
                 hyperparameters={'epochs': 30}
                 distributions={'smdistributed':
                 "dataparallel": {"enabled": True}})

对于 PyTorch 估算器,需要进行相同的修改。

SageMaker 支持两种不同的机制来将输入数据传输给底层算法:文件模式和管道模式。默认情况下,SageMaker 使用文件模式,将输入数据下载到用于训练的 EBS 卷中。但是,如果数据量很大,这可能会减慢训练速度。在这种情况下,您可以使用管道模式,它会从 S3(使用 Linux FIFO)流式传输数据,而无需进行额外的复制。

在 TF 的情况下,您可以简单地从 sagemaker-tensorflow 扩展中使用 PipeModeDataset,如 github.com/aws/sagemak… 所示:

from sagemaker_tensorflow import PipeModeDataset
ds = PipeModeDataset(channel='training', record_format='TFRecord'

然而,使用管道模式训练 PyTorch 模型需要更多的工程化努力。因此,我们将为您指引一个笔记本示例,深入描述每个步骤:github.com/aws/amazon-sagemaker-examples/blob/main/advanced_functionality/pipe_bring_your_own/pipe_bring_your_own.ipynb

分布策略和管道模式应该通过扩展底层计算资源和提高数据传输吞吐量来加速训练。然而,如果它们不足以满足需求,您可以尝试利用另外两种与 SageMaker 兼容的更高效的数据存储服务:亚马逊弹性文件系统EFS)和亚马逊完全托管的共享存储FSx),它是为 Lustre 文件系统而构建的。有关更多详细信息,请分别参阅它们的官方页面:aws.amazon.com/efs/aws.amazon.com/fsx/lustre/

SageMaker 与 Horovod

SageMaker 分布式训练的另一选择是使用Horovod,这是一个基于消息传递接口MPI)原理的免费开源框架,用于分布式 DL 训练。MPI 是一种标准消息传递库,在并行计算架构中被广泛使用。Horovod 假设 MPI 用于工作节点的发现和减少协调。Horovod 还可以利用 Gloo 替代 MPI,这是一个开源的集体通信库。这里是为 Horovod 配置的分布参数示例:

distribution={"mpi": {"enabled":True, 
                        "processes_per_host":2 }}

在前面的代码片段中,我们使用 MPI 实现了机器之间的协调。processes_per_host定义了在每个实例上运行的进程数量。这相当于在 MPI 和 Horovod 中使用-H参数定义进程数量,以控制程序在 MPI 和 Horovod 中的执行。

在下面的代码片段中,我们选择了控制训练脚本执行数量的并行进程数(-np参数)。然后,使用指定的-H参数值将此数目分配到具体的机器上。使用以下命令,每台机器将运行两次train.py。这在每台有两个 GPU 的四台机器的典型设置中是典型的。分配给-H进程的总和不能超过-np值:

mpirun -np 8 -H server1:2,server2:2,server3:2,server4:2 … (other parameters) python train.py  

我们将在下一节中深入讨论 Horovod,讲述如何在由 EC2 实例组成的独立 Horovod 集群上训练 DL 模型。

记住的事情

a. SageMaker 提供了一个优秀的工具,SageMaker Studio,允许您快速进行初始数据探索和训练基线模型。

b. sagemaker.estimator.Estimator对象是使用 SageMaker 训练模型的重要组件。它还支持在一组具有不同 CPU 和 GPU 配置的机器上进行分布式训练。

c. 使用 SageMaker 进行 TF 和 PyTorch 模型训练可以通过专为每种框架设计的估算器来实现。

现在,让我们看看如何在没有 SageMaker 的情况下使用 Horovod 进行分布式模型训练。

使用 Horovod 训练模型

尽管我们在介绍 SageMaker 时介绍了 Horovod,但 Horovod 设计用于仅支持分布式训练(horovod.ai/)。它旨在通过为流行的 DL 框架(包括 TensorFlow 和 PyTorch)提供良好的集成来以简单的方式支持分布式训练。

正如在SageMaker 与 Horovod部分中提到的,Horovod 的核心原则基于 MPI 概念,例如 size、rank、local rank、allreduce、allgather、broadcast 和 alltoall(horovod.readthedocs.io/en/stable/c…

在本节中,我们将学习如何使用 EC2 实例设置 Horovod 集群。然后,我们将描述在 TF 和 PyTorch 脚本中进行的修改,以在 Horovod 集群上训练模型所需的步骤。

设置 Horovod 集群

要使用 EC2 实例设置 Horovod 集群,您必须按照以下步骤进行:

  1. 转到 EC2 实例控制台:console.aws.amazon.com/ec2/。

  2. 点击右上角的启动实例按钮。

  3. 选择安装了 TF、PyTorch 和 Horovod 的Deep Learning AMI(Amazon Machine Image)。点击右下角的**下一步…**按钮。

  4. 为您的训练选择正确的实例类型。您可以选择符合需求的 CPU 或 GPU 实例类型。点击右下角的**下一步…**按钮:

![图 6.6 – EC2 实例控制台中的实例类型选择图像 B18522_06_06.jpg

图 6.6 – EC2 实例控制台中的实例类型选择

  1. 选择您希望组成 Horovod 集群的实例数量。在这里,您还可以请求 AWS Spot 实例(基于稀疏 EC2 容量的廉价实例,可以被中断,因此仅适合容错任务)。但为简单起见,让我们使用按需资源。

  2. 选择适当的网络和子网设置。在实际操作中,这些信息将由 DevOps 部门提供。

  3. 在同一页上,选择添加实例到放置组添加到新的放置组,输入您想要用于组的名称,并选择cluster作为放置组策略

  4. 在同一页上,提供您的身份和访问管理(IAM)角色,以便您可以访问 S3 存储桶。点击右下角的**下一步…**按钮。

  5. 为您的实例选择正确的存储大小。在右下角的**下一步……**按钮。

  6. 为您的实例选择唯一的标签(docs.aws.amazon.com/general/lat…

  7. 创建安全组或选择现有的安全组。同样,您必须与 DevOps 部门沟通以获取适当的信息。在右下角的**下一步……**按钮。

  8. 审查所有信息并启动。您将被要求提供用于认证的隐私增强邮件PEM)密钥。

完成这些步骤后,所需数量的实例将启动。如果您在步骤 10中没有添加名称标签,则您的实例将没有任何名称。在这种情况下,您可以导航到 EC2 实例控制台并手动更新名称。在写作时,您可以请求称为弹性 IP 的静态 IPv4 地址并将其分配给您的实例(docs.aws.amazon.com/AWSEC2/late…

最后,确保实例能够无问题地相互通信。您应检查安全组设置,并根据需要为 SSH 和其他流量添加入站规则。

此时,您只需将本地计算机上的 PEM 密钥复制到主 EC2 实例即可。对于 Ubuntu AMI,您可以运行以下命令:

scp -i <your_pem_key_path> ubuntu@<IPv4_Public_IP>:/home/ubuntu/.ssh/ 

现在,您可以使用 SSH 连接到主 EC2 实例。接下来要做的是通过以下命令在 EC2 实例之间设置无密码连接,并在 SSH 命令中提供您的 PEM 密钥:

eval 'ssh-agent'
ssh-add <your_pem_key>

在上述代码片段中,eval命令设置由ssh-agent命令提供的环境变量,而ssh-add命令将 PEM 身份添加到认证代理中。

现在,集群已准备好支持 Horovod!完成后,必须在 Web 控制台上停止或终止集群。否则,将持续收取资源费用。

在接下来的两个部分中,我们将学习如何更改 Horovod 的 TF 和 PyTorch 训练脚本。

为 Horovod 配置 TensorFlow 训练脚本

要使用 Horovod 训练 TF 模型,您需要horovod.tensorflow.keras模块。首先,您需要导入tensorflowhorovod.tensorflow.keras模块。我们将horovod.tensorflow.keras称为hvd。然后,您需要按以下方式初始化 Horovod 集群:

import tensorflow as tf
import horovod.tensorflow.keras as hvd
# Initialize Horovod
hvd.init()

此时,您可以使用hvd.size函数检查集群的大小。Horovod 中的每个进程将被分配一个等级(从 0 到集群大小,即您要运行的进程或要使用的设备的数量),您可以通过hvd.rank函数访问此等级。在每个实例上,每个进程都被分配一个不同的编号,从 0 到该实例上的进程数,称为本地等级(每个实例唯一的数字,但在不同实例之间重复)。可以使用hvd.local_rank函数访问当前进程的本地等级。

您可以使用本地排名来为每个进程固定特定的 GPU 设备。此示例还展示了如何使用tf.config.experimental.set_memory_growth设置 GPU 的内存增长:

gpus = tf.config.experimental.list_physical_devices('GPU')
for gpu in gpus:
    tf.config.experimental.set_memory_growth(gpu, True)
if gpus:
    tf.config.experimental.set_visible_devices(gpus[hvd.local_rank()], 'GPU')

在下面的代码中,我们根据等级来拆分数据,以便每个进程在不同的示例集上训练:

dataset = np.array_split(dataset, hvd.size())[hvd.rank()]

对于模型架构,您可以按照第三章中的在 TensorFlow 中实现和训练模型部分的说明进行操作:

model = …

接下来,您需要配置优化器。在以下示例中,学习率将按照 Horovod 大小进行缩放。此外,优化器需要用 Horovod 优化器包装:

opt = tf.optimizers.Adam(0.001 * hvd.size())
opt = hvd.DistributedOptimizer(opt)

下一步是编译您的模型,并将网络架构定义和优化器放在一起。当您使用早于 v2.2 的 TF 版本调用compile函数时,您需要禁用experimental_run_tf_function,以便 TF 使用hvd.DistributedOptimizer来计算梯度:

model.compile(loss=tf.losses.SparseCategoricalCrossentropy(),
              optimizer=opt,
              metrics=['accuracy'],
              experimental_run_tf_function=False)

您需要配置的另一个组件是回调函数。您需要添加hvd.callbacks.BroadcastGlobalVariablesCallback(0)。这将从等级 0 向所有其他机器和进程广播权重和偏差的初始值。这是确保一致的初始化或正确从检查点恢复训练所必需的:

callbacks=[
    hvd.callbacks.BroadcastGlobalVariablesCallback(0)
]

使用rank可以在特定实例上执行特定操作。例如,通过检查rank是否为 0 (hvd.rank()==0),可以在主节点上记录和保存工件,如下面的代码片段所示:

# Save checkpoints only on the instance with rank 0 to prevent other workers from corrupting them.
If hvd.rank()==0:
    callbacks.append(keras.callbacks.ModelCheckpoint('./checkpoint-{epoch}.h5'))

现在,您可以触发fit函数。以下示例显示了如何使用 Horovod 集群的大小来缩放每个 epoch 的步数。fit函数的消息只会在主节点上可见:

if hvd.rank()==0:
    ver = 1
else:
    ver = 0
model.fit(dataset,
          steps_per_epoch=hvd.size(),
          callbacks=callbacks,
          epochs=num_epochs,
          verbose=ver)

这是您需要更改以在分布式Horovod 中训练 TF 模型的所有内容。您可以在 horovod.rea[找到完整示例](horovod.readthedocs.io/en/stable/k…). Keras 版本可在 horovod.rea[dthedocs.io/en/stable/k… 找到。此外,您可以修改](horovod.readthedocs.io/en/stable/e… AWS Spot 实例并显著降低培训成本。

配置用于 Horovod 的 PyTorch 训练脚本

不幸的是,PL 目前尚无适当的 Horovod 支持文档。因此,在本节中,我们将专注于 PyTorch。与前一节中描述的类似,我们将演示您为 PyTorch 训练脚本进行的代码更改。对于 PyTorch,您需要horovod.torch模块,我们将再次称之为hvd。在以下代码片段中,我们正在导入必要的模块并初始化集群:

import torch
import horovod.torch as hvd
# Initialize Horovod
hvd.init()

如 TF 示例所述,您需要使用本地排名为当前进程绑定 GPU 设备:

torch.cuda.set_device(hvd.local_rank())

训练脚本的其余部分需要进行类似的修改。数据集需要使用torch.utils.data.distributed.DistributedSampler在实例之间分发,并且优化器必须使用hvd.DistributedOptimizer进行包装。主要区别在于hvd.broadcast_parameters(model.state_dict(), root_rank=0),用于广播模型权重。您可以在以下代码片段中找到详细信息:

# Define dataset...
train_dataset = ...
# Partition dataset among workers using DistributedSampler
train_sampler = torch.utils.data.distributed.DistributedSampler(
    train_dataset, num_replicas=hvd.size(), rank=hvd.rank())
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=..., sampler=train_sampler)
# Build model...
model = ...
model.cuda()
optimizer = optim.SGD(model.parameters())
# Add Horovod Distributed Optimizer
optimizer = hvd.DistributedOptimizer(optimizer, named_parameters=model.named_parameters())
# Broadcast parameters from rank 0 to all other processes.
hvd.broadcast_parameters(model.state_dict(), root_rank=0)

现在,您已经准备好训练模型了。训练循环不需要任何修改。您只需将输入张量传递给模型,并通过触发lossbackward函数和optimizerstep函数来触发反向传播。以下代码片段描述了训练逻辑的主要部分:

for epoch in range(num_ephos):
   for batch_idx, (data, target) in enumerate(train_loader):
       optimizer.zero_grad()
       output = model(data)
       loss = F.nll_loss(output, target)
       loss.backward()
       optimizer.step()

完整描述可以在官方 Horovod 文档页面找到:horovod.readthedocs.io/en/stable/p…

作为使用 Horovod 训练模型部分的最后一个内容,接下来的部分将解释如何使用horovodrunmpirun命令启动模型训练过程。

在 Horovod 集群上训练 DL 模型

Horovod 使用 MPI 原则协调进程之间的工作。要在单台机器上运行四个进程,可以使用以下其中一个命令:

horovodrun -np 4 -H localhost:4 python train.py
mpirun -np 4 python train.py

在两种情况下,-np参数定义了train.py脚本并行运行的次数。-H参数可用于定义每台机器上的进程数(请参见上述示例中的horovodrun命令)。随着我们学习如何在单台机器上运行,可以省略-H,如在mpirun命令中所示。其他mpirun参数在 www.open-mpi.org/doc/v4.0/ma… 中描述。

如果未安装 MPI,可以使用 Gloo 运行horovodrun命令。要在localhost上使用 Gloo 运行相同脚本四次(四个进程),只需添加--gloo标志:

horovodrun --gloo -np 4 -H localhost:4 python train.py

扩展到多个实例非常简单。以下命令展示了如何使用horovodrun在四台机器上运行训练脚本:

horovodrun -np 4 -H server1:1,server2:1,server3:1,server4:1 python train.py 

以下命令展示了如何使用mpirun在四台机器上运行训练脚本:

mpirun -np 4 -H server1:1,server2:1,server3:1,server4:1 python train.py

当主节点触发上述任一命令后,您将看到每个实例运行一个训练进程。

需要记住的事情

a. 要使用 Horovod,您需要一个具有节点间开放交叉通信的集群。

b. Horovod 提供了一种简单有效的方法,用于实现 TF 和 PyTorch 的数据并行。

c. 可以使用horovodrunmpirun命令在 Horovod 集群上执行训练脚本。

在下一节中,我们将描述 Ray,另一个流行的用于分布式训练的框架。

使用 Ray 训练模型

Ray 是一个开源的执行框架,用于跨多台机器扩展 Python 工作负载(www.ray.io)。Ray 支持以下 Python 工作负载:

Ray 的关键优势在于其集群定义的简单性;您可以定义具有不同类型和来源的机器的集群。例如,Ray 允许您通过混合 AWS EC2 按需实例和具有不同 CPU 和 GPU 配置的 EC2 Spot 实例来构建实例群集(基于每个节点的灵活和弹性资源策略),从而简化了集群的创建和与 DL 框架的集成,使其成为分布式 DL 模型训练过程的有效工具。

首先,我们将学习如何设置 Ray 集群。

设置 Ray 集群

您可以通过两种方式设置 Ray 集群:

  • Ray 集群启动器:Ray 提供的工具,用于利用云服务(包括 AWS、GCP 和 Azure)的实例构建集群。

  • 手动集群构建:所有节点都需要手动连接到 Ray 集群。

Ray 集群包括一个头节点(主节点)和工作节点。形成集群的实例应配置为通过网络互相通信。Ray 实例之间的通信基于传输控制协议TCP)连接,必须打开相应的端口。在接下来的两个部分中,我们将更详细地介绍 Ray 集群启动器和手动集群构建。

使用 Ray Cluster Launcher 设置 Ray 集群

使用 Ray 集群启动器时,需要使用 YAML 文件来配置集群。您可以在 Ray 的 GitHub 仓库中找到多个用于不同配置的示例 YAML 文件:github.com/ray-project/ray/tree/master/python/ray/autoscaler

在本节中,我们将介绍最基本的配置。YAML 文件从集群的基本信息开始,例如集群名称、最大工作节点数和扩展速度,如下所示:

cluster_name: BookDL
max_workers: 5
upscaling_speed: 1.0

接下来,它配置云服务提供商:

provider:
    type: aws
    region: us-east-1
    availability_zone: us-east-1c, us-east-1b, us-east-1a
    cache_stopped_nodes: True 
    ssh_user: ubuntu
    ssh_private_key: /Users/BookDL/.ssh/BookDL.pem

在上述示例中,我们指定了提供者类型(type: aws),并选择将提供实例的区域和可用区(region: us-east-1availability_zone: us-east-1c, us-east-1b, us-east-1a)。然后,我们定义节点在未来是否可以重复使用(cache_stopped_nodes: True)。最后的配置是用户认证(ssh_user:ubuntussh_private_key:/Users/BookDL/.ssh/BookDL.pem)。

接下来需要指定节点配置。首先,我们将从头节点开始:

available_node_types:
    ray.head.default:
        node_config:
            KeyName:"BookDL.pem"

接下来,我们必须设置安全设置。详细设置必须与 DevOps 协商,以监控和保护实例:

            SecurityGroupIds:
                - sg-XXXXX
                - sg-XXXXX
            SubnetIds: [subnet-XXXXX]

下面的配置适用于应使用的实例类型和 AMI:

            InstanceType: m5.8xlarge
            ImageId: ami-09ac68f361e5f4a13

在以下代码片段中,我们提供了存储配置:

            BlockDeviceMappings:
                  - DeviceName: /dev/sda1
                    Ebs:
                    VolumeSize: 580

您可以按如下方式轻松定义 Tags

            TagSpecifications:
                - ResourceType:"instance"
                  Tags:
                      - Key:"Developer"
                        Value:"BookDL"

如果需要,可以为访问特定 S3 存储桶提供 IAM 实例配置文件:

            IamInstanceProfile:
                Arn:arn:aws:iam::XXXXX

YAML 文件的下一个部分,我们需要为工作节点提供配置:

    ray.worker.default:
            min_workers: 2
            max_workers: 4

首先,我们必须指定工作节点的数量(min_workersmax_workers)。然后,我们可以定义类似于我们定义主节点配置的节点配置:

        node_config:
            KeyName: "BookDL.pem"
            SecurityGroupIds:
                - sg-XXXXX
                - sg-XXXXX
            SubnetIds: [subnet-XXXXX]
            InstanceType: p2.8xlarge
            ImageId: ami-09ac68f361e5f4a13
            TagSpecifications:
                - ResourceType: "instance"
                  Tags:
                      - Key: "Developer"
                        Value: "BookDL"
            IamInstanceProfile:
                Arn: arn:aws:iam::XXXXX
            BlockDeviceMappings:
              - DeviceName: /dev/sda1
                Ebs:
                  VolumeSize: 120

此外,您可以在 YAML 文件中指定要在每个节点上运行的一系列 shell 命令:

setup_commands:
    - (stat $HOME/anaconda3/envs/tensorflow2_p38/ &> /dev/null && echo 'export PATH="$HOME/anaconda3/envs/tensorflow2_p38/bin:$PATH"' >> ~/.bashrc) || true
    - source activate tensorflow2_p38 && pip install --upgrade pip
    - pip install awscli
    - pip install Cython
    - pip install -U ray
    - pip install -U ray[rllib] ray[tune] ray
    - pip install mlflow
    - pip install dvc

在这个例子中,我们将在路径中添加tensorflow2_p38以供conda环境使用,激活环境,并使用pip安装一些其他模块。如果您想在头节点或工作节点上运行其他命令,可以分别在head_setup_commandsworker_setup_commands中指定它们。它们将在setup_commands中定义的命令执行后执行。

最后,YAML 文件以启动 Ray 集群的命令结束:

head_start_ray_commands:
    - ray stop
    - source activate tensorflow2_p38 && ray stop
    - ulimit -n 65536; source activate tensorflow2_p38 && ray start --head --port=6379 --object-manager-port=8076 --autoscaling-config=~/ray_bootstrap_config.yaml
worker_start_ray_commands:      
    - ray stop
    - source activate tensorflow2_p38 && ray stop
    - ulimit -n 65536; source activate tensorflow2_p38 && ray start --address=$RAY_HEAD_IP:6379 --object-manager-port=8076

起初,使用 YAML 文件设置 Ray 集群可能看起来很复杂。但是,一旦您习惯了,您会发现为未来项目调整集群设置变得相当简单。此外,它显著减少了启动正确定义的集群所需的时间,因为您可以重用来自先前项目的安全组、子网、标签和 IAM 配置信息。

如果您需要其他详细信息,我们建议您花些时间查阅官方文档:docs.ray.io/en/latest/c…

值得一提的是,Ray 集群启动器支持自动缩放和使用实例群,无论是否使用 EC2 Spot 实例。我们在前面的示例中使用了 AMI,但您也可以为您的实例提供特定的 Docker 镜像。通过灵活使用 YAML 配置文件,您可以使用单个文件构建任何集群配置。

正如我们在本节开头提到的,您还可以通过手动添加单独实例来设置 Ray 集群。接下来我们将看看这个选项。

手动设置 Ray 集群

鉴于您拥有一组具有网络连接的机器,第一步是在每台机器上安装 Ray。接下来,您需要更改每台机器的安全设置,以便它们可以相互通信。之后,您需要选择一个节点作为头节点,并在其上运行以下命令:

ray start --head --redis-port=6379  

上述命令建立了 Ray 集群;Redis 服务器(用于集中控制平面)已启动,并且其 IP 地址在终端上打印出来(例如123.45.67.89:6379)。

接下来,您需要在所有其他节点上运行以下命令:

ray start --address=<redis server ip address>

您需要提供的地址是从头节点命令打印出的地址。

现在,您的机器已准备好支持 Ray 应用程序。在手动设置的情况下,需要手动执行以下步骤:启动机器,连接到头节点终端,将训练文件复制到所有节点,并停止机器。让我们看看如何利用 Ray 集群启动器来帮助完成这些任务。

在这个阶段,您应该能够使用 YAML 文件指定所需的 Ray 集群设置。一旦准备好,您可以使用以下命令启动您的第一个 Ray 集群:

ray up your_cluster_setting_file.yaml

要在头节点上获取远程终端,您可以运行以下命令:

ray attach your_cluster_setting_file.yaml

要终止集群,可以使用以下命令:

ray down your_cluster_setting_file.yaml

现在,是时候学习如何在 Ray 集群上执行 DL 模型训练了。

使用 Ray 进行分布式模型训练

Ray 提供 Ray Train 库,通过处理幕后的分布式训练来帮助您专注于定义训练逻辑。Ray Train 支持 TF 和 PyTorch。此外,Ray Datasets 存在,通过分布式数据转换提供分布式数据加载。最后,Ray 通过 Ray Tune 库提供超参数调整功能。

调整 TF 训练逻辑以适应 Ray 类似于我们在 TensorFlow 中的数据并行 ism 部分中描述的方式。主要区别来自 Ray Train 库,它帮助我们设置 TF_CONFIG

调整后的训练逻辑如下所示:

def train_func_distributed():
    per_worker_batch_size = 64
    tf_config = json.loads(os.environ['TF_CONFIG'])
    num_workers = len(tf_config['cluster']['worker'])
    strategy = tf.distribute.MultiWorkerMirroredStrategy()
    global_batch_size = per_worker_batch_size * num_workers
    multi_worker_dataset = dataset(global_batch_size)
    with strategy.scope():
        multi_worker_model = build_and_compile_your_model()
    multi_worker_model.fit(multi_worker_dataset, epochs=20, steps_per_epoch=50)

然后,您可以使用 Ray Trainer 运行训练,如下所示:

import ray
from ray.train import Trainer
ray.init()
trainer = Trainer(backend="tensorflow", num_workers=4, use_gpu=True)
trainer.start()
trainer.run(train_func_distributed)
trainer.shutdown()

在前面的例子中,模型定义与单设备案例类似,唯一的区别在于应使用特定策略进行编译:MultiWorkerMirroredStrategy。数据集在 dataset 函数内部分割,为每个工作节点提供不同的样本集。最后,Trainer 实例处理分布式训练。

使用 Ray 训练 PyTorch 模型 可以通过少量变更来实现。一些示例在 docs.ray.io/en/latest/train/examples.html#pytorch 上呈现。

此外,您还可以将 Ray 与 Horovod 结合使用,利用 Elastic Horovod 以容错的方式进行训练。Ray 将通过简化主机的发现和 orc 串来自动缩放训练过程。我们不会详细介绍,但您可以在 docs.ray.io/en/latest/train/examples/horovod/horovod_example.html 找到一个很好的起点。

需要记住的事情

a. Ray 的主要优势在于其集群定义的简易性。

b. Ray 集群可以通过手动连接每台机器或使用名为 Ray Cluster Launcher 的内置工具来创建。

c. Ray 提供了良好的支持来自动缩放训练过程。它简化了主机的发现和编排。

最后,让我们学习如何使用 Kubeflow 进行分布式训练。

使用 Kubeflow 训练模型

Kubeflow (www.kubeflow.org) 涵盖了模型开发的每个步骤,包括数据探索、预处理、特征提取、模型训练、模型服务、推断和版本控制。通过利用容器和 Kubernetes,Kubeflow 允许您轻松从本地开发环境扩展到生产集群。

如果您的组织已经在使用 Kubernetes生态系统,那么 Kubeflow 可能是您进行分布式训练的首选。

介绍 Kubernetes

Kubernetes 是一个开源的编排平台,用于管理容器化工作负载和服务(kubernetes.io):

  • Kubernetes 有助于持续交付、集成和部署。

  • 它将开发环境与部署环境分离。您可以构建一个容器镜像并同时开发应用程序。

  • 基于容器的方法确保了开发、测试和生产环境的一致性。环境将在桌面计算机或云中保持一致,从而最大限度地减少从一步到另一步所需的修改。

我们假设您已经安装了 Kubeflow 及其所有依赖项,并且运行着一个 Kubernetes 集群。本节中我们将描述的步骤是通用的,适用于任何集群设置 - Minikube(一个本地版本的 Kubernetes)、AWS 弹性 Kubernetes 服务EKS),或者一个多节点的集群。这正是容器化工作负载和服务的美妙之处。您可以在以下网址找到本地 Minikube 安装步骤:minikube.sigs.k8s.io/docs/start/。对于 EKS,我们建议您查阅 AWS 用户指南:docs.aws.amazon.com/eks/latest/userguide/getting-started.html

为 Kubeflow 设置模型训练

第一步是将您的训练代码打包成一个容器。这可以通过一个 Docker 文件实现。取决于您的起始点,您可以使用来自 NVIDIA 容器镜像空间的容器(TF 位于 https://docs.nvidia.com/deeplearning/frameworks/tensorflow-release-notes/running.html 或 PyTorch 在 docs.nv[idia.com/deeplearnin…](hub.docker.com/r/tensorflo… DL 框架获取容器(TF 位于 hub.docker.com/r/tensorflo… 或 PyTorch 位于 hub.docker.com/r/pytorch/p…

让我们看一个 TF Docker 文件的示例(kubeflow/tf_example_job):

FROM tensorflow/tensorflow:latest-gpu-jupyter
RUN pip install minio –upgrade
RUN pip install –upgrade pip 
RUN pip install pandas –upgrade 
… 
RUN mkdir -p /opt/kubeflow
COPY train.py /opt/kubeflow
ENTRYPOINT ["python", "/opt/kubeflow/train.py"]

在前述的 Docker 定义中,train.py脚本是一个典型的 TF 训练脚本。

现在,我们假设单机将用于训练。换句话说,它将是一个单容器作业。假设您已经准备好一个 Docker 文件和一个训练脚本,您可以使用以下命令构建您的容器并将其推送到仓库:

docker build -t kubeflow/tf_example_job:1.0
docker push kubeflow/tf_example_job:1.0

我们将使用TFJob,这是 Kubeflow 的一个自定义组件,其中包含一个将TFJob表示为描述容器镜像、训练脚本和执行参数的 YAML 文件。让我们看看一个 YAML 文件,tf_example_job.yaml,其中包含在单台机器上运行的 Kubeflow 模型训练作业:

apiVersion: "kubeflow.org/v1" 
kind: "TFJob"
metadata:
    name: "tf_example_job"
spec:
    tfReplicaSpecs:
        Worker:
            replicas: 1
        restartPolicy: Never
        template:
            specs:
                containers:
                    - name: tensorflow 
                      image: kubeflow/tf_example_job:1.0

API 版本在第一行中定义。然后列出了您自定义资源的类型,kind: "TFJob"metadata字段用于通过提供自定义名称来识别您的作业。集群在tfReplicaSpecs字段中定义。如前面的示例所示,脚本(tf_example_job:1.0)将执行一次(replicas: 1)。

要将定义的TFJob部署到您的集群中,您可以使用kubectl命令,如下所示:

kubectl apply -f tf_example_job.yaml

您可以使用以下命令监视您的作业(使用元数据中定义的名称):

kubectl describe tfjob tf_example_job 

要执行分布式训练,您可以使用带有特定tf.distribute.Strategy的 TF 代码,创建一个新的容器,并修改TFJob。我们将在下一节中查看TFJob所需的更改。

使用 Kubeflow 在分布式环境中训练 TensorFlow 模型

假设我们已经有了来自MultiWorkerMirroredStrategy的 TF 训练代码。为了支持此策略,需要在spec字段中调整tfReplicaSpecs。我们可以通过 YAML 文件定义以下类型的副本:

  • 主节点(主机):编排计算任务

  • 工作节点:运行计算任务

  • 参数服务器:管理模型参数的存储

  • 评估节点:在模型训练期间运行评估

作为最简单的例子,我们将定义一个作为主节点之一的工作节点。参数serverevaluator不是必须的。

让我们看看调整后的 YAML 文件,tf_example_job_dist.yaml,用于分布式 TF 训练:

apiVersion: "kubeflow.org/v1"
kind: "TFJob"
metadata:
    name: "tf_example_job_dist"
spec:
    cleanPodPolicy: None
    tfReplicaSpecs:
        Worker:
            replicas: 4
            restartPolicy: Never
            template:
                specs:
                    containers:
                        - name: tensorflow 
                          image: kubeflow/tf_example_job:1.1

前述的 YAML 文件将基于MultiWorkerMirroredStrategy在新的容器kubeflow/tf_example_job:1.1上运行训练作业。我们可以使用相同的命令将TFJob部署到集群:

kubectl apply -f tf_example_job_dist.yaml

在下一节中,我们将学习如何使用 PyTorch 和 Ray。

使用 Kubeflow 在分布式环境中训练 PyTorch 模型

对于 PyTorch,我们只需将TFJob更改为PyTorchJob,并提供一个 PyTorch 训练脚本。至于训练脚本本身,请参阅PyTorch 中的数据并行 ism部分。如下面的代码片段所示,YAML 文件需要相同的修改:

apiVersion: "kubeflow.org/v1 
kind: "PyTorchJob"
metadata:
    name: "pt_example_job_dist"
spec:
    pytorchReplicaSpecs:
        Master:
            replicas: 1
            restartPolicy: Never
            template:
                specs:
                    containers:
                        - name: pytorch 
                          image: kubeflow/pt_example_job:1.0
        Worker:
            replicas: 5
            restartPolicy: OnFailure
            template:
                specs:
                    containers:
                        - name: pytorch 
                          image: kubeflow/pt_example_job:1.0

在此示例中,我们有一个主节点和五个工作节点的副本。完整详情请查看www.kubeflow.org/docs/components/training/pytorch

需要记住的事项

a. Kubeflow 允许您轻松地从本地开发环境扩展到利用容器和 Kubernetes 的大型集群。

b. TFJobPyTorchJob分别允许您以分布式方式在 Kubeflow 中运行 TF 和 PyTorch 训练作业。

在本节中,我们描述了如何利用 Kubeflow 以分布式方式训练 TF 和 PyTorch 模型。

总结

通过意识到多设备和多机器并行带来的好处,我们学习了多种训练深度学习模型的方法。首先,我们学习了如何在单台机器上利用多个 CPU 和 GPU 设备。然后,我们介绍了如何利用 TF 和 PyTorch 的内置功能以分布式方式进行训练,其中底层集群由显式管理。之后,我们学习了如何使用 SageMaker 进行分布式训练和扩展。最后,最后三节描述了专为分布式训练设计的框架:Horovod、Ray 和 Kubeflow。

在下一章中,我们将介绍模型理解。我们将学习关于模型理解的流行技术,这些技术在训练过程中提供一些关于模型内部运行情况的见解。