【课程作业经验】使用NPU进行MindSpore模型训练

准备工作使用MindSpore进行模型训练需要做以下的准备工作训练的网络模型训练数据以及数据处理训练回调函数启动训练的python脚本网络模型MindSpore提供了一个MindVision的开源网络工具箱,其中包含了很多的网络结构以及预训练数据,但在云端的NPU环境中 (当时使用的是mindspore_1.5.1-cann_5.0.3-py_3.7-euler_2.8.3-aarch64) ,会直接报错,因此需要我们自己编写网络结构。编写网络结构可以参考MindSpore的官网,以下是一个简单的ResNet18的网络结构。编写网络结构的要点在于,在 __init__ 方法中定义网络中不同层的结构,在 construct 方法中设置不同层之间的连接关系。import mindspore.nn as nn

class ResBlock2RT(nn.Cell):

def __init__(self, in_channel):
    super(ResBlock2RT, self).__init__()
    self.seq = nn.SequentialCell(
        [
            nn.Conv2d(in_channel, in_channel, kernel_size=3, stride=1),
            nn.BatchNorm2d(in_channel),
            nn.ReLU(),
            nn.Conv2d(in_channel, in_channel, kernel_size=3, stride=1),
            nn.BatchNorm2d(in_channel),
        ])
    self.relu = nn.ReLU()

def construct(self, x):
    x = self.seq(x) + x
    return self.relu(x)

class ResBlock2DS(nn.Cell):

def __init__(self, in_channel, out_channel, stride):
    super(ResBlock2DS, self).__init__()
    self.seq = nn.SequentialCell(
        [
            nn.Conv2d(in_channel, out_channel, 3, stride=stride),
            nn.BatchNorm2d(out_channel),
            nn.ReLU(),
            nn.Conv2d(out_channel, out_channel, 3, stride=1),
            nn.BatchNorm2d(out_channel),
        ])
    self.shortup = nn.SequentialCell(
        [
            nn.Conv2d(in_channel, out_channel, 1, stride=stride),
            nn.BatchNorm2d(out_channel),
        ])
    self.relu = nn.ReLU()

def construct(self, x):
    y = self.seq(x) + self.shortup(x)
    return self.relu(y)

class ResNet18(nn.Cell):

