为了更好的理解交叉熵的意义,先介绍一下相对熵的概念

1、相对熵

  • 基本概念
    相对熵又称为KL散度(Kullback–Leibler divergence),用来描述两个概率分布的差异性。假设有对同一变量 x x x q ( x ) q(x) q(x) p ( x ) p(x) p(x)两个概率分布,那么两者之间的相对熵可由以下定义:
    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(pq)=i=1Np(xi)log(q(xi)p(xi))
    对于实际应用, p ( x ) p(x) p(x)是目标分布, q ( x ) q(x) q(x)是预测的匹配分布。

  • 理解相对熵(参考
    因为 p ( x ) p(x) p(x)是目标分布,所以用 p p p来表示该事件是最好的。但是现在用了 q ( x ) q(x) q(x),多了一些不确定性因素,这个增加的信息量就是相对熵。

  • 性质
    相对熵有一个性质:两个分布差异越大,KL散度越大。实际应用需要两个分布尽可能的相等,于是就需要KL散度尽可能的小

  • 相对熵的非负性
    根据Jensen不等式,当 f f f是一个凸函数时,满足以下函数:
    f ( θ 1 x 1 + … + θ n x n ) ≤ θ 1 f ( x 1 ) + … + θ n f ( x n ) , 0 ≤ θ i ≤ 1 , θ 1 + … + θ n = 1 f\left(\theta_{1} x_{1}+\ldots+\theta_{n} x_{n}\right) \leq \theta_{1} f\left(x_{1}\right)+\ldots+\theta_{n} f\left(x_{n}\right) ,0 \leq \theta_{i} \leq 1, \theta_{1}+\ldots+\theta_{n}=1 f(θ1x1++θnxn)θ1f(x1)++θnf(xn),0θi1,θ1++θn=1
    对照相对熵的表达式,这里做几个映射关系:

Jensen不等式相对熵
θ i \theta_{i} θi p ( x i ) p(x_i) p(xi)
x i x_{i} xi q ( x i ) p ( x i ) \frac{q\left(x_{i}\right)}{p\left(x_{i}\right)} p(xi)q(xi)
f ( ) f() f() − l o g ( ) -log() log()

根据上表中的关系得到下式:
D K L ( p ∥ q ) = ∑ i = 1 N p ( x i ) log ⁡ ( p ( x i ) q ( x i ) ) = ∑ i = 1 N p ( x i ) ⋅ − log ⁡ ( q ( x i ) p ( x i ) ) ≥ − log ⁡ ( ∑ i = 1 N p ( x i ) ⋅ q ( x i ) p ( x i ) ) = 0 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)=\sum_{i=1}^{N} p\left(x_{i}\right) \cdot-\log \left(\frac{q\left(x_{i}\right)}{p\left(x_{i}\right)}\right) \geq -\log \left(\sum_{i=1}^{N} p\left(x_{i}\right) \cdot \frac{q\left(x_{i}\right)}{p\left(x_{i}\right)}\right)=0 DKL(pq)=i=1Np(xi)log(q(xi)p(xi))=i=1Np(xi)log(p(xi)q(xi))log(i=1Np(xi)p(xi)q(xi))=0
显然,只有当 q ( x ) q(x) q(x)= p ( x ) p(x) p(x)时, D K L ( p ∥ q ) D_{K L}(p \| q) DKL(pq)才有最小值,这也意味着预测的越相近越好。

  • 与交叉熵的关系
    D K L ( p ∥ q ) = ∑ i = 1 N p ( x i ) log ⁡ ( p ( x i ) q ( x i ) ) = ∑ i = 1 N p ( x i ) log ⁡ p ( x i ) − ∑ i = 1 N p ( x i ) log ⁡ q ( x i ) = − H ( p ) + H ( p , q ) 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)=\sum_{i=1}^{N} p\left(x_{i}\right) \log p\left(x_{i}\right)-\sum_{i=1}^{N} p\left(x_{i}\right) \log q\left(x_{i}\right)=-H(p)+H(p, q) DKL(pq)=i=1Np(xi)log(q(xi)p(xi))=i=1Np(xi)logp(xi)i=1Np(xi)logq(xi)=H(p)+H(p,q)
    上式中的 H ( p , q ) H(p, q) H(p,q),而当一个目标事件确定后, H ( p ) H(p) H(p)便为一个常数,所以最小化相对熵的问题化简成了计算 H ( p , q ) H(p, q) H(p,q)的最小值。

