Pytorch学习笔记(四):损失函数

重要:本文最后更新于,某些文章具有时效性,若有错误或失效,请在下方留言

准备好数据,设计好模型,接下来是选择合适的损失函数,并且采用合适的优化器进行优化(训练)模型。本篇笔记将配合代码对于此部分进行详细说明。

部分学习资源链接:

之前的相关文章:

代码中的基本用法

criterion = LossCriterion() #构造函数有自己的参数
loss = criterion(x, y) #调用标准时也有参数

Pytorch中的十四个常用损失函数

在深度学习中要用到各种各样的损失函数(loss function),这些损失函数可看作是一种特殊的 layer ,PyTorch也将这些损失函数实现为 nn.Module 的子类。然而在实际使用中通常将这些 loss function 专门提取出来,和主模型互相独立。

我们所说的优化,即优化网络权值使得损失函数值变小。但是,损失函数值变小是否能代表模型的分类/回归精度变高呢?那么多种损失函数,应该如何选择呢?要解答这些就首先要了解Pytorch中的损失函数都有哪些和他们的机制,来看一下吧。

我们先定义两个二维数组,然后用不同的损失函数计算其损失值。

import torch
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
sample = Variable(torch.ones(2,2))
a=torch.Tensor(2,2)
a[0,0]=0
a[0,1]=1
a[1,0]=2
a[1,1]=3
target = Variable (a)

sample 的值为:[[1,1],[1,1]]。 target 的值为:[[0,1],[2,3]]

1. L1loss

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

功能:

计算 outputtarget 之差的绝对值的平均,可选返回同维度的 tensor 或者是一个标量

计算公式:

\[ loss(x,y)=\frac{1}{N}\sum_{i=1}^{N}\left | x-y \right | \]

代码:

criterion = nn.L1Loss()
loss = criterion(sample, target)
print(loss)

最后结果是:1。

它的计算逻辑是这样的:

先计算绝对差总和:|0-1|+|1-1|+|2-1|+|3-1|=4;

然后再平均:4/4=1。

2. MSELoss

CLASS torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')

功能:

平方损失函数。其计算公式是预测值和真实值之间的平方和的平均数。

计算公式:

\[ loss(x,y)=\frac{1}{N}\sum_{i=1}^{N}\left | x-y \right |^{2} \]

代码:

criterion = nn.MSELoss()
loss = criterion(sample, target)
print(loss)

最后结果是:1.5

计算过程很简单,是上面计算过程中每个差的绝对值的平方和的平均,不予赘述。

3. CrossEntropyLoss

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

功能:

将输入经过softmax激活函数之后,再计算其与target的交叉熵损失。即该方法将nn.LogSoftmax()和 nn.NLLLoss()进行了结合。严格意义上的交叉熵损失函数应该是nn.NLLLoss()。

说明:

在使用Pytorch时经常碰见这些函数cross_entropy,CrossEntropyLoss, log_softmax, softmax。 首先要知道上面提到的这些函数一部分是来自于 torch.nn ,而另一部分则来自于 torch.nn.functional (常缩写为F)。二者函数的区别可参见 知乎:torch.nn和funtional函数区别是什么?

torch.nntorch.nn.functional (F)
CrossEntropyLosscross_entropy
LogSoftmaxlog_softmax
NLLLossnll_loss

下面将主要介绍 torch.nn.functional 中的函数为主, torch.nn 中对应的函数其实就是对F里的函数进行包装以便管理变量等操作。

在介绍 cross_entropy 之前先介绍两个基本函数:

log_softmax: 这个很好理解,其实就是 logsoftmax 合并在一起执行。

nll_loss: 该函数的全称是 negative log likelihood loss,函数表达式为:

\[ f(x,class)=-x[class] \]

例如假设 \( x=[1,2,3] \) , \( class=2x=[1,2,3] \) , \( class=2 \) ,那么 \(f(x,class)=−x[2]=−3 \)。

