Model基本使用

Ascend GPU CPU 模型开发

在线运行下载Notebook下载样例代码查看源文件

概述

编程指南的网络构建部分讲述了如何定义前向网络、损失函数和优化器,并介绍了如何将这些结构封装成训练、评估网络并执行。在此基础上,本文档讲述如何使用高阶APIModel进行模型训练和评估。

通常情况下,定义训练和评估网络并直接运行,已经可以满足基本需求,但仍然建议通过Model来进行模型训练和评估。一方面,Model可以在一定程度上简化代码。例如:无需手动遍历数据集;在不需要自定义TrainOneStepCell的场景下,可以借助Model自动构建训练网络;可以使用Modeleval接口进行模型评估,直接输出评估结果,无需手动调用评价指标的clearupdateeval函数等。另一方面,Model提供了很多高阶功能,如数据下沉、混合精度等,在不借助Model的情况下,使用这些功能需要花费较多的时间仿照Model进行自定义。

本文档首先对Model进行基本介绍,然后重点讲解如何使用Model进行模型训练、评估和推理。

下述例子中,参数初始化使用了随机值,在具体执行中输出的结果可能与本地执行输出的结果不同;如果需要稳定输出固定的值,可以设置固定的随机种子,设置方法请参考mindspore.set_seed()

Model基本介绍

Model是MindSpore提供的模型训练高阶API,可以进行模型训练、评估和推理。

Model中包含入参:

  • network (Cell):一般情况下为前向网络,输入数据和标签,输出预测值。

  • loss_fn (Cell):所使用的损失函数。

  • optimizer (Cell):所使用的优化器。

  • metrics (set):进行模型评估时使用的评价指标,在不需要模型评估时使用默认值None

  • eval_network (Cell):模型评估所使用的网络,在部分简单场景下不需要指定。

  • eval_indexes (List):用于指示评估网络输出的含义,配合eval_network使用,该参数的功能可通过nn.Metricset_indexes代替,建议使用set_indexes

  • amp_level (str):用于指定混合精度级别。设置为“O0”或“O3”级别时,默认不使用LossScale,设置为“O2”级别时,默认使用动态LossScale策略。通过kwargs可修改LossScale策略。

  • boost_level (str):用于指定Boost级别。

  • kwargs:用于指定混合精度、LossScale、Boost相关策略。

Model提供了以下接口用于模型训练、评估和推理:

  • train:用于在训练集上进行模型训练。

  • eval:用于在验证集上进行模型评估。

  • predict:用于对输入的一组数据进行推理,输出预测结果。

相关特性如下:

模型训练、评估和推理

对于简单场景的神经网络,可以在定义Model时指定前向网络network、损失函数loss_fn、优化器optimizer和评估指标metrics。此时,Model会使用network作为推理网络,并使用nn.WithLossCellnn.TrainOneStepCell构建训练网络,使用nn.WithEvalCell构建评估网络。

构建训练与评估网络中使用的线性回归为例:

[1]:
import mindspore.nn as nn
from mindspore.common.initializer import Normal

class LinearNet(nn.Cell):
    def __init__(self):
        super().__init__()
        self.fc = nn.Dense(1, 1, Normal(0.02), Normal(0.02))

    def construct(self, x):
        return self.fc(x)

net = LinearNet()
# 设定损失函数
crit = nn.MSELoss()
# 设定优化器
opt = nn.Momentum(net.trainable_params(), learning_rate=0.005, momentum=0.9)
# 设定评价指标
metrics = {"mae"}

构建训练与评估网络中讲述了通过nn.WithLossCellnn.TrainOneStepCellnn.WithEvalCell构建训练和评估网络并直接运行方式。使用Model时则不需要手动构建训练和评估网络,用以下方式定义Model并调用traineval接口能够达到相同的效果。

创建训练集和验证集:

[2]:
import numpy as np
import mindspore.dataset as ds

