第四章 深度学习中的损失函数(工具)

概述

官网:torch.nn - PyTorch中文文档 (pytorch-cn.readthedocs.io)

损失函数 torch.nn 用途 特点 应用场景
交叉熵损失 CrossEntropyLoss 多分类问题 当模型对真实类别的预测概率低时,损失迅速增加 适用于分类任务,特别是输出层使用Softmax函数时
二元交叉熵损失 BCELoss
BCEWithLogitsLoss
二分类问题 计算模型预测的概率与实际标签之间的差异 常用于二分类任务,如垃圾邮件检测、医学诊断等
负对数似然损失 NLLLoss 多分类问题 需要在应用此损失之前对模型输出应用Softmax函数 与Softmax一起使用时,适用于多类分类任务
均方误 MSELoss 回归任务 对异常值非常敏感,误差的平方随着误差的增加而增加 适用于需要预测连续数值的任务,如房价预测、股价预测等
平均绝对误差损失 L1Loss 回归任务 相比于MSE,对异常值不那么敏感 当数据含有异常值时,通常优于MSE
回归边界框 Smooth L1 Loss 回归任务 相比于MSE,对异常值不那么敏感 当数据含有异常值时,通常优于MSE

解析CrossEntropyLoss

0.Quick Start

简单定义两个Tensor,其中pre为模型的预测值,tgt为类别真实标签,采用one-hot形式表示。

import torch.nn as nn
loss_func = nn.CrossEntropyLoss()
pre = torch.tensor([0.8, 0.5, 0.2, 0.5], dtype=torch.float)
tgt = torch.tensor([1, 0, 0, 0], dtype=torch.float)
print(loss_func(pre, tgt))

输出为:

tensor(1.1087)

1.参数

torch.nn.CrossEntropyLoss(weight=None,size_average=None,ignore_index=-100,reduce=None,reduction=‘mean’,label_smoothing=0.0)

最常用的参数为 reduction(str, optional) ,可设置其值为 mean, sum, none ,默认为 mean。该参数主要影响多个样本输入时,损失的综合方法。mean表示损失为多个样本的平均值,sum表示损失的和,none表示不综合。其他参数读者可查阅官方文档。

loss_func_none = nn.CrossEntropyLoss(reduction="none")
loss_func_mean = nn.CrossEntropyLoss(reduction="mean")
loss_func_sum = nn.CrossEntropyLoss(reduction="sum")
pre = torch.tensor([[0.8, 0.5, 0.2, 0.5],
                    [0.2, 0.9, 0.3, 0.2],
                    [0.4, 0.3, 0.7, 0.1],
                    [0.1, 0.2, 0.4, 0.8]], dtype=torch.float)
tgt = torch.tensor([[1, 0, 0, 0],
                    [0, 1, 0, 0],
                    [0, 0, 1, 0],
                    [0, 0, 0, 1]], dtype=torch.float)
print(loss_func_none(pre, tgt))
print(loss_func_mean(pre, tgt))
print(loss_func_sum(pre, tgt))

输出为:

tensor([1.1087, 0.9329, 1.0852, 0.9991])
tensor(1.0315)
tensor(4.1259)

2.计算过程

我们还是使用Quick Start中的例子。

loss_func = nn.CrossEntropyLoss()
pre = torch.tensor([0.8, 0.5, 0.2, 0.5], dtype=torch.float)
tgt = torch.tensor([1, 0, 0, 0], dtype=torch.float)
print("手动计算:")
print("1.softmax")
print(torch.softmax(pre, dim=-1))
print("2.取对数")
print(torch.log(torch.softmax(pre, dim=-1)))
print("3.与真实值相乘")
print(-torch.sum(torch.mul(torch.log(torch.softmax(pre, dim=-1)), tgt), dim=-1))
print()
print("调用损失函数:")
print(loss_func(pre, tgt))

输出为:

手动计算:
1.softmax
tensor([0.3300, 0.2445, 0.1811, 0.2445])
2.取对数
tensor([-1.1087, -1.4087, -1.7087, -1.4087])
3.与真实值相乘
tensor(1.1087)

调用损失函数:
tensor(1.1087)

由此可见:

①交叉熵损失函数会自动对输入模型的预测值进行softmax。因此在多分类问题中,如果使用nn.CrossEntropyLoss(),则预测模型的输出层无需添加softmax层。

②nn.CrossEntropyLoss()=nn.LogSoftmax()+nn.NLLLoss().

其实官方文档中说的很明白了:

The input is expected to contain the unnormalized logits for each class (which donotneed to be positive or sum to 1, in general)
Note that this case is equivalent to the combination of LogSoftmax and NLLLoss.

3.损失函数输入及输出的Tensor形状

为了直观显示函数输出结果,我们将参数reduction设置为none。此外pre表示模型的预测值,为4*4的Tensor,其中的每行表示某个样本的类别预测(4个类别);tgt表示样本类别的真实值,有两种表示形式,一种是类别的index,另一种是one-hot形式。