交叉熵的计算公式为:

\[cross\_entropy=-\sum_{k=1}^{N}(p_{k}*logq_{k})\]

其中p表示真实值,在这个公式中是 one-hot 形式;q是预测值,在这里假设已经是经过 softmax 后的结果了。

仔细观察可以知道,因为p的元素不是0就是1,而且又是乘法,所以很自然地我们如果知道1所对应的 index,那么就不用做其他无意义的运算了。所以在pytorch代码中 target 不是以 one-hot 形式表示的,而是直接用 scalar 表示。所以交叉熵的公式(m表示真实类别)可变形为:

\[ cross\_entropy=-\sum_{k=1}^{N}(p_{k}*logq_{k})=-logq_{m} \]

仔细看看,是不是就是等同于log_softmaxnll_loss两个步骤。

所以Pytorch中的 F.cross_entropy 会自动调用上面介绍的 log_softmaxnll_loss 来计算交叉熵,其计算方式如下:

\[ loss(x,class)=weight[class](-x[class]+log\sum_{j=1}^{N}e^{x[j]} ) \]

参数:

weight(Tensor):为每个类别的loss设置权值,常用于类别不均衡问题。weight必须是float类型的tensor,其长度要于类别C一致,即每一个类别都要设置有weight。

size_average(bool): 当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。

reduce(bool):返回值是否为标量,默认为True

ignore_index(int):忽略某一类别,不计算其loss,其loss会为0,并且,在采用size_average时,不会计算那一类的loss,除的时候的分母也不会统计那一类的样本。

代码示例:

import torch
import torch.nn as nn
import numpy as np
import math

# ----------------------------------- CrossEntropy loss: base

loss_f = nn.CrossEntropyLoss(weight=None, size_average=True, reduce=False)
# 生成网络输出 以及 目标输出
output = torch.ones(2, 3, requires_grad=True) * 0.5      # 假设一个三分类任务,batchsize=2,假设每个神经元输出都为0.5
target = torch.from_numpy(np.array([0, 1])).type(torch.LongTensor)

loss = loss_f(output, target)

print('--------------------------------------------------- CrossEntropy loss: base')
print('loss: ', loss)
print('由于reduce=False,所以可以看到每一个样本的loss,输出为[1.0986, 1.0986]')


# 熟悉计算公式,手动计算第一个样本
output = output[0].detach().numpy()
output_1 = output[0]              # 第一个样本的输出值
target_1 = target[0].numpy()

# 第一项
x_class = output[target_1]
# 第二项
exp = math.e
sigma_exp_x = pow(exp, output[0]) + pow(exp, output[1]) + pow(exp, output[2])
log_sigma_exp_x = math.log(sigma_exp_x)
# 两项相加
loss_1 = -x_class + log_sigma_exp_x
print('---------------------------------------------------  手动计算')
print('第一个样本的loss:', loss_1)


# ----------------------------------- CrossEntropy loss: weight

weight = torch.from_numpy(np.array([0.6, 0.2, 0.2])).float()
loss_f = nn.CrossEntropyLoss(weight=weight, size_average=True, reduce=False)
output = torch.ones(2, 3, requires_grad=True) * 0.5  # 假设一个三分类任务,batchsize为2个,假设每个神经元输出都为0.5
target = torch.from_numpy(np.array([0, 1])).type(torch.LongTensor)
loss = loss_f(output, target)
print('\n\n--------------------------------------------------- CrossEntropy loss: weight')
print('loss: ', loss)  #
print('原始loss值为1.0986, 第一个样本是第0类,weight=0.6,所以输出为1.0986*0.6 =', 1.0986*0.6)

# ----------------------------------- CrossEntropy loss: ignore_index

loss_f_1 = nn.CrossEntropyLoss(weight=None, size_average=False, reduce=False, ignore_index=1)
loss_f_2 = nn.CrossEntropyLoss(weight=None, size_average=False, reduce=False, ignore_index=2)

