自定义损失函数

image0image1image2

损失函数用于衡量预测值与真实值差异的程度。深度学习中,模型训练就是通过不停地迭代来缩小损失函数值的过程。因此在模型训练过程中损失函数的选择非常重要,定义一个好的损失函数,可以有效提高模型的性能。

MindSpore提供了许多通用损失函数供用户选择,但这些通用损失函数并不适用于所有场景,有时需要用户自定义所需的损失函数。因此,本文介绍损失函数的自定义构建方法。

自定义损失函数示例

Cell是MindSpore的基本网络单元,可以用于构建网络,损失函数也可以通过Cell来定义。使用Cell定义损失函数的方法与定义一个普通的网络相同,差别在于,其执行逻辑在于计算前向网络输出与真实值之间的误差。

下面使用Cell定义损失函数L1Loss,用于计算两个输入数据的绝对值误差:

[1]:
import mindspore.nn as nn
import mindspore.ops as ops
from mindspore.nn.loss.loss import _Loss

class L1Loss(nn.Cell):
    def __init__(self):
        super(L1Loss, self).__init__()
        self.abs = ops.Abs()
        self.reduce_mean = ops.ReduceMean()

    def construct(self, predict, target):
        x = self.abs(predict - target)
        return self.reduce_mean(x)

首先,__init__方法实例化所需的算子,并在construct中调用这些算子。这样一个用于计算L1Loss的损失函数就定义好了。

代码中使用nn.Cell作为L1Loss的基类,最后在construct中调用基类提供的predict, target方法。reduction的合法参数有meansumnone,分别表示求均值、求和与输出原值。

在定义损失函数时还可以继承损失函数的基类_Loss_Loss提供了get_loss方法,用于对损失值求和或求均值,输出一个标量。L1Loss使用_Loss作为基类的定义如下:

[2]:
class L1Loss(_Loss):
    def __init__(self, reduction="mean"):
        super(L1Loss, self).__init__(reduction)
        self.abs = ops.Abs()

    def construct(self, base, target):
        x = self.abs(base - target)
        return self.get_loss(x)

首先使用_Loss作为L1Loss的基类,然后给__init__增加一个参数reduction,并通过super传给基类,最后在construct中调用基类提供的get_loss方法。reduction的合法参数有meansumnone,分别表示求均值、求和与输出原值。

损失函数与模型训练

接下来使用通过基类LossBase定义的L1Loss进行模型训练。以简单的线性拟场景作为样例。

线性拟合详细介绍可参考教程实现简单线性函数拟合

定义数据集与网络

定义训练数据集生成函数,并增强为MindSpore可训练的数据类型。

  • get_data:数据生成函数。

  • create_dataset:将numpy数据转换为MindSpore可训练的函数,并构造数据集的batch增强方法。

[3]:
import numpy as np
from mindspore import 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

定义网络,nn.Dense将数据集定义为所有的函数。

[4]:
from mindspore.common.initializer import Normal

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

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

使用Model进行模型训练

Model是MindSpore提供的用于模型训练、评估和推理的高阶API。创建数据集并定义一个Model就可以使用train接口进行模型训练。接下来我们使用Model来训练LinearNet,选择MindSpore提供的Momemtum作为优化器,并采用之前定义好的L1Loss作为此次训练的损失函数。

[5]:
net = LinearNet()
loss = L1Loss()
opt = nn.Momentum(net.trainable_params(), learning_rate=0.005, momentum=0.9)

定义Model时需要指定前向网络、损失函数和优化器,Model内部会将它们关联起来,组成一张训练网。

[6]:
from mindspore import Model

model = Model(net, loss, opt)

创建数据集,并调用train接口进行模型训练。

参数解释:

  • epoch:训练数据集的迭代次数。

  • train_dataset:训练数据集。

  • callbacksmodel.train的回调函数参数,可监控训练过程中参数的变化情况。

  • LossMonitor:损失函数值监视器,用于打印训练过程中的模型损失值。

  • dataset_sink_mode(bool):数据下沉模式,可加快训练。Ascend和GPU平台支持开启该功能(True)。

[7]:
from mindspore.train.callback import LossMonitor

ds_train = create_dataset(num_data=160)
model.train(epoch=1, train_dataset=ds_train, callbacks=[LossMonitor()], dataset_sink_mode=False)
epoch: 1 step: 1, loss is 7.9989004
epoch: 1 step: 2, loss is 10.780429
epoch: 1 step: 3, loss is 12.113883
epoch: 1 step: 4, loss is 8.991383
epoch: 1 step: 5, loss is 9.228854
epoch: 1 step: 6, loss is 7.8051577
epoch: 1 step: 7, loss is 9.328038
epoch: 1 step: 8, loss is 7.665909
epoch: 1 step: 9, loss is 4.955902
epoch: 1 step: 10, loss is 7.7147307