loss_func = nn.CrossEntropyLoss(reduction="none")
pre_data = torch.tensor([[0.8, 0.5, 0.2, 0.5],
                         [0.2, 0.9, 0.3, 0.2],
                         [0.4, 0.3, 0.7, 0.1],
                         [0.1, 0.2, 0.4, 0.8]], dtype=torch.float)
tgt_index_data = torch.tensor([0,
                               1,
                               2,
                               3], dtype=torch.long)
tgt_onehot_data = torch.tensor([[1, 0, 0, 0],
                                [0, 1, 0, 0],
                                [0, 0, 1, 0],
                                [0, 0, 0, 1]], dtype=torch.float)
print("pre_data: {}".format(pre_data.size()))
print("tgt_index_data: {}".format(tgt_index_data.size()))
print("tgt_onehot_data: {}".format(tgt_onehot_data.size()))

输出为:

pre_data: torch.Size([4, 4])
tgt_index_data: torch.Size([4])
tgt_onehot_data: torch.Size([4, 4])

3.1简单情况(一个样本)

构造数据:

pre = pre_data[0]
tgt_index = tgt_index_data[0]
tgt_onehot = tgt_onehot_data[0]
print(pre)
print(tgt_index)
print(tgt_onehot)

输出为:

tensor([0.8000, 0.5000, 0.2000, 0.5000])
tensor(0)
tensor([1., 0., 0., 0.])

**pre形状为Tensor©;两种tgt的形状分别为Tensor(), Tensor© 。**此时①手动计算损失②损失函数+tgt_index形式③损失函数+tgt_onehot形式:

print(-torch.sum(torch.mul(torch.log(torch.softmax(pre, dim=-1)), tgt_onehot), dim=-1))
print(loss_func(pre, tgt_index))
print(loss_func(pre, tgt_onehot))

输出为:

tensor(1.1087)
tensor(1.1087)
tensor(1.1087)

可见torch.nn.CrossEntropyLoss()接受两种形式的标签输入,一种是类别index,一种是one-hot形式,官方文档中的描述是:

Input: Shape ©, (N,C) or (N,C,d1,d2,…,dK) with K≥1 in the case of K-dimensional loss.
Target: If containing class indices, shape (), (N) or(N,d1​,d2​,…,dK​) with K≥1 in the case of K-dimensional loss where each value should be between [0,C). If containing class probabilities, same shape as the input and each value should be between [0,1].

3.2多个样本(一个batch)

构造数据:

pre = pre_data[0:2]
tgt_index = tgt_index_data[0:2]
tgt_onehot = tgt_onehot_data[0:2]
print(pre)
print(tgt_index)
print(tgt_onehot)

输出为:

tensor([[0.8000, 0.5000, 0.2000, 0.5000],
        [0.2000, 0.9000, 0.3000, 0.2000]])
tensor([0, 1])
tensor([[1., 0., 0., 0.],
        [0., 1., 0., 0.]])

**pre形状为Tensor(N, C);两种tgt的形状分别为Tensor(N), Tensor(N, C) 。**此时①手动计算损失②损失函数+tgt_index形式③损失函数+tgt_onehot形式:

print(-torch.sum(torch.mul(torch.log(torch.softmax(pre, dim=-1)), tgt_onehot), dim=-1))
print(loss_func(pre, tgt_index))
print(loss_func(pre, tgt_onehot))

输出为:

tensor([1.1087, 0.9329])
tensor([1.1087, 0.9329])
tensor([1.1087, 0.9329])

3.3三维情况(多样本+多通道)

构造数据:

pre = torch.stack((pre_data[0:2].transpose(0, 1), pre_data[2:4].transpose(0, 1)))
tgt_index = torch.stack((tgt_index_data[0:2], tgt_index_data[2:4]))
tgt_onehot = torch.stack((tgt_onehot_data[0:2].transpose(0, 1), tgt_onehot_data[2:4].transpose(0, 1)))
print(pre)
print(tgt_index)
print(tgt_onehot)

输出为:

tensor([[[0.8000, 0.2000],
         [0.5000, 0.9000],
         [0.2000, 0.3000],
         [0.5000, 0.2000]],

        [[0.4000, 0.1000],
         [0.3000, 0.2000],
         [0.7000, 0.4000],
         [0.1000, 0.8000]]])
tensor([[0, 1],
        [2, 3]])
tensor([[[1., 0.],
         [0., 1.],
         [0., 0.],
         [0., 0.]],

        [[0., 0.],
         [0., 0.],
         [1., 0.],
         [0., 1.]]])

pre形状为Tensor(N, C, d);两种tgt的形状分别为Tensor(N, d), Tensor(N, C, d) 。

在构造数据时,我们使用了stack、transpose函数,不熟悉的读者可以自行查阅,在这里我们只需要知道应用这些函数后,我们构造了三个Tensor。

形状为(N, C, d)的Tensor有什么意义呢?N表示batch的大小,我们单独拿出batch中的一个样本,这时Tensor的形状为(C, d)。在图像分类时,我们可以用d表示不同的通道;在自然语言处理中,d可以表示句子中不同位置的词语。比如刚才我们构造的Tensor(2, 4, 2):

