本章节主要对模型迁移场景所必需的数据集、模型和训练、推理流程等在MindSpore上的构建方法做简单介绍。同时展示MindSpore和PyTorch在数据集包装、模型构建、训练流程代码上的差别。模型分析在进行正式的代码迁移前需要对即将进行迁移的代码做一些简单的分析判断哪些代码可以直接复用哪些代码必须迁移到MindSpore。一般来说只有与硬件相关的代码部分才必须迁移到MindSpore比如模型输入相关包含模型参数加载、数据集包装等模型构建和执行的代码模型输出相关包含模型参数保存等。像Numpy、OpenCV等CPU上计算的三方库以及Configuration、Tokenizer等不需要昇腾、GPU处理的Python操作可以直接复用原始代码。数据集包装MindSpore提供了多种典型开源数据集的解析读取如MNIST、CIFAR-10、CLUE、LJSpeech等详情可参考mindspore.dataset。自定义数据加载 GeneratorDataset在迁移场景最常用的数据加载方式是GeneratorDataset只需对Python迭代器做简单包装就可以直接对接MindSpore模型进行训练、推理。import numpy as np from mindspore import dataset as ds num_parallel_workers 2 # 多线程/进程数 world_size 1 # 并行场景使用通信group_size rank 0 # 并行场景使用通信rank_id class MyDataset: def __init__(self): self.data np.random.sample((5, 2)) self.label np.random.sample((5, 1)) def __getitem__(self, index): return self.data[index], self.label[index] def __len__(self): return len(self.data) dataset ds.GeneratorDataset(sourceMyDataset(), column_names[data, label], num_parallel_workersnum_parallel_workers, shuffleTrue, num_shards1, shard_id0) train_dataset dataset.batch(batch_size2, drop_remainderTrue, num_parallel_workersnum_parallel_workers)一个典型的数据集构造如上构造一个Python类必须有__getitem__和__len__方法分别表示每一步迭代取的数据和整个数据集遍历一次的大小。其中index表示每次取数据的索引当shuffleFalse时按顺序递增当shuffleTrue时随机打乱。GeneratorDataset至少需要包含source一个Python迭代器column_names迭代器__getitem__方法每个输出的名字。更多使用方法参考GeneratorDataset。dataset.batch将数据集中连续batch_size条数据组合为一个批数据至少需要包含batch_size指定每个批处理数据包含的数据条目。更多使用方法参考Dataset.batch。模型构建网络基本构成单元 CellMindSpore的网络搭建主要使用Cell进行图的构造用户需要定义一个类继承Cell这个基类在init里声明需要使用的API及子模块在construct里进行计算PyTorchMindSpore 心灵孢子import torch class Network(torch.nn.Module): def __init__(self, forward_net): super(Network, self).__init__() self.net forward_net def forward(self, x): y self.net(x) return torch.nn.functional.relu(y) inner_net torch.nn.Conv2d(120, 240, kernel_size4, biasFalse) net Network(inner_net) for i in net.parameters(): print(i) from mindspore import mint, nn class Network(nn.Cell): def __init__(self, forward_net): super(Network, self).__init__() self.net forward_net def construct(self, x): y self.net(x) return mint.nn.functional.relu(y) inner_net mint.nn.Conv2d(120, 240, kernel_size4, biasFalse) net Network(inner_net) for i in net.get_parameters(): print(i)MindSpore和PyTorch构建模型的方法差不多使用算子的差别可以参考API差异文档。模型保存和加载PyTorch提供了state_dict()用于参数状态的查看及保存load_state_dict用于模型参数的加载。MindSpore可以使用 save_checkpoint 与load_checkpoint 。MindSpore 可以使用 save_checkpoint 与 load_checkpoint 。PyTorchMindSpore 心灵孢子# 使用torch.save()把获取到的state_dict保存到pkl文件中 torch.save(pt_model.state_dict(), save_path) # 使用torch.load()加载保存的state_dict # 然后使用load_state_dict将获取到的state_dict加载到模型中 state_dict torch.load(save_path) pt_model.load_state_dict(state_dict) # 模型权重保存 ms.save_checkpoint(ms_model, save_path) # 使用ms.load_checkpoint()加载保存的ckpt文件 # 然后使用load_state_dict将获取到的param_dict加载到模型中 param_dict ms.load_checkpoint(save_path) ms_model.load_state_dict(param_dict)优化器PyTorch和MindSpore同时支持的优化器异同比较详见API映射表。优化器的执行和使用差异PyTorch单步执行优化器时一般需要手动执行zero_grad()方法将历史梯度设置为0然后使用loss.backward()计算当前训练step的梯度最后调用优化器的step()方法实现网络权重的更新使用MindSpore中的优化器时只需要直接对梯度进行计算然后使用optimizer(grads)执行网络权重的更新。如果在训练过程中需要动态调整学习率PyTorch提供了LRScheduler类用于学习率管理。使用动态学习率时将optimizer实例传入LRScheduler子类中通过循环调用scheduler.step()执行学习率修改并将修改同步至优化器中。MindSpore提供了Cell和list两种动态修改学习率的方法。使用时对应动态学习率对象直接传入优化器学习率的更新在优化器中自动执行具体请参考动态学习率。PyTorchMindSpore 心灵孢子optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9) scheduler ExponentialLR(optimizer, gamma0.9) optimizer.zero_grad() output model(input) loss loss_fn(output, target) loss.backward() optimizer.step() scheduler.step() import mindspore from mindspore import nn lr nn.exponential_decay_lr(0.01, decay_rate, total_step, step_per_epoch, decay_epoch) optimizer nn.SGD(model.trainable_params(), learning_ratelr, momentum0.9) grad_fn mindspore.value_and_grad(forward_fn, None, optimizer.parameters, has_auxTrue) (loss, _), grads grad_fn(data, label) # 在优化器里自动做学习率更新 optimizer(grads)自动微分MindSpore 和 PyTorch 都提供了自动微分功能让我们在定义了正向网络后可以通过简单的接口调用实现自动反向传播以及梯度更新。但需要注意的是MindSpore 和 PyTorch 构建反向图的逻辑是不同的这个差异也会带来 API 设计上的不同。PyTorch的自动微分MindSpore的自动微分# torch.autograd: # backward是累计的更新完之后需清空optimizer import torch.nn as nn import torch.optim as optim # 实例化模型和优化器 model PT_Model() optimizer optim.SGD(model.parameters(), lr0.01) # 定义损失函数均方误差MSE loss_fn nn.MSELoss() # 前向传播计算模型输出 y_pred model(x) # 计算损失将预测值与真实标签计算损失 loss loss_fn(y_pred, y_true) # 反向传播计算梯度 loss.backward() # 优化器更新 optimizer.step() # ms.grad: # 使用grad接口输入正向图输出反向图 import mindspore as ms from mindspore import nn # 实例化模型和优化器 model MS_Model() optimizer nn.SGD(model.trainable_params(), learning_rate0.01) # 定义损失函数均方误差MSE loss_fn nn.MSELoss() def forward_fn(x, y_true): # 前向传播计算模型输出 y_pred model(x) # 计算损失将预测值与真实标签计算损失 loss loss_fn(y_pred, y_true) return loss, y_pred # 计算loss和梯度 grad_fn ms.value_and_grad(forward_fn, None, optimizer.parameters, has_auxTrue) (loss, _), grads grad_fn(x, y_true) # 优化器更新 optimizer(grads)模型训练和推理下面是一个在MindSpore的Trainer的例子包含了训练和训练时推理。训练部分主要包含将数据集、模型、优化器等模块组合训练推理部分主要包含评估指标获取、保存最优模型参数等。import mindspore as ms from mindspore import nn from mindspore.amp import StaticLossScaler, all_finite from mindspore.communication import init, get_group_size class Trainer: 一个有两个loss的训练示例 def __init__(self, net, loss1, loss2, optimizer, train_dataset, loss_scale1.0, eval_datasetNone, metricNone): self.net net self.loss1 loss1 self.loss2 loss2 self.opt optimizer self.train_dataset train_dataset self.train_data_size self.train_dataset.get_dataset_size() # 获取训练集batch数 self.weights self.opt.parameters # 注意value_and_grad的第一个参数是需要做梯度求导的图一般包含网络和loss。这里可以是一个函数也可以是Cell self.value_and_grad ms.value_and_grad(self.forward_fn, None, weightsself.weights, has_auxTrue) # 分布式场景使用 self.grad_reducer self.get_grad_reducer() self.loss_scale StaticLossScaler(loss_scale) self.run_eval eval_dataset is not None if self.run_eval: self.eval_dataset eval_dataset self.metric metric self.best_acc 0 def get_grad_reducer(self): grad_reducer nn.Identity() # 判断是否是分布式场景分布式场景的设置参考上面通用运行环境设置 group_size get_group_size() reducer_flag (group_size ! 1) if reducer_flag: grad_reducer nn.DistributedGradReducer(self.weights) return grad_reducer def forward_fn(self, inputs, labels): 正向网络构建注意第一个输出必须是最后需要求梯度的那个输出 logits self.net(inputs) loss1 self.loss1(logits, labels) loss2 self.loss2(logits, labels) loss loss1 loss2 loss self.loss_scale.scale(loss) return loss, loss1, loss2 ms.jit # jit加速需要满足图模式构建的要求否则会报错 def train_single(self, inputs, labels): (loss, loss1, loss2), grads self.value_and_grad(inputs, labels) loss self.loss_scale.unscale(loss) grads self.loss_scale.unscale(grads) grads self.grad_reducer(grads) self.opt(grads) return loss, loss1, loss2 def train(self, epochs): train_dataset self.train_dataset.create_dict_iterator(num_epochsepochs) self.net.set_train(True) for epoch in range(epochs): # 训练一个epoch for batch, data in enumerate(train_dataset): loss, loss1, loss2 self.train_single(data[image], data[label]) if batch % 100 0: print(fstep: [{batch}/{self.train_data_size}] floss: {loss}, loss1: {loss1}, loss2: {loss2}, flushTrue) # 保存当前epoch的模型和优化器权重 ms.save_checkpoint(self.net, fepoch_{epoch}.ckpt) ms.save_checkpoint(self.opt, fopt_{epoch}.ckpt) # 推理并保存最好的那个checkpoint if self.run_eval: eval_dataset self.eval_dataset.create_dict_iterator(num_epochs1) self.net.set_train(False) self.eval(eval_dataset, epoch) self.net.set_train(True) def eval(self, eval_dataset, epoch): self.metric.clear() for batch, data in enumerate(eval_dataset): output self.net(data[image]) self.metric.update(output, data[label]) accuracy self.metric.eval() print(fepoch {epoch}, accuracy: {accuracy}, flushTrue) if accuracy self.best_acc: # 保存最好的那个checkpoint self.best_acc accuracy ms.save_checkpoint(self.net, best.ckpt) print(fUpdate best acc: {accuracy})
MindSpore-模型迁移案例
本章节主要对模型迁移场景所必需的数据集、模型和训练、推理流程等在MindSpore上的构建方法做简单介绍。同时展示MindSpore和PyTorch在数据集包装、模型构建、训练流程代码上的差别。模型分析在进行正式的代码迁移前需要对即将进行迁移的代码做一些简单的分析判断哪些代码可以直接复用哪些代码必须迁移到MindSpore。一般来说只有与硬件相关的代码部分才必须迁移到MindSpore比如模型输入相关包含模型参数加载、数据集包装等模型构建和执行的代码模型输出相关包含模型参数保存等。像Numpy、OpenCV等CPU上计算的三方库以及Configuration、Tokenizer等不需要昇腾、GPU处理的Python操作可以直接复用原始代码。数据集包装MindSpore提供了多种典型开源数据集的解析读取如MNIST、CIFAR-10、CLUE、LJSpeech等详情可参考mindspore.dataset。自定义数据加载 GeneratorDataset在迁移场景最常用的数据加载方式是GeneratorDataset只需对Python迭代器做简单包装就可以直接对接MindSpore模型进行训练、推理。import numpy as np from mindspore import dataset as ds num_parallel_workers 2 # 多线程/进程数 world_size 1 # 并行场景使用通信group_size rank 0 # 并行场景使用通信rank_id class MyDataset: def __init__(self): self.data np.random.sample((5, 2)) self.label np.random.sample((5, 1)) def __getitem__(self, index): return self.data[index], self.label[index] def __len__(self): return len(self.data) dataset ds.GeneratorDataset(sourceMyDataset(), column_names[data, label], num_parallel_workersnum_parallel_workers, shuffleTrue, num_shards1, shard_id0) train_dataset dataset.batch(batch_size2, drop_remainderTrue, num_parallel_workersnum_parallel_workers)一个典型的数据集构造如上构造一个Python类必须有__getitem__和__len__方法分别表示每一步迭代取的数据和整个数据集遍历一次的大小。其中index表示每次取数据的索引当shuffleFalse时按顺序递增当shuffleTrue时随机打乱。GeneratorDataset至少需要包含source一个Python迭代器column_names迭代器__getitem__方法每个输出的名字。更多使用方法参考GeneratorDataset。dataset.batch将数据集中连续batch_size条数据组合为一个批数据至少需要包含batch_size指定每个批处理数据包含的数据条目。更多使用方法参考Dataset.batch。模型构建网络基本构成单元 CellMindSpore的网络搭建主要使用Cell进行图的构造用户需要定义一个类继承Cell这个基类在init里声明需要使用的API及子模块在construct里进行计算PyTorchMindSpore 心灵孢子import torch class Network(torch.nn.Module): def __init__(self, forward_net): super(Network, self).__init__() self.net forward_net def forward(self, x): y self.net(x) return torch.nn.functional.relu(y) inner_net torch.nn.Conv2d(120, 240, kernel_size4, biasFalse) net Network(inner_net) for i in net.parameters(): print(i) from mindspore import mint, nn class Network(nn.Cell): def __init__(self, forward_net): super(Network, self).__init__() self.net forward_net def construct(self, x): y self.net(x) return mint.nn.functional.relu(y) inner_net mint.nn.Conv2d(120, 240, kernel_size4, biasFalse) net Network(inner_net) for i in net.get_parameters(): print(i)MindSpore和PyTorch构建模型的方法差不多使用算子的差别可以参考API差异文档。模型保存和加载PyTorch提供了state_dict()用于参数状态的查看及保存load_state_dict用于模型参数的加载。MindSpore可以使用 save_checkpoint 与load_checkpoint 。MindSpore 可以使用 save_checkpoint 与 load_checkpoint 。PyTorchMindSpore 心灵孢子# 使用torch.save()把获取到的state_dict保存到pkl文件中 torch.save(pt_model.state_dict(), save_path) # 使用torch.load()加载保存的state_dict # 然后使用load_state_dict将获取到的state_dict加载到模型中 state_dict torch.load(save_path) pt_model.load_state_dict(state_dict) # 模型权重保存 ms.save_checkpoint(ms_model, save_path) # 使用ms.load_checkpoint()加载保存的ckpt文件 # 然后使用load_state_dict将获取到的param_dict加载到模型中 param_dict ms.load_checkpoint(save_path) ms_model.load_state_dict(param_dict)优化器PyTorch和MindSpore同时支持的优化器异同比较详见API映射表。优化器的执行和使用差异PyTorch单步执行优化器时一般需要手动执行zero_grad()方法将历史梯度设置为0然后使用loss.backward()计算当前训练step的梯度最后调用优化器的step()方法实现网络权重的更新使用MindSpore中的优化器时只需要直接对梯度进行计算然后使用optimizer(grads)执行网络权重的更新。如果在训练过程中需要动态调整学习率PyTorch提供了LRScheduler类用于学习率管理。使用动态学习率时将optimizer实例传入LRScheduler子类中通过循环调用scheduler.step()执行学习率修改并将修改同步至优化器中。MindSpore提供了Cell和list两种动态修改学习率的方法。使用时对应动态学习率对象直接传入优化器学习率的更新在优化器中自动执行具体请参考动态学习率。PyTorchMindSpore 心灵孢子optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9) scheduler ExponentialLR(optimizer, gamma0.9) optimizer.zero_grad() output model(input) loss loss_fn(output, target) loss.backward() optimizer.step() scheduler.step() import mindspore from mindspore import nn lr nn.exponential_decay_lr(0.01, decay_rate, total_step, step_per_epoch, decay_epoch) optimizer nn.SGD(model.trainable_params(), learning_ratelr, momentum0.9) grad_fn mindspore.value_and_grad(forward_fn, None, optimizer.parameters, has_auxTrue) (loss, _), grads grad_fn(data, label) # 在优化器里自动做学习率更新 optimizer(grads)自动微分MindSpore 和 PyTorch 都提供了自动微分功能让我们在定义了正向网络后可以通过简单的接口调用实现自动反向传播以及梯度更新。但需要注意的是MindSpore 和 PyTorch 构建反向图的逻辑是不同的这个差异也会带来 API 设计上的不同。PyTorch的自动微分MindSpore的自动微分# torch.autograd: # backward是累计的更新完之后需清空optimizer import torch.nn as nn import torch.optim as optim # 实例化模型和优化器 model PT_Model() optimizer optim.SGD(model.parameters(), lr0.01) # 定义损失函数均方误差MSE loss_fn nn.MSELoss() # 前向传播计算模型输出 y_pred model(x) # 计算损失将预测值与真实标签计算损失 loss loss_fn(y_pred, y_true) # 反向传播计算梯度 loss.backward() # 优化器更新 optimizer.step() # ms.grad: # 使用grad接口输入正向图输出反向图 import mindspore as ms from mindspore import nn # 实例化模型和优化器 model MS_Model() optimizer nn.SGD(model.trainable_params(), learning_rate0.01) # 定义损失函数均方误差MSE loss_fn nn.MSELoss() def forward_fn(x, y_true): # 前向传播计算模型输出 y_pred model(x) # 计算损失将预测值与真实标签计算损失 loss loss_fn(y_pred, y_true) return loss, y_pred # 计算loss和梯度 grad_fn ms.value_and_grad(forward_fn, None, optimizer.parameters, has_auxTrue) (loss, _), grads grad_fn(x, y_true) # 优化器更新 optimizer(grads)模型训练和推理下面是一个在MindSpore的Trainer的例子包含了训练和训练时推理。训练部分主要包含将数据集、模型、优化器等模块组合训练推理部分主要包含评估指标获取、保存最优模型参数等。import mindspore as ms from mindspore import nn from mindspore.amp import StaticLossScaler, all_finite from mindspore.communication import init, get_group_size class Trainer: 一个有两个loss的训练示例 def __init__(self, net, loss1, loss2, optimizer, train_dataset, loss_scale1.0, eval_datasetNone, metricNone): self.net net self.loss1 loss1 self.loss2 loss2 self.opt optimizer self.train_dataset train_dataset self.train_data_size self.train_dataset.get_dataset_size() # 获取训练集batch数 self.weights self.opt.parameters # 注意value_and_grad的第一个参数是需要做梯度求导的图一般包含网络和loss。这里可以是一个函数也可以是Cell self.value_and_grad ms.value_and_grad(self.forward_fn, None, weightsself.weights, has_auxTrue) # 分布式场景使用 self.grad_reducer self.get_grad_reducer() self.loss_scale StaticLossScaler(loss_scale) self.run_eval eval_dataset is not None if self.run_eval: self.eval_dataset eval_dataset self.metric metric self.best_acc 0 def get_grad_reducer(self): grad_reducer nn.Identity() # 判断是否是分布式场景分布式场景的设置参考上面通用运行环境设置 group_size get_group_size() reducer_flag (group_size ! 1) if reducer_flag: grad_reducer nn.DistributedGradReducer(self.weights) return grad_reducer def forward_fn(self, inputs, labels): 正向网络构建注意第一个输出必须是最后需要求梯度的那个输出 logits self.net(inputs) loss1 self.loss1(logits, labels) loss2 self.loss2(logits, labels) loss loss1 loss2 loss self.loss_scale.scale(loss) return loss, loss1, loss2 ms.jit # jit加速需要满足图模式构建的要求否则会报错 def train_single(self, inputs, labels): (loss, loss1, loss2), grads self.value_and_grad(inputs, labels) loss self.loss_scale.unscale(loss) grads self.loss_scale.unscale(grads) grads self.grad_reducer(grads) self.opt(grads) return loss, loss1, loss2 def train(self, epochs): train_dataset self.train_dataset.create_dict_iterator(num_epochsepochs) self.net.set_train(True) for epoch in range(epochs): # 训练一个epoch for batch, data in enumerate(train_dataset): loss, loss1, loss2 self.train_single(data[image], data[label]) if batch % 100 0: print(fstep: [{batch}/{self.train_data_size}] floss: {loss}, loss1: {loss1}, loss2: {loss2}, flushTrue) # 保存当前epoch的模型和优化器权重 ms.save_checkpoint(self.net, fepoch_{epoch}.ckpt) ms.save_checkpoint(self.opt, fopt_{epoch}.ckpt) # 推理并保存最好的那个checkpoint if self.run_eval: eval_dataset self.eval_dataset.create_dict_iterator(num_epochs1) self.net.set_train(False) self.eval(eval_dataset, epoch) self.net.set_train(True) def eval(self, eval_dataset, epoch): self.metric.clear() for batch, data in enumerate(eval_dataset): output self.net(data[image]) self.metric.update(output, data[label]) accuracy self.metric.eval() print(fepoch {epoch}, accuracy: {accuracy}, flushTrue) if accuracy self.best_acc: # 保存最好的那个checkpoint self.best_acc accuracy ms.save_checkpoint(self.net, best.ckpt) print(fUpdate best acc: {accuracy})