def __init__(self, class_num=10, input_size=224):
    super(ResNet18, self).__init__()
    self.PreProcess = nn.SequentialCell([
        nn.Conv2d(3, 64, 7, stride=2),
        nn.BatchNorm2d(64),
        nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same'),
    ])
    self.ResBlock = nn.SequentialCell([
        ResBlock2RT(64),
        ResBlock2RT(64),
        ResBlock2DS(64, 128, 2),
        ResBlock2RT(128),
        ResBlock2DS(128, 256, 2),
        ResBlock2RT(256),
        ResBlock2DS(256, 512, 2),
        ResBlock2RT(512),
    ])
    self.LastProcess = nn.SequentialCell([
        nn.AvgPool2d(input_size // 32),
        nn.Flatten(),
        nn.Dense(512, 1024),
        nn.ReLU(),
        nn.Dense(1024, 512),
        nn.ReLU(),
        nn.Dropout(keep_prob=0.5),
        nn.Dense(512, class_num),
        nn.Softmax(),
    ])

def construct(self, x):
    x = self.PreProcess(x)
    x = self.ResBlock(x)
    y = self.LastProcess(x)
    return y

数据与数据处理数据挂载以及解压既然使用云端NPU进行训练,固然要将数据上传到云端。MindSpore可以使用华为云的对象存储服务存储需要训练的数据;在启动训练后,MindSpore可以挂载Obs桶内的一个文件夹到本地的一个文件夹中,这样用户就可以通过Obs桶进行加载训练数据。如果我们的训练数据是由大量的小文件组织起来,上传会比较麻烦。所以我们可以将训练数据进行压缩,然后可以使用python自带的 zipfile 库进行文件解压,使用起来比较简单,解压代码只有一行,其余的代码是用于判断文件存在性以及输出日志。import os
import zipfile
import logging

def unzip_file(file_name, target):

logging.info('unzip file {}'.format(file_name))
with zipfile.ZipFile(file_name) as zf:
    zf.extractall(path=target)
logging.info('file {} unzipped.'.format(file_name))

数据处理对分类模型,其数据格式是一张图片对应一个标签。我们可以使用一个类将硬盘数据转换为MindSpore模型,也可以使用列表、迭代器,其比较重要的是 __len__ 以及 __getitem__ 这两个方法,作用是计算数据个数以及使用索引的方式获取数据。其大概的格式如下,注意 __getitem__ 方法中需要同时返回图片的数据以及标签。class DatasetGenerator:

def __init__(self, data_path: str, label_path: str, resize=None):
    self.data = []
    self.label = []
    ...

def __getitem__(self, item):
    ...
    return img, label

def __len__(self):
    return len(self.label)

数据分割如果不是所有数据都会用于训练,就要进行数据分割。数据分割可以手工分割,也可以通过代码实现。以下是实现数据分割的简单的代码。import time
import numpy as np

class SubSet:

def __init__(self, dataset, ls):
    self.dataset = dataset
    self.ls = ls

def __getitem__(self, item):
    return self.dataset[self.ls[item]]

def __str__(self):
    return str(self.ls)

def __len__(self):
    return len(self.ls)

def random_split(dataset, target_size, seed=int(time.time())):

data_length = len(dataset)
combine_ls = list(range(data_length))  # get the dataset index

out_ls = []

np.random.seed(seed)
for item in target_size:
    x = np.random.choice(combine_ls, size=int(data_length * item), replace=False)
    tuple(map(combine_ls.remove, x))
    out_ls.append(SubSet(dataset, x))
return out_ls

具体的使用方式是这样的, random_split 会使用列表保存几个分割后的子集。x = [i for i in range(100)]
y = random_split(x, (0.1, 0.2, 0.3, 0.4))
数据转换使用 mindspore.dataset.GeneratorDataset 将数据转换为MindSpore的数据格式,以下代码包含了数据加载以及数据分割,结果是将数据导入为训练以及测试数据集。import mindspore.dataset as ds

ds_gen = DatasetGenerator(data_path=data_path,

                          label_path=label_path,
                          # resize=resize
                          )

split dataset to train dataset and valid dataset

train_gen, valid_gen = random_split(ds_gen, (0.75, 0.25))
train_ds = ds.GeneratorDataset(train_gen, ["image", "label"], shuffle=True)
valid_ds = ds.GeneratorDataset(valid_gen, ["image", "label"], shuffle=True)
仅仅如此还是不够的,我们还需要进行下一步的处理,即需要图片大小放缩随机水平翻转随机角度旋转数据类型转换将(h,w,c)通道的格式转换为(c,h,w)格式其中(1),(5)是数据格式变换,为必选项;(2)-(4)为数据增强,是可选项。具体的代码如下:import mindspore.dataset.vision.c_transforms as cv_trans
import mindspore.dataset.transforms.c_transforms as c_trans

resize_op = cv_trans.Resize(size=(resize, resize), interpolation=Inter.BICUBIC)
random_hor_flip_op = cv_trans.RandomHorizontalFlip()
random_rotation_op = cv_trans.RandomRotation(10, resample=Inter.BICUBIC)
type_cast_op_image = c_trans.TypeCast(dtype.float32)
type_cast_op_label = c_trans.TypeCast(dtype.int32)
HWC2CHW = cv_trans.HWC2CHW()

preprocess_operation = [resize_op,

            random_hor_flip_op,
            random_rotation_op,
            type_cast_op_image,
            HWC2CHW]

train_ds = train_ds.map(operations=preprocess_operation,

            input_columns="image")

train_ds = train_ds.map(operations=type_cast_op_label, input_columns="label")
train_ds = train_ds.batch(batch_size)

valid_ds = valid_ds.map(operations=preprocess_operation,

            input_columns="image")

valid_ds = valid_ds.map(operations=type_cast_op_label, input_columns="label")
valid_ds = valid_ds.batch(batch_size)
训练回调训练时如果需要查看模型的精度,以及保存模型训练过程中迭代最优的结果,需要重载 mindspore.train.callback.Callback 类,以下是回调类的样式。import logging

import mindspore
import mindspore.nn as nn
from mindspore.train.callback import Callback

def get_accuracy(network, datasets):

metric = nn.Accuracy('classification')
for x, y in datasets:
    y_hat = network(x)
    metric.update(y_hat, y)
return metric.eval()

def get_loss(network, loss_f, dataset):

loss = 0
for x, y in dataset:
    y_hat = network(x)
    loss += loss_f(y_hat, y)
return loss

class AccuracyMonitor(Callback):

def __init__(self, save_url, valid_data, export):
    self.save_file_pth = save_url + 'auto-save.ckpt'
    self.best_file_pth = save_url + 'best.ckpt'
    self.local_best_pt = './best.ckpt'

    self.valid_data = valid_data
    self.export = export

    self.max_valid_acc = 0

def epoch_end(self, run_context):
    """Called after each epoch finished."""

    callback_params = run_context.original_args()

    cur_epoch_num = callback_params.cur_epoch_num
    epoch_num = callback_params.epoch_num

    network = callback_params.network

    train_data = callback_params.train_dataset
    train_accu = get_accuracy(network, train_data)
    valid_accu = get_accuracy(network, self.valid_data)

    loss = get_loss(network, callback_params.loss_fn, train_data)
    loss /= callback_params.batch_num

    if valid_accu > self.max_valid_acc:
        self.max_valid_acc = valid_accu
        mindspore.save_checkpoint(network, self.local_best_pt)
        mindspore.save_checkpoint(network, self.best_file_pth)
        logging.info('Save the best state to {}'.format(self.local_best_pt))
    elif not cur_epoch_num % 10 and cur_epoch_num:
        mindspore.save_checkpoint(network, self.save_file_pth)
        logging.debug('Auto-save state to {}'.format(self.save_file_pth))
    print('epoch:[{}/{}] Loss:{} Train Accuracy:{} Valid Accuracy:{}'.format(cur_epoch_num, epoch_num,
                                                                                    loss, train_accu, valid_accu))

模型训练训练代码MindSpore封装了高阶的训练接口,训练时只需要定义好训练网络(net)、损失函数(loss)、优化器(optim)、训练轮次(args.epochs)、训练数据(t_dt)、回调列表(callback_list)即可,如下所示。loss = ...
optim = ...
callback_list = [...]
model = Model(net, loss_fn=loss, optimizer=optim)
model.train(args.epochs, t_dt, callback_list, False)
启动脚本使用云端NPU进行训练,需要使用命令行进行交互,可以使用python的 argparse 的库进行操作,具体可以参考https://docs.python.org/3/lib...。由于Modelarts进行云端训练是通过Shell的方式进行启动的,点击新建训练能看到如下所示的启动代码
【课程作业经验】使用NPU进行MindSpore模型训练_第1张图片
因此,我们要在训练脚本中设置解析参数。使用云端NPU进行训练Modelarts是华为提供的一个PaaS,使用Modelarts进行训练,需要进行以下工作。建立文件夹在对象存储服务中新建一个桶,在桶的对象中建立如下目录resnet/

|-code/
|-data/
    |-images/
|-log/
|-output/

其中,resnet是我们的项目工程的总目录,属于可选项;code目录用于存放代码; data目录用于存放数据信息,其中的images目录存放图片数据,dataset.txt存放图片的数据的标签; log目录用于存放输出的日志信息;output目录用于存放输出的数据信息。上传数据具体是将数据上传到obs桶中的data/目录中,可以使用obsutil进行上传;也可以压缩后打包上传,再进行解压缩。上传算法将所有的算法文件上传到obs桶的/resnet/code/目录下。创建算法进入Modelarts界面,点击算法管理,创建,选择AI引擎为Ascend-Powered-Engine,mindspore,设置代码目录为/resnet/code/,选择启动文件。然后在输入/输出数据配置,设置启动脚本中的路径名称,并设置超参,如下所示。
【课程作业经验】使用NPU进行MindSpore模型训练_第2张图片
创建训练进入Modelarts界面,点击训练管理-训练作业,创建训练作业,输入名称,配置输入文件路径、输出文件路径、超参数、日志,选择资源为Ascend,点击提交,即可开始训练。
【课程作业经验】使用NPU进行MindSpore模型训练_第3张图片
总结具体代码可以查看 https://gitee.com/ruqy/res-net

你可能感兴趣的:(人工智能算法)