Pytorch(10)模型训练-损失函数

[PyTorch 学习笔记] 损失函数

一、损失函数

损失函数是衡量模型输出与真实标签之间的差异。我们还经常听到代价函数目标函数,它们之间差异如下:

  • 损失函数(Loss Function)是计算一个样本的模型输出与真实标签的差异 Loss [公式]

  • 代价函数(Cost Function)是计算整个样本集的模型输出与真实标签的差异,是所有样本损失函数的平均值 [公式]

  • 目标函数(Objective Function)就是代价函数加上正则项

在 PyTorch 中的损失函数也是继承于nn.Module,所以损失函数也可以看作网络层。

在逻辑回归的实验中,我使用了交叉熵损失函数loss_fn = nn.BCELoss()[公式] 的继承关系:nn.BCELoss() -> _WeightedLoss -> _Loss -> Module。在计算具体的损失时loss = loss_fn(y_pred.squeeze(), train_y),这里实际上在 Loss 中进行一次前向传播,最终调用BCELoss()forward()函数F.binary_cross_entropy(input, target, weight=self.weight, reduction=self.reduction)

1.1 nn.CrossEntropyLoss = softmax(x)+log(x)+nn.NLLLoss

1
nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')

功能:把nn.LogSoftmax()nn.NLLLoss()结合,计算交叉熵。nn.LogSoftmax()的作用是把输出值归一化到了 [0,1] 之间。

  • weight:各类别的 loss 设置权值
  • ignore_index:忽略某个类别的 loss 计算
  • reduction:计算模式,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量)

==下面介绍熵的一些基本概念==

  • 自信息[公式]
  • 信息熵就是求自信息的期望:[公式]
  • 相对熵,也被称为 KL 散度,用于衡量两个分布的相似性(距离):[公式]。其中 [公式] 是真实分布,[公式] 是拟合的分布
  • 交叉熵[公式]

相对熵展开可得:

[公式]

所以交叉熵 = 信息熵 + 相对熵,即 [公式],又由于信息熵 [公式] 是固定的,因此==优化交叉熵 [公式] 等价于优化相对熵== [公式]

所以对于每一个样本的 Loss 计算公式为:

[公式],因为 [公式][公式]

所以 [公式]

如果了类别的权重,则 [公式]

下面设有 3 个样本做 2 分类。inputs 的形状为 [公式],表示每个样本有两个神经元输出两个分类。target 的形状为 [公式],注意类别从 0 开始,类型为torch.long

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np

# fake data
inputs = torch.tensor([[1, 2], [1, 3], [1, 3]], dtype=torch.float)
target = torch.tensor([0, 1, 1], dtype=torch.long)

# def loss function
loss_f_none = nn.CrossEntropyLoss(weight=None, reduction='none')
loss_f_sum = nn.CrossEntropyLoss(weight=None, reduction='sum')
loss_f_mean = nn.CrossEntropyLoss(weight=None, reduction='mean')

# forward
loss_none = loss_f_none(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)

# view
print("Cross Entropy Loss:", loss_none, loss_sum, loss_mean)

输出为:

1
Cross Entropy Loss: tensor([1.3133, 0.1269, 0.1269]) tensor(1.5671) tensor(0.5224)

1.2 nn.NLLLoss

1
nn.NLLLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')

功能:实现负对数似然函数中的符号功能

主要参数:

  • weight:各类别的 loss 权值设置
  • ignore_index:忽略某个类别
  • reduction:计算模式,,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量)

每个样本的 loss 公式为:[公式]。还是使用上面的例子,第一个样本的输出为 [1,2],类别为 0,则第一个样本的 loss 为 -1;第一个样本的输出为 [1,3],类别为 1,则第一个样本的 loss 为 -3。

1.3 nn.BCELoss

1
nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean')

功能:计算二分类的交叉熵。需要注意的是:输出值区间为 [0,1]。

主要参数:

  • weight:各类别的 loss 权值设置
  • ignore_index:忽略某个类别
  • reduction:计算模式,,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量)

计算公式为:[公式]

使用这个函数有两个不同的地方:

  • 预测的标签需要经过 sigmoid 变换到 [0,1] 之间
  • 真实的标签需要转换为 one hot 向量,类型为torch.float

1.4 nn.BCEWithLogitsLoss

1
nn.BCEWithLogitsLoss(weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None)

功能:结合 sigmoid 与二分类交叉熵。需要注意的是,网络最后的输出不用经过 sigmoid 函数。这个 loss 出现的原因是有时网络模型最后一层输出不希望是归一化到 [0,1] 之间,但是在计算 loss 时又需要归一化到 [0,1] 之间。

主要参数:

  • weight各输出类别的 loss 权值设置
  • pos_weight==设置输入样本类别对应的神经元的输出的 loss 权值==
  • ignore_index:忽略某个类别
  • reduction:计算模式,,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量)

1.5 nn.L1Loss

1
nn.L1Loss(size_average=None, reduce=None, reduction='mean')

功能:计算 inputs 与 target 之差的绝对值

主要参数:

  • reduction:计算模式,,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量)

公式:[公式]

1.6 nn.MSELoss

功能:计算 inputs 与 target 之差的平方

公式:[公式]

主要参数:

  • reduction:计算模式,,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量)

1.7 nn.SmoothL1Loss

1
nn.SmoothL1Loss(size_average=None, reduce=None, reduction='mean')

功能:平滑的 L1Loss

公式:[公式]

下图中橙色曲线是 L1Loss,蓝色曲线是 Smooth L1Loss

img

主要参数:

  • reduction:计算模式,,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量)

1.8 nn.PoissonNLLLoss

1
nn.PoissonNLLLoss(log_input=True, full=False, size_average=None, eps=1e-08, reduce=None, reduction='mean')

