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.2∗log0.40.2+0.4∗log0.20.4+0.4∗log0.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^)=−(y∗log(y^)+(1−y)∗log(1−y^))(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^)=∑y∗log(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^)=−∑y∗log(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)=0∗log(0.61)+1∗log(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^)=−(y∗log(y^)+(1−y)∗log(1−y^))(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=1∑n(yi∗log(y^i)+(1−yi)∗log(1−y^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((0∗log0.6+1∗log0.4)+(0∗log0.4+1∗log0.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公式