def get_data(num, w=2.0, b=3.0):
    for _ in range(num):
        x = np.random.uniform(-10.0, 10.0)
        noise = np.random.normal(0, 1)
        y = x * w + b + noise
        yield np.array([x]).astype(np.float32), np.array([y]).astype(np.float32)

def create_dataset(num_data, batch_size=16):
    dataset = ds.GeneratorDataset(list(get_data(num_data)), column_names=['data', 'label'])
    dataset = dataset.batch(batch_size)
    return dataset

# 创建数据集
train_dataset = create_dataset(num_data=160)
eval_dataset = create_dataset(num_data=80)

定义Model并进行模型训练,通过LossMonitor回调函数查看在训练过程中的损失函数值:

[3]:
from mindspore import Model
from mindspore.train.callback import LossMonitor

model = Model(network=net, loss_fn=crit, optimizer=opt, metrics=metrics)
# 获取训练过程数据
epochs = 2
model.train(epochs, train_dataset, callbacks=[LossMonitor()], dataset_sink_mode=False)
epoch: 1 step: 1, loss is 145.92702
epoch: 1 step: 2, loss is 64.700424
epoch: 1 step: 3, loss is 6.557139
epoch: 1 step: 4, loss is 28.182562
epoch: 1 step: 5, loss is 47.01909
epoch: 1 step: 6, loss is 84.31042
epoch: 1 step: 7, loss is 90.51094
epoch: 1 step: 8, loss is 8.339919
epoch: 1 step: 9, loss is 6.141535
epoch: 1 step: 10, loss is 43.75537
epoch: 2 step: 1, loss is 35.802357
epoch: 2 step: 2, loss is 44.16835
epoch: 2 step: 3, loss is 23.275116
epoch: 2 step: 4, loss is 2.7919912
epoch: 2 step: 5, loss is 10.78731
epoch: 2 step: 6, loss is 44.453777
epoch: 2 step: 7, loss is 32.597885
epoch: 2 step: 8, loss is 16.546305
epoch: 2 step: 9, loss is 0.89161384
epoch: 2 step: 10, loss is 6.7151947

执行模型评估,获取评估结果:

[4]:
eval_result = model.eval(eval_dataset)
print(eval_result)
{'mae': 4.40472822189331}

使用predict进行推理:

[5]:
for d in eval_dataset.create_dict_iterator():
    data = d["data"]
    break

output = model.predict(data)
print(output)
[[ 7.277156  ]
 [-4.7413435 ]
 [10.358773  ]
 [ 5.426777  ]
 [-4.0304213 ]
 [ 0.8506899 ]
 [-2.249575  ]
 [-8.091422  ]
 [-2.5145457 ]
 [-6.993862  ]
 [ 5.4423256 ]
 [ 4.464966  ]
 [ 5.0804405 ]
 [ 0.04378986]
 [ 2.9789488 ]
 [-0.4881246 ]]

一般情况下需要对推理结果进行后处理才能得到比较直观的推理结果。

与构建网络后直接运行不同,使用Model进行模型训练、推理和评估时,不需要set_train配置网络结构的执行模式。

自定义场景的Model应用

损失函数构建训练与评估网络中已经提到过,MindSpore提供的网络封装函数nn.WithLossCellnn.TrainOneStepCellnn.WithEvalCell并不适用于所有场景,实际场景中常常需要自定义网络的封装方式。这种情况下Model使用这些封装函数自动地进行封装显然是不合理的。接下来介绍这些场景下如何正确地使用Model

手动连接前向网络与损失函数

在有多个数据或者多个标签的场景下,可以手动将前向网络和自定义的损失函数链接起来作为Modelnetworkloss_fn使用默认值None,此时Model内部便会直接使用nn.TrainOneStepCellnetworkoptimizer组成训练网络,而不会经过nn.WithLossCell

这里使用损失函数文档中的例子:

  1. 定义多标签数据集

[6]:
import numpy as np
import mindspore.dataset as ds

