KL散度、交叉熵与JS散度数学公式以及代码例子

KL散度、交叉熵与JS散度数学公式以及代码例子

1.1 KL 散度概述

KL 散度 ,Kullback-Leibler divergence,(也称相对熵,relative entropy)是概率论和信息论中十分重要的一个概念,是两个概率分布(probability distribution)间差异的非对称性度量。

对离散概率分布的 KL 散度 计算公式为:
K L ( p ∣ ∣ q ) = ∑ p ( x ) log ⁡ p ( x ) q ( x ) (1) KL(p||q)=\sum p(x)\log{{p(x)}\over{q(x)}} \tag{1} KL(p∣∣q)=p(x)logq(x)p(x)(1)

对连续概率分布的 KL 散度 计算公式为:
K L ( p ∣ ∣ q ) = ∫ p ( x ) log ⁡ p ( x ) q ( x ) d x (2) KL(p||q)=\int p(x)\log {{p(x)}\over{q(x)}}dx \tag{2} KL(p∣∣q)=p(x)logq(x)p(x)dx(2)

一般情况下,我们得到的数据都是离散的。

KL 散度 的重要性质:

  • KL 散度 的结果是非负的。
  • KL 散度 是非对称的,即 K L ( p ∣ ∣ q )   ! =   K L ( q ∣ ∣ p ) KL(p||q) \ !=\ KL(q||p) KL(p∣∣q) != KL(q∣∣p)

具体内容参考 [1] 《深度学习轻松学》。

1.2 KL 散度计算方法的代码实现

1.2.1 自己编写代码

请结合 公式 (1) 理解以下代码:

import numpy as np
import math

def KL(p,q):
    # p,q 为两个 list,表示对应取值的概率 且 sum(p) == 1 ,sum(q) == 1
    return sum(_p*math.log(_p/_q) for (_p,_q) in zip(p,q) if _p != 0 )

P = [0.2, 0.4, 0.4]
Q = [0.4, 0.2, 0.4]

print(KL(P,Q))

输出内容为:

0.13862943611198905

计算过程 K L ( P , Q ) = 0.2 ∗ log ⁡ 0.2 0.4 + 0.4 ∗ log ⁡ 0.4 0.2 + 0.4 ∗ log ⁡ 0.4 0.4 = 0.13862943611198905 KL(P,Q) = 0.2*\log{{0.2}\over{0.4}}+0.4*\log{{0.4}\over{0.2}}+0.4*\log{{0.4}\over{0.4}}=0.13862943611198905 KL(P,Q)=0.2log0.40.2+0.4log0.20.4+0.4log0.40.4=0.13862943611198905

当然如果是 二分类问题 计算过程也是一样的。

import numpy as np
import math

def KL(p,q):
    # p,q 为两个 list,表示对应取值的概率 且 sum(p) == 1 ,sum(q) == 1
    return sum(_p*math.log(_p/_q) for (_p,_q) in zip(p,q) if _p != 0 )

P = [0, 1]
Q = [0.4,0.6]

print(KL(P,Q))

输出结果为:

0.5108256237659907

可以自己复制粘贴填写更多测试数值,需要 保证

  • P 中每一组的各项的和为 1
  • Q 中每一组各项的和为 1
  • Q 中每一组不允许出现 0

注意: 以上内容只考虑维度为 1 的情况,如 P=[[0.1,0.2,0.7],[0.2,0.3,0.5]] 这种情况没有考虑到。

1.2.2 使用已存在的库 scipy

from scipy import stats

P = [0.2, 0.4, 0.4]
Q = [0.4, 0.2, 0.4]
stats.entropy(P,Q) 

输出内容为:

0.13862943611198905

和上面的例子一样,可以测试那些数据。

2.1 交叉熵基本概述

交叉熵(Cross Entropy)是 Shannon 信息论中一个重要概念,主要用于度量两个概率分布间的差异性信息。

二分类 任务的 交叉熵 计算公式为:

C r o s s E n t r o p y   ( y , y ^ ) = − ( y ∗ log ⁡ ( y ^ ) + ( 1 − y ) ∗ log ⁡ ( 1 − y ^ ) ) (3) Cross Entropy\ (y, {\hat y}) = -(y*\log(\hat y)+(1-y)*\log(1-\hat y)) \tag{3} CrossEntropy (y,y^)=(ylog(y^)+(1y)log(1y^))(3)
其中, y y y 可以理解为数据集中的 label,也就是接下来例子中的 y t r u e y_{true} ytrue;而 y ^ \hat y y^ 可以理解与模型的预测标签,也就是接下来的例子中的 y p r e d y_{pred} ypred

