Skip to main content

Azure机器学习一天教程 - 使用自己的数据训练模型

分类:  Azure机器学习 标签:  #Azure #人工智能 #Python #机器学习 发布于: 2023-06-11 21:35:41

我们这个系列是基于Python SDK的一天的学习文章,虽然这个系列的文章很简单,但是实际上已经将如何在Azure机器学习平台上进行机器学习的基本要素都已经讲清楚了,我想在这里再重复一遍我们这几篇文章主要讲的是什么:

  • 通过运行Hello World来给大家介绍基本步骤和构成,以及项目的基本结构。
  • 通过训练第一个基于云平台的模型,给大家介绍如何在云平台上训练模型。
  • 如何使用自己的数据来在云平台上训练模型。

我们本章要学习的就是第三点,如何使用自己的数据来在云平台上训练模型,在开始本章之前,我先向大家介绍一下基本的要点。

我们本章还是会使用前一章的基本脚本,我们会在本章对前面的脚本进行改进,之前我们项目里一共有三个文件:

  • model.py: 用于定义神经网络
  • train.py: 用于训练模型。
  • run-pytorch.py: 控制脚本,用于为在云平台训练脚本提供各种资源的准备和设置。

基本思路

我们回顾一下训练脚本train.py的基本逻辑:

  • 该脚本使用PyTorchDataset中自带的数据,下载数据到目录srd/data中,并使用该数据来训练模型。
  • 该脚本所有的变量基本都在脚本内定义,没有办法传入参数。

我们需要使用自己的数据,那么第一个需要思考的是: 我们的数据存储在哪里,第二个我们需要思考的是:我们如何将存放好的数据传给训练脚本。

先考虑第一个问题,假设我们现在不需要考虑在云平台,假设是在本地训练,那么我们只需要使用一个文件目录将自己的数据存入到该目录里面就可以解决这个问题了。跟着第二个问题也据顺理成章的只需要更改训练脚本让它支持传入数据目录的参数就好了。

这个是基本的思路,下面我们使用Python的模块argparse来处理传入脚本的参数。

使训练脚本支持传入参数

我们前面说过了使用Python模块argparse来处理传入参数,那么我们更改train.py脚本如下:

