自定义损失函数

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

损失函数,亦称目标函数,用于衡量预测值与真实值差异的程度。

在深度学习中,模型训练就是通过不断迭代来缩小损失函数值的过程。因此,在模型训练过程中损失函数的选择非常重要,一个好的损失函数能有效提升模型的性能。

mindspore.nn模块中提供了许多通用损失函数,但这些通用损失函数无法满足所有需求,很多情况需要用户自定义所需的损失函数。因此,本教程介绍如何自定义损失函数。

lossfun.png

内置损失函数

首先介绍mindspore.nn模块中内置的损失函数

下例以nn.L1Loss为例,计算预测值和目标值之间的平均绝对误差:

\[\ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad \text{with } l_n = \left| x_n - y_n \right|\]

其中N为数据集中的batch_size值。

\[\begin{split}\ell(x, y) = \begin{cases} \operatorname{mean}(L), & \text{if reduction} = \text{'mean';}\\ \operatorname{sum}(L), & \text{if reduction} = \text{'sum'.} \end{cases}\end{split}\]

nn.L1Loss中的参数reduction取值可为meansum,或none。若reductionmeansum,则输出一个经过均值或求和后的标量Tensor(降维);若reductionnone,则所输出Tensor的shape为广播后的shape。

[1]:
import numpy as np
from mindspore import nn
from mindspore import Tensor

# 输出loss均值
loss = nn.L1Loss()
# 输出loss和
loss_sum = nn.L1Loss(reduction='sum')
# 输出loss原值
loss_none = nn.L1Loss(reduction='none')

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:", loss(input_data, target_data))
print("loss_sum:", loss_sum(input_data, target_data))
print("loss_none:", loss_none(input_data, target_data))
loss: 1.5
loss_sum: 9.0
loss_none: [[1. 0. 2.]
 [1. 2. 3.]]

自定义损失函数

自定义损失函数的方法有两种:一是基于nn.Cell来定义损失函数;二是nn.LossBase来定义损失函数。nn.LossBase继承自nn.Cell,额外提供了get_loss方法,利用reduction参数对损失值求和或求均值,输出一个标量。

下面将分别使用继承Cell和继承LossBase的方法,来定义平均绝对误差损失函数(Mean Absolute Error,MAE),MAE算法的公式如下所示:

\[loss= \frac{1}{m}\sum_{i=1}^m\lvert y_i-f(x_i) \rvert\]

上式中\(f(x)\)为预测值,\(y\)为样本真实值,\(loss\)为预测值与真实值之间距离的平均值。

基于nn.Cell构造损失函数

nn.Cell是MindSpore的基类,不但可用于构建网络,还可用于定义损失函数。使用nn.Cell定义损失函数的过程与定义一个普通的网络相似,差别在于,其执行逻辑部分要计算的是前向网络输出与真实值之间的误差。

下面展示怎样基于nn.Cell自定义损失函数MAELoss

[2]:
from mindspore import ops
import mindspore as ms

class MAELoss(nn.Cell):
    """自定义损失函数MAELoss"""
    def construct(self, base, target):
        return ops.abs(base - target).mean()

loss = MAELoss()

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)
0.033333335

基于nn.LossBase构造损失函数

基于nn.LossBase构造损失函数MAELoss与基于nn.Cell构造损失函数的过程类似,都要重写__init__方法和construct方法。

nn.LossBase可使用方法get_lossreduction应用于损失计算。

[3]:
class MAELoss(nn.LossBase):
    """自定义损失函数MAELoss"""
    def construct(self, base, target):
        x = ops.abs(base - target)
        return self.get_loss(x)  # 返回loss均值

loss = MAELoss()

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)
0.033333335

损失函数与模型训练

损失函数MAELoss自定义完成后,可使用MindSpore的接口Modeltrain接口进行模型训练,构造Model时需传入前向网络、损失函数和优化器,Model会在内部将它们关联起来,生成一个可用于训练的网络模型。

Model中,前向网络和损失函数通过nn.WithLossCell关联起来,nn.WithLossCell支持两个输入,分别为datalabel

[4]:
from mindspore.train import Model, LossMonitor
from mindspore.dataset import GeneratorDataset

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 = GeneratorDataset(list(get_data(num_data)), column_names=['data', 'label'])
    dataset = dataset.batch(batch_size)
    return dataset

train_dataset = create_dataset(num_data=160)
network = nn.Dense(1, 1)
loss_fn = MAELoss()
optimizer = nn.Momentum(network.trainable_params(), learning_rate=0.005, momentum=0.9)

# 使用model接口将网络、损失函数和优化器关联起来
model = Model(network, loss_fn, optimizer)
model.train(10, train_dataset, callbacks=[LossMonitor(10)])
epoch: 1 step: 10, loss is 6.525373935699463
epoch: 2 step: 10, loss is 4.005467414855957
epoch: 3 step: 10, loss is 2.1115174293518066
epoch: 4 step: 10, loss is 2.7334954738616943
epoch: 5 step: 10, loss is 1.7042752504348755
epoch: 6 step: 10, loss is 1.6317998170852661
epoch: 7 step: 10, loss is 1.035435438156128
epoch: 8 step: 10, loss is 0.6060740351676941
epoch: 9 step: 10, loss is 1.0374044179916382
epoch: 10 step: 10, loss is 0.736151397228241

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