注意: 如果二分类问题时, y y y 的取值并非 [0,1] 两种这种正常模式,需要分别求和再求均值,这一部分内容将会在后面 2.3 进行讨论。

多分类 任务的交叉熵计算公式为:

C r o s s E n t r o p y   ( y , y ^ ) = ∑ y ∗ log ⁡ ( 1 y ^ ) (4) Cross Entropy\ (y, {\hat y}) = \sum y*\log ({{1}\over{\hat y}}) \tag{4} CrossEntropy (y,y^)=ylog(y^1)(4)
公式 4 可以写成如下格式:

C r o s s E n t r o p y   ( y , y ^ ) = − ∑ y ∗ log ⁡ ( y ^ ) (5) Cross Entropy\ (y, {\hat y}) = -\sum y*\log ({\hat y}) \tag{5} CrossEntropy (y,y^)=ylog(y^)(5)

其中, 多分类 任务的计算公式同样适用于 二分类 ,因为二分类任务直接等于两个概率运算的和,所以没必要加 ∑ \sum 符号。

2.2 交叉熵计算方法的代码实现

2.2.1 自己编写代码

参考 公式4 ,编写代码比较简单。

import math

def CE(p,q):
    # p,q 为两个 list,表示对应取值的概率 且 sum(p) == 1 ,sum(q) == 1
    return sum(_p*math.log( 1 /_q) for (_p,_q) in zip(p,q) if _q != 0 )

P = [0.,1.]
Q = [0.6,0.4]

print(CE(P,Q))

计算过程 C E ( P , Q ) = 0 ∗ log ⁡ ( 1 0.6 ) + 1 ∗ log ⁡ ( 1 0.4 ) = log ⁡ ( 2.5 ) = 0.91629073187415506518352721176801 CE(P, Q)=0*\log({{1}\over{0.6}})+1*\log({{1}\over{0.4}})=\log(2.5)=0.91629073187415506518352721176801 CE(P,Q)=0log(0.61)+1log(0.41)=log(2.5)=0.91629073187415506518352721176801

输出内容为:

0.9162907318741551

注意对比前文中对 KL 的实现的代码,非常相似,除了函数名就改动两个地方。

根据 公式5,代码实现如下:

import math

def CE(p,q):
    # p,q 为两个 list,表示对应取值的概率 且 sum(p) == 1 ,sum(q) == 1
    return -sum(_p*math.log(_q) for (_p,_q) in zip(p,q) if _q != 0 )

P = [0.,1.]
Q = [0.6,0.4]

print(CE(P,Q))

输出结果为:

0.916290731874155

2.2.1 使用 tensorflow2

主要是因为使用 tensorflow 的时候可能会用到这个函数,所以特地在这里介绍一下计算过程。

注意 这里只适合 Binary 这种情况,也就是说标签只是 0 与 1 两种。具体参考 [3]

这里直接上例子,然后解释计算过程:

import tensorflow as tf

y_true = [0., 1.]
y_pred = [0.6, 0.4]
bce = tf.keras.losses.BinaryCrossentropy()
bce(y_true, y_pred).numpy()

输出内容为:

0.9162905

在官方文档中给出的例子有两组数据,代码如下:

import tensorflow as tf

y_true = [[0., 1.], [0., 1.]]
y_pred = [[0.6, 0.4], [0.4, 0.6]]
# Using 'auto'/'sum_over_batch_size' reduction type.
bce = tf.keras.losses.BinaryCrossentropy()
bce(y_true, y_pred).numpy()  

输出内容为:

0.71355796

这种情况只是分别求两组结果,再进行平均即可。

2.3 当测试数据为全0或全1 的二分类时

上面的公式以及自己编写的代码中,都没有考虑到当测试数据为全0 或全 1 这种情况,接下来在这里讨论,在二分类问题中,应该如何计算结果。