import os
import argparse
import torch
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from model import Net
from azureml.core import Run
run = Run.get_context()
if __name__ == "__main__":
    # 定义参数
    # 使用参数 --data_path 指明我们数据保存的目录
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--data_path',
        type=str,
        help='Path to the training data'
    )
    parser.add_argument(
        '--learning_rate',
        type=float,
        default=0.001,
        help='Learning rate for SGD'
    )
    parser.add_argument(
        '--momentum',
        type=float,
        default=0.9,
        help='Momentum for SGD'
    )
    args = parser.parse_args()
    print("===== DATA =====")
    print("DATA PATH: " + args.data_path)
    print("LIST FILES IN DATA PATH...")
    print(os.listdir(args.data_path))
    print("================")
    # prepare DataLoader for CIFAR10 data
    transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
    ])
    trainset = torchvision.datasets.CIFAR10(
        root=args.data_path,
        train=True,
        download=False,
        transform=transform,
    )
    trainloader = torch.utils.data.DataLoader(
        trainset,
        batch_size=4,
        shuffle=True,
        num_workers=2
    )
    # define convolutional network
    net = Net()
    # set up pytorch loss /  optimizer
    criterion = torch.nn.CrossEntropyLoss()
    optimizer = optim.SGD(
        net.parameters(),
        lr=args.learning_rate,
        momentum=args.momentum,
    )
    # train the network
    for epoch in range(2):
        running_loss = 0.0
        for i, data in enumerate(trainloader, 0):
            # unpack the data
            inputs, labels = data
            # zero the parameter gradients
            optimizer.zero_grad()
            # forward + backward + optimize
            outputs = net(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
            # print statistics
            running_loss += loss.item()
            if i % 2000 == 1999:
                loss = running_loss / 2000
                run.log('loss', loss)  # log loss metric to AML
                print(f'epoch={epoch + 1}, batch={i + 1:5}: loss {loss:.2f}')
                running_loss = 0.0
    print('Finished Training')

理解这段代码中最为需要注意的一部分:

# 给脚本定义参数
parser = argparse.ArgumentParser()
parser.add_argument('--data_path', type=str, help='Path to the training data')
parser.add_argument('--learning_rate', type=float, default=0.001, help='Learning rate for SGD')
parser.add_argument('--momentum', type=float, default=0.9, help='Momentum for SGD')
args = parser.parse_args()
# ... other code

现在我们的脚本已经可以带入参数了,你可以通过如下的命令行来运行它

注意
请使用前面一章的脚本先把数据下载下来,如果想保证该脚本本地可以运行,需要确认数据已经下载到了目录src/data

本地运行脚本, 需要注意的是我们已经设置了conda环境里运行

python train.py --data_path='.\data\' --learning_rate=0.001 --momentum=0.9

我们现在通过将自己的数据保存在目录.\src\data中,解决了自己数据保存的问题,通过改写训练脚本,使得脚本接受路径参数,解决了让训练脚本了解去哪里读取需要的数据的问题。

那么如果我们在云平台上如何处理这两个问题呢?

云平台如何保存用户的数据

当我们创建一个Azure Machine Learning的资源的时候,默认和该资源相匹配的会同时创建一个Azure Storage Account用于Azure机器学习平台的默认存储,在这个存储里不仅仅存放日志,也可以存放用户的数据。因此我们可以将自己的数据传送到和Azure Machine learning相关联的存储中。

我们前面说过了Azure机器学习平台默认会有的相关的Azure Storage Account, 那么如果你有另外一个用于存储数据的Azure Storage account, 并且数据量巨大,你可以考虑将这些数据移动到Azure Machine learning默认的Storage里,但是这非常耗费时间,因此Azure Machine Learning还提供了一个工具 - DataStoreDataStore的作用是指向一个外部的存储,它像一个引用,引用外部或者内部的数据,同时如果想访问该DataStore引用的数据,需要使用另外一个工具 - DataSetDataSet用于访问DataStore引用的数据。

因此当前我们有这样几个工具:

  • 可以使用Azure Storage AcountBlog来存放自己的数据。
  • 使用DataStore来引用这个Blog
  • 使用DataSet访问该数据。

那么我们先来将自己的数据上传到Azure Storage account,

这里有几个方法:

  • 使用工具,像AzCopy 或者 Azure Storage Explorer, 特别是大量的数据,非常适合用AzCopy
  • 小数据使用代码

我们下面使用Python代码来演示一下,如何通过对象DataStoreDataSet来上传数据到默认的storage

在项目的根目录,创建Python脚本:upload-data.py

# upload-data.py
from azureml.core import Workspace
ws = Workspace.from_config()
datastore = ws.get_default_datastore()
datastore.upload(src_dir='./data',
                 target_path='datasets/cifar10',
                 overwrite=True)

好了,我们现在已经解决了在云平台自己数据存储的问题,我们接着要解决,如果将存储在storage里的数据位置告诉即将在云平台运行的脚本。

将云平台的数据路径传给在云平台上训练的脚本

我们需要理解一下Azure Storage实际上可以作为一个网络文件系统来使用,例如通过samba协议挂在到windows机器上,假如我们可以让一个DataStore作为一个网络文件系统挂载到运行训练脚本的机器上,那么对于训练脚本就像存取本地文件系统是一样的,这样也不需要额外的编程,如果你也这么想的,那么恭喜你,Azure Machine Learning也是这么想的。

那么我们接下来看看控制脚本是如何做到这一个的,我们要使用控制脚本来落实这个思路。

修改控制脚本run-pytorch-data.py注意这个文件在根目录

# run-pytorch-data.py
from azureml.core import Workspace
from azureml.core import Experiment
from azureml.core import Environment
from azureml.core import ScriptRunConfig
from azureml.core import Dataset

if __name__ == "__main__":
    ws = Workspace.from_config()
    datastore = ws.get_default_datastore()
    dataset = Dataset.File.from_files(path=(datastore, 'datasets/cifar10'))

    experiment = Experiment(workspace=ws, name='day1-experiment-data')

    config = ScriptRunConfig(
        source_directory='./src',
        script='train.py',
        compute_target='cpu-cluster',
        arguments=[
            '--data_path', dataset.as_named_input('input').as_mount(),
            '--learning_rate', 0.003,
            '--momentum', 0.92],
    )

    # use curated pytorch environment 
    env = ws.environments['AzureML-PyTorch-1.6-CPU']
    config.run_config.environment = env

    run = experiment.submit(config)
    aml_url = run.get_portal_url()
    print("Submitted to compute cluster. Click link below")
    print("")
    print(aml_url)

我们前面在改写train.py训练脚本的时候,使用了参数--data_path用于传入数据的路径,我们看到在ScriptRunConfig脚本运行对象里,我们使用了参数arguments来给训练脚本传入它所需要的参数:

  config = ScriptRunConfig(
        source_directory='./src',
        script='train.py',
        compute_target='cpu-cluster',
        arguments=[
            '--data_path', dataset.as_named_input('input').as_mount(),
            '--learning_rate', 0.003,
            '--momentum', 0.92],
    )

可以从这里看到,训练脚本所需要的参数全部由ScriptRunConfig来传入。

同时需要注意到另外一句:dataset.as_named_input('input').as_mount(), 我们刚钢的猜想就是是不是可以将Azure Storage里的文件目录作为一个网络文件系统挂载到运行训练脚本的机器上,这一句正好是就是解决这个问题的,as_mount, 即在Linux/Unix上的文件系统挂载。我们通过运行这个控制脚本之后,也可以在Studio上看到日志的输出:

Processing 'input'.
Processing dataset FileDataset
{
  "source": [
    "('workspaceblobstore', 'datasets/cifar10')"
  ],
  "definition": [
    "GetDatastoreFiles"
  ],
  "registration": {
    "id": "XXXXX",
    "name": null,
    "version": null,
    "workspace": "Workspace.create(name='XXXX', subscription_id='XXXX', resource_group='X')"
  }
}
Mounting input to /tmp/tmp9kituvp3.
Mounted input to /tmp/tmp9kituvp3 as folder.
Exit __enter__ of DatasetContextManager
Entering Run History Context Manager.
Current directory:  /mnt/batch/tasks/shared/LS_root/jobs/dsvm-aml/azureml/tutorial-session-3_1600171983_763c5381/mounts/workspaceblobstore/azureml/tutorial-session-3_1600171983_763c5381
Preparing to call script [ train.py ] with arguments: ['--data_path', '$input', '--learning_rate', '0.003', '--momentum', '0.92']
After variable expansion, calling script [ train.py ] with arguments: ['--data_path', '/tmp/tmp9kituvp3', '--learning_rate', '0.003', '--momentum', '0.92']

Script type = None
===== DATA =====
DATA PATH: /tmp/tmp9kituvp3
LIST FILES IN DATA PATH...
['cifar-10-batches-py', 'cifar-10-python.tar.gz']

从日志上可以很清楚的看到确实是从Azure Storage mount到了训练的机器上。

另外也需要注意到如何创建的DataSet对象:Dataset.File.from_files(path=(datastore, 'datasets/cifar10'))

至此Python一天教程就结束了,虽然这个部分的教程不长,但是实际上它是真的将如何使用Python SDKAzure Machine learning上训练模型的基本原理也已经讲清楚了。