粗略版快速总结
条件熵 H ( Q ∣ P ) = 联合熵 H ( P , Q ) − H ( P ) 条件熵H(Q∣P)=联合熵H(P,Q)−H(P) 条件熵H(Q∣P)=联合熵H(P,Q)−H(P)
信息增益 I ( P , Q ) = H ( P ) − H ( P ∣ Q ) = H ( P ) + H ( Q ) − H ( P , Q ) 信息增益 I(P,Q)=H(P)−H(P∣Q)=H(P)+H(Q)-H(P,Q) 信息增益I(P,Q)=H(P)−H(P∣Q)=H(P)+H(Q)−H(P,Q),也就是Information Gain,互信息
KL散度(相对熵) K L ( P , Q ) = − H ( P ) + 交叉熵 C ( P , Q ) KL(P,Q)=-H(P)+交叉熵C(P,Q) KL(P,Q)=−H(P)+交叉熵C(P,Q)
详细定义
如果一个样本是n类其中之一,也就是说target是onehot形式,例如三类那么target=[0,0,1],拿target=[0,0,1]来说就是
p
0
=
0
p_0=0
p0=0,
p
1
=
0
p_1=0
p1=0,
p
2
=
1
p_2=1
p2=1。写成表达式可以是
p
i
p_i
pi,n=3
那么经过神经网络运算出来的Logits可能是在(-inf,inf)之间,那么一般会通过softmax归一化到(0,1)之间,这个归一化到(0,1)之间的数我们可以用
q
i
q_i
qi来表示,当然对于上面有3类的例子来说,n=3
好了,既然明确了
p
i
p_i
pi是第i个类的在(0,1)之间target,
q
i
q_i
qi是第i个类的logit归一化到(0,1)之间的结果,那么开始各种定义了
相对熵(KL散度)
K L ( P , Q ) = ∑ i ∈ [ 0 , n − 1 ] p i l o g p i q i KL(P,Q)=\sum _{i \in[0,n-1]}p_i log \frac{p_i}{q_i} KL(P,Q)=i∈[0,n−1]∑pilogqipi
交叉熵(CE Loss)
C
E
(
P
,
Q
)
=
−
∑
i
∈
[
0
,
n
−
1
]
p
i
l
o
g
q
i
K
L
(
P
,
Q
)
=
H
(
P
)
+
C
E
(
P
,
Q
)
CE(P,Q)=-\sum _{i \in[0,n-1]}p_i log q_i \\ KL(P,Q) = H(P)+CE(P,Q)
CE(P,Q)=−i∈[0,n−1]∑pilogqiKL(P,Q)=H(P)+CE(P,Q)
来看一下Pytorch里的交叉熵是怎么实现的,手动验证下:
import torch
from torch import nn
import math
loss_f = nn.CrossEntropyLoss(reduction='mean')
output = torch.randn(2,3) #表示2个样本,3个类别
# target = torch.from_numpy(np.array([1, 0])).type(torch.LongTensor)
target = torch.LongTensor([0,2]) #表示label0和label2
loss = loss_f(output, target)
print('CrossEntropy loss: ', loss)
print(f'reduction=none,所以可以看到每一个样本loss,输出为[{loss}]')
def manual_cal(sample_index, target, output):
#输入是样本下标
sample_output = output[sample_index]
sample_target = target[sample_index]
x_class = sample_output[sample_target]
sample_output_len = len(sample_output)
log_sigma_exp_x = math.log(sum(math.exp(sample_output[i]) for i in range(sample_output_len)))
sample_loss = -x_class + log_sigma_exp_x
print(f'交叉熵手动计算loss{sample_index}:{sample_loss}')
return sample_loss
for i in range(2):
manual_cal(i, target, output)
# 如果nn.CrossEntropyLoss(reduction='mean')模式,刚好是手动计算的每个样本的loss取平均,最后输出的是一个值
# 如果nn.CrossEntropyLoss(reduction='none')模式,手动计算的loss0和loss1都会被列出来
(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这里不是严格意义上的交叉熵损失函数(下面会详细解释,pytorch中交叉熵不够严格主要是因为只能接受one hot),而是先将input经过softmax激活函数,将向量“归一化”成概率形式,然后再与target计算严格意义上交叉熵损失。 在多分类任务中,经常采用softmax激活函数+交叉熵损失函数,因为交叉熵描述了两个概率分布的差异,然而神经网络输出的是向量,并不是概率分布的形式。所以需要softmax激活函数将一个向量进行“归一化”成概率分布的形式,再采用交叉熵损失函数计算loss。 再回顾PyTorch的CrossEntropyLoss(),官方文档中提到时将nn.LogSoftmax()和 nn.NLLLoss()进行了结合,nn.LogSoftmax() 相当于激活函数 , nn.NLLLoss()是损失函数;
来感受一下交叉熵取值的妙处:当
q
i
q_i
qi很接近1时,
−
l
o
g
q
i
-logq_i
−logqi很接近0,如果此时
p
i
p_i
pi是1,这时候整体loss会很小;当
q
i
q_i
qi很接近0时,
−
l
o
g
q
i
-logq_i
−logqi很大,
p
i
p_i
pi是1,这时候整体loss会很大。所以
p
i
p_i
pi就是筛选的功能,在Pytorch中CrossEntropyLoss等于LogSoftmax和NLLLoss的结合:LogSoftmax是上面公式里的
l
o
g
(
e
x
p
(
x
[
c
l
a
s
s
]
)
∑
j
e
x
p
(
x
[
j
]
)
)
log(\frac{exp(x[class])}{\sum_jexp(x[j])})
log(∑jexp(x[j])exp(x[class])),实现了整个
l
o
g
q
i
logq_i
logqi的效果;NLLLoss就是给前面加了一个负号。所以在torch中的CrossEntropy = NLLLoss(LogSoftmax)
pytorch中交叉熵不够严格主要是因为只能接受one hot,也就是说torch中的target只能明确指明是哪个target,而不是上面公式
p
i
p_i
pi是(0,1)之间,所以在Pytorch中还保留了KLDivLoss这个loss来接受广泛的取值:
import torch.nn.functional as F
import torch
import torch.nn as nn
# nn.CrossEntropyLoss() 和 KLDivLoss 关系
y_pred = torch.tensor([[10.0, 0.0, -10.0], [8.0, 8.0, 8.0]])
y_true = torch.tensor([0, 2])
ce = nn.CrossEntropyLoss(reduction="none")(y_pred, y_true)
print(ce)
'''
输出shape是2,tensor([4.5418e-05, 1.0986e+00])
'''
# NLLLoss要求target只能是第几类下标,例如[0,2]表示[label0,label2],转成onehot就是[[1,0,0],[0,0,1]]
nll_log_softmax = nn.NLLLoss(reduction="none")(F.log_softmax(y_pred, dim=-1), y_true)
print(nll_log_softmax)
'''
输出shape是2,tensor([4.5418e-05, 1.0986e+00])
'''
one_hot = F.one_hot(y_true) #将第几类的下标转换成onehot形式,例如输入[0,2]表示[label0,label2],输出onehot就是[[1,0,0],[0,0,1]]
'''
# KLDivLoss要求target为float形式编码,one_hot是longtensor,
所以要one_hot.float();如果是普通的logics,要过一下softmax
# KLDivLoss也要求Logits经过LogSoftmax激活。LogSoftmax会把(-inf,inf)的Logits映射到(0,1)再映射到(-inf,0):
当用NLLLoss时,刚好多个负号loss变成(0,inf);当用KLDivLoss时,刚好多个熵。
回顾klLoss的公式 p_i*log(p_i/q_i),其中p_i是(0,1)范围内的targets
q_i是将logits映射到(0,1)范围内的结果,所以p_i和q_i都是(0,1)之间
KLDivLoss这个函数的特点就是把log(q_i)这一步扔给输入自己算,这个函数管的只是p_i*log(p_i)-p_i*input
NLLLoss这个函数的特点就是把p_i*log(p_i)也没了,只有-p_i*input,所以和LogSoftmax组合起来是CE
'''
kl = nn.KLDivLoss(reduction="none")(F.log_softmax(y_pred, dim=-1), one_hot.float())
print(kl) #输出shape是2*3
'''
tensor([[4.5418e-05, 0.0000e+00, 0.0000e+00],
[0.0000e+00, 0.0000e+00, 1.0986e+00]])
'''
a = F.softmax(torch.randn(2,3))
print(nn.KLDivLoss(reduction="none")(torch.log(a), a))
'''
输出是
tensor([[0., 0., 0.],
[0., 0., 0.]])
回顾klLoss的公式 p_i*log(p_i/q_i),其中p_i是(0,1)范围内的targets
q_i是将logits映射到(0,1)范围内的结果,所以p_i和q_i都是(0,1)之间
KLDivLoss这个函数的特点就是把log(q_i)这一步扔给输入自己算,这个函数管的只是p_i*log(p_i)-p_i*input
NLLLoss这个函数的特点就是把p_i*log(p_i)也没了,只有-p_i*input,所以和LogSoftmax组合起来是CE
'''
联合熵
H ( P , Q ) = − ∑ i ∈ [ 0 , n − 1 ] P ( p i , q i ) l o g P ( p i , q i ) H(P,Q)=-\sum _{i \in[0,n-1]}P(p_i,q_i)logP(p_i,q_i) H(P,Q)=−i∈[0,n−1]∑P(pi,qi)logP(pi,qi)
条件熵
注意下面
P
(
q
i
∣
p
i
)
P(q_i|p_i)
P(qi∣pi)表示
p
i
p_i
pi和
q
i
q_i
qi对应变量的条件概率,
P
(
p
i
,
q
i
)
P(p_i,q_i)
P(pi,qi)表示
p
i
p_i
pi和
q
i
q_i
qi对应变量的联合概率,写成这样只是为了简化但不够严谨。
H
(
Q
∣
P
)
=
∑
i
∈
[
0
,
n
−
1
]
p
i
H
(
Q
∣
P
=
p
i
)
H
(
Q
∣
P
)
=
−
∑
i
∈
[
0
,
n
−
1
]
p
i
∗
P
(
q
i
∣
p
i
)
l
o
g
P
(
q
i
∣
p
i
)
H
(
Q
∣
P
)
=
−
∑
i
∈
[
0
,
n
−
1
]
P
(
p
i
,
q
i
)
l
o
g
P
(
q
i
∣
p
i
)
H(Q|P)=\sum _{i \in[0,n-1]}p_iH(Q|P=p_i) \\ H(Q|P)=-\sum _{i \in[0,n-1]}p_i*P(q_i|p_i)logP(q_i|p_i) \\ H(Q|P)=-\sum _{i \in[0,n-1]}P(p_i,q_i)logP(q_i|p_i)
H(Q∣P)=i∈[0,n−1]∑piH(Q∣P=pi)H(Q∣P)=−i∈[0,n−1]∑pi∗P(qi∣pi)logP(qi∣pi)H(Q∣P)=−i∈[0,n−1]∑P(pi,qi)logP(qi∣pi)
上面就解释了为啥log里面是条件,外面是联合,更进一步地把里面也展开
H
(
Q
∣
P
)
=
−
∑
i
∈
[
0
,
n
−
1
]
P
(
p
i
,
q
i
)
l
o
g
P
(
q
i
∣
p
i
)
H
(
Q
∣
P
)
=
−
H
(
P
,
Q
)
−
∑
i
∈
[
0
,
n
−
1
]
P
(
p
i
,
q
i
)
l
o
g
P
(
p
i
)
H
(
Q
∣
P
)
=
−
H
(
P
,
Q
)
+
H
(
P
)
H(Q|P)=-\sum _{i \in[0,n-1]}P(p_i,q_i)logP(q_i|p_i) \\ H(Q|P)=-H(P,Q)-\sum _{i \in[0,n-1]}P(p_i,q_i)logP(p_i) \\ H(Q|P)=-H(P,Q)+H(P)
H(Q∣P)=−i∈[0,n−1]∑P(pi,qi)logP(qi∣pi)H(Q∣P)=−H(P,Q)−i∈[0,n−1]∑P(pi,qi)logP(pi)H(Q∣P)=−H(P,Q)+H(P)
至于熵为什么是这个定义请参考 为什么信息熵要定义成-Σp*log§?(https://blog.csdn.net/taoqick/article/details/72852255)。简单来说就是-log§就是信息量,单位用比特表示,例如中国队夺世界杯的信息量远比法国队夺世界杯信息量大。把一个系统里所有的-log§再乘以p就是熵,表示所有信息量加权平均,或者说熵就是信息量的数学期望
还有3个重要结论:
-
最小化交叉熵和极大似然本质上是一样的,更多推导参考:最小化交叉熵损失与极大似然 - 知乎(https://zhuanlan.zhihu.com/p/51099880)
-
为什么分类问题用相对熵不用MSE,原因之一是求解时相对熵的梯度下降更快一些,这样可以实现错误越大,下降的越快的效果,更多推导请参考: 分类问题中为什么用交叉熵而不用MSE KL散度和交叉熵的关系_taoqick的专栏-CSDN博客_mse和交叉熵 (https://blog.csdn.net/taoqick/article/details/102621605)
-
李航老师书里说的最大熵模型是条件熵最大化,想法就是某些知识已经先验知道了,剩下的随机变量尽量等概率随机,这样条件熵最大。学习概率模型时,在满足约束(特征函数)的所有的可能的概率分布中,熵最大的模型就是最大的模型。最大熵模型是判别式模型。
更多推导请参考李航老师的书和数学之美。