2、交叉熵

  • 简单概念
    交叉熵是信息熵论中的概念,它原本是用来估算平均编码长度的。在深度学习中,可以看作通过概率分布 q ( x ) q(x) q(x)表示概率分布 p ( x ) p(x) p(x)的困难程度。其表达式为:
    H ( p , q ) = ∑ i = 1 n p ( x i ) log ⁡ 1 q ( x i ) = − ∑ i = 1 n p ( x i ) log ⁡ q ( x i ) H(p, q)=\sum_{i=1}^{n} p\left(x_{i}\right) \log \frac{1}{q\left(x_{i}\right)}=-\sum_{i=1}^{n} p\left(x_{i}\right) \log {q\left(x_{i}\right)} H(p,q)=i=1np(xi)logq(xi)1=i=1np(xi)logq(xi)
  • 简单性质
    交叉熵刻画的是两个概率分布的距离,也就是说交叉熵值越小(相对熵的值越小),两个概率分布越接近
    因为上面说到,当 q ( x ) q(x) q(x)= p ( x ) p(x) p(x)时, D K L ( p ∥ q ) D_{K L}(p \| q) DKL(pq)才有最小值,即下式取等号。

D K L ( p ∥ q ) = − H ( p ) + H ( p , q ) ≥ 0 ⇒ H ( p , q ) ≥ H ( p ) {{D}_{KL}}(p\|q)=-H(p)+H(p,q)\ge 0\Rightarrow H\left( p,q \right)\ge H\left( p \right) DKL(pq)=H(p)+H(p,q)0H(p,q)H(p)

下面将给出两个具体样例来直观地说明通过交叉熵可以判断预测答案和真实答案之间的距离。假设有个三分类问题,某个正确答案和一个经过 s o f t m a x softmax softmax回归后的预测答案如下:

x 1 x_1 x1 x 2 x_2 x2 x 3 x_3 x3
p ( x i ) p(x_i) p(xi)100
q 1 ( x i ) q_1(x_i) q1(xi)0.50.40.1
q 2 ( x i ) q_2(x_i) q2(xi)0.80.10.1

那么 p ( x ) p(x) p(x) q 1 ( x ) q_1(x) q1(x)的交叉熵为:(log计算取10为底,即lg)
H ( ( 1 , 0 , 0 ) , ( 0.5 , 0.4 , 0.1 ) ) = − ( 1 × log ⁡ 0.5 + 0 × log ⁡ 0.4 + 0 × log ⁡ 0.1 ) ≈ 0.3 \mathrm{H}((1,0,0),(0.5,0.4,0.1))=-(1 \times \log 0.5+0 \times \log 0.4+0 \times \log 0.1) \approx 0.3 H((1,0,0),(0.5,0.4,0.1))=(1×log0.5+0×log0.4+0×log0.1)0.3
p ( x ) p(x) p(x) q 2 ( x ) q_2(x) q2(x)的交叉熵为:
H ( ( 1 , 0 , 0 ) , ( 0.8 , 0.1 , 0.1 ) ) = − ( 1 × log ⁡ 0.8 + 0 × log ⁡ 0.1 + 0 × log ⁡ 0.1 ) ≈ 0.1 \mathrm{H}((1,0,0),(0.8,0.1,0.1))=-(1 \times \log 0.8+0 \times \log 0.1+0 \times \log 0.1) \approx 0.1 H((1,0,0),(0.8,0.1,0.1))=(1×log0.8+0×log0.1+0×log0.1)0.1
从直观上可以看到第二个预测的结果要优于第一个,并且通过计算交叉熵,结果也是一致的。

  • TensorFlow中实现交叉熵