C r o s s E n t r o p y   ( y , y ^ ) = − ( y ∗ log ⁡ ( y ^ ) + ( 1 − y ) ∗ log ⁡ ( 1 − y ^ ) ) (3) Cross Entropy\ (y, {\hat y}) = -(y*\log(\hat y)+(1-y)*\log(1-\hat y)) \tag{3} CrossEntropy (y,y^)=(ylog(y^)+(1y)log(1y^))(3)
公式3 所示,在 输入数据的 y y y 为 [0,1] 两种时,使用 公式3,4,5 计算都可以。但是如果输入数据的 y y y 全部为 0 的时候,公式3 与公式4 则不适用。

这个时候需要使用 公式3 进行计算,并且同样需要做一次求均值,如 公式6 所示。

C r o s s E n t r o p y   ( y , y ^ ) = − 1 n ∑ i = 1 n ( y i ∗ log ⁡ ( y ^ i ) + ( 1 − y i ) ∗ log ⁡ ( 1 − y ^ i ) ) (6) Cross Entropy\ (y, {\hat y}) = -{{1}\over{n}}\sum_{i=1}^n(y_i*\log(\hat y_i)+(1-y_i)*\log(1-\hat y_i)) \tag{6} CrossEntropy (y,y^)=n1i=1n(yilog(y^i)+(1yi)log(1y^i))(6)

对应的代码实现如下:

import math

def CE(p,q):
    return -sum((_p*math.log(_q)+(1-_p)*math.log(1-_q) )for (_p,_q) in zip(p,q) if _q != 0 )/len(p)

P = [0.,0.]
Q = [0.6,0.4]

print(CE(P,Q))

输出结果为:

0.7135581778200728

计算过程 C E ( p , q ) = − 1 2 ( ( 0 ∗ log ⁡ 0.6 + 1 ∗ log ⁡ 0.4 ) + ( 0 ∗ log ⁡ 0.4 + 1 ∗ log ⁡ 0.6 ) ) = − 1 2 log ⁡ 0.24 = 0.71355817782007287419452065403584 CE(p,q)=-{{1}\over{2}}((0*\log 0.6 + 1*\log 0.4)+(0*\log 0.4+1*\log0.6))=-{{1}\over{2}}\log0.24=0.71355817782007287419452065403584 CE(p,q)=21((0log0.6+1log0.4)+(0log0.4+1log0.6))=21log0.24=0.71355817782007287419452065403584

对应的 tensorflow2 代码如下:

import tensorflow as tf

y_true =  [0., 0.]
y_pred = [0.4, 0.6]

bce = tf.keras.losses.BinaryCrossentropy()
bce(y_true, y_pred).numpy()  

输出结果为:

0.71355796

如果测试数据为 [1.,1.] 的时候,输出结果是相同的。

3.1 JS 散度概述

JS 散度 Jensen-Shannon divergence 用于描述两个概率分布的相似程度。和上面的 KL 的描述一致的话,JS 散度是两个概率分布间差异的对称性度量。

JS 散度的求解公式如下:
J S ( P   ∣ ∣   Q ) = 1 2 K L ( P   ∣ ∣   P + Q 2 ) + 1 2 K L ( Q   ∣ ∣   P + Q 2 ) (7) JS(P \ ||\ Q ) = {{1}\over{2}}KL(P\ ||\ {{P+Q}\over{2}})+{{1}\over{2}}KL(Q \ ||\ {{P+Q}\over{2}}) \tag{7} JS(P ∣∣ Q)=21KL(P ∣∣ 2P+Q)+21KL(Q ∣∣ 2P+Q)(7)

很明显,等式是对称成立的,也就是说 J S ( P   ∣ ∣   Q ) = = J S ( Q   ∣ ∣   P ) JS(P \ || \ Q) == JS(Q\ ||\ P) JS(P ∣∣ Q)==JS(Q ∣∣ P)

3.2 JS散度计算方法的代码实现

公式7 是通过计算 KL 散度来计算 JS 散度的,因此代码实现需要用到前面的 KL 散度,具体实现如下:

实验 1

import math

def KL(p,q):
    # p,q 为两个 list,表示对应取值的概率 且 sum(p) == 1 ,sum(q) == 1
    return sum(_p*math.log(_p/_q) for (_p,_q) in zip(p,q) if _p != 0 )

def JS(p,q):
    M = [0.5*(_p +_q) for (_p,_q) in zip(p,q)]
    return 0.5*(KL(p,M)+KL(q,M))

P = [0.,1.]
Q = [0.6,0.4]

