损失函数

Ascend GPU CPU 模型开发

查看源文件

概述

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

MindSpore提供了许多通用损失函数供用户选择,但这些通用损失函数并不适用于所有场景,很多情况需要用户自定义所需的损失函数。因此,本教程介绍损失函数的写作方法。

目前MindSpore主要支持的损失函数有L1LossMSELossSmoothL1LossSoftmaxCrossEntropyWithLogitsSampledSoftmaxLossBCELossCosineEmbeddingLoss

MindSpore的损失函数全部是Cell的子类实现,所以也支持用户自定义损失函数,其构造方法在定义损失函数中进行介绍。

内置损失函数

  • L1Loss

    计算两个输入数据的绝对值误差,用于回归模型。reduction参数默认值为mean,返回loss平均值结果,若reduction值为sum,返回loss累加结果,若reduction值为none,返回每个loss的结果。

  • MSELoss

    计算两个输入数据的平方误差,用于回归模型。reduction参数同L1Loss

  • SmoothL1Loss

    SmoothL1Loss为平滑L1损失函数,用于回归模型,阈值beta默认参数为1。

  • SoftmaxCrossEntropyWithLogits

    交叉熵损失函数,用于分类模型。当标签数据不是one-hot编码形式时,需要输入参数sparse为True。reduction参数默认值为none,其参数含义同L1Loss

  • CosineEmbeddingLoss

    CosineEmbeddingLoss用于衡量两个输入相似程度,用于分类模型。margin默认为0.0,reduction参数同L1Loss

  • BCELoss

    二值交叉熵损失,用于二分类。weight是一个batch中每个训练数据的损失的权重,默认值为None,表示权重均为1。reduction参数默认值为none,其参数含义同L1Loss

  • SampledSoftmaxLoss

    抽样交叉熵损失函数,用于分类模型,一般在类别数很大时使用。num_sampled是抽样的类别数,num_classes是类别总数,num_true是每个用例的类别数,sampled_values是默认值为None的抽样候选值。remove_accidental_hits是移除“误中抽样”的开关, seed是默认值为0的抽样的随机种子,reduction参数默认值为none,其参数含义同L1Loss。

内置损失函数应用实例

MindSpore的损失函数全部在mindspore.nn下,使用方法如下所示:

import numpy as np
import mindspore.nn as nn
from mindspore import Tensor

loss = nn.L1Loss()
input_data = Tensor(np.array([[1, 2, 3], [2, 3, 4]]).astype(np.float32))
target_data = Tensor(np.array([[0, 2, 5], [3, 1, 1]]).astype(np.float32))
print(loss(input_data, target_data))
1.5

此用例构造了两个Tensor数据,利用nn.L1Loss接口定义了loss,将input_datatarget_data传入loss,执行L1Loss的计算,结果为1.5。若loss = nn.L1Loss(reduction=’sum’),则结果为9.0。若loss = nn.L1Loss(reduction=’none’),结果为[[1. 0. 2.] [1. 2. 3.]]

定义损失函数

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

以MindSpore提供的损失函数L1Loss为例,损失函数的定义方法如下:

import mindspore.nn as nn
import mindspore.ops as ops

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

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

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

给定一组预测值和真实值,调用损失函数,就可以得到这组预测值和真实值之间的差异,如下所示:

import numpy as np
from mindspore import Tensor

loss = L1Loss()
input_data = Tensor(np.array([0.1, 0.2, 0.3]).astype(np.float32))
target_data = Tensor(np.array([0.1, 0.2, 0.2]).astype(np.float32))

output = loss(input_data, target_data)
print(output)

Ascend后端为例,输出结果如下:

0.03333334

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

import mindspore.ops as ops
from mindspore.nn import LossBase

class L1Loss(LossBase):
    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,分别表示求均值、求和与输出原值。

损失函数与模型训练

接下来使用定义好的L1Loss进行模型训练。

定义数据集和网络

这里使用简单的线性拟场景作为样例,数据集和网络结构定义如下:

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

  1. 定义数据集

    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
    
  2. 定义网络

    from mindspore.common.initializer import Normal
    import mindspore.nn as nn
    
    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进行模型训练,并采用之前定义好的L1Loss作为此次训练的损失函数。

  1. 定义前向网络、损失函数和优化器

    使用之前定义的LinearNetL1Loss作为前向网络和损失函数,并选择MindSpore提供的Momemtum作为优化器。

    # define network
    net = LinearNet()
    # define loss function
    loss = L1Loss()
    # define optimizer
    opt = nn.Momentum(net.trainable_params(), learning_rate=0.005, momentum=0.9)
    
  2. 定义Model

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

    from mindspore import Model
    
    # define Model
    model = Model(net, loss, opt)
    
  3. 创建数据集,并调用train接口进行模型训练

    调用train接口时必须指定迭代次数epoch和训练数据集train_dataset,我们将epoch设置为1,将create_dataset创建的数据集作为训练集。callbackstrain接口的可选参数,在callbacks中使用LossMonitor可以监控训练过程中损失函数值的变化。dataset_sink_mode也是一个可选参数,这里设置为False,表示使用非下沉模式进行训练。

    from mindspore.train.callback import LossMonitor
    
    # create dataset
    ds_train = create_dataset(num_data=160)
    # training
    model.train(epoch=1, train_dataset=ds_train, callbacks=[LossMonitor()], dataset_sink_mode=False)
    

完整代码如下:

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

import numpy as np

import mindspore.nn as nn
import mindspore.ops as ops
from mindspore import Model
from mindspore import dataset as ds
from mindspore.nn import LossBase
from mindspore.common.initializer import Normal
from mindspore.train.callback import LossMonitor

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)

