import torch import torch.nn as nn import numpy as np import math import torch.nn.functional as F
1.L1Loss
class torch.nn.L1Loss(size_average=None, reduce=None)
官方文档中仍有reduction='elementwise_mean’参数,但代码实现中已经删除该参数
功能: 计算output和target之差的绝对值,可选返回同维度的tensor或者是一个标量。
计算公式:
参数: reduce(bool)- 返回值是否为标量,默认为True
size_average(bool)- 当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。
# 设置预测值 output = torch.tensor([[2,4],[6,8]]).float() # tensor([[2., 4.], # [6., 8.]]) # 设置标签值 target = torch.ones(2,2) # tensor([[1., 1.], # [1., 1.]]) # 设置三种不同参数的L1Loss # reduce=False返回一个非标量, reduceFalse_averageTrue = nn.L1Loss(size_average=True, reduce=False) loss1 = reduceFalse_averageTrue(output,target) # reduce=True返回一个标量,并求平均 reduceTrue_averageTrue = nn.L1Loss(size_average=True, reduce=True) loss2 = reduceTrue_averageTrue(output,target) # reduce=True返回一个标量,但不用求平均而是求和 reduceFalse_averageTrue = nn.L1Loss(size_average=False, reduce=True) loss3 = reduceFalse_averageTrue(output,target) loss1,loss2,loss3 # 输出 # loss1的值:输出一个数组而不是标量 # (tensor([[1., 3.], # [5., 7.]]), # loss2的值:输出一个标量而且求平均 # tensor(4.), # loss3的值:输出一个标量求全部loss的和 # tensor(16.)) # 一般来说使用第二种,也是默认的那种
2.MSELoss
class torch.nn.MSELoss(size_average=None, reduce=None)
官方文档中仍有reduction='elementwise_mean’参数,但代码实现中已经删除该参数
功能: 计算output和target之差的平方,可选返回同维度的tensor或者是一个标量。
计算公式:
参数: reduce(bool)- 返回值是否为标量,默认为True
size_average(bool)- 当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。
# 设置预测值 output = torch.tensor([[2,4],[6,8]]).float() # tensor([[2., 4.], # [6., 8.]]) # 设置标签值 target = torch.ones(2,2) # tensor([[1., 1.], # [1., 1.]]) # 设置三种不同参数的MSELoss # reduce=False返回一个非标量, reduceFalse_averageTrue = nn.MSELoss(size_average=True, reduce=False) loss1 = reduceFalse_averageTrue(output,target) # reduce=True返回一个标量,并求平均 reduceTrue_averageTrue = nn.MSELoss(size_average=True, reduce=True) loss2 = reduceTrue_averageTrue(output,target) # reduce=True返回一个标量,但不用求平均而是求和 reduceFalse_averageTrue = nn.MSELoss(size_average=False, reduce=True) loss3 = reduceFalse_averageTrue(output,target) loss1,loss2,loss3 # 输出 # loss1的值:输出一个数组而不是标量 # (tensor([[ 1., 9.], # [25., 49.]]), # loss2的值:输出一个标量而且求平均 # tensor(21.), # loss3的值:输出一个标量求全部loss的和 # tensor(84.) # 一般来说使用第二种,也是默认的那种
3.NLLoss
class torch.nn.NLLLoss(weight=None, size_average=None, ignore_index=-100, reduce=None,reduction=‘elementwise_mean’)
计算公式:loss(input, class) = -input[class]
功能: 不好用言语描述其功能!举个例,三分类任务,input=[-1.233, 2.657, 0.534], 真实标签为2(class=2),则loss为-0.534。就是对应类别上的输出,取一个负号!感觉被NLLLoss的名字欺骗了。
实际应用: 常用于多分类任务,但是input在输入NLLLoss()之前,需要对input进行log_softmax函数激活,即将input转换成概率分布的形式,并且取对数。其实这些步骤在CrossEntropyLoss中就有,如果不想让网络的最后一层是log_softmax层的话,就可以采用CrossEntropyLoss完全代替此函数。
即 CrossEntropyLoss = log_softmax + NLLLoss
参数:
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,除的时候的分母也不会统计那一类的样本。
例子可以参考:https://blog.csdn.net/weixin_44751294/article/details/115281926 中的对应部分
# 设置预测值 output = torch.tensor([[0.6,0.2],[0.4,1.2],[0.5,0.8]]) # tensor([[0.6000, 0.2000], # [0.4000, 1.2000], # [0.5000, 0.8000]]) # 设置标签值 target = torch.tensor([0,1,0]) # tensor([0, 1, 0]) # 可以理解为现在将三张图片分成两类,而真实的类比已经在target中给出,分别是第一类,第二类与第三类 # 所以如果分别对每一张图片求最大释然估计,也就是nlloss时,也就是标签对应的那个类别添加一个符号 # 对于第一张照片来说:相当于是 -0.6 # 对于第二张照片来说:相当于是 -1.2 # 对于第三张照片来说:相当于是 -0.5 # 下面进行计算验证 # 设置三种不同参数的NLLoss # reduce=False返回一个非标量, reduceFalse_averageTrue = nn.NLLLoss(size_average=True, reduce=False) loss1 = reduceFalse_averageTrue(output,target) # reduce=True返回一个标量,并求平均 reduceTrue_averageTrue = nn.NLLLoss(size_average=True, reduce=True) loss2 = reduceTrue_averageTrue(output,target) # reduce=True返回一个标量,但不用求平均而是求和 reduceFalse_averageTrue = nn.NLLLoss(size_average=False, reduce=True) loss3 = reduceFalse_averageTrue(output,target) loss1,loss2,loss3 # 输出 # loss1的值:输出一个数组而不是标量 # tensor([-0.6000, -1.2000, -0.5000]) # loss2的值:输出一个标量而且求平均 # tensor(-0.7667) # loss3的值:输出一个标量求全部loss的和 # tensor(-2.3000) # 一般来说使用第二种,也是默认的那种
4.CrossEntropyLoss
class torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None,reduction=‘elementwise_mean’)
功能: 将输入经过softmax激活函数之后,再计算其与target的交叉熵损失。即该方法将nn.LogSoftmax()和 nn.NLLLoss()进行了结合。严格意义上的交叉熵损失函数应该是nn.NLLLoss()。
补充:小谈交叉熵损失函数 交叉熵损失(cross-entropy Loss) 又称为对数似然损失(Log-likelihood Loss)、对数损失;二分类时还可称之为逻辑斯谛回归损失(Logistic Loss)。交叉熵损失函数表达式为 L = - sigama(y_i * log(x_i))。pytroch这里不是严格意义上的交叉熵损失函数,而是先将input经过softmax激活函数,将向量“归一化”成概率形式,然后再与target计算严格意义上交叉熵损失。 在多分类任务中,经常采用softmax激活函数+交叉熵损失函数,因为交叉熵描述了两个概率分布的差异,然而神经网络输出的是向量,并不是概率分布的形式。所以需要softmax激活函数将一个向量进行“归一化”成概率分布的形式,再采用交叉熵损失函数计算loss。 再回顾PyTorch的CrossEntropyLoss(),官方文档中提到时将nn.LogSoftmax()和 nn.NLLLoss()进行了结合,nn.LogSoftmax() 相当于激活函数 , nn.NLLLoss()是损失函数,将其结合,完整的是否可以叫做softmax+交叉熵损失函数呢?
CrossEntropyLoss = log_softmax + NLLLoss
例子可以参考:https://blog.csdn.net/weixin_44751294/article/details/115281926 中的对应部分
# 设置预测值 output = torch.tensor([[0.6,0.1,0.3],[1.2,0.4,0.6]],requires_grad=True) # tensor([[0.6000, 0.1000, 0.3000], # [1.2000, 0.4000, 0.6000]], requires_grad=True) # 设置标签值 target = torch.tensor([0,1]).type(torch.LongTensor) # tensor([0, 1]) # 现在对其求交叉熵,得到一个标量,并且求平均 loss_f = nn.CrossEntropyLoss(weight=None, size_average=True, reduce=True) loss = loss_f(output, target) # 输出为:tensor(1.1728, grad_fn=<NllLossBackward>) # 现在进行求解论证 # 其中我们先对其求softmax,然后再取对数 log_output = F.log_softmax(output) # tensor([[-0.8533, -1.3533, -1.1533], # [-0.6922, -1.4922, -1.2922]], grad_fn=<LogSoftmaxBackward>) # 此时,由于我们设置的标签值是0,1,也就是对于第一张图像选择的是-0.8533,对第二张图像选择是-1.4922 # 然后将这两个值进行最大释然运算,也就是 # -(-0.8533 + -1.4922)/2 = 1.1728 # CrossEntropyLoss = log_softmax + NLLLoss
其实可以从公式上对这个操作进行理解,我们现在是希望预测值是离真实值越接近越好的,也就是希望softmax生成出来的数据的某一个数据接近于1,这样的话取log对数之后这个数值会接近于0,而此时如果标签值也恰好地选定为该数值,则损失函数输出值便为0,达到了最优的效果。
而如果softmax之后是一个比较小的数值,也就是神经网络判断该类别为一个比较低的概率之后,也就是接近于0,而此时取log对数之后会变成一个很小的数值,也就是一个很大的负数负数。而去过此时标签值正好为这个数,那么loss函数输出值便会很高,也就是预测严重的错误。
基于这个思想,深度学习网络就是希望不断的优化,调整这个loss输出,使得预测值比较的符合标签值。
5.KLDivLoss
class torch.nn.KLDivLoss(size_average=None, reduce=None, reduction=‘elementwise_mean’)
功能: 计算input和target之间的KL散度( Kullback–Leibler divergence)。
计算公式:
真实的计算式子为:loss = p(x) * [logp(x) - q(x)]
补充:KL散度 KL散度( Kullback–Leibler divergence) 又称为相对熵(Relative Entropy),用于描述两个概率分布之间的差异。计算公式(离散时):
其中p表示真实分布,q表示p的拟合分布, D(P||Q)表示当用概率分布q来拟合真实分布p时,产生的信息损耗。这里的信息损耗,可以理解为损失,损失越低,拟合分布q越接近真实分布p。同时也可以从另外一个角度上观察这个公式,即计算的是 p 与 q 之间的对数差在 p 上的期望值。 特别注意,D(p||q) ≠ D(q||p), 其不具有对称性,因此不能称为K-L距离。
信息熵 = 交叉熵 - 相对熵 从信息论角度观察三者,其关系为信息熵 = 交叉熵 - 相对熵。在机器学习中,当训练数据固定,最小化相对熵 D(p||q) 等价于最小化交叉熵 H(p,q) 。
参数:(与上类似)
size_average(bool)- 当reduce=True时有效。为True时,返回的loss为平均值,平均值为element-wise的,而不是针对样本的平均;为False时,返回是各样本各维度的loss之和。 reduce(bool)- 返回值是否为标量,默认为True。
使用注意事项: 要想获得真正的KL散度,需要如下操作:
- reduce = True ;size_average=False
- 计算得到的loss 要对batch进行求平均
# 设置预测值与标签值 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() # output,target # 输出为: # (tensor([[0.1132, 0.5477, 0.3390]], requires_grad=True), # tensor([[0.8541, 0.0511, 0.0947]])) # 进行两组实验,分别取平均或者不取平均 loss_f = nn.KLDivLoss(size_average=True, reduce=False) loss_f_mean = nn.KLDivLoss(size_average=True, reduce=True) # 求出数值 loss_1 = loss_f(output, target) loss_mean = loss_f_mean(output, target) # loss_1,loss_mean # 输出为: # (tensor([[-0.2314, -0.1800, -0.2553]], grad_fn=<KlDivBackward>), # tensor(-0.2222, grad_fn=<KlDivBackward>)) # 下面进行手动的验算 # 先转换成numpy的格式 target = target.detach().numpy() output = output.detach().numpy() # 利用公式进行验算 # loss = p(x) * [logp(x) - q(x)] loss = target*(np.log(target)-output) # 输出为: # array([[-0.23138168, -0.17995737, -0.25531512]], dtype=float32) # 验证结果是与上类似的
6.BCELoss
class torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction=‘elementwise_mean’)
功能: 二分类任务时的交叉熵计算函数。此函数可以认为是nn.CrossEntropyLoss函数的特例。其分类限定为二分类,y必须是{0,1}。还需要注意的是,input应该为概率分布的形式,这样才符合交叉熵的应用。所以在BCELoss之前,input一般为sigmoid激活层的输出,官方例子也是这样给的。该损失函数在自编码器中常用。
计算公式:
参数:
weight(Tensor)- 为每个类别的loss设置权值,常用于类别不均衡问题。
size_average(bool)- 当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。
reduce(bool)- 返回值是否为标量,默认为True
对于BCELoss来说,输出值与真实值的数据的shape必须是相同的,否则会出错
# 设置输出值 output = torch.tensor([[0.2,0.8],[0.9,0.1]],requires_grad=True).float() # output.shape:torch.Size([2, 2]) # 设置真实值 target = torch.tensor([[0,1],[1,0]]).float() # target.shape:torch.Size([2, 2]) # 将输出值变成一个概率分布,因为由于涉及log的计算,所以输出值必须是0-1之间的 sig_output = F.sigmoid(output) # tensor([[0.5498, 0.6900], # [0.7109, 0.5250]], grad_fn=<SigmoidBackward>) # 设置两种损失值的求法 loss_rf = nn.BCELoss(size_average=True,reduce=False) loss_rt = nn.BCELoss(size_average=True,reduce=True) # 计算损失值 loss1 = loss_rf(sig_output,target) loss2 = loss_rt(sig_output,target) # loss1,loss2:输出为: # (tensor([[0.7981, 0.3711], # [0.3412, 0.7444]], grad_fn=<BinaryCrossEntropyBackward>), # tensor(0.5637, grad_fn=<BinaryCrossEntropyBackward>)) # 下面进行手动的验算 # 转换格式 output = output.detach().numpy() target = target.detach().numpy() # 根据公式:ln = -[yn*log(xn)+(1-yn)*log(1-xn)] (math.log(sig_output[0][0])*target[0][0]+math.log(1-sig_output[0][0])*(1-target[0][0]))*-1 # 输出为:tensor(0.7981),与0.7981相同,验证成功 # 而假设现在输出值进行训练优化之后变成了 output = torch.tensor([[0.1,0.9],[0.95,0.05]],requires_grad=True).float() sig_output = F.sigmoid(output) # 输出为: # tensor([[0.5250, 0.7109], # [0.7211, 0.5125]], grad_fn=<SigmoidBackward>) # 相较之前的每个参数都有了一点点的进步,现在来计算更新后的每个样本的损失值 loss1 = loss_rf(sig_output,target) loss2 = loss_rt(sig_output,target) # loss1,loss2输出为: # (tensor([[0.7444, 0.3412], # [0.3270, 0.7185]], grad_fn=<BinaryCrossEntropyBackward>), # tensor(0.5327, grad_fn=<BinaryCrossEntropyBackward>)) # 可以看见,无论是个体的样本还是总体的损失函数值,都得到了改进,也就是达到了优化的目的
参考链接:https://zhuanlan.zhihu.com/p/61379965