print(JS(P,Q))
print(JS(Q,P))

输出内容为:

0.27435846855026524
0.27435846855026524

可以看出 J S ( P , Q ) JS(P,Q) JS(P,Q) J S ( Q , P ) JS(Q,P) JS(Q,P) 相等。


实验 2

接下来看一下两个概率分布差异大小在 JS 值上的直观反映:

import math

def KL(p,q):
    # p,q 为两个 list,表示对应取值的概率 且 sum(p) == 1 ,sum(q) == 1
    return sum(_p*math.log(_p/_q) for (_p,_q) in zip(p,q) if _p != 0 )

def JS(p,q):
    M = [0.5*(_p +_q) for (_p,_q) in zip(p,q)]
    return 0.5*(KL(p,M)+KL(q,M))

P = [0.,1.]
Q = [0.01,0.99]

print(JS(P,Q))

P = [0.,1.]
Q = [0.1,0.9]

print(JS(P,Q))


P = [0.,1.]
Q = [0.5,0.5]

print(JS(P,Q))

P = [0.,1.]
Q = [0.9,0.1]

print(JS(P,Q))

P = [0.,1.]
Q = [0.99,0.01]

print(JS(P,Q))

输出内容如下:

0.003478298769743019
0.03597375665014844
0.21576155433883565
0.5255973270178643
0.665096412549155

可以看出,最开始的数据两个概率分布是非常接近的,因为 JS 值比较小,接着两个概率分布差异越来越多,JS 值也越来越大。


实验 3

接着使用相同的数据,测试一下 KL 值的大小与概率分布的关系,因为 KL 计算是非对称的,因此每次需要输出两个结果。

import math

def KL(p,q):
    # p,q 为两个 list,表示对应取值的概率 且 sum(p) == 1 ,sum(q) == 1
    return sum(_p*math.log(_p/_q) for (_p,_q) in zip(p,q) if _p != 0 )


P = [0.1,0.9]
Q = [0.01,0.99]

print(KL(P,Q),KL(Q,P))

P = [0.1,0.9]
Q = [0.1,0.9]

print(KL(P,Q),KL(Q,P))

P = [0.1,0.9]
Q = [0.5,0.5]

print(KL(P,Q),KL(Q,P))

P = [0.1,0.9]
Q = [0.9,0.1]

print(KL(P,Q),KL(Q,P))

P = [0.1,0.9]
Q = [0.99,0.01]

print(KL(P,Q),KL(Q,P))

输出结果为:

0.14447934747551233 0.07133122707634103
0.0 0.0
0.3680642071684971 0.5108256237659907
1.7577796618689758 1.7577796618689758
3.8205752275831846 2.224611312865836

可以看出,当 P 和 Q 相同分布时,KL 散度为 0;KL 散度随着分布差异的增大而增大,随着分布差异的减小而减小。


小结

根据以上实验,可以看出:

  • 随着两个概率分布差异的增大,KL 散度与 JS 散度的数值将增大;反之亦然。
  • 随着两个概率分布差异的增大,KL 散度的增大时不均匀的,而 JS 散度的增大时均匀的。
  • KL 散度的不对称性可能带来一些潜在的问题。

4. 总结

本文总结了 KL 散度、交叉熵以及 JS 散度数学公式以及一些性质,并且通过 python 代码实现。在阅读论文或实际编码中,如果忘记了这方面的内容,可以考虑参考一下。

如有任何疑问,欢迎留言评论!

感谢您的阅读!如果有帮助到您的话,欢迎点赞 + 关注 !感谢!

Smileyan
2021.3.28 22:15

[1] 《深度学习轻松学》核心算法与视觉实践 冯超著 电子工业出版社 P33
[2] 《相对熵(KL散度)计算过程》
[3] tensorlflow文档 tf.keras.metrics.BinaryCrossentropy
[4] 《熵与信息增益》
[5] 百度百科 相对熵 KL 散度
[6] 百度百科 交叉熵
[7] 熵与信息增益
[8] 二分类、多分类交叉熵的计算
[9] tensorflow BinaryCrossentropy 源码
[10] 知乎 为什么交叉熵(cross-entropy)可以用于计算代价?
[11] 交叉熵、相对熵(KL散度)、JS散度和Wasserstein距离(推土机距离)
[12] tensorflow官网 KL公式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

smile-yan

感谢您的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值