def get_multilabel_data(num, w=2.0, b=3.0):
    for _ in range(num):
        x = np.random.uniform(-10.0, 10.0)
        noise1 = np.random.normal(0, 1)
        noise2 = np.random.normal(-1, 1)
        y1 = x * w + b + noise1
        y2 = x * w + b + noise2
        yield np.array([x]).astype(np.float32), np.array([y1]).astype(np.float32), np.array([y2]).astype(np.float32)

def create_multilabel_dataset(num_data, batch_size=16):
    dataset = ds.GeneratorDataset(list(get_multilabel_data(num_data)), column_names=['data', 'label1', 'label2'])
    dataset = dataset.batch(batch_size)
    return dataset
  1. 自定义多标签损失函数

[7]:
import mindspore.ops as ops
from mindspore.nn import LossBase

class L1LossForMultiLabel(LossBase):
    def __init__(self, reduction="mean"):
        super(L1LossForMultiLabel, self).__init__(reduction)
        self.abs = ops.Abs()

    def construct(self, base, target1, target2):
        x1 = self.abs(base - target1)
        x2 = self.abs(base - target2)
        return self.get_loss(x1)/2 + self.get_loss(x2)/2
  1. 连接前向网络和损失函数,net使用上一节定义的LinearNet

[8]:
import mindspore.nn as nn

class CustomWithLossCell(nn.Cell):
    def __init__(self, backbone, loss_fn):
        super(CustomWithLossCell, self).__init__(auto_prefix=False)
        self._backbone = backbone
        self._loss_fn = loss_fn

    def construct(self, data, label1, label2):
        output = self._backbone(data)
        return self._loss_fn(output, label1, label2)
net = LinearNet()
loss = L1LossForMultiLabel()
loss_net = CustomWithLossCell(net, loss)
  1. 定义Model并进行模型训练

[9]:
from mindspore.train.callback import LossMonitor
from mindspore import Model

opt = nn.Momentum(net.trainable_params(), learning_rate=0.005, momentum=0.9)
model = Model(network=loss_net, optimizer=opt)
multi_train_dataset = create_multilabel_dataset(num_data=160)
model.train(epoch=1, train_dataset=multi_train_dataset, callbacks=[LossMonitor()], dataset_sink_mode=False)
epoch: 1 step: 1, loss is 11.781048
epoch: 1 step: 2, loss is 10.031279
epoch: 1 step: 3, loss is 10.033403
epoch: 1 step: 4, loss is 9.735289
epoch: 1 step: 5, loss is 9.50007
epoch: 1 step: 6, loss is 9.082216
epoch: 1 step: 7, loss is 7.857524
epoch: 1 step: 8, loss is 9.13205
epoch: 1 step: 9, loss is 6.9698105
epoch: 1 step: 10, loss is 3.9609227
  1. 模型评估

    Model默认使用nn.WithEvalCell构建评估网络,在不满足需求的情况下同样需要手动构建评估网络,多数据和多标签便是一个典型的场景。Model提供了eval_network用于设置自定义的评估网络。手动构建评估网络的方式如下:

    自定义评估网络的封装方式:

[10]:
import mindspore.nn as nn

class CustomWithEvalCell(nn.Cell):
    def __init__(self, network):
        super(CustomWithEvalCell, self).__init__(auto_prefix=False)
        self.network = network

    def construct(self, data, label1, label2):
        output = self.network(data)
        return output, label1, label2

手动构建评估网络:

[11]:
eval_net = CustomWithEvalCell(net)

使用Model进行模型评估:

[12]:
from mindspore.train.callback import LossMonitor
from mindspore import Model

mae1 = nn.MAE()
mae2 = nn.MAE()
mae1.set_indexes([0, 1])
mae2.set_indexes([0, 2])