功能:泊松分布的负对数似然损失函数

主要参数:

  • log_input:输入是否为对数形式,决定计算公式

    • 当 log_input = True,表示输入数据已经是经过对数运算之后的,loss(input, target) = exp(input) - target * input
    • 当 log_input = False,,表示输入数据还没有取对数,loss(input, target) = input - target * log(input+eps)
  • full:计算所有 loss,默认为 loss

  • eps:修正项,避免 log(input) 为 nan

1.9 nn.KLDivLoss

1
nn.KLDivLoss(size_average=None, reduce=None, reduction='mean')

功能:计算 KLD(divergence),KL 散度,相对熵

注意事项:需要提前将输入计算 log-probabilities,如通过nn.logsoftmax()

主要参数:

  • reduction:计算模式,,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量),batchmean(batchsize 维度求平均值)

公式:[公式]

对于每个样本来说,计算公式如下,其中 [公式] 是真实值 [公式][公式] 是经过对数运算之后的预测值 [公式]

[公式]

1.10 nn.MarginRankingLoss

1
nn.MarginRankingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')

功能:计算两个向量之间的相似度,用于排序任务

特别说明:该方法计算 两组数据之间的差异,返回一个 [公式] 的 loss 矩阵

主要参数:

  • margin:边界值,[公式][公式] 之间的差异值
  • reduction:计算模式,,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量)

计算公式:[公式][公式] 的取值有 +1 和 -1。

  • [公式] 时,希望 [公式],当 [公式],不产生 loss
  • [公式] 时,希望 [公式],当 [公式],不产生 loss

1.11 nn.SoftMarginLoss

1
nn.SoftMarginLoss(size_average=None, reduce=None, reduction='mean')

功能:计算二分类的 logistic 损失

主要参数:

  • reduction:计算模式,,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量)

计算公式:[公式]

1.12 nn.MultiMarginLoss

1
nn.MultiMarginLoss(p=1, margin=1.0, weight=None, size_average=None, reduce=None, reduction='mean')

功能:计算多分类的折页损失

主要参数:

  • p:可以选择 1 或 2
  • weight:各类别的 loss 权值设置
  • margin:边界值
  • reduction:计算模式,,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量)

计算公式:[公式],其中 y 表示真实标签对应的神经元输出,x 表示其他神经元的输出。

1.13 nn.CosineEmbeddingLoss

1
torch.nn.CosineEmbeddingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')

功能:采用余弦相似度计算两个输入的相似性

主要参数:

  • margin:边界值,可取值 [-1, 1],推荐为 [0, 0.5]
  • reduction:计算模式,,可以为 none(逐个元素计算),sum(所有元素求和,返回标量),mean(加权平均,返回标量)

计算公式:[公式]

其中 [公式]

二、损失函数Q&A

2.1 nn.CrossEntropyLoss = softmax(x)+log(x)+nn.NLLLoss?

在各种深度学习框架中,我们最常用的损失函数就是交叉熵(torch.nn.CrossEntropyLoss),熵是用来描述一个系统的混乱程度,通过交叉熵我们就能够确定预测数据与真是数据之间的相近程度。交叉熵越小,表示数据越接近真实样本。

交叉熵计算公式:

img

softmax函数又称为归一化指数函数,它可以把一个多维向量压缩在(0,1)之间,并且它们的和为1.

计算公式:

1

1
2
3
4
5
6
7
8
import math
z = [1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0]
z_exp = [math.exp(i) for i in z]
print(z_exp) # Result: [2.72, 7.39, 20.09, 54.6, 2.72, 7.39, 20.09]
sum_z_exp = sum(z_exp)
print(sum_z_exp) # Result: 114.98
softmax = [round(i / sum_z_exp, 3) for i in z_exp]
print(softmax) # Result: [0.024, 0.064, 0.175, 0.475, 0.024, 0.064, 0.175]

log_softmax是指在softmax函数的基础上,再进行一次log运算,此时结果有正有负,log函数的值域是负无穷到正无穷,当x在0—1之间的时候,log(x)值在负无穷到0之间

nn.NLLLoss的结果就是把上面的输出与Label对应的那个值拿出来,再去掉负号,再求均值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import torch
input=torch.randn(3,3)
soft_input = torch.nn.Softmax(dim=0)
soft_input(input)
Out[20]:
tensor([[0.7284, 0.7364, 0.3343],
[0.1565, 0.0365, 0.0408],
[0.1150, 0.2270, 0.6250]])

#对softmax结果取log
torch.log(soft_input(input))
Out[21]:
tensor([[-0.3168, -0.3059, -1.0958],
[-1.8546, -3.3093, -3.1995],
[-2.1625, -1.4827, -0.4701]])

假设标签是[0,1,2],第一行取第0个元素,第二行取第1个,第三行取第2个,去掉负号,即[0.3168,3.3093,0.4701],求平均值,就可以得到损失值。

1
2
3
4
5
6
7
(0.3168+3.3093+0.4701)/3
Out[22]: 1.3654000000000002
#验证一下
loss=torch.nn.NLLLoss()
target=torch.tensor([0,1,2])
loss(input,target)
Out[26]: tensor(0.1365)

nn.CrossEntropyLoss

1
2
3
4
5
6
7
8
9
10
11
loss=torch.nn.NLLLoss()
target=torch.tensor([0,1,2])
loss(input,target)
Out[26]: tensor(-0.1399)
loss =torch.nn.CrossEntropyLoss()
input = torch.tensor([[ 1.1879, 1.0780, 0.5312],
[-0.3499, -1.9253, -1.5725],
[-0.6578, -0.0987, 1.1570]])
target = torch.tensor([0,1,2])
loss(input,target)
Out[30]: tensor(0.1365)