class L1Loss(LossBase):
    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)

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

# define network
net = LinearNet()
# define loss functhon
loss = L1Loss()
# define optimizer
opt = nn.Momentum(net.trainable_params(), learning_rate=0.005, momentum=0.9)
# define Model
model = Model(net, loss, opt)
# create dataset
ds_train = create_dataset(num_data=160)
# training
model.train(epoch=1, train_dataset=ds_train, callbacks=[LossMonitor()], dataset_sink_mode=False)

执行结果如下:

epoch: 1 step: 1, loss is 8.328788
epoch: 1 step: 2, loss is 8.594973
epoch: 1 step: 3, loss is 13.299595
epoch: 1 step: 4, loss is 9.04059
epoch: 1 step: 5, loss is 8.991402
epoch: 1 step: 6, loss is 6.5928526
epoch: 1 step: 7, loss is 8.239887
epoch: 1 step: 8, loss is 7.3984795
epoch: 1 step: 9, loss is 7.33724
epoch: 1 step: 10, loss is 4.3588376

多标签损失函数与模型训练

上一章定义了一个简单的损失函数L1Loss,其他损失函数可以仿照L1Loss进行编写。但许多深度学习应用的数据集较复杂,例如目标检测网络Faster R-CNN的数据中就包含多个标签,而不是简单的data和label,这时候损失函数的定义和使用略有不同。

Faster R-CNN网络结构较复杂,不便在此处详细展开。本章对上一章中描述的线性拟合场景进行扩展,手动构建一个多标签数据集,介绍在这种场景下如何定义损失函数,并通过Model进行训练。

定义多标签数据集

首先定义数据集。对之前定义的数据集稍作修改:

  1. get_multilabel_data中产生两个标签y1y2

  2. GeneratorDatasetcolumn_names参数设置为[‘data’, ‘label1’, ‘label2’]

这样通过create_multilabel_dataset产生的数据集就有一个数据data,两个标签label1label2

import numpy as np
from mindspore import 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

定义多标签损失函数

针对上一步创建的数据集,定义损失函数L1LossForMultiLabel。此时,损失函数construct的输入有三个,预测值base,真实值target1target2,我们在construct中分别计算预测值与真实值target1target2之间的误差,将这两个误差的均值作为最终的损失函数值,具体如下:

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

使用Model进行多标签模型训练

刚才提到过,Model内部会关联用户指定的前向网络、损失函数和优化器。其中,前向网络和损失函数是通过nn.WithLossCell关联起来的,nn.WithLossCell会将前向网络和损失函数连接起来,如下:

import mindspore.nn as nn

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

    def construct(self, data, label):
        output = self._backbone(data)
        return self._loss_fn(output, label)

注意到Model默认使用的nn.WithLossCell只有两个输入,datalabel,对于多个标签的场景显然不适用。此时,如果想要使用Model进行模型训练就需要用户将前向网络与损失函数连接起来,具体如下:

  1. 定义适用于当前场景的CustomWithLossCell

    仿照nn.WithLossCell进行定义,将construct的输入修改为三个,将数据部分传给backend,将预测值和两个标签传给loss_fn

    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)
    
  2. 使用CustomWithLossCell将前向网络和损失函数连接起来

    前向网络使用上一章定义的LinearNet,损失函数使用L1LossForMultiLabel,用CustomWithLossCell将它们连接起来,如下:

    net = LinearNet()
    loss = L1LossForMultiLabel()
    loss_net = CustomWithLossCell(net, loss)
    

    这样loss_net中就包含了前向网络和损失函数的运算逻辑。

  3. 定义Model并进行模型训练

    Modelnetwork指定为loss_netloss_fn不指定,优化器仍使用Momentum。此时用户未指定loss_fnModel则认为network内部已经实现了损失函数的逻辑,便不会用nn.WithLossCell对前向函数和损失函数进行封装。

    使用create_multilabel_dataset创建多标签数据集并进行训练:

    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)
    ds_train = create_multilabel_dataset(num_data=160)
    model.train(epoch=1, train_dataset=ds_train, callbacks=[LossMonitor()], dataset_sink_mode=False)
    

完整代码如下:

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

import numpy as np

import mindspore.nn as nn
import mindspore.ops as ops
from mindspore import Model
from mindspore import dataset as ds
from mindspore.nn import LossBase
from mindspore.common.initializer import Normal
from mindspore.train.callback import LossMonitor

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)

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

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)

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

net = LinearNet()
loss = L1LossForMultiLabel()
# build loss network
loss_net = CustomWithLossCell(net, loss)

opt = nn.Momentum(net.trainable_params(), learning_rate=0.005, momentum=0.9)
model = Model(network=loss_net, optimizer=opt)
ds_train = create_multilabel_dataset(num_data=160)
model.train(epoch=1, train_dataset=ds_train, callbacks=[LossMonitor()], dataset_sink_mode=False)

执行结果如下:

epoch: 1 step: 1, loss is 11.039986
epoch: 1 step: 2, loss is 7.7847576
epoch: 1 step: 3, loss is 9.236277
epoch: 1 step: 4, loss is 8.3316345
epoch: 1 step: 5, loss is 6.957058
epoch: 1 step: 6, loss is 9.231144
epoch: 1 step: 7, loss is 9.1072
epoch: 1 step: 8, loss is 6.7703295
epoch: 1 step: 9, loss is 6.363703
epoch: 1 step: 10, loss is 5.014839

本章节简单讲解了多标签数据集场景下,如何定义损失函数并使用Model进行模型训练。在很多其他场景中,也可以采用此类方法进行模型训练。