cross_entropy = -tf.reduce_mean( y_ * tf.log(tf.clip.by_value(y, 1e-10, 1.0)))

其中y_代表正确结果,y代表预测结果。

3、Logistic回归风险函数与交叉熵

在Logistic回归任务中,常见的输出定义为:
y ^ = σ ( w T x + b ) ,   w h e r e   σ ( z ) = 1 1 + e − z \widehat{y}=\sigma \left( {{w}^{T}}x+b \right),\text{ }where\text{ }\sigma \left( z \right)=\frac{1}{1+{{e}^{-z}}} y =σ(wTx+b), where σ(z)=1+ez1
定义 y = 1 y=1 y=1的例子的后验概率为
P ( y = 1 ∣ x ) = y ^ = σ ( w T x + b ) P(y=1|x)=\widehat{y}=\sigma \left( {{w}^{T}}x+b \right) P(y=1x)=y =σ(wTx+b)
以及 y = 0 y=0 y=0的例子的后验概率为
P ( y = 0 ∣ x ) = 1 − y ^ = 1 − σ ( w T x + b ) P(y=0|x)=1-\widehat{y}=1-\sigma \left( {{w}^{T}}x+b \right) P(y=0x)=1y =1σ(wTx+b)
那么可以定义后验概率 P ( y ∣ x ) P(y|x) P(yx)如下:
P ( y ∣ x ) = y ^ y ( 1 − y ^ ) ( 1 − y ) P\left( y|x \right)={{\widehat{y}}^{y}}{{\left( 1-\widehat{y} \right)}^{\left( 1-y \right)}} P(yx)=y y(1y )(1y)
回归的目标应为该后验概率越大越好,根据最大似然估计,在所有样本满足独立同分布的情况下,可以确定一组参数使得上述概率最大。

因为 l o g log log函数是单调递增函数,所以求上述函数的最大化,等价于求 l o g ( P ( y ∣ x ) ) log(P(y|x)) log(P(yx))的最大化。

对上述概率对数化可得:
log ⁡ ( P ( y ∣ x ) ) = y log ⁡ ( y ^ ) + ( 1 − y ) log ⁡ ( 1 − y ^ ) \log \left( P\left( y|x \right) \right)=y\log \left( \widehat{y} \right)+\left( 1-y \right)\log \left( 1-\widehat{y} \right) log(P(yx))=ylog(y )+(1y)log(1y )
即逻辑回归的目标是最大化上述函数。对于代价函数来说,一般都是最小化函数,所以可以增加一个负号。那么,对于 m m m个样本来说,代价函数即为交叉熵函数:
J ( w , b ) = − ∑ i = 1 m ( y log ⁡ y ^ + ( 1 − y ) log ⁡ ( 1 − y ^ ) ) J\left( w,b \right)=-\sum\limits_{i=1}^{m}{\left( y\log \widehat{y}+\left( 1-y \right)\log \left( 1-\widehat{y} \right) \right)} J(w,b)=i=1m(ylogy +(1y)log(1y ))

4、Pytorch中CrossEntropy的形式

pytorch中torch.nn模块和torch.nn.functional中均有对应的交叉熵的模块,但是两者是相通的。不过torch.nn.functional中可以理解为是一个接口,而torch.nn是具体类的定义。这里仅用nn.functional的应用来简单说一下怎么用交叉熵,有关torch.nntorch.nn.functional的相关讨论可以参考知乎问题:PyTorch 中,nn 与 nn.functional 有什么区别?

1. torch.nn.functional.cross_entropy

首先,看定义代码为