tensor([[[0.8000, 0.2000],
         [0.5000, 0.9000],
         [0.2000, 0.3000],
         [0.5000, 0.2000]],

        [[0.4000, 0.1000],
         [0.3000, 0.2000],
         [0.7000, 0.4000],
         [0.1000, 0.8000]]])

在自然语言处理的背景下,可以将其看作两个句子。拿出第一个句子来,其形状为Tensor(4, 2):

tensor([[0.8000, 0.2000],
         [0.5000, 0.9000],
         [0.2000, 0.3000],
         [0.5000, 0.2000]])

2表示句子由两个词语组成,每一个词语的预测值形状为Tensor(4),即词表的大小为4。

此时①手动计算损失(注意这里dim=1, 表示在第2个维度上softmax)②损失函数+tgt_index形式③损失函数+tgt_onehot形式:

print(-torch.sum(torch.mul(torch.log(torch.softmax(pre, dim=1)), tgt_onehot), dim=1))
print(loss_func(pre, tgt_index))
print(loss_func(pre, tgt_onehot))

输出为:

tensor([[1.1087, 0.9329],
        [1.0852, 0.9991]])
tensor([[1.1087, 0.9329],
        [1.0852, 0.9991]])
tensor([[1.1087, 0.9329],
        [1.0852, 0.9991]])

在三维输入Tensor(N, C, d)的情况下,torch.nn.CrossEntropyLoss()在第二个维度,也就是维度C上求CE损失,最后得到的损失输出为Tensor(N, d)。

但通常我们的惯性思维是,一维是Tensor©,二维是Tensor(N, C),那三维自然应该是Tensor(d, N, C),这样输入CE函数后得到的loss形状为Tensor(d, N)。但是在torch.nn.CrossEntropyLoss()损失函数的背景下,这样的思维方式是错误的,一定要注意。遇见“Tensor形状不匹配”的错误还好,更可怕的是凑巧形状匹配,但计算结果怎么也不对。

对比三种交叉熵损失函数

首先,这几个类分别对应的函数为:

nn.CrossEntropyLoss()` ——》`nn.functional.cross_entropy()
nn.BCEloss()` ——》`nn.functional.binary_cross_entropy()
nn.BCEWithLogitsLoss()` ——》`nn.functional.binary_cross_entropy_with_logits()

类及其对应的函数,它们的label的形式都是一致的;同时类BCEloss()和类BCEWithLogitsLoss()label形式是完全一致的。所以后面我直接用了函数形式,以此便于读者阅读。

CrossEntropyLoss()是可以进行多分类的交叉熵。BCEloss()是指的二分类的交叉熵。首先在我的个人臆测中,如果做二分类的话,那既可以用CrossEntropyLoss()也可以用BCEloss(),即使输入方式不同,应该达到相同效果才对。

但是实际上,CrossEntropyLoss()内部将input做了softmax后再与label进行交叉熵!BCEloss()内部啥也没干直接将inputlabel做了交叉熵!BCEWithLogitsLoss()内部将input做了sigmoid后再与label进行交叉熵!

Smooth L1 Loss

Smooth L1 Loss(也称为Huber Loss)是在机器学习中常用的一种损失函数,特别是在回归任务和某些类型的目标检测任务(如Faster R-CNN)中。它是L1 Loss(绝对差值损失)和L2 Loss(平方差损失)的结合,旨在减少离群点(outliers)对模型训练的影响。

Smooth L1 Loss的公式

Smooth L1 Loss定义如下:
SmoothL1Loss ( x , y ) = { 0.5 × ( x − y ) 2 / β , if  ∣ x − y ∣ < β ∣ x − y ∣ − 0.5 × β , otherwise \text{SmoothL1Loss}(x, y) = \begin{cases} 0.5 \times (x - y)^2 / \beta, & \text{if } |x - y| < \beta \\ |x - y| - 0.5 \times \beta, & \text{otherwise} \end{cases} SmoothL1Loss(x,y)={0.5×(xy)2/β,xy0.5×β,if xy<βotherwise

其中 ( x ) 和 ( y ) 分别是预测值和真实值,( \beta ) 是一个小的阈值。

特点

  1. 减少离群点的影响:当预测值与真实值之间的差异很大时(即离群点),Smooth L1 Loss表现得像L1 Loss,这减少了对大误差的惩罚。当误差小于阈值 ( \beta ) 时,它表现得像L2 Loss。

  2. 梯度平滑:与L1 Loss相比,Smooth L1 Loss在 ( x ) 接近 ( y ) 时有更平滑的梯度,这有助于改善训练的稳定性和性能。

  3. 因为它对于大的误差不太敏感,这在边界框回归中是有益的,因为预测的和真实的边界框之间可能存在大的偏差;,在预测值接近实际值的位置(x=0位置),是连续可导的,收敛速度会更快。

应用

Smooth L1 Loss主要用于回归问题,特别是在目标检测任务中,用于回归边界框的位置。由于其对离群点的鲁棒性,它在这些场景中比传统的L1或L2 Loss表现更好。

你可能感兴趣的:(工具,机器学习,深度学习,深度学习,人工智能)