Azure 上的 Python 机器学习(第二部分):使用 Python、GitHub Codespaces 和 Azure ML 创建 PyTorch 模型





5.00/5 (1投票)
如何使用 GitHub Codespaces 和 GitHub Actions 在每次推送更改到存储库后自动训练 PyTorch 模型。
在本系列文章中,我们将探讨在 Azure 上使用 Python 创建和使用机器学习模型的各种方法。上一篇文章 使用了带有机器学习扩展的 Visual Studio Code 在 Azure 上训练 XGBoost 模型。本文将演示如何使用 GitHub Codespaces,仅通过 Web 浏览器即可使用 Visual Studio Code。此外,它还将展示如何在我提交更改到存储库时,自动在 Azure 上训练 PyTorch 模型。为此,我们将使用 GitHub Actions。
您可以在 GitHub 上找到本文的示例代码。
必备组件
要学习本文中的示例,您需要一个 Web 浏览器和 Azure 订阅。如果您没有订阅,可以注册一个免费的 Azure 帐户。此外,要配置使用 Azure 资源的 GitHub Actions,您还需要对分配给您订阅的 Azure AD 具有管理员访问权限。
设置 GitHub Codespaces
如果您曾经需要在不安装任何东西的情况下访问功能齐全的开发环境(例如,从平板电脑访问),现在您可以做到了。GitHub Codespaces 提供了一个在云端带有 Visual Studio Code 的环境。GitHub Team 或 Enterprise 计划。
如果您还没有访问 GitHub Teams 计划的权限,则需要一个才能继续。要创建它,请点击 **Continue with Team** 并提供您组织的详细信息,包括付款信息。您可以在此处使用您常规的 GitHub 登录名作为成员名称。仅团队计划不足以使用 Codespaces。要激活此功能,您需要转到您组织的设置,从侧边菜单中选择 **Codespaces**,然后为其启用所有用户或选定用户。
此外,您还需要定义一个大于 0 美元的关联支出限额。要做到这一点,请从组织的设置侧边菜单中选择 **Billing & plans**,然后选择 **Manage spending limit**。
最后,完成这些步骤后,您应该可以在您组织任何存储库的 **Code** 菜单上访问新的 **Codespaces** 选项卡。
创建 codespace 时,它始终属于单个存储库。因此,请在继续之前创建一个。您可以在此处使用提供的示例代码,但请确保将其内容复制到您存储库的根目录。
现在,当您点击 **New codespace** 时,您可以选择运行您环境的虚拟机 (VM) 的大小。
通常对于云资源而言,配置越强大,每小时的费用就越高。由于计划将所有实际工作委托给 Azure,因此最小的双核实例就足够了。
配置 Codespace 环境
选择 **Create codespace** 选项后,新的云环境将在几分钟内可用。它是一个功能齐全的 Visual Studio Code,具有所有功能,但可以通过 Web 浏览器访问。
如果需要,可以使用一系列新的与 codespaces 相关的配置选项。只需按下标准的 Visual Studio Code 快捷键(Cmd/Ctrl+Shift+P),然后开始键入“Codespaces”即可筛选选项。
例如,要更改开发环境,请选择 **Add Development Container Configuration Files**,然后选择一个预定义的模板。
对于我们的目的,默认环境包含我们需要的一切(即 azure-cli 和 python 3.8),因此我们可以跳过此步骤。但我们需要一个机器学习扩展来扩展 azure-cli(仍在预览中)。我们可以使用 Azure CLI 添加它。
$ az extension add -n ml -y
设置 Azure Machine Learning 工作区
如果您还没有遵循本系列上一篇文章中的示例,现在可以从您的 codespace 中进行。previous article。无论如何,要继续,我们需要一个 Azure Machine Learning 工作区。
我们可以按照上一篇文章中的步骤配置工作区,使用 Azure Portal,或在 codespace 中使用 `azure-cli`。在这里,我们将使用 `azure-cli`,从登录 Azure 开始。
$ az login —use-device-code
在遵循命令返回的登录说明后,我们创建资源组和 Azure Machine Learning 工作区。
$ export SUBSCRIPTION="<your-subscription-id>"
$ export GROUP="azureml-rg"
$ export WORKSPACE="demo-ws"
$ export LOCATION="<your-location (e.g., westeurope)>”
$ az group create -n $GROUP -l $LOCATION
$ az ml workspace create --name $WORKSPACE --subscription $SUBSCRIPTION --resource-group $GROUP
下载 MNIST 数据集
现在我们需要使用上一篇文章中介绍的代码下载 MNIST 数据集。
import os
import urllib.request
DATA_FOLDER = 'datasets/mnist-data'
DATASET_BASE_URL = 'https://azureopendatastorage.blob.core.windows.net/mnist/'
os.makedirs(DATA_FOLDER, exist_ok=True)
urllib.request.urlretrieve(
os.path.join(DATASET_BASE_URL, 'train-images-idx3-ubyte.gz'),
filename=os.path.join(DATA_FOLDER, 'train-images.gz'))
urllib.request.urlretrieve(
os.path.join(DATASET_BASE_URL, 'train-labels-idx1-ubyte.gz'),
filename=os.path.join(DATA_FOLDER, 'train-labels.gz'))
urllib.request.urlretrieve(
os.path.join(DATASET_BASE_URL, 't10k-images-idx3-ubyte.gz'),
filename=os.path.join(DATA_FOLDER, 'test-images.gz'))
urllib.request.urlretrieve(
os.path.join(DATASET_BASE_URL, 't10k-labels-idx1-ubyte.gz'),
filename=os.path.join(DATA_FOLDER, 'test-labels.gz'))
现在我们将此代码保存在 `download-dataset.py` 文件中,我们可以运行它。
$ python download-dataset.py
将数据集上传到 Azure Machine Learning 工作区
在 Azure Machine Learning 工作区中处理数据的首选方式是使用数据集。要从刚刚下载的文件创建数据集,我们准备 `aml-mnist-dataset.yml` 文件并包含其定义。
$schema: https://azuremlschemas.azureedge.net/latest/dataset.schema.json
name: mnist-dataset
version: 1
local_path: datasets/mnist-data
接下来,我们使用 `azure-cli` 创建数据集。
$ az ml dataset create --file aml-mnist-dataset.yml --subscription $SUBSCRIPTION
--resource-group $GROUP --workspace-name $WORKSPACE
编写 PyTorch 模型训练代码
现在我们已经在 Azure Machine Learning 工作区中注册了数据集,我们可以编写代码来训练我们的模型。我们将使用 PyTorch 框架,并将所有代码保存到 `code/train/train.py` 文件中。
让我们从导入开始。
import os
import gzip
import struct
import numpy as np
import argparse
import mlflow
import torch
import torch.optim as optim
from torch.nn import functional as F
from torch import nn
from torchvision import transforms
from torch.utils.data import DataLoader
from azureml.core import Run
from azureml.core.model import Model
接下来,我们需要代码来加载、解码、归一化和重塑数据集中的图像。
def load_dataset(dataset_path):
def unpack_mnist_data(filename: str, label=False):
with gzip.open(filename) as gz:
struct.unpack('I', gz.read(4))
n_items = struct.unpack('>I', gz.read(4))
if not label:
n_rows = struct.unpack('>I', gz.read(4))[0]
n_cols = struct.unpack('>I', gz.read(4))[0]
res = np.frombuffer(gz.read(n_items[0] * n_rows * n_cols), dtype=np.uint8)
res = res.reshape(n_items[0], n_rows * n_cols) / 255.0
else:
res = np.frombuffer(gz.read(n_items[0]), dtype=np.uint8)
res = res.reshape(-1)
return res
X_train = unpack_mnist_data(os.path.join(dataset_path, 'train-images.gz'), False)
y_train = unpack_mnist_data(os.path.join(dataset_path, 'train-labels.gz'), True)
X_test = unpack_mnist_data(os.path.join(dataset_path, 'test-images.gz'), False)
y_test = unpack_mnist_data(os.path.join(dataset_path, 'test-labels.gz'), True)
return X_train.reshape(-1,28,28,1), y_train, X_test.reshape(-1,28,28,1), y_test
现在,我们可以在 PyTorch 中创建一个简单的卷积神经网络 (CNN)。
class NetMNIST(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
self.fc1 = nn.Linear(320, 50)
self.fc2 = nn.Linear(50, 10)
def forward(self, x):
x = F.max_pool2d(F.relu(self.conv1(x)), (2,2))
x = F.max_pool2d(F.dropout(F.relu(self.conv2(x)), p=0.2), (2,2))
x = x.view(-1, 320)
x = F.relu(self.fc1(x))
x = F.dropout(x, p=0.2, training=self.training)
x = self.fc2(x)
return F.log_softmax(x, dim=1)
PyTorch 需要一个自定义数据集类来处理数据加载。为此,我们创建一个支持标记数据(用于训练)和未标记数据(用于推理)的类。
class DatasetMnist(torch.utils.data.Dataset):
def __init__(self, X, y=None):
self.X, self.y = X,y
self.transform = transforms.Compose([
transforms.ToTensor()])
def __len__(self):
return len(self.X)
def __getitem__(self, index):
item = self.transform(self.X[index])
if self.y is None:
return item.float()
label = self.y[index]
return item.float(), np.long(label)
现在是训练时间了。我们从训练单个 epoch 的方法开始。请注意此处MLflow 记录的使用。这是 Azure ML 中推荐的方法,并且可以在云端或本地工作。
def train_epoch(model, device, train_loader, optimizer, epoch):
model.train()
epoch_loss = 0.0
epoch_acc = 0.0
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
epoch_loss += loss.item()
_, preds = torch.max(output.data, 1)
epoch_acc += (preds == target).sum().item()
if batch_idx % 200 == 0 and batch_idx != 0:
print(f"[{epoch:2d}:{batch_idx:5d}] \tBatch loss: {loss.item():.5f}, \
Epoch loss: {epoch_loss:.5f}")
epoch_acc /= len(train_loader.dataset)
print(f"[{epoch:2d} EPOCH] \tLoss: {epoch_loss:.6f} \tAcc: {epoch_acc:.6f}")
mlflow.log_metrics({
'loss': epoch_loss,
'accuracy': epoch_acc})
现在我们使用此方法训练所有 epoch 并保存训练好的模型。
def train_model(X, y, model_filename, epochs=5, batch_size=64):
RANDOM_SEED = 101
use_cuda = torch.cuda.is_available()
torch.manual_seed(RANDOM_SEED)
device = torch.device("cuda" if use_cuda else "cpu")
print(f"Device: {device}")
if use_cuda:
cuda_kwargs = {'num_workers': 1,
'pin_memory': True,
'shuffle': True}
else:
cuda_kwargs = {}
train_dataset = DatasetMnist(X, y)
train_loader = torch.utils.data.DataLoader(train_dataset, \
batch_size=batch_size, **cuda_kwargs)
model = NetMNIST().to(device)
optimizer = optim.Adam(model.parameters())
for epoch in range(1, epochs+1):
train_epoch(model, device, train_loader, optimizer, epoch)
torch.save(model.state_dict(), model_filename)
训练模型后,我们希望在测试数据上对其进行评估。以下方法加载先前保存的模型,然后使用它进行预测,并将结果与已知的测试标签进行比较。
def evaluate_model(X, y, model_filename, batch_size=64):
test_dataset = DatasetMnist(X)
test_loader = DataLoader(test_dataset, batch_size=batch_size)
model = NetMNIST()
model.load_state_dict(torch.load(model_filename))
preds = []
with torch.no_grad():
for batch in test_loader:
batch_preds = model(batch).numpy()
preds.extend(np.argmax(batch_preds, axis=1))
accscore = (preds == y).sum().item()
accscore /= len(test_dataset)
mlflow.log_metric('test_accuracy', accscore)
我们将训练好的模型注册到 Azure Machine Learning 工作区。
def register_model(ws, model_filename):
model = Model.register(
workspace=ws,
model_name=model_filename,
model_path=model_filename,
model_framework=Model.Framework.PYTORCH,
model_framework_version=torch.__version__
)
最后两个辅助方法是获取当前 Azure Machine Learning 工作区并解析执行参数。这里我们只解析一个参数:`data`。
def get_aml_workspace():
run = Run.get_context()
ws = run.experiment.workspace
return ws
def parse_arguments():
parser = argparse.ArgumentParser()
parser.add_argument('--data', type=str, required=True)
args = parser.parse_known_args()[0]
return args
最后,我们准备好运行脚本的主要方法。
def main():
args = parse_arguments()
ws = get_aml_workspace()
mlflow.set_tracking_uri(ws.get_mlflow_tracking_uri())
mlflow.start_run()
X_train, y_train, X_test, y_test = load_dataset(args.data)
model_filename = "mnist.pt_model"
train_model(X_train, y_train, model_filename)
evaluate_model(X_test, y_test, model_filename)
register_model(ws, model_filename)
if __name__ == "__main__":
main()
注意与 MLflow 相关的代码。`mlflow.set_tracing_uri` 方法确保将日志记录的信息保存在 Azure:Machine Learning 工作区中。
介绍 GitHub Actions
我们(几乎)拥有在本地运行训练所需的所有代码。然而,我们希望使用 Azure 进行训练。此外,我们希望每次将更改推送到存储库时,训练都能自动运行。我们将使用 GitHub Actions 和 azure-cli 来实现这一点。值得指出的是,GitHub Actions 与 codespaces 没有直接关系。您可以在任何 GitHub 存储库中使用此机制,即使只有一个免费帐户。
GitHub Actions 允许我们创建自动执行的作业 — 例如,推送到存储库、拉取请求以及许多其他触发器。遵循约定优于配置的原则,我们将操作定义在存储库根目录下的 `.github/workflows` 文件夹中的一个或多个 YAML 文件中。
将 GitHub Actions 与 Azure ML 结合使用
虽然有一组专用的 Azure Machine Learning GitHub Actions 任务,例如 aml-workspace、aml-compute 或 aml-run,但它们目前被标记为已弃用。
推荐的替代方法是使用 `azure-cli`,尽管 `azure-cli` 解决方案仍处于预览阶段。尽管如此,在 GitHub Actions 中使用 `azure-cli` 的一个显著优点是,我们可以使用相同的脚本从命令行手动运行 Azure ML 任务,并通过 GitHub Actions 自动运行。考虑到这一点,我们将遵循 `azure-cli` 方法。
定义 Azure ML 作业
为了继续,我们需要定义 Azure ML 作业来设置计算资源并运行训练。这些作业几乎是相同的,正如上一篇文章中所述。
`aml-compute-cpu.yml` 文件中的计算定义作业如下所示。
$schema: https://azuremlschemas.azureedge.net/latest/compute.schema.json
name: aml-comp-cpu-01
type: amlcompute
size: Standard_D2_v2
min_instances: 0
max_instances: 1
idle_time_before_scale_down: 3600
location: westeurope
`aml-job-train.yml` 文件中的训练作业如下所示。
$schema: https://azuremlschemas.azureedge.net/latest/commandJob.schema.json
code:
local_path: code/train
command: python train.py --data ${{inputs.mnist_data}}
environment: azureml:AzureML-pytorch-1.9-ubuntu18.04-py37-cuda11-gpu:10
compute: azureml:aml-comp-cpu-01
environment_variables:
AZUREML_COMPUTE_USE_COMMON_RUNTIME: "false"
inputs:
mnist_data:
dataset: azureml:mnist-dataset:1
mode: ro_mount
experiment_name: pytorch-mnist-experiment
这里最大的变化是不同的环境(已安装 PyTorch)和更大的计算规模(之前的计算规模不足以满足 PyTorch 环境)。考虑到我们任务的大小,仅 CPU 计算完全可以。您可能需要 GPU 计算来处理更大的数据集和模型。
请注意 MNIST 数据集的版本。我们使用:`inputs/mnist_data/dataset: azureml:mnist-dataset:1`。您可以删除 `:1` 后缀以使用最新版本,或者在需要时更新此值。
配置 GitHub 服务主体
执行 Azure 上的任何操作都需要授权。GitHub Actions 也不例外。服务主体是授权应用程序使用 Azure 资源的正确方法。以下 `azure-cli` 命令创建一个新的 Azure 服务主体并返回其密钥。
$ az ad sp create-for-rbac --name $AML_SP \
--role contributor \
--scopes /subscriptions/$SUBSCRIPTION/resourceGroups/$GROUP \
--sdk-auth
此命令的输出应为一个具有以下架构的 JSON。
{
"clientId": "<GUID>",
"clientSecret": "<GUID>",
"subscriptionId": "<GUID>",
"tenantId": "<GUID>",
(...)
}
记住此值。如果您丢失了它,则必须创建一个新密钥。如果之前的命令失败,请确保您拥有分配给您订阅的 Azure AD 管理员权限。成功后,我们将返回到存储库设置,并将返回的 JSON 添加为 **AZURE_CREDENTIALS** 密钥。
现在我们可以为我们的训练创建一个 GitHub 工作流。
使用 GitHub 工作流进行模型训练
从技术上讲,我们在 GitHub Actions 中有两种运行 `azure-cli` 命令的选项。第一种是直接使用工作流的 `run` 步骤运行它们,因为 `azure-cli` 在 GitHub worker 上是默认可用的。或者,我们可以使用专用的 Azure CLI action。如果您想显式控制 `azure-cli` 版本,则需要依赖后者。不过,这可能会适得其反。
要在 Azure 上执行代码,我们始终需要至少两个 action。第一个是 Azure 登录 action,然后是具有实际逻辑的下一个步骤。问题是,虽然我们可以控制 Azure CLI action 使用的 `azure-cli` 版本,但我们无法选择对 Azure 登录 action 执行相同操作。这可能会导致旧的固定 `azure-cli` 版本与当前的 Azure 登录 action 不兼容的情况。值得记住这种情况,因为它不久前发生过。无论如何,您始终可以使用 Azure CLI。
我们的训练作业定义的 Azure CLI 版本如下所示。
on:
push:
branches: [ main ]
name: AzureMLTrain
jobs:
setup-aml-and-train:
runs-on: ubuntu-latest
env:
AZURE_SUBSCRIPTION: “<your-subscription-id>”
RESOURCE_GROUP: "azureml-rg"
AML_WORKSPACE: "demo-ws"
steps:
- name: Checkout Repository
id: checkout_repository
uses: actions/checkout@v2
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
allow-no-subscriptions: true
- name: Azure CLI script - Prepare and run MNIST Training on Azure ML
uses: azure/CLI@v1
with:
azcliversion: 2.30
inlineScript: |
az extension add -n ml -y
az ml compute create --file aml-compute-cpu.yml --subscription $AZURE_SUBSCRIPTION
--resource-group $RESOURCE_GROUP --workspace-name $AML_WORKSPACE
az ml job create --file aml-job-train.yml --subscription $AZURE_SUBSCRIPTION
--resource-group $RESOURCE_GROUP --workspace-name $AML_WORKSPACE
首先,我们定义作业的触发器(主分支上的推送),然后配置 worker 和环境变量。最后,我们定义三个步骤来创建和更新计算资源并运行训练。
- 存储库签出
- 登录 Azure
- 执行 Azure ML 子步骤
或者,您可以通过用以下一组“纯粹的” `run` 命令替换 `azure/CLI@v1` 步骤来实现相同效果。
- name: Add ML Extension To azure-cli
run: az extension add -n ml -y
- name: Create or Update AML Workspace Compute
run: az ml compute create --file aml-compute-cpu.yml --subscription $AZURE_SUBSCRIPTION
--resource-group $RESOURCE_GROUP --workspace-name $AML_WORKSPACE
- name: Run Training on AML Workspace
run: az ml job create --file aml-job-train.yml --subscription $AZURE_SUBSCRIPTION
--resource-group $RESOURCE_GROUP --workspace-name $AML_WORKSPACE
触发 GitHub 训练工作流
要运行定义的工作流,我们只需将更改提交并推送到存储库即可。然后,当我们导航到存储库的 **Actions** 选项卡时,我们可以监视其执行进度。
如果一切顺利,该 action 将在我们的 **Azure: Machine Learning** 工作区中启动训练。GitHub 工作流立即完成,而 Azure 训练仍在继续。
在机器学习上监控训练
我们启动 Microsoft Azure Machine Learning Studio 来监控训练。
如果我们太快,可能需要等待我们的计算 VM 准备就绪。
然后,我们可以监控训练的进度。
请注意,根据您 Azure 区域的部署情况,Azure 可能会使用不同的 ML 运行时来运行您的训练(具有不同的日志文件结构)。如果您偏好旧的运行时,请确保在您的训练作业定义文件 `aml-job-train.yml` 中包含以下行。
environment_variables:
AZUREML_COMPUTE_USE_COMMON_RUNTIME: "false"
除了原始日志之外,我们还可以观察 MLflow 在训练期间和完成后记录的指标和其他信息。
太棒了!正如我们所见,我们的准确率达到了 98%。
清理 Azure 和 GitHub 上的资源
为避免为未使用的资源付费,我们必须停止或删除它们。最安全的方法是删除整个资源组。如果我们想保留我们的数据和历史记录,我们至少应该停止所有我们不再使用的计算集群。不过,如果您忘记了,也不用担心。我们当前的配置会在一小时不活动后自动将其退役。
除了 Azure 资源之外,我们还需要停止 GitHub 的 codespace 实例。我们可以在 **Code** 选项卡上选择 **Manage all** 后完成此操作。
我们必须为每个活动的 codespace 实例点击省略号(**…**)并选择 **stop code space** 选项。
后续步骤
本文介绍了如何使用 GitHub codespaces、Actions 和 Azure 云自动训练 PyTorch 模型。只需稍加努力,您就应该能够将此方法应用于任何图像分类任务。
在本篇文章和上一篇文章中,我们从模型训练代码开始。在实际的 ML 项目中,此步骤之前通常会有一个研究和实验阶段。支持此类工作最流行的工具集之一是 Jupyter notebooks 和 JupyterLab。下一篇文章将展示如何使用 Azure ML 工作区运行 notebook。
要了解开始使用 Azure Machine Learning 所需的所有内容,请查看快速入门:创建工作区资源。