def cross_entropy(input, target, weight=None, size_average=None, ignore_index=-100,
                  reduce=None, reduction='mean'):
    # type: (Tensor, Tensor, Optional[Tensor], Optional[bool], int, Optional[bool], str) -> Tensor

    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)

看上面代码也能知道inputtarget是必选项,并且是Tensor类型的。最后一行说明functional.cross_entropy实际计算过程就是先计算Tensor的log_softmax,然后再计算nll_loss

那么问题来了,log_softmax是怎么计算的,干了些什么,用上面表格的数据来举例来说:

Tensor的log_softmax函数和functional的函数作用一样,都是先对数据进行softmax,然后进行log函数,这里的log以e为底,即ln。log_softmax和softmax中的数字表示按照什么维度计算。0代表按列计算,softmax函数计算后的数据按列加起来为1;1代表按行计算,softmax函数计算后的数据按行加起来为1。

import torch.nn.functional as F
import torch
truth = torch.tensor([[1, 0, 0]], dtype=torch.float)
predicted1 = torch.tensor([[0.5, 0.4, 0.1]], dtype=torch.float)
print(truth.softmax(0))
print(truth.softmax(1))
print(F.log_softmax(predicted1, 1))
print(truth.log_softmax(-1))

上面代码的输出结果为:

tensor([[1., 1., 1.]])
tensor([[0.5017, 0.2491, 0.2491]])
tensor([[-0.9459, -1.0459, -1.3459]])
tensor([[-0.6897, -1.3897, -1.3897]])

第一行的输出结果可以理解,按照列计算只有一个数据,所以计算后每列的只有1个1。
第二行的输出结果可以通过计算得到:

e 1 e 1 + e 0 + e 0 = e e + 2 = 0.5017 e 0 e 1 + e 0 + e 0 = 1 e + 2 = 0.2491 e 0 e 1 + e 0 + e 0 = 1 e + 2 = 0.2491 \frac{{{e}^{1}}}{{{e}^{1}}+{{e}^{0}}+{{e}^{0}}}=\frac{e}{e+2}=0.5017 \\ \frac{{{e}^{0}}}{{{e}^{1}}+{{e}^{0}}+{{e}^{0}}}=\frac{1}{e+2}=0.2491 \\ \frac{{{e}^{0}}}{{{e}^{1}}+{{e}^{0}}+{{e}^{0}}}=\frac{1}{e+2}=0.2491 \\ e1+e0+e0e1=e+2e=0.5017e1+e0+e0e0=e+21=0.2491e1+e0+e0e0=e+21=0.2491
第四行的输出结果通过计算得到:
ln ⁡ ( 0.5017 ) = − 0.68975 ln ⁡ ( 0.2491 ) = − 1.38990 ln ⁡ ( 0.2491 ) = − 1.38990 \ln \left( 0.5017 \right)=-0.68975 \\ \ln \left( 0.2491 \right)=-1.38990 \\ \ln \left( 0.2491 \right)=-1.38990 \\ ln(0.5017)=0.68975ln(0.2491)=1.38990ln(0.2491)=1.38990

知道了log_softmax是干嘛的,现在来了解nll_loss是干嘛的。

truth = torch.tensor([0], dtype=torch.int64)
predicted1 = torch.tensor([[0.5, 0.4, 0.1]], dtype=torch.float)

loss = nn.NLLLoss()

print(F.log_softmax(predicted1, 1))
print(loss(F.log_softmax(predicted1, 1), truth))
print(F.cross_entropy(predicted1, truth))

上面的输出结果为:

tensor([[-0.9459, -1.0459, -1.3459]])
tensor(0.9459)
tensor(0.9459)

可以看到truth中的值就是log_softmax结果后的数组的idx,即truth0对应-0.9459的位置,将-0.9459取出后取负数便为NLLLoss的结果。同样地,若truth的值为1,那么第二行和第三行的输出结果为1.0459