上述定义了一个简单的平均绝对误差损失函数MAELoss,但许多深度学习应用的数据集较复杂,如目标检测网络Faster R-CNN的数据中就包含多个标签,而非简单的一条数据对应一个标签,这时损失函数的定义和使用略有不同。

本节介绍在多标签数据集场景下,如何定义多标签损失函数(Multi label loss function),并使用Model进行模型训练。

多标签数据集

下例通过get_multilabel_data函数拟合两组线性数据\(y1\)\(y2\),拟合的目标函数为:

\[f(x)=2x+3\]

最终数据集应随机分布于函数周边,这里按以下公式的方式生成,其中noise为服从标准正态分布的随机值。get_multilabel_data函数返回数据\(x\)\(y1\)\(y2\)

\[f(x)=2x+3+noise\]

通过create_multilabel_dataset生成多标签数据集,并将GeneratorDataset中的column_names参数设置为[‘data’, ‘label1’, ‘label2’],最终返回的数据集格式为一条数据data对应两个标签label1label2

[5]:
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 = GeneratorDataset(list(get_multilabel_data(num_data)), column_names=['data', 'label1', 'label2'])
    dataset = dataset.batch(batch_size)  # 每个batch有16个数据
    return dataset

多标签损失函数

针对上一步创建的多标签数据集,定义多标签损失函数MAELossForMultiLabel

\[loss1= \frac{1}{m}\sum_{i=1}^m\lvert y1_i-f(x_i) \rvert\]
\[loss2= \frac{1}{m}\sum_{i=1}^m\lvert y2_i-f(x_i) \rvert\]
\[loss = \frac{(loss1 + loss2)}{2}\]

上式中,\(f(x)\) 为样例标签的预测值,\(y1\)\(y2\) 为样例标签的真实值,\(loss1\) 为预测值与真实值 \(y1\) 之间距离的平均值,\(loss2\) 为预测值与真实值 \(y2\) 之间距离的平均值 ,\(loss\) 为损失值 \(loss1\) 与损失值 \(loss2\) 平均值。

MAELossForMultiLabel中的construct方法的输入有三个,预测值base,真实值target1target2,在construct中分别计算预测值与真实值target1、预测值与真实值target2之间的误差,将两误差取平均后作为最终的损失函数值。

示例代码如下:

[6]:
class MAELossForMultiLabel(nn.LossBase):

    def construct(self, base, target1, target2):
        x1 = ops.abs(base - target1)
        x2 = ops.abs(base - target2)
        return (self.get_loss(x1) + self.get_loss(x2)) / 2

多标签模型训练

使用Model连接前向网络、多标签损失函数和优化器时,Model的网络network指定为自定义的损失网络loss_net,损失函数loss_fn不指定,优化器仍使用Momentum

在未指定loss_fn时,Model会默认network内部已实现损失函数的逻辑,不再在内部使用nn.WithLossCell关联前向网络和损失函数。

[7]:
train_dataset = create_multilabel_dataset(num_data=160)

# 定义多标签损失函数
loss_fn = MAELossForMultiLabel()
# 定义优化器
opt = nn.Momentum(network.trainable_params(), learning_rate=0.005, momentum=0.9)
[8]:
def forward_fn(data, label1, label2):
    output = network(data)
    return loss_fn(output, label1, label2)

grad_fn = ms.value_and_grad(forward_fn, None, opt.parameters)

def train_step(data, label1, label2):
    loss, grads = grad_fn(data, label1, label2)
    opt(grads)
    return loss

def train(model, dataset):
    size = dataset.get_dataset_size()
    model.set_train()
    for batch, (data, label1, label2) in enumerate(dataset.create_tuple_iterator()):
        loss = train_step(data, label1, label2)

        if batch % 2 == 0:
            loss, current = loss.asnumpy(), batch
            print(f"loss: {loss:>7f}  [{current:>3d}/{size:>3d}]")
[9]:
epochs = 5
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train(network, train_dataset)
print("Done!")
Epoch 1
-------------------------------
loss: 0.739832  [  0/ 10]
loss: 0.949316  [  2/ 10]
loss: 1.052085  [  4/ 10]
loss: 0.982260  [  6/ 10]
loss: 0.784400  [  8/ 10]
Epoch 2
-------------------------------
loss: 0.963160  [  0/ 10]
loss: 0.899232  [  2/ 10]
loss: 0.934914  [  4/ 10]
loss: 0.757601  [  6/ 10]
loss: 0.965961  [  8/ 10]
Epoch 3
-------------------------------
loss: 0.815042  [  0/ 10]
loss: 0.999898  [  2/ 10]
loss: 1.008266  [  4/ 10]
loss: 1.024307  [  6/ 10]
loss: 0.798073  [  8/ 10]
Epoch 4
-------------------------------
loss: 0.844747  [  0/ 10]
loss: 0.958094  [  2/ 10]
loss: 0.898447  [  4/ 10]
loss: 0.879910  [  6/ 10]
loss: 0.969592  [  8/ 10]
Epoch 5
-------------------------------
loss: 0.917983  [  0/ 10]
loss: 0.862990  [  2/ 10]
loss: 0.947069  [  4/ 10]
loss: 0.854086  [  6/ 10]
loss: 0.910622  [  8/ 10]
Done!

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