model = Model(network=loss_net, optimizer=opt, eval_network=eval_net, metrics={"mae1": mae1, "mae2": mae2})
multi_eval_dataset = create_multilabel_dataset(num_data=80)
result = model.eval(multi_eval_dataset, dataset_sink_mode=False)
print(result)
{'mae1': 5.680728149414063, 'mae2': 5.3038993835449215}
  • 在进行模型评估时,评估网络的输出会透传给评估指标的update函数,也就是说,update函数将接收到三个输入,分别为logitslabel1label2nn.MAE仅允许在两个输入上计算评价指标,因此使用set_indexes指定mae1使用下标为0和1的输入,也就是logitslabel1,计算评估结果;指定mae2使用下标为0和2的输入,也就是logitslabel2,计算评估结果。

  • 在实际场景中,往往需要所有标签同时参与评估,这时候就需要自定义Metric,灵活使用评估网络的所有输出计算评估结果。Metric自定义方法详见:https://www.mindspore.cn/docs/programming_guide/zh-CN/r1.6/self_define_metric.html

  1. 推理

    Model没有提供用于指定自定义推理网络的参数,此时可以直接运行前向网络获得推理结果。

[13]:
for d in multi_eval_dataset.create_dict_iterator():
    data = d["data"]
    break

output = net(data)
print(output)
[[-2.8008308 ]
 [ 5.6060047 ]
 [-2.913618  ]
 [-6.4244847 ]
 [ 5.680258  ]
 [-6.449928  ]
 [ 5.8908176 ]
 [-3.6554213 ]
 [-4.1632366 ]
 [ 8.986961  ]
 [-8.218882  ]
 [ 7.7593    ]
 [-0.14718263]
 [ 7.31277   ]
 [ 1.1601944 ]
 [-9.319818  ]]

自定义训练网络

在自定义TrainOneStepCell时,需要手动构建训练网络作为Modelnetworkloss_fnoptimizer均使用默认值None,此时Model会使用network作为训练网络,而不会进行任何封装。

自定义TrainOneStepCell的场景可参考构建训练与评估网络,这里列举一个简单的例子,其中loss_netopt为上一节定义的CustomWithLossCellMomentum

[14]:
from mindspore.nn import TrainOneStepCell as CustomTrainOneStepCell
from mindspore import Model
from mindspore.train.callback import LossMonitor

# 手动构建训练网络
train_net = CustomTrainOneStepCell(loss_net, opt)
# 定义`Model`并执行训练
model = Model(train_net)
multi_train_ds = create_multilabel_dataset(num_data=160)
model.train(epoch=1, train_dataset=multi_train_ds, callbacks=[LossMonitor()], dataset_sink_mode=False)
epoch: 1 step: 1, loss is 5.2428284
epoch: 1 step: 2, loss is 4.616859
epoch: 1 step: 3, loss is 4.36201
epoch: 1 step: 4, loss is 3.67413
epoch: 1 step: 5, loss is 2.428535
epoch: 1 step: 6, loss is 2.7347116
epoch: 1 step: 7, loss is 2.4106321
epoch: 1 step: 8, loss is 2.0769718
epoch: 1 step: 9, loss is 2.5951004
epoch: 1 step: 10, loss is 3.4012628

此时train_net即为训练网络。自定义训练网络时,同样需要自定义评估网络,进行模型评估和推理的方式与上一节手动连接前向网络与损失函数相同。

当自定义训练网络的标签和预测值均为单一值时,评价函数不需要特殊处理(自定义或使用set_indexes),其他场景仍然需要注意评价指标的正确使用方式。

自定义网络的权重共享

构建训练与评估网络中已经介绍过权重共享的机制,使用MindSpore构建不同网络结构时,只要这些网络结构是在同一个实例的基础上封装的,那这个实例中的所有权重便是共享的,一个网络结构中的权重发生变化,意味着其他网络结构中的权重同步发生了变化。

在使用Model进行训练时,对于简单的场景,Model内部使用nn.WithLossCellnn.TrainOneStepCellnn.WithEvalCell在前向network实例的基础上构建训练和评估网络,Model本身确保了推理、训练、评估网络之间权重共享。但对于自定义使用Model的场景,用户需要注意前向网络仅实例化一次。如果构建训练网络和评估网络时分别实例化前向网络,那在使用eval进行模型评估时,便需要手动加载训练网络中的权重,否则模型评估使用的将是初始的权重值。