2. torch.nn.functional.binary_cross_entropy
	def binary_cross_entropy(input, target, weight=None, size_average=None,
                         reduce=None, reduction='mean'):
    # type: (Tensor, Tensor, Optional[Tensor], Optional[bool], Optional[bool], str) -> Tensor

用上面的数据,先来个例子

import torch.nn as nn
import torch.nn.functional as F
import torch
truth = torch.tensor([[1, 0, 0]], dtype=torch.int64)
predicted1 = torch.tensor([[0.5, 0.4, 0.1]], dtype=torch.float)
predicted2 = torch.tensor([[0.8, 0.1, 0.1]], dtype=torch.float)
print(F.binary_cross_entropy(predicted1, truth.float()))
print(F.binary_cross_entropy(predicted2, truth.float()))

上面代码的输出结果为:

tensor(0.4364)
tensor(0.1446)

这个计算过程比较简单(呦呵,这公式编辑的咋分不开了呢):
− 1 3 ( y log ⁡ y ^ + ( 1 − y ) log ⁡ ( 1 − y ^ ) ) = − 1 3 × ( log ⁡ ( 0.5 ) + log ⁡ ( 1 − 0.4 ) + log ⁡ ( 1 − 0.1 ) ) = 0.4364 − 1 3 ( y log ⁡ y ^ + ( 1 − y ) log ⁡ ( 1 − y ^ ) ) = − 1 3 × ( log ⁡ ( 0.8 ) + log ⁡ ( 1 − 0.1 ) + log ⁡ ( 1 − 0.1 ) ) = 0.1446 -\frac{1}{3}\left( y\log \hat{y}+\left( 1-y \right)\log \left( 1-\hat{y} \right) \right)=-\frac{1}{3}\times \left( \log \left( 0.5 \right)+\log \left( 1-0.4 \right)+\log (1-0.1) \right)=0.4364 \\ \\ \\ -\frac{1}{3}\left( y\log \hat{y}+\left( 1-y \right)\log \left( 1-\hat{y} \right) \right)=-\frac{1}{3}\times \left( \log \left( 0.8 \right)+\log \left( 1-0.1 \right)+\log (1-0.1) \right)=0.1446 \\ 31(ylogy^+(1y)log(1y^))=31×(log(0.5)+log(10.4)+log(10.1))=0.436431(ylogy^+(1y)log(1y^))=31×(log(0.8)+log(10.1)+log(10.1))=0.1446

3. torch.nn.functional.binary_cross_entropy_with_logits
def binary_cross_entropy_with_logits(input, target, weight=None, size_average=None,
                                     reduce=None, reduction='mean', pos_weight=None):
    # type: (Tensor, Tensor, Optional[Tensor], Optional[bool], Optional[bool], str, Optional[Tensor]) -> Tensor

这个函数在计算上一个函数之前,对input参数做了sigmoid计算。来看个例子:

import torch.nn as nn
import torch.nn.functional as F
import torch
truth = torch.tensor([[1, 0, 0]], dtype=torch.int64)
predicted1 = torch.tensor([[0.5, 0.4, 0.1]], dtype=torch.float)
predicted2 = torch.tensor([[0.8, 0.1, 0.1]], dtype=torch.float)
print(F.binary_cross_entropy_with_logits(predicted1, truth.float()))
print(F.binary_cross_entropy_with_logits(predicted2, truth.float()))

代码的输出结果为:

tensor(0.7105)
tensor(0.6200)

不解释了,看下面代码

truth = torch.tensor([[1, 0, 0]], dtype=torch.int64)
predicted1 = torch.tensor([[0.5, 0.4, 0.1]], dtype=torch.float).sigmoid()
predicted2 = torch.tensor([[0.8, 0.1, 0.1]], dtype=torch.float).sigmoid()
print(F.binary_cross_entropy(predicted1, truth.float()))
print(F.binary_cross_entropy(predicted2, truth.float()))

输出结果:

tensor(0.7105)
tensor(0.6200)
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