文章目录
- 基础概念
- PyTorch实现与机制
- nn.CrossEntropyLoss
- nn.NLLLoss()
- nn.BCELoss()
- nn.BCEWithLogitsLoss()
- nn.L1Loss()
- nn.MSELoss()
- nn.SmoothL1Loss()
- nn.PoissonNLLLoss()
- nn.KLDivLoss()
- nn.MarginRankingLoss()
- nn.MultiLabelMarginLoss()
- nn.SoftMarginLoss()
- nn.MultiLabelSoftMarginLoss()
- nn.MultiMarginLoss()
- nn.TripletMarginLoss()
- nn.HingeEmbeddingLoss()
- nn.CosineEmbeddingLoss()
- nn.CTCLoss()
该篇笔记整理自余庭嵩的讲解。如果主要看损失函数如何使用则先看交叉熵损失函数的使用部分,该部分对损失函数pytorch实现时的各变量解释详细,并且其他损失函数的reduction变量与其大同小异,在这些部分里就不再重新说明reduction变量了。
基础概念
损失函数就是衡量模型输出与真实标签之间的差异。除损失函数外,平常接触到似乎也包含类似功能的还有代价函数和目标函数。那么这三者之间的区别和联系是什么呢?
损失函数(Loss Function)
功能:计算一个样本输出和真实标签之间的差异,表达式如下
代价函数(Cost Function)
功能:计算整个训练集的loss的平均值,表达式如下
目标函数(Objective Function)
定义:在机器学习中代表最终的训练目标,表达式如下
这里的regularization代表正则化。
PyTorch实现与机制
在定义损失函数的时候,经常可以看到首先会对损失函数的计算规则进行定义,以交叉熵损失函数为例,定义语句如下:
criterion = torch.nn.CrossEntropyLoss()
执行这句话的时候,就可以看到,其实所有损失函数都会继承一个父类,这个父类的名称是_Loss,其内容如下:
class _Loss(Module):
def __init__(self, size_average=None, reduce=None, reduction='mean'):
super(_Loss, self).__init__()
if size_average is not None or reduce is not None:
self.reduction = _Reduction.legacy_get_string(size_average, reduce)
else:
self.reduction = reduction
定义完成之后,就是执行部分了,还是以交叉熵损失为例,执行的语句如下:
loss = criterion(outputs, labels)
这句话本质上也是属于前向传播的一部分,同样也是会执行torch.nn.Module的forward函数。forward函数内容如下:
def forward(self, input, target):
return F.cross_entropy(input, target, weight=self.weight,
ignore_index=self.ignore_index, reduction=self.reduction)
而forward函数的核心是调用了torch.nn.functional里定义的函数cross_entropy,这个函数的内容如下:
if size_average is not None or reduce is not None:
reduction = _Reduction.legacy_get_string(size_average, reduce)
return nll_loss(log_softmax(input, 1), target, weight, None, ignore_index, None, reduction)
于是我们就可以通过这样的调用关系清楚了解到损失函数的功能与运行机制。
nn.CrossEntropyLoss
功能与使用
由前面pytorch实现机制中的介绍可以看到,交叉熵函数是nn.Logsoftmax()与nn.NLLLoss()结合,进行交叉熵计算。这里的nn.Logsoftmax()函数将输入的数据进行了归一化处理,变成了概率分布的形式,再取log后进行输出。而nn.NLLLoss()则是取负号操作。整个过程的数学表达式如下,无weight时:
有weight时:
在使用CrossEntropyLoss函数时,其主要参数有
- weight:各类别的loss设置权值(例如某类计算好loss后再乘1.5倍)
- ignore_ index:忽略某个类别(在某类不计算loss)
- reduction :计算模式,可为none/sum/mean
- size_average&reduce:不管,现在这两个变量用reduction就可以替代其作用了
其中
none代表逐个元素计算
sum代表所有元素求和,返回标量
mean代表加权平均,返回标量
reduction需要以字符串的的形式赋值上面三个字符串中的任意一个。
数学原理
如果需要了解交叉熵,首先需要了解信息熵以及相对熵的概念。
信息熵
先来解释信息熵的概念,信息熵由香农提出,从热力学领域借鉴而来,其主要衡量了信息的不确定程度,一个信息的熵越大说明越不确定。其表达式如下:
这里可以发现,如果把log以及后面看成一个整体,信息熵似乎是在求某个东西的期望,这个东西就叫做自信息,其表达式如下:
这样就可以理解熵的概念,信息熵就是描述整个概率分布的不确定性,所以需要对自信息求期望,这样得出的结果才能评价整个概率分布。
值得一提的是,如果只有两个类别,那么两个类别概率相等的时候信息熵最大,此时的loss值是0.69。这就说明模型对数据没有任何判别能力。
相对熵(KL散度)
接下来解释相对熵:相对熵主要衡量了两个分布之间的差异,描述了两个分布之间的距离。但是需要注意的是,相对熵和距离函数有着本质区别。距离函数满足一个重要性质就是对称性:点P到点Q的距离等于点Q到点P的距离,而相对熵并不满足这个基本性质。其表达式如下:
上式中,第一个等式就是相对熵的定义式。其中P是真实的分布而Q是模型拟合的分布,上面表达式的意思就是用模型拟合的分布Q去逼近真实的分布P。
交叉熵
交叉熵的表达式如下:
结合信息熵和相对熵的表达式,可得如下关系
这里就可以明确,当训练目标是最小化交叉熵的时候,其实就是最小化相对熵,因为P代表真实的分布,也就是训练集的概率分布(其实就是one-hot),这是固定不变的,所以
是一个常值。
使用实例
现随机构造一个输入数据,这里注意标签的数据类型必须是torch.long:
inputs = torch.tensor([[1, 2], [1, 3], [1, 3]], dtype=torch.float)
target = torch.tensor([0, 1, 1], dtype=torch.long)
使用交叉熵损失函数的几个模式来对比一下结果:
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')
loss_none = loss_f_none(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)
print("Cross Entropy Loss:\n ", loss_none, loss_sum, loss_mean)
输出结果如下:
Cross Entropy Loss:
tensor([1.3133, 0.1269, 0.1269]) tensor(1.5671) tensor(0.5224)
由此可见三种reduction的作用。
nn.NLLLoss()
功能与使用
功能:实现负对数似然函数中的负号功能,也就是对输入取了个负号。
其各项参数与nn.CrossEntropyLoss的参数一样。
还是使用crossentropy的输入数据,观察NLLLoss在这个输入下的三种情况的输出
weights = torch.tensor([1, 1], dtype=torch.float)
loss_f_none_w = nn.NLLLoss(weight=weights, reduction='none')
loss_f_sum = nn.NLLLoss(weight=weights, reduction='sum')
loss_f_mean = nn.NLLLoss(weight=weights, reduction='mean')
loss_none_w = loss_f_none_w(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)
print("\nweights: ", weights)
print("NLL Loss", loss_none_w, loss_sum, loss_mean)
输出结果为
weights: tensor([1., 1.])
NLL Loss tensor([-1., -3., -3.]) tensor(-7.) tensor(-2.3333)
数学原理
其数学表达式很简单,描述如下
nn.BCELoss()
功能与使用
功能:二分类交叉熵损失函数,是交叉熵损失函数的一个特例。输入的标签是二分类的,要么是0,要么是1。
主要参数和前面的交叉熵损失函数的参数一致。
BCE原理是每一个神经元一一对应的去计算loss,所以输入的标签不再是整形而是浮点数,同时对输入的数据也有要求,BCE要求其范围必须在0到1之间,否则就会报错。所以对于输入inputs再输入进BCE损失函数之前要先走sigmoid函数将其映射成一个概率值。具体输入与使用举例如下:
inputs = torch.tensor([[1, 2], [2, 2], [3, 4], [4, 5]], dtype=torch.float)
target = torch.tensor([[1, 0], [1, 0], [0, 1], [0, 1]], dtype=torch.float)
inputs = torch.sigmoid(inputs)
weights = torch.tensor([1, 1], dtype=torch.float)
loss_f_none_w = nn.BCELoss(weight=weights, reduction='none')
loss_f_sum = nn.BCELoss(weight=weights, reduction='sum')
loss_f_mean = nn.BCELoss(weight=weights, reduction='mean')
loss_none_w = loss_f_none_w(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)
print("\nweights: ", weights)
print("BCE Loss", loss_none_w, loss_sum, loss_mean)
输出结果为
weights: tensor([1., 1.])
BCE Loss tensor([[0.3133, 2.1269],
[0.1269, 2.1269],
[3.0486, 0.0181],
[4.0181, 0.0067]]) tensor(11.7856) tensor(1.4732)
从输出结果就理解了什么是每一个神经元一一对应地计算loss。
数学原理
其数学原理和交叉熵差不多类似,在此不多赘述,直接看数学表达式即可理解:
nn.BCEWithLogitsLoss()
功能:结合Sigmoid与二分类交叉熵,也就是添加了sigmoid函数,是上面BCELoss的弥补。
在主要参数上有一点不同,这个函数在上面主要参数的基础上添加了pos_weight,代表正样本权值。这个参数主要是为了解决样本不均衡的问题,例如训练集中有100个正样本和900个负样本,那么我们的pos_weight就可以设置为9,以更关注正样本的loss,从而达到不偏斜的目的。
nn.L1Loss()
功能与使用
功能:计算inputs与target之差的绝对值
使用实例如下:
inputs = torch.ones((2, 2))
target = torch.ones((2, 2)) * 3
loss_f = nn.L1Loss(reduction='none')
loss = loss_f(inputs, target)
print("input:{}\ntarget:{}\nL1 loss:{}".format(inputs, target, loss))
输出结果为:
input:tensor([[1., 1.],
[1., 1.]])
target:tensor([[3., 3.],
[3., 3.]])
L1 loss:tensor([[2., 2.],
[2., 2.]])
数学原理
数学表达式很简单理解,如下所示
nn.MSELoss()
功能与使用
功能:计算inputs与target之差的平方
使用实例如下:
inputs = torch.ones((2, 2))
target = torch.ones((2, 2)) * 3
loss_f_mse = nn.MSELoss(reduction='none')
loss_mse = loss_f_mse(inputs, target)
print("MSE loss:{}".format(loss_mse))
输出结果为:
MSE loss:tensor([[4., 4.],
[4., 4.]])
数学原理
数学表达式很简单理解,如下所示
nn.SmoothL1Loss()
功能与使用
功能:平滑的L1Loss
使用实例如下,从-3到3的区间上平均取500个数据点,target都是0。用smoothL1和L1分别计算这500个样本的loss。
inputs = torch.linspace(-3, 3, steps=500)
target = torch.zeros_like(inputs)
loss_f = nn.SmoothL1Loss(reduction='none')
loss_smooth = loss_f(inputs, target)
loss_l1 = np.abs(inputs.numpy())
plt.plot(inputs.numpy(), loss_smooth.numpy(), label='Smooth L1 Loss')
plt.plot(inputs.numpy(), loss_l1, label='L1 loss')
plt.xlabel('x_i - y_i')
plt.ylabel('loss value')
plt.legend()
plt.grid()
plt.show()
输出结果就是下面数学原理部分的曲线图。
数学原理
采用平滑的L1Loss可以减轻离群点带来的影响,其数学表达式如下,首先整体误差的计算为(这里的reduction为’mean’)
对于
,其计算公式为
可能从公式上不能直观理解平滑L1loss改进了哪里,那么看一下下图两个损失函数的曲线图即可有直观感受:
nn.PoissonNLLLoss()
功能与使用
功能:泊松分布的负对数似然函数,适用于输出应该是泊松分布时的损失函数。
主要参数
- log_input:这里需要指定输入是否是对数形式,这影响了之后的计算公式,这是布尔型变量
- full:计算所有的loss,默认是false
- eps:修正项,避免输入是nan(log_input为false时使用,input是0的话输入就是nan,为避免对0取对数,所以在每个输入上加上eps)
使用实例如下:
inputs = torch.randn((2, 2))
target = torch.randn((2, 2))
loss_f = nn.PoissonNLLLoss(log_input=True, full=False, reduction='none')
loss = loss_f(inputs, target)
print("input:{}\ntarget:{}\nPoisson NLL loss:{}".format(inputs, target, loss))
输出结果为:
input:tensor([[0.6614, 0.2669],
[0.0617, 0.6213]])
target:tensor([[-0.4519, -0.1661],
[-1.5228, 0.3817]])
Poisson NLL loss:6.368335723876953
数学原理
运算过程的伪代码解释如下
log_input = True
loss(input, target) = exp (input) - target * input
log_input = False
loss(input, target) = input - target * log(input+eps)
nn.KLDivLoss()
功能与使用
功能:计算KLD(divergence),即KL散度,也叫做相对熵。
主要参数reduction中,除了经典的三个以外,还多出了batchmean这一选项,如果给reduction赋值为’batchmean’,那么就代表着在batchsize维度求平均值(就是计算得到的每一个元素的损失全部加起来之后除以batchsize,而不是mean计算方法下的元素个数)。
具体使用实例如下:
inputs = torch.tensor([[0.5, 0.3, 0.2], [0.2, 0.3, 0.5]])
inputs_log = torch.log(inputs)
target = torch.tensor([[0.9, 0.05, 0.05], [0.1, 0.7, 0.2]], dtype=torch.float)
loss_f_none = nn.KLDivLoss(reduction='none')
loss_f_mean = nn.KLDivLoss(reduction='mean')
loss_f_bs_mean = nn.KLDivLoss(reduction='batchmean')
loss_none = loss_f_none(inputs, target)
loss_mean = loss_f_mean(inputs, target)
loss_bs_mean = loss_f_bs_mean(inputs, target)
print("loss_none:\n{}\nloss_mean:\n{}\nloss_bs_mean:\n{}".format(loss_none, loss_mean, loss_bs_mean))
输出结果为:
loss_none:
tensor([[-0.5448, -0.1648, -0.1598],
[-0.2503, -0.4597, -0.4219]])
loss_mean:
-0.3335360586643219
loss_bs_mean:
-1.000608205795288
数学原理
KL散度求解的数学表达式前面交叉熵部分已经提过,回顾一下
但是在实际pytorch执行的时候和公式略有不同,损失值是以如下方式计算的
这里就解释了为什么需要提前将输入计算一个log-probability,也就是提前使用nn.logsoftmax()。
nn.MarginRankingLoss()
功能与使用
功能:计算两个向量间的相似度,通常用于排序任务。具体就是计算两组数据之间的差异,返回一个nxn的loss矩阵。
主要参数多了一个margin,代表的是边界值,也就是两个向量之间的差异值,默认值是0。
使用这个函数时,标签取-1或1,用来指示希望哪个元素大。
x1 = torch.tensor([[1], [2], [3]], dtype=torch.float)
x2 = torch.tensor([[2], [2], [2]], dtype=torch.float)
target = torch.tensor([1, 1, -1], dtype=torch.float)
loss_f_none = nn.MarginRankingLoss(margin=0, reduction='none')
loss = loss_f_none(x1, x2, target)
print(loss)
输出结果为:
tensor([[1., 1., 0.],
[0., 0., 0.],
[0., 0., 1.]])
数学原理
其数学表达式为
当y=1时,希望x1比x2大,当满足这个关系时,不产生loss;
当y=-1时,希望x2比x1大,当满足这个关系时,不产生loss。
nn.MultiLabelMarginLoss()
功能与使用
功能:多标签边界损失函数。
例如四分类任务,样本x属于0类与3类,那么x的标签是[0, 3, -1, -1],而不是我们通俗认为的one-hot。
使用实例:
x = torch.tensor([[0.1, 0.2, 0.4, 0.8]])
y = torch.tensor([[0, 3, -1, -1]], dtype=torch.long)
loss_f = nn.MultiLabelMarginLoss(reduction='none')
loss = loss_f(x, y)
print(loss)
输出结果为:
tensor([0.8500])
数学原理
标签所在的神经元减去不是标签所在的神经元的值。只有标签所在神经元比非标签所在神经元的值大超过1的时候才不会产生loss,否则都会产生loss。公式的python实现如下
x = x[0]
item_1 = (1-(x[0] - x[1])) + (1 - (x[0] - x[2]))
item_2 = (1-(x[3] - x[1])) + (1 - (x[3] - x[2]))
loss_h = (item_1 + item_2) / x.shape[0]
print(loss_h)
nn.SoftMarginLoss()
功能与使用
功能:计算二分类的logistic损失
使用实例如下:
inputs = torch.tensor([[0.3, 0.7], [0.5, 0.5]])
target = torch.tensor([[-1, 1], [1, -1]], dtype=torch.float)
loss_f = nn.SoftMarginLoss(reduction='none')
loss = loss_f(inputs, target)
print("SoftMargin: ", loss)
输出结果为:
SoftMargin: tensor([[0.8544, 0.4032],
[0.4741, 0.9741]])
数学原理
其数学表达如下:
nn.MultiLabelSoftMarginLoss()
功能与使用
功能:softmarginloss 的多标签版本
使用实例如下:
inputs = torch.tensor([[0.3, 0.7, 0.8]])
target = torch.tensor([[0, 1, 1]], dtype=torch.float)
loss_f = nn.MultiLabelSoftMarginLoss(reduction='none')
loss = loss_f(inputs, target)
print("MultiLabel SoftMargin: ", loss)
输出结果为:
MultiLabel SoftMargin: tensor([0.5429])
数学原理
数学表达式如下
其中i代表每一个神经元,C代表类的数量。这里标签就需要是01形式的,也就是说如果当前样本属于0类和3类,那么标签就是[1, 0, 0, 1]。上面表达式的意思就是当标签为1时计算前面一项,当标签为0时计算后面一项。
nn.MultiMarginLoss()
功能与使用
功能:计算多分类的折页损失
主要参数为margin和p,这里的p可选1或2。
使用实例
x = torch.tensor([[0.1, 0.2, 0.7], [0.2, 0.5, 0.3]])
y = torch.tensor([1, 2], dtype=torch.long)
loss_f = nn.MultiMarginLoss(reduction='none')
loss = loss_f(x, y)
print("Multi Margin Loss: ", loss)
输出结果
Multi Margin Loss: tensor([0.8000, 0.7000])
可见是一个样本产生一个loss。
数学原理
数学表达式为:
还是标签所在神经元与非标签所在神经元的差,这里的i不能等于标签。
nn.TripletMarginLoss()
功能与使用
功能:计算三元组损失(人脸验证中常使用的损失函数)
主要参数和上面一个损失函数一样,有p(范数的阶,默认为2)以及margin。
使用实例
anchor = torch.tensor([[1.]])
pos = torch.tensor([[2.]])
neg = torch.tensor([[0.5]])
loss_f = nn.TripletMarginLoss(margin=1.0, p=1)
loss = loss_f(anchor, pos, neg)
print("Triplet Margin Loss", loss)
输出结果
Triplet Margin Loss tensor(1.5000)
数学原理
数学表达式为
其中
a为anchor,p为positive,n为negative。这个损失函数的训练目标可描绘如下图所示
就是通过训练,使得positive距离anchor的距离比negative距离anchor的距离近。在人脸识别中的应用场景就是,anchor是自己的人脸,positive也是自己的人脸,negative是别人的人脸。由此训练即可达到最终人脸识别的目标。
nn.HingeEmbeddingLoss()
功能与使用
功能:计算两个输入的相似性,常用于非线性embedding和半监督学习。主要参数有margin(默认值是1)。这里要注意的是,输入的x应为两个输入之差的绝对值。
使用实例
inputs = torch.tensor([[1., 0.8, 0.5]])
target = torch.tensor([[1, 1, -1]])
loss_f = nn.HingeEmbeddingLoss(margin=1, reduction='none')
loss = loss_f(inputs, target)
print("Hinge Embedding Loss", loss)
输出结果
Hinge Embedding Loss tensor([[1.0000, 0.8000, 0.5000]])
数学原理
数学表达式为
nn.CosineEmbeddingLoss()
功能与使用
功能:采用余弦相似度计算两个输入之间的相似性。主要参数有margin,这里margin可取值为[-1, 1],推荐为[0, 0.5]。cosine更加关注的是方向上的差异。
使用实例
x1 = torch.tensor([[0.3, 0.5, 0.7], [0.3, 0.5, 0.7]])
x2 = torch.tensor([[0.1, 0.3, 0.5], [0.1, 0.3, 0.5]])
target = torch.tensor([[1, -1]], dtype=torch.float)
loss_f = nn.CosineEmbeddingLoss(margin=0., reduction='none')
loss = loss_f(x1, x2, target)
print("Cosine Embedding Loss", loss)
输出结果
Cosine Embedding Loss tensor([[0.0167, 0.9833]])
数学原理
其数学表达式为
在程序内部的运算规则表达式为
nn.CTCLoss()
功能与使用
功能:计算CTC损失,解决时序类数据的分类,CTC损失的全称是Connection Temporal Classification。主要参数有:
- blank:blank label
- zero_infinity:无穷大的值或梯度置0
- reduction:略
使用实例
T = 50 # Input sequence length
C = 20 # Number of classes (including blank)
N = 16 # Batch size
S = 30 # Target sequence length of longest target in batch
S_min = 10 # Minimum target length, for demonstration purposes
# Initialize random batch of input vectors, for *size = (T,N,C)
inputs = torch.randn(T, N, C).log_softmax(2).detach().requires_grad_()
# Initialize random batch of targets (0 = blank, 1:C = classes)
target = torch.randint(low=1, high=C, size=(N, S), dtype=torch.long)
input_lengths = torch.full(size=(N,), fill_value=T, dtype=torch.long)
target_lengths = torch.randint(low=S_min, high=S, size=(N,), dtype=torch.long)
ctc_loss = nn.CTCLoss()
loss = ctc_loss(inputs, target, input_lengths, target_lengths)
print("CTC loss: ", loss)
输出结果
CTC loss: tensor(7.5385, grad_fn=<MeanBackward1>)
数学原理
详情请参考文献Connectionist Temporal Classification: Labelling Unsegmented Labelling Unsegmented Sequence Data with Recurrent Neural Networks