前言
本文首先重温了一些常见的对数函数运算法则,接着介绍了什么是信息量和熵,然后通过KL散度推出交叉熵损失函数的表达式,最后详解PyTorch常用的交叉熵损失函数CrossEntropyLoss()并进行代码实现。
对数函数运算法则
- 乘法规则
log ( a ⋅ b ) = log ( a ) + log ( b ) \log(a \cdot b) = \log(a) + \log(b) log(a⋅b)=log(a)+log(b) - 除法规则
log ( a b ) = log ( a ) − log ( b ) \log\left(\frac{a}{b}\right) = \log(a) - \log(b) log(ba)=log(a)−log(b) - 幂次规则
log ( b a ) = a ⋅ log ( b ) \log(b^a) = a \cdot \log(b) log(ba)=a⋅log(b) - 对数的换底公式
log a ( N ) = log b ( N ) log b ( a ) = ln ( N ) ln ( a ) = lg ( N ) lg ( a ) \log_a(N) = \frac{\log_b(N)}{\log_b(a)} = \frac{\ln(N)}{\ln(a)} = \frac{\lg(N)}{\lg(a)} loga(N)=logb(a)logb(N)=ln(a)ln(N)=lg(a)lg(N)
自然对数 ln 是以常数 e 为底的对数,其中 e 是一个无理数,大约等于 2.71828。
对数 lg 是以常数 10 为底的对数。
信息量
- 什么是信息量?
信息量是对事件的不确定性的度量。
假设我们听到了两件事,分别如下:
事件A:巴西队进入了2018世界杯决赛圈。
事件B:中国队进入了2018世界杯决赛圈。
仅凭直觉来说,显而易见事件B的信息量比事件A的信息量要大。
究其原因,是因为事件A发生的概率很大,事件B发生的概率很小。
所以当越不可能的事件发生了,我们获取到的信息量就越大。越可能发生的事件发生了,我们获取到的信息量就越小。(事件发生的概率越小,信息量越大;事件发生的概率越大,信息量越小。)
具体而言,对于一个离散随机事件 x,其发生的概率为 p(x),则该事件所包含的信息量 l(x) 定义为:
l
(
x
)
=
log
(
1
p
(
x
)
)
=
−
log
(
p
(
x
)
)
l(x)= \log\left(\frac{1}{p(x)}\right) = -\log(p(x))
l(x)=log(p(x)1)=−log(p(x))
在这里,对数函数可以采用任意底数,常见的有自然对数(以 e 为底)和常用对数(以 2为底)。选择不同的对数底数会影响信息量的单位,比如使用常用对数时,信息量的单位是比特(bit);使用自然对数时,单位是纳特(nat)。
熵
- 什么是熵?
熵指的是随机变量的熵;熵是随机变量不确定度的度量。
设X是一个离散型随机变量,分布律为 p(x) = p(X = x) ,x ∈ X 为取值空间集合 ,则随机变量X的熵 H(X) 定义为: H ( X ) = − ∑ x ∈ X p ( x ) log b p ( x ) \mathrm{H}(\mathrm{X})=-\sum_{\mathrm{x} \in \mathrm{X}} \mathrm{p}(\mathrm{x}) \log _{b} \mathrm{p}(\mathrm{x}) H(X)=−x∈X∑p(x)logbp(x)
-
熵的单位
单位取决于定义用到对数的底。
当b = 2,熵的单位是比特(bit);
当b = e,熵的单位是纳特(nat)。 -
例子
假设我们有一个离散随机变量 X,它有四个可能的结果:x1、x2、x3 和 x4,对应的概率分别为 0.1、0.2、0.3 和 0.4。我们要计算这个随机变量X的熵。
首先计算每个结果的信息量。根据信息量的定义,我们有:
I(x1) = -log(0.1)
I(x2) = -log(0.2)
I(x3) = -log(0.3)
I(x4) = -log(0.4)
然后,我们将这些信息量与对应的概率相乘,并将结果相加,得到随机变量 X 的熵:
H(X) = 0.1 * I(x1) + 0.2 * I(x2) + 0.3 * I(x3) + 0.4 * I(x4)
- 信息量是事件的信息量,熵是随机变量的信息量;
相对熵(KL散度)
-
什么是相对熵?
相对熵又称KL散度,如果我们对于同一个随机变量 X 有两个单独的概率分布 P(x) 和 Q(x),我们可以使用 KL 散度(Kullback-Leibler (KL) divergence)来衡量这两个分布的差异。 -
KL散度的计算公式
D K L ( p ∥ q ) = ∑ i = 1 n p ( x i ) log ( p ( x i ) q ( x i ) ) D_{K L}(p \| q)=\sum_{i=1}^{n} p\left(x_{i}\right) \log \left(\frac{p\left(x_{i}\right)}{q\left(x_{i}\right)}\right) DKL(p∥q)=i=1∑np(xi)log(q(xi)p(xi))
n代表的是概率分布的样本空间的大小,即随机变量可能的取值个数。
D K L {D_{KL}} DKL的值越小,表示Q分布越接近P分布。 -
在机器学习中的应用
在机器学习中,P往往用来表示样本的真实分布,比如[1,0,0]表示当前样本属于第一类。Q用来表示模型所预测的分布,比如[0.7,0.2,0.1]。
交叉熵
-
什么是交叉熵?
交叉熵是一个衡量两个概率分布之间差异的很好的度量。 -
KL散度公式变形
由于 log ( q ( x i ) p ( x i ) ) = log ( p ( x i ) ) − log ( q ( x i ) ) \log \left(\frac{q(x_i)}{p(x_i)}\right) = \log(p(x_i)) - \log(q(x_i)) log(p(xi)q(xi))=log(p(xi))−log(q(xi))
我们对KL散度的公式进行变形,可以得到:
D K L ( p ∥ q ) = ∑ i = 1 n p ( x i ) log ( p ( x i ) ) − ∑ i = 1 n p ( x i ) log ( q ( x i ) ) D_{KL}(p \| q) = \sum_{i=1}^{n} p(x_i) \log(p(x_i)) - \sum_{i=1}^{n} p(x_i) \log(q(x_i)) DKL(p∥q)=i=1∑np(xi)log(p(xi))−i=1∑np(xi)log(q(xi))
这里,第一个求和项是概率分布 p {p} p的熵(在前面加个“-”号)(entropy),记作 H ( p ) H(p) H(p) :
H ( p ) = ∑ i = 1 n p ( x i ) log ( p ( x i ) ) = − H ( X ) H(p) = \sum_{i=1}^{n} p(x_i) \log(p(x_i))=-H(X) H(p)=i=1∑np(xi)log(p(xi))=−H(X)
第二个求和项则是 p {p} p 相对于 q {q} q 的交叉熵,记做 H ( p , q ) {H(p,q)} H(p,q):
H ( p , q ) = − ∑ i = 1 n p ( x i ) log ( q ( x i ) ) H(p, q) = -\sum_{i=1}^{n} p(x_i) \log(q(x_i)) H(p,q)=−i=1∑np(xi)log(q(xi))
因此,我们可以将KL散度重写为熵和交叉熵的差:
D K L ( p ∥ q ) = H ( p ) − H ( p , q ) D_{KL}(p \| q) = H(p) - H(p, q) DKL(p∥q)=H(p)−H(p,q) -
交叉熵损失函数
由于 H ( p ) {H(p)} H(p),即熵,它描述了随机变量的不确定性。在机器学习中,训练数据的分布通常是固定的,因此 H ( p ) {H(p)} H(p)是一个常数,不影响模型的训练过程。我们的目标是使模型预测的概率分布 q {q} q尽可能接近真实分布
p {p} p,就是最小化交叉熵 H ( p , q ) H(p, q) H(p,q),而不是去调整 H ( p ) {H(p)} H(p)。
所以我们把交叉熵损失函数定义为:
H ( p , q ) = − ∑ i = 1 n p ( x i ) log ( q ( x i ) ) H(p, q) = -\sum_{i=1}^{n} p(x_i) \log(q(x_i)) H(p,q)=−i=1∑np(xi)log(q(xi))
其中, n {n} n为类别数量, x i {x_i} xi代表第 i {i} i个类别的示例, p ( x i ) {p({x_i})} p(xi)是真实分布中属于第 i {i} i类的概率(对于二分类问题,如果示例属于正类,则 p ( x i ) = 1 {p({x_i})=1} p(xi)=1,否则 p ( x i ) = 0 {p({x_i})=0} p(xi)=0), q ( x i ) {q({x_i})} q(xi)是模型预测的第 i {i} i类的概率。
通常情况下,真实标签label( p ( x i ) {p({x_i})} p(xi))是一个 1 ∗ n {1*n} 1∗n的向量,里面有 n {n} n个值,并且只有一个值是1,其他 n − 1 {n-1} n−1个值都是0。(即真实标签对应位置的那个值是1,其他都是0),所以这个公式有一个更简单的形式:
H ( p , q ) = − l o g ( q ( x i ) ) H(p, q) = -log(q(x_i)) H(p,q)=−log(q(xi))
由于真实标签label( p ( x i ) {p({x_i})} p(xi))中有且只有一个值是1,我们只需要计算真实值对应的概率熵值就可以了。
CrossEntropyLoss
- 参考文章
- CrossEntropyLoss
CrossEntropyLoss是PyTorch中一个常用的损失函数,主要用于处理多分类问题。它结合了Softmax函数、对数损失(Log Loss)和负对数似然损失(Negative Log Likelihood Loss),使得模型能够学习到更精确的概率分布,从而提高分类性能。 - 具体来说
Pytorch中CrossEntropyLoss()函数的主要是将softmax-log-NLLLoss合并到一块得到的结果。
1、Softmax后的数值都在0~1之间,所以 ln 之后值域是负无穷到0。
2、然后将Softmax之后的结果取log,将乘法改成加法减少计算量,同时保障函数的单调性 。
3、NLLLoss 的结果就是把上面的输出与 Label 对应的那个值拿出来,去掉负号,再求均值。 - 代码示例
一个实例:直接使用pytorch中的 nn.CrossEntropyLoss() 计算得到的结果与 softmax-log-NLLLoss计算得到的结果是一致的。
import torch
import torch.nn as nn
x_input = torch.randn(3, 3) # 随机生成输入
print('x_input:\n', x_input)
y_target = torch.tensor([1, 2, 0]) # 设置输出具体值 print('y_target\n',y_target)
# 计算输入softmax,此时可以看到每一行加到一起结果都是1
softmax_func = nn.Softmax(dim=1)
soft_output = softmax_func(x_input)
print('soft_output:\n', soft_output)
# 在softmax的基础上取log
log_output = torch.log(soft_output)
print('log_output:\n', log_output)
# 对比softmax与log的结合与nn.LogSoftmaxloss(负对数似然损失)的输出结果,发现两者是一致的。
logsoftmax_func=nn.LogSoftmax(dim=1)
logsoftmax_output=logsoftmax_func(x_input)
print('logsoftmax_output:\n', logsoftmax_output)
# pytorch中关于NLLLoss的默认参数配置为:reducetion=True、size_average=True
nllloss_func = nn.NLLLoss()
nlloss_output = nllloss_func(logsoftmax_output, y_target)
print('nlloss_output:\n', nlloss_output)
# 直接使用pytorch中的loss_func=nn.CrossEntropyLoss()看与经过NLLLoss的计算是不是一样
crossentropyloss = nn.CrossEntropyLoss()
crossentropyloss_output = crossentropyloss(x_input, y_target)
print('crossentropyloss_output:\n', crossentropyloss_output)
代码运行结果:
x_input:
tensor([[ 0.3321, 0.5043, -1.1833],
[-2.1702, -1.8563, 1.0706],
[ 1.7341, 0.2554, -1.3358]])
soft_output:
tensor([[0.4153, 0.4934, 0.0913],
[0.0358, 0.0490, 0.9152],
[0.7847, 0.1789, 0.0364]])
log_output:
tensor([[-0.8787, -0.7064, -2.3940],
[-3.3295, -3.0155, -0.0886],
[-0.2424, -1.7212, -3.3123]])
logsoftmax_output:
tensor([[-0.8787, -0.7064, -2.3940],
[-3.3295, -3.0155, -0.0886],
[-0.2424, -1.7212, -3.3123]])
nlloss_output:
tensor(0.3458)
crossentropyloss_output:
tensor(0.3458)
binary_cross_entropy
- 说明
binary_cross_entropy 是二分类的交叉熵,实际是多分类 CrossEntropyLoss 的一种特殊情况,当多分类中,类别只有两类时,即 0 或者 1,即为二分类,二分类也是一个逻辑回归问题,也可以套用逻辑回归的损失函数。 - 使用
在PyTorch中,可以通过torch.nn.functional模块中的binary_cross_entropy函数来实现这一损失函数。 - 代码示例
import torch
import torch.nn as nn
input = torch.rand(1, 3, 3)
target = torch.rand(1, 3, 3).random_(2)
print(input)
print(target)
input = torch.sigmoid(input)
output = torch.nn.functional.binary_cross_entropy(input, target)
print(output)
代码运行效果:
# input
tensor([[[0.0091, 0.8304, 0.5860],
[0.8770, 0.7744, 0.5909],
[0.3012, 0.0784, 0.7481]]])
# target
tensor([[[1., 1., 0.],
[0., 0., 0.],
[1., 0., 0.]]])
# output
tensor(0.8790)
- 实际在计算中如果使用binary_cross_entropy_with_logits 就不需要先做softmax处理。
在机器学习中,binary_cross_entropy 和 binary_cross_entropy_with_logits 都是用于二分类问题的损失函数,但它们在处理模型输出方面有所不同。
binary_cross_entropy 通常用于处理已经通过 sigmoid 或其他激活函数转换过的模型输出,这些输出表示为概率值,范围在 0 到 1 之间。在这种情况下,损失函数直接比较模型预测的概率分布和真实标签。
然而,binary_cross_entropy_with_logits 适用于处理未经激活函数转换的原始模型输出,也称为 logits。在这种情况下,损失函数内部会自动应用 sigmoid 函数将 logits 转换为概率值,然后再计算损失。这意味着在使用 binary_cross_entropy_with_logits 时,开发者无需手动对模型输出进行 sigmoid 转换。
- 代码示例
不需要对输入进行sigmoid处理,因为binary_cross_entropy_with_logits函数会自动对输入进行sigmoid处理。以下是修改后的代码:
import torch
import torch.nn as nn
input = torch.rand(1, 3, 3)
target = torch.rand(1, 3, 3).random_(2)
print(input)
print(target)
output = torch.nn.functional.binary_cross_entropy_with_logits(input, target)
print(output)
代码运行效果:
tensor([[[0.7857, 0.6012, 0.4140],
[0.0689, 0.9600, 0.7193],
[0.9429, 0.6011, 0.7957]]])
tensor([[[0., 1., 1.],
[1., 0., 1.],
[1., 1., 1.]]])
tensor(0.6205)