output = torch.ones(3, 3, requires_grad=True) * 0.5  # 假设一个三分类任务,batchsize为2个,假设每个神经元输出都为0.5
target = torch.from_numpy(np.array([0, 1, 2])).type(torch.LongTensor)

loss_1 = loss_f_1(output, target)
loss_2 = loss_f_2(output, target)

print('\n\n--------------------------------------------------- CrossEntropy loss: ignore_index')
print('ignore_index = 1: ', loss_1)     # 类别为1的样本的loss为0
print('ignore_index = 2: ', loss_2)     # 类别为2的样本的loss为0
--------------------------------------------------- CrossEntropy loss: base
loss:  tensor([1.0986, 1.0986], grad_fn=<NllLossBackward>)
由于reduce=False,所以可以看到每一个样本的loss,输出为[1.0986, 1.0986]
---------------------------------------------------  手动计算
第一个样本的loss: 1.0986122886681098


--------------------------------------------------- CrossEntropy loss: weight
loss:  tensor([0.6592, 0.2197], grad_fn=<NllLossBackward>)
原始loss值为1.0986, 第一个样本是第0类,weight=0.6,所以输出为1.0986*0.6 = 0.65916


--------------------------------------------------- CrossEntropy loss: ignore_index
ignore_index = 1:  tensor([1.0986, 0.0000, 1.0986], grad_fn=<NllLossBackward>)
ignore_index = 2:  tensor([1.0986, 1.0986, 0.0000], grad_fn=<NllLossBackward>)

补充:

output 不仅可以是向量,还可以是图片,即对图像进行像素点的分类,这个例子可以从 NLLLoss() 中看到,这在图像分割当中很有用。

4. NLLLoss

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

功能:

不好用言语描述其功能,请看计算公式:

\[ f(x,class)=-x[class] \]

举个例,三分类任务,input=[-1.233, 2.657, 0.534], 真实标签为2(class=2),则loss为-0.534。就是对应类别上的输出,取一个负号。

实际应用:

常用于多分类任务,但是 input 在输入 NLLLoss() 之前,需要对input进行 log_softmax 函数激活,即将 input 转换成概率分布的形式,并且取对数。其实这些步骤在 CrossEntropyLoss 中就有,如果不想让网络的最后一层是 log_softmax 层的话,就可以采用 CrossEntropyLoss 完全代替此函数。

参数:

weight(Tensor):为每个类别的loss设置权值,常用于类别不均衡问题。weight必须是float类型的tensor,其长度要于类别C一致,即每一个类别都要设置有weight。

size_average(bool):当reduce=True时有效。为True时,返回的loss为除以权重之和的平均值;为False时,返回的各样本的loss之和。

reduce(bool):返回值是否为标量,默认为True。

ignore_index(int):忽略某一类别,不计算其loss,其loss会为0,并且,在采用size_average时,不会计算那一类的loss,除的时候的分母也不会统计那一类的样本。

代码示例:

import torch
import torch.nn as nn
import numpy as np

# ----------------------------------- log likelihood loss

# 各类别权重
weight = torch.from_numpy(np.array([0.6, 0.2, 0.2])).float()

# 生成网络输出 以及 目标输出
output = torch.from_numpy(np.array([[0.7, 0.2, 0.1], [0.4, 1.2, 0.4]])).float()  
output.requires_grad = True
target = torch.from_numpy(np.array([0, 0])).type(torch.LongTensor)


loss_f = nn.NLLLoss(weight=weight, size_average=True, reduce=False)
loss = loss_f(output, target)

print('\nloss: \n', loss)
print('\n第一个样本是0类,loss = -(0.6*0.7)={}'.format(loss[0]))
print('\n第二个样本是0类,loss = -(0.6*0.4)={}'.format(loss[1]))
loss: 
 tensor([-0.4200, -0.2400], grad_fn=<NllLossBackward>)

