Model基本使用

下载Notebook下载样例代码查看源文件

通常情况下,定义训练和评估网络并直接运行,已经可以满足基本需求。

一方面,Model可以在一定程度上简化代码。例如:无需手动遍历数据集;在不需要自定义nn.TrainOneStepCell的场景下,可以借助Model自动构建训练网络;可以使用Modeleval接口进行模型评估,直接输出评估结果,无需手动调用评价指标的clearupdateeval函数等。

另一方面,Model提供了很多高阶功能,如数据下沉、混合精度等,在不借助Model的情况下,使用这些功能需要花费较多的时间仿照Model进行自定义。

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

model

Model基本介绍

Model是MindSpore提供的高阶API,可以进行模型训练、评估和推理。其接口的常用参数如下:

  • network:用于训练或推理的神经网络。

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

  • optimizer:所使用的优化器。

  • metrics:用于模型评估的评价函数。

  • eval_network:模型评估所使用的网络,未定义情况下,Model会使用networkloss_fn进行封装。

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

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

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

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

使用Model接口

对于简单场景的神经网络,可以在定义Model时指定前向网络network、损失函数loss_fn、优化器optimizer和评价函数metrics

此时,Model会使用network作为前向网络,并使用nn.WithLossCellnn.TrainOneStepCell构建训练网络,使用nn.WithEvalCell构建评估网络。

[1]:
import numpy as np
import mindspore.dataset as ds
import mindspore.nn as nn
import mindspore as ms
from mindspore.common.initializer import Normal

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

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)

train_dataset = create_dataset(num_data=160)
net = LinearNet()
crit = nn.MSELoss()
opt = nn.Momentum(net.trainable_params(), learning_rate=0.005, momentum=0.9)

# 使用Model构建训练网络
model = ms.Model(network=net, loss_fn=crit, optimizer=opt, metrics={"mae"})

模型训练

使用train接口执行模型训练,train接口的常用参数如下:

  • epoch:训练执行轮次,通常每个epoch都会使用全量数据集进行训练。

  • train_dataset:一个训练数据集迭代器。

  • callbacks:训练过程中需要执行的回调对象或者回调对象列表。

有意思的是,如果网络模型定义了loss_fn,则数据和标签会被分别传给networkloss_fn,此时数据集需要返回一个元组(data, label)。如果数据集中有多个数据或者标签,可以设置loss_fn为None,并在network中实现自定义损失函数,此时数据集返回的所有数据组成的元组(data1, data2, data3, …)会传给network

如下示例使用train接口执行模型训练,通过LossMonitor回调函数查看在训练过程中的损失函数值。

[2]:
from mindvision.engine.callback import LossMonitor

# 模型训练,LossMonitor的入参0.005为学习率
model.train(1, train_dataset, callbacks=[LossMonitor(0.005)])
Epoch:[  0/  1], step:[    1/   10], loss:[115.354/115.354], time:242.467 ms, lr:0.00500
Epoch:[  0/  1], step:[    2/   10], loss:[86.149/100.751], time:0.650 ms, lr:0.00500
Epoch:[  0/  1], step:[    3/   10], loss:[17.299/72.934], time:0.712 ms, lr:0.00500
Epoch:[  0/  1], step:[    4/   10], loss:[21.070/59.968], time:0.744 ms, lr:0.00500
Epoch:[  0/  1], step:[    5/   10], loss:[42.781/56.530], time:0.645 ms, lr:0.00500
Epoch:[  0/  1], step:[    6/   10], loss:[52.374/55.838], time:0.577 ms, lr:0.00500
Epoch:[  0/  1], step:[    7/   10], loss:[53.629/55.522], time:0.588 ms, lr:0.00500
Epoch:[  0/  1], step:[    8/   10], loss:[16.356/50.626], time:0.624 ms, lr:0.00500
Epoch:[  0/  1], step:[    9/   10], loss:[5.504/45.613], time:0.730 ms, lr:0.00500
Epoch:[  0/  1], step:[   10/   10], loss:[5.396/41.591], time:0.766 ms, lr:0.00500
Epoch time: 259.696 ms, per step time: 25.970 ms, avg loss: 41.591

模型评估

使用eval接口进行评估,eval接口参数如下:

  • valid_dataset:评估模型的数据集。

  • callbacks:评估过程中需要执行的回调对象或回调对象列表。

  • dataset_sink_mode:数据是否直接下沉至处理器进行处理。

[3]:
eval_dataset = create_dataset(num_data=80)  # 创建评估数据集
eval_result = model.eval(eval_dataset)      # 执行模型评估
print(eval_result)
{'mae': 4.2325128555297855}

