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

准备工作

使用MindSpore进行模型训练需要做以下的准备工作

  1. 训练的网络模型
  2. 训练数据以及数据处理
  3. 训练回调函数
  4. 启动训练的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 nnclass 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 osimport zipfileimport loggingdef 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 timeimport numpy as npclass 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 dsds_gen = DatasetGenerator(data_path=data_path,                              label_path=label_path,                              # resize=resize                              )# split dataset to train dataset and valid datasettrain_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)

仅仅如此还是不够的,我们还需要进行下一步的处理,即需要

  1. 图片大小放缩
  2. 随机水平翻转
  3. 随机角度旋转
  4. 数据类型转换
  5. 将(h,w,c)通道的格式转换为(c,h,w)格式

其中(1),(5)是数据格式变换,为必选项;(2)-(4)为数据增强,是可选项。具体的代码如下:

import mindspore.dataset.vision.c_transforms as cv_transimport mindspore.dataset.transforms.c_transforms as c_transresize_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 loggingimport mindsporeimport mindspore.nn as nnfrom mindspore.train.callback import Callbackdef 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 lossclass 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/library/argparse.html。

由于Modelarts进行云端训练是通过Shell的方式进行启动的,点击新建训练能看到如下所示的启动代码

因此,我们要在训练脚本中设置解析参数。

使用云端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-Enginemindspore,设置代码目录为/resnet/code/,选择启动文件。

然后在输入/输出数据配置,设置启动脚本中的路径名称,并设置超参,如下所示。

创建训练

进入Modelarts界面,点击训练管理-训练作业,创建训练作业,输入名称,配置输入文件路径、输出文件路径、超参数、日志,选择资源为Ascend,点击提交,即可开始训练。

总结

具体代码可以查看 https://gitee.com/ruqy/res-net

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章