目录
先决条件
设置GitHub代码空间
配置代码空间环境
设置Azure机器学习工作区
下载MNIST数据集
将我们的数据集上传到Azure机器学习工作区
编写PyTorch模型训练代码
介绍GitHub操作
将GitHub Actions与Azure ML结合使用
定义Azure ML作业
配置GitHub服务主体
使用GitHub工作流进行模型训练
触发GitHub训练工作流程
监控机器学习训练
清理Azure和GitHub上的资源
下一步
本文展示了使用PyTorch创建和训练模型,然后在Azure ML上运行模型训练的代码。随后将进行简短的演示,以表明经过训练的模型可以按预期工作。
在本系列中,我们将通过几种方法在Azure上的Python中创建和使用机器学习模型。上一篇文章使用带有机器学习扩展的Visual Studio Code 在Azure上训练XGBoost模型。本文展示了如何使用GitHub Codespaces仅通过Web浏览器使用Visual Studio Code。此外,它还展示了每次我们将更改提交到存储库时如何在Azure上自动训练PyTorch模型。为此,我们使用GitHub Actions。
您可以在GitHub上找到本文的示例代码。
先决条件要遵循本文中的示例,您需要一个Web浏览器并访问Azure订阅。如果您没有订阅,可以注册一个免费的Azure帐户。此外,要配置使用Azure资源的GitHub操作,您还需要对分配给您的订阅的Azure AD的管理员访问权限。
设置GitHub代码空间如果您曾经需要在不安装任何东西(例如,从平板电脑)的情况下访问功能齐全的开发环境,现在您可以了。GitHub Codespaces提供对云中Visual Studio Code环境的访问。不过,目前,拥有一个免费的GitHub帐户是不够的。它需要GitHub团队或企业计划。
如果您还没有访问GitHub Teams计划的权限,则需要一个才能继续。要创建它,请单击Continue with Team并提供您组织的详细信息,包括付款详细信息。您可以在此处使用您的常规GitHub登录名作为成员名。但是,仅团队计划不足以使用Codespaces。要激活此功能,您需要转到组织的设置,从侧面菜单中选择Codespaces,然后为所有或选定的用户启用它。
此外,您需要使用任何高于0美元的值来定义相关的支出限额。为此,请从组织的设置侧菜单中选择计费和计划,然后选择管理支出限额:
最后,在这些步骤之后,您应该可以访问您组织的任何存储库的代码菜单上的新代码空间选项卡。
创建代码空间时,它始终位于单个存储库中。因此,在继续之前创建一个。您可以在此处使用提供的示例代码,但请确保将其内容复制到存储库的根目录。
现在,当您单击New codespace时,您可以选择在您的环境中运行的虚拟机(VM)的大小:
通常对于云资源,配置越强大,每小时的使用成本就越高。因为计划是将所有实际工作委托给Azure,所以最小的两核实例是可以接受的。
配置代码空间环境选择Create codespace选项后,新的云环境应该会在几分钟内可用。它是一个功能齐全的Visual Studio Code,具有所有的花里胡哨,但可以使用Web浏览器使用。
如果需要,可以使用一系列新的与代码空间相关的配置选项。只需按下标准的Visual Studio Code快捷方式(Cmd/Ctrl+Shift+P),然后开始输入“Codespaces”来过滤选项:
例如,要更改开发环境,请选择添加开发容器配置文件,然后选择预定义模板之一。
出于我们的目的,默认环境包含我们需要的一切(即azure-cli和python 3.8),因此我们可以跳过这一步。但我们需要azure-cli的机器学习扩展(仍处于预览阶段)。我们可以使用Azure CLI添加它:
$ az extension add -n ml -y
如果您还没有按照本系列上一篇文章中的示例进行操作,您现在可以在您的代码空间中进行操作。无论如何,要继续,我们需要一个Azure机器学习工作区。
我们可以按照上一篇文章中的步骤配置工作区,使用Azure门户或azure-cli在代码空间中使用。在这里,我们使用azure-cli,从登录到Azure开始:
$ az login —use-device-code
按照命令返回的说明进行登录后,我们创建资源组和Azure机器学习工作区:
$ export SUBSCRIPTION=""
$ export GROUP="azureml-rg"
$ export WORKSPACE="demo-ws"
$ export LOCATION=""
$ az group create -n $GROUP -l $LOCATION
$ az ml workspace create --name $WORKSPACE --subscription $SUBSCRIPTION --resource-group $GROUP
现在我们需要使用上一篇文章介绍的代码下载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机器学习工作区中处理数据的首选方法是使用数据集。为了从刚刚下载的文件创建数据集,我们准备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
现在我们已经在Azure机器学习工作区中注册了数据集,我们可以编写代码来训练我们的模型。我们使用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机器学习工作区中注册训练好的模型:
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机器学习工作区并解析执行参数。在这里,我们只解析一个:数据:
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:机器学习工作区中。
介绍GitHub操作我们(几乎)拥有在本地运行训练所需的所有代码。不过,我们想使用Azure进行训练。此外,我们希望每次将更改推送到存储库时自动运行训练。我们使用GitHub Actions和azure-cli来实现这一点。值得指出的是,GitHub Actions与代码空间没有直接关系。您可以在任何GitHub存储库中使用此机制,即使只有免费帐户。
GitHub Actions允许我们创建自动执行的作业——例如,推送到存储库、拉取请求和许多其他触发器。按照约定优于配置的方法,我们在存储库根文件夹中的.github/workflows文件夹中的一个或多个YAML文件中定义我们的操作。
将GitHub Actions与Azure ML结合使用虽然GitHub操作有一组专用的Azure机器学习任务,例如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": "",
"clientSecret": "",
"subscriptionId": "",
"tenantId": "",
(...)
}
记住这个值。如果您丢失了它,您必须创建一个新的秘密。如果上一个命令失败,请确保您具有分配给订阅的Azure AD的管理员权限。成功后,我们返回存储库的设置并将返回的JSON添加为AZURE_CREDENTIALS机密:
现在我们可以为我们的训练创建一个GitHub工作流。
使用GitHub工作流进行模型训练从技术上讲,我们有两个选项可以在GitHub Actions中运行azure-cli命令。首先,我们可以使用工作流的运行步骤直接运行它们,因为azure-cli默认情况下在GitHub工作人员上是可用的。或者,我们可以使用专用的Azure CLI操作。如果要显式控制azure-cli版本,则需要依赖后者。不过,这种控制可能会适得其反。
我们总是需要至少两个操作来在Azure上执行代码。首先是Azure登录操作,然后是具有实际逻辑的以下步骤。问题在于,虽然我们可以控制azure-cli Azure CLI操作使用的版本,但我们无法对Azure登录操作执行相同操作。这可能会导致修复的旧azure-cli版本与当前的Azure登录操作不兼容的情况。这种情况值得记住,因为它发生在不久前。在任何情况下,您始终可以使用Azure CLI。
我们的训练作业定义的Azure CLI版本如下所示:
on:
push:
branches: [ main ]
name: AzureMLTrain
jobs:
setup-aml-and-train:
runs-on: ubuntu-latest
env:
AZURE_SUBSCRIPTION: ""
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
首先,我们为我们的工作定义触发器(推送主分支),然后我们配置工作者和环境变量。最后,我们定义了三个步骤来创建和更新计算并运行训练:
- 存储库签出
- 登录到Azure
- Azure ML子步骤的执行
或者,您可以通过将azure/CLI@v1步骤替换为以下一组“vanilla”运行项来实现相同的效果:
- 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
运行已定义的工作流,我们只需要提交更改并将更改推送到存储库。然后,当我们导航到存储库的Actions选项卡时,我们可以监控其执行进度:
如果一切顺利,该操作将在我们的Azure:机器学习工作区中启动训练。GitHub工作流立即完成,而Azure训练继续进行。
监控机器学习训练我们启动了Microsoft Azure机器学习工作室来监控训练。
如果我们太快,我们可能需要等到我们的计算虚拟机准备好。
然后,我们可以监控训练的进度。
请注意,根据您在Azure区域的部署情况,Azure可能会使用不同的ML运行时来运行您的训练(使用不同的日志文件结构)。如果您更喜欢旧版本,请确保在训练作业定义文件aml-job-train.yml中有以下行:
environment_variables:
AZUREML_COMPUTE_USE_COMMON_RUNTIME: "false"
除了原始日志,我们还可以观察MLflow在完成训练期间和之后记录的指标和其他信息:
伟大的!正如我们所见,我们的准确率达到了98%。
清理Azure和GitHub上的资源为了避免为未使用的资源付费,我们必须停止或删除它们。最安全的解决方案是删除整个资源组。如果我们想保留我们的数据和历史记录,我们至少应该停止所有不再使用的计算集群。不过,如果您忘记了它,请不要担心。我们当前的配置会在一个小时不活动后自动停用它们。
除了Azure资源,我们还需要停止GitHub的代码空间实例。我们可以在代码选项卡上选择全部管理后执行此操作:
我们必须为每个活动代码空间实例单击省略号( … )和停止代码空间选项。
下一步本文展示了如何使用GitHub代码空间、Actions和Azure云自动训练PyTorch模型。不费吹灰之力,您应该能够使这种方法适应任何图像分类任务。
我们从本文和上一篇文章中的模型训练代码开始。在现实生活中的ML项目中,研究和实验阶段通常在此步骤之前。支持此类工作的最受欢迎的工具集之一是Jupyter notebooks和JupyterLab。以下文章介绍了如何使用Azure ML工作区运行笔记本。
若要了解开始使用Azure机器学习所需的一切,请查看快速入门:创建工作区资源。
https://www.codeproject.com/Articles/5321644/Python-Machine-Learning-on-Azure-Part-2-Creating-a