模型推理

使用predict接口进行推理,predict接口参数如下:

  • predict_data:预测样本,数据可以是单个张量、张量列表或张量元组。

[4]:
eval_data = eval_dataset.create_dict_iterator()
data = next(eval_data)
# 执行模型预测
output = model.predict(data["data"])
print(output)
[[-6.9463778 ]
 [ 1.3816066 ]
 [13.233659  ]
 [11.863918  ]
 [ 0.73616135]
 [-0.1280173 ]
 [ 7.579297  ]
 [-4.9149694 ]
 [ 7.416003  ]
 [10.491856  ]
 [-5.7275047 ]
 [ 9.984399  ]
 [-7.156473  ]
 [ 2.7091386 ]
 [-6.3339615 ]
 [-6.0259247 ]]

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

自定义场景

MindSpore提供的网络封装函数nn.WithLossCellnn.TrainOneStepCellnn.WithEvalCell并不适用于所有场景,实际场景中常常需要自定义网络的封装函数,这种情况下Model使用这些封装函数自动地进行封装显然是不合理的。

接下来介绍在自定义网络封装函数时如何正确地使用Model

自定义损失网络

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

[5]:
import numpy as np
import mindspore.dataset as ds
import mindspore.ops as ops
import mindspore.nn as nn
import mindspore as ms
from mindspore.nn import LossBase
from mindvision.engine.callback import LossMonitor

def get_multilabel_data(num, w=2.0, b=3.0):
    """生成多标签数据,产生一组数据x对应两个标签y1和y2"""
    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):
    """生成多标签数据集,一个数据data对应两个标签label1和label2"""
    dataset = ds.GeneratorDataset(list(get_multilabel_data(num_data)), column_names=['data', 'label1', 'label2'])
    dataset = dataset.batch(batch_size)
    return dataset

class L1LossForMultiLabel(LossBase):
    """自定义多标签损失函数"""

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

    def construct(self, base, target1, target2):
        """输入有三个,分别为预测值base,真实值target1和target2"""
        x1 = self.abs(base - target1)
        x2 = self.abs(base - target2)
        return self.get_loss(x1) / 2 + self.get_loss(x2) / 2