第一个样本是0类,loss = -(0.6*0.7)=-0.42000001668930054

第二个样本是0类,loss = -(0.6*0.4)=-0.24000000953674316

特别注意:

当带上权值,并且 reduction 不是"none"(默认为 mean

\[ l(x,y)=\sum_{n=1}^{N}\frac{1}{\sum_{n=1}^{N}w_{yn}}l_{n}, \quad if \quad reduction="mean" \]

\[ l(x,y)=\sum_{n=1}^{N}l_{n}, \quad if \quad reduction="sum" \]

例如当input为[[0.6, 0.2, 0.2], [0.4, 1.2, 0.4]],target= [0, 1], weight = [0.6, 0.2, 0.2]

l1 = - 0.6×0.6 = - 0.36

l2 = - 1.2×0.2 = - 0.24

loss = -0.36/(0.6+0.2) + -0.24/(0.6+0.2) = -0.75

5. PoissonNLLLoss

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

功能:

用于target服从泊松分布的分类任务。

计算公式:

target∼Poisson(input)loss(input,target)=input−target∗log(input)+log(target!)

参数:

log_input(bool):为True时,计算公式为:loss(input,target)=exp(input) - target * input; 为False时,loss(input,target)=input - target * log(input+eps)

full(bool):是否计算全部的loss。例如,当采用斯特林公式近似阶乘项时,此为 target*log(target) - target+0.5∗log(2πtarget)

eps(float): 当log_input = False时,用来防止计算log(0),而增加的一个修正项。即 loss(input,target)=input - target * log(input+eps)

size_average(bool):当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。

reduce(bool):返回值是否为标量,默认为True

代码示例:

import torch
import torch.nn as nn
import numpy as np

# ----------------------------------- Poisson NLLLoss

# 生成网络输出 以及 目标输出
log_input = torch.randn(5, 2, requires_grad=True)
target = torch.randn(5, 2)

loss_f = nn.PoissonNLLLoss()
loss = loss_f(log_input, target)
print('\nloss: \n', loss)
loss: 
 tensor(3.4186, grad_fn=<MeanBackward0>)

6. KLDivLoss

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

功能:

计算input和target之间的KL散度( Kullback–Leibler divergence) 。 KL 散度,又叫做相对熵,算的是两个分布之间的距离,越相似则越接近零。

计算公式:

\[ loss(x,y)=\frac{1}{N}\sum_{i=1}^{N}[y_{i}*log(y_{i})-x_{i}] \]

注意这里的 xi 是 log 概率 。

信息熵 = 交叉熵 - 相对熵,从信息论角度观察三者,其关系为信息熵 = 交叉熵 - 相对熵。在机器学习中,当训练数据固定,最小化相对熵等价于最小化交叉熵。

参数:

size_average(bool):当reduce=True时有效。为True时,返回的loss为平均值,平均值为element-wise的,而不是针对样本的平均;为False时,返回是各样本各维度的loss之和。

reduce(bool):返回值是否为标量,默认为True。

使用注意事项:

要想获得真正的KL散度,需要如下操作:

  1. reduce = True ;size_average=False
  2. 计算得到的loss 要对batch进行求平均

代码示例:

import torch
import torch.nn as nn
import numpy as np

# -----------------------------------  KLDiv loss

loss_f = nn.KLDivLoss(size_average=False, reduce=False)
loss_f_mean = nn.KLDivLoss(size_average=True, reduce=True)

# 生成网络输出 以及 目标输出
output = torch.from_numpy(np.array([[0.1132, 0.5477, 0.3390]])).float()
output.requires_grad = True
target = torch.from_numpy(np.array([[0.8541, 0.0511, 0.0947]])).float()

loss_1 = loss_f(output, target)
loss_mean = loss_f_mean(output, target)

print('\nloss: ', loss_1)
print('\nloss_mean: ', loss_mean)


# 熟悉计算公式,手动计算样本的第一个元素的loss,注意这里只有一个样本,是 element-wise计算的

output = output[0].detach().numpy()
output_1 = output[0]           # 第一个样本的第一个元素
target_1 = target[0][0].numpy()

loss_1 = target_1 * (np.log(target_1) - output_1)

print('\n第一个样本第一个元素的loss:', loss_1)
loss:  tensor([[-0.2314, -0.1800, -0.2553]], grad_fn=<KlDivBackward>)

loss_mean:  tensor(-0.2222, grad_fn=<KlDivBackward>)

第一个样本第一个元素的loss: -0.23138168

7. BCELoss

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

功能:

二分类任务时的交叉熵计算函数。此函数可以认为是 nn.CrossEntropyLoss 函数的特例。其分类限定为二分类,y必须是{0,1}。还需要注意的是,input 应该为概率分布的形式,这样才符合交叉熵的应用。所以在 BCELoss 之前,input一般为 sigmoid 激活层的输出,官方例子也是这样给的。该损失函数在自编码器中常用。

计算公式:

\[ loss(x,y)=-w_{i}[y_{i}log(x_{i})+(1-y_{i})log(1-x_{i})] \]

参数:

weight(Tensor):为每个类别的loss设置权值,常用于类别不均衡问题。

size_average(bool):当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。

reduce(bool):返回值是否为标量,默认为True

代码示例:

import torch.nn.functional as F
loss_fn = torch.nn.BCELoss(reduce=False, size_average=False)
input = Variable(torch.randn(3, 4))
target = Variable(torch.FloatTensor(3, 4).random_(2))
loss = loss_fn(F.sigmoid(input), target)
print(input); print(target); print(loss)
tensor([[ 0.3095, -0.5742,  0.0208,  0.2977],
        [-0.9108, -0.3528,  0.4661,  1.9696],
        [ 0.5279,  0.7624,  0.5095,  1.5507]])
tensor([[1., 0., 0., 1.],
        [0., 1., 1., 1.],
        [0., 0., 0., 0.]])
tensor([[0.5503, 0.4467, 0.7036, 0.5554],
        [0.3380, 0.8850, 0.4870, 0.1306],
        [0.9915, 1.1453, 0.9800, 1.7430]])

这里比较奇怪的是,权重的维度不是 2,而是和 x, y 一样,有时候遇到正负例样本不均衡的时候,可能要多写一句话。

class_weight = Variable(torch.FloatTensor([1, 10])) # 这里正例比较少,因此权重要大一些
target = Variable(torch.FloatTensor(3, 4).random_(2))
weight = class_weight[target.long()] # (3, 4)
loss_fn = torch.nn.BCELoss(weight=weight, reduce=False, size_average=False)

其实这样子做的话,如果每次 batch_size 长度不一样,只能每次都定义 loss_fn 了,不知道有没有更好的解决方案。

8. BCEWithLogitsLoss

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

功能:

SigmoidBCELoss 结合,类似于 CrossEntropyLoss (将nn.LogSoftmax()和 nn.NLLLoss()进行结合)。即 input 会经过 Sigmoid 激活函数,将 input 变成概率分布的形式。

上面的 nn.BCELoss 需要手动加上一个 Sigmoid 层,这里是结合了两者,这样做能够利用 log_sum_exp trick,使得数值结果更加稳定(numerical stability)。建议使用这个损失函数。

值得注意的是,文档里的参数只有 weight, size_average 两个,但是实际测试 reduce 参数也是可以用的。此外两个损失函数的 target 要求是 FloatTensor,而且不一样是只能取 0, 1 两种值,任意值应该都是可以的。

9. MarginRankingLoss

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

功能:

计算两个向量之间的相似度,当两个向量之间的距离大于margin,则loss为正,小于margin,loss为0。

计算公式:

\[ loss(x_{1},x_{2},y)=max(0,-y*(x_{1}-x_{2})+margin) \]

y == 1时, \( x_{1}\) 要比 \(x_{2} \) 大,才不会有loss,反之,y == -1 时, \( x_{1} \) 要比 \( x_{2} \) 小,才不会有loss。

参数:

margin(float):x1和x2之间的差异。

size_average(bool):当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。

reduce(bool):返回值是否为标量,默认为True。

10. MultiLabelMarginLoss

CLASS torch.nn.MultiLabelMarginLoss(size_average=None, reduce=None, reduction='mean')

功能:

用于一个样本属于多个类别时的分类任务。例如一个四分类任务,样本x属于第0类,第1类,不属于第2类,第3类。

计算公式:

\[ loss(x,y)=\frac{1}{N}\sum_{i=1 \, i\neq y_{j}}^{n}\sum_{j=1}^{y_{j}\neq0}[max(0,1-(x_{y_{j}}-x_{i}))] \]

参数:

size_average(bool):当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。

reduce(bool):返回值是否为标量,默认为True。

Input: (C) or (N,C) where N is the batch size and C is the number of classes.

Target: (C) or (N,C), same shape as the input.

11. SoftMarginLoss

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

功能:

多标签二分类问题,这 N项都是二分类问题,其实就是把 N 个二分类的 loss 加起来,化简一下。其中 y 只能取 1,−1两种,代表正类和负类。和下面的其实是等价的,只是 y 的形式不同。

计算公式:

\[ loss(x,y)=\frac{1}{N}log(1+e^{-y_{i}x_{i}}) \]

参数:

size_average(bool):当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。

reduce(bool):返回值是否为标量,默认为True。

12. MultiLabelSoftMarginLoss

CLASS torch.nn.MultiLabelSoftMarginLoss(weight=None, size_average=None, reduce=None, reduction='mean')

功能:

上面的多分类版本,根据最大熵的多标签 one-versue-all 损失,其中 yy 只能取 1,01,0 两种,代表正类和负类。

计算公式:

\[ loss(x,y)=-\sum_{i=1}^{N}[y_{i}log\frac{e^{x_{i}}}{1+e^{x_{i}}}+(1-y_{i})log\frac{1}{1+e^{x_{i}}}] \]

参数:

weight(Tensor):为每个类别的loss设置权值。weight必须是float类型的tensor,其长度要于类别C一致,即每一个类别都要设置有weight。

13. CosineEmbeddingLoss

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

功能:

用Cosine函数来衡量两个输入是否相似。

计算公式:

\[ loss(x,y)=1-cos(x,y)\quad if\: y=1 \]

\[ loss(x,y)=max(0,cos(x,y)+margin)\quad if\:y=-1 \]

参数:

margin(float): 取值范围[-1,1], 推荐设置范围 [0, 0.5]

size_average(bool):当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。

reduce(bool):返回值是否为标量,默认为True。

14. MultiMarginLoss

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

功能:

计算多分类的折页损失。

计算公式:

\[ loss(x,y)=\frac{1}{N}\sum_{i=1\, i\neq y}^{N}max(0,(margin-x_{y}+x_{i})^{p}) \]

其中 1≤y≤N1≤y≤N 表示标签,pp 默认取 1,marginmargin 默认取 1,也可以取别的值。

参数:

p(int):默认值为1,仅可选1或者2。

margin(float):默认值为1。

weight(Tensor):为每个类别的loss设置权值。weight必须是float类型的tensor,其长度要于类别C一致,即每一个类别都要设置有weight

size_average(bool):当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。

reduce(bool):返回值是否为标量,默认为True。

展开阅读更多
来源:孟繁阳的博客
本篇文章来源于孟繁阳的博客,如需转载,请注明出处,谢谢配合。
孟繁阳的博客 » Pytorch学习笔记(四):损失函数
Loading...

发表评论

表情
图片 链接 代码

孟繁阳的博客

博客首页 文章归档