class CustomWithLossCell(nn.Cell):
    """连接前向网络和损失函数"""

    def __init__(self, backbone, loss_fn):
        """输入有两个,前向网络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)  # 得到多标签损失值

multi_train_dataset = create_multilabel_dataset(num_data=160)

# 构建线性回归网络
net = LinearNet()
# 多标签损失函数
loss = L1LossForMultiLabel()

# 连接线性回归网络和多标签损失函数
loss_net = CustomWithLossCell(net, loss)
opt = nn.Momentum(net.trainable_params(), learning_rate=0.005, momentum=0.9)

# 使用Model连接网络和优化器,此时Model内部不经过nn.WithLossCell
model = ms.Model(network=loss_net, optimizer=opt)
# 使用train接口进行模型训练
model.train(epoch=1, train_dataset=multi_train_dataset, callbacks=[LossMonitor(0.005)])
Epoch:[  0/  1], step:[    1/   10], loss:[11.036/11.036], time:212.864 ms, lr:0.00500
Epoch:[  0/  1], step:[    2/   10], loss:[9.984/10.510], time:0.592 ms, lr:0.00500
Epoch:[  0/  1], step:[    3/   10], loss:[9.300/10.107], time:0.660 ms, lr:0.00500
Epoch:[  0/  1], step:[    4/   10], loss:[7.526/9.462], time:0.787 ms, lr:0.00500
Epoch:[  0/  1], step:[    5/   10], loss:[6.959/8.961], time:0.715 ms, lr:0.00500
Epoch:[  0/  1], step:[    6/   10], loss:[10.290/9.183], time:0.716 ms, lr:0.00500
Epoch:[  0/  1], step:[    7/   10], loss:[10.067/9.309], time:0.770 ms, lr:0.00500
Epoch:[  0/  1], step:[    8/   10], loss:[8.924/9.261], time:0.909 ms, lr:0.00500
Epoch:[  0/  1], step:[    9/   10], loss:[7.257/9.038], time:0.884 ms, lr:0.00500
Epoch:[  0/  1], step:[   10/   10], loss:[6.138/8.748], time:0.955 ms, lr:0.00500
Epoch time: 232.046 ms, per step time: 23.205 ms, avg loss: 8.748

自定义训练网络

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

如下示例自定义训练网络CustomTrainOneStepCell,然后通过Model接口构建训练网络。

[6]:
import mindspore.ops as ops
import mindspore as ms
from mindvision.engine.callback import LossMonitor

class CustomTrainOneStepCell(nn.Cell):
    """自定义训练网络"""

    def __init__(self, network, optimizer, sens=1.0):
        """入参有三个:训练网络,优化器和反向传播缩放比例"""
        super(CustomTrainOneStepCell, self).__init__(auto_prefix=False)
        self.network = network                    # 定义前向网络
        self.network.set_grad()                   # 构建反向网络
        self.optimizer = optimizer                # 定义优化器
        self.weights = self.optimizer.parameters  # 待更新参数
        self.grad = ops.GradOperation(get_by_list=True, sens_param=True)  # 反向传播获取梯度

    def construct(self, *inputs):
        loss = self.network(*inputs)                    # 执行前向网络,计算当前输入的损失函数值
        grads = self.grad(self.network, self.weights)(*inputs, loss)  # 进行反向传播,计算梯度
        loss = ops.depend(loss, self.optimizer(grads))  # 使用优化器更新梯度
        return loss

multi_train_ds = create_multilabel_dataset(num_data=160)

# 手动构建训练网络
train_net = CustomTrainOneStepCell(loss_net, opt)
# 构建训练网络
model = ms.Model(train_net)
# 执行模型训练
model.train(epoch=1, train_dataset=multi_train_ds, callbacks=[LossMonitor(0.01)])
Epoch:[  0/  1], step:[    1/   10], loss:[5.165/5.165], time:183.006 ms, lr:0.01000
Epoch:[  0/  1], step:[    2/   10], loss:[4.042/4.603], time:0.800 ms, lr:0.01000
Epoch:[  0/  1], step:[    3/   10], loss:[3.385/4.197], time:0.886 ms, lr:0.01000
Epoch:[  0/  1], step:[    4/   10], loss:[2.438/3.758], time:0.896 ms, lr:0.01000
Epoch:[  0/  1], step:[    5/   10], loss:[2.457/3.498], time:0.819 ms, lr:0.01000
Epoch:[  0/  1], step:[    6/   10], loss:[2.546/3.339], time:0.921 ms, lr:0.01000
Epoch:[  0/  1], step:[    7/   10], loss:[4.569/3.515], time:0.973 ms, lr:0.01000
Epoch:[  0/  1], step:[    8/   10], loss:[4.031/3.579], time:1.271 ms, lr:0.01000
Epoch:[  0/  1], step:[    9/   10], loss:[6.138/3.864], time:1.035 ms, lr:0.01000
Epoch:[  0/  1], step:[   10/   10], loss:[3.055/3.783], time:1.263 ms, lr:0.01000
Epoch time: 203.473 ms, per step time: 20.347 ms, avg loss: 3.783

自定义评估网络

Model默认使用nn.WithEvalCell构建评估网络,在不满足需求的情况下需要手动构建评估网络,如多数据和多标签场景下。

如下示例自定义评估网络CustomWithEvalCell,然后使用Model接口构建评估网络。

[7]:
import mindspore.nn as nn
import mindspore as ms


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

# 构建多标签评估数据集
multi_eval_dataset = create_multilabel_dataset(num_data=80)

# 构建评估网络
eval_net = CustomWithEvalCell(net)

# 评估函数
mae1 = nn.MAE()
mae2 = nn.MAE()
mae1.set_indexes([0, 1])
mae2.set_indexes([0, 2])

# 使用Model构建评估网络
model = ms.Model(network=loss_net, optimizer=opt, eval_network=eval_net,
                 metrics={"mae1": mae1, "mae2": mae2})
result = model.eval(multi_eval_dataset)
print(result)
{'mae1': 2.5686439752578734, 'mae2': 2.4921266555786135}

上述代码在进行模型评估时,评估网络的输出会透传给评估指标的update函数,update函数将接收到三个输入,分别为logitslabel1label2

nn.MAE仅允许在两个输入上计算评价指标,因此使用set_indexes指定mae1使用下标为0和1的输入,也就是logitslabel1,计算评估结果;指定mae2使用下标为0和2的输入,也就是logitslabel2,计算评估结果。

网络推理

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

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

output = net(data)
print(output)
[[-21.598358 ]
 [ -1.0123782]
 [ 10.457726 ]
 [ 12.409237 ]
 [ 19.666183 ]
 [ -5.846529 ]
 [  9.387393 ]
 [  2.6558673]
 [-15.15129  ]
 [-14.876989 ]
 [ 19.112661 ]
 [ 22.647848 ]
 [  4.9035554]
 [ 20.119627 ]
 [ -8.339532 ]
 [ -2.7513359]]