1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 信息量 熵 交叉熵 KL散度 JS散度杂谈及代码实现

信息量 熵 交叉熵 KL散度 JS散度杂谈及代码实现

时间:2024-04-08 23:07:35

相关推荐

信息量 熵 交叉熵 KL散度 JS散度杂谈及代码实现

信息量、熵、交叉熵、KL散度、JS散度杂谈及代码实现

信息量

任何事件都会承载着一定的信息量,包括已经发生的事件和未发生的事件,只是它们承载的信息量会有所不同。如昨天下雨这个已知事件,因为已经发生,既定事实,那么它的信息量就为 0。如明天会下雨这个事件,因为未有发生,那么这个事件的信息量就大。

从上面例子可以看出信息量是一个与事件发生概率相关的概念,而且可以得出,事件发生的概率越小,其信息量越大。这也很好理解,比如某明星被爆出轨、逃税等,这种事件信息量就很大,我们在口语中也会称这种新闻 “信息量很大” ,因为是小概率事件,然而近几年看起来好像已经不是小概率事件了。而对于我是我妈生的,这种事件,信息量就几乎为零,因为这是确定事件。

另外,独立时间的信息量是可叠加的,比如 ”a. 张三喝了阿萨姆红茶,b. 李四喝了英式红茶“ 的信息量就应该是a和b的信息量之和,如果张三李四喝什么茶是两个独立事件。

我们已知某个事件的信息量是与它发生的概率有关,那我们可以通过如下公式计算信息量:假设 XXX 是一个离散型随机变量,其取值集合为 xxx ,概率分布函数 P(x)=Pr(X=x),x∈XP(x)=Pr(X=x),x\in XP(x)=Pr(X=x),x∈X ,则定义事件 x=x0x=x_0x=x0​ 的信息量为:

I(x0)=−logP(x0)I(x_0)=-logP(x_0) I(x0​)=−logP(x0​)

我们可以看到,当某个时间的概率为1时,即其一定会发生,则此时它的信息量为0:I=−log1=0I=-log1=0I=−log1=0。

熵就是信息量的期望,即:

S(x)=−∑iP(xi)logP(xi)S(x)=-\sum_iP(x_i)logP(x_i) S(x)=−i∑​P(xi​)logP(xi​)

可以认为,熵表征的是随机事件的不确定性的大小,即事件可能性越多、越不确定,熵越大。这从公式中也可以看出来:

P(xi)P(x_i)P(xi​) 作为事件的概率肯定小于 1,取对数肯定小于 0,再取负号肯定大于 0。也就是说,熵累加的每一项都是正数,因此:可能性越多,熵越大。再看每一项的确定性,当 P(xi)=1P(x_i)=1P(xi​)=1 或 000 ,分别代表必定发生或必定不发生,这两种情况事件的确定性最小,从式子中也可以看出 P(xi)=1P(x_i)=1P(xi​)=1 或 000 ,该项都为 0。而当 P(xi)P(x_i)P(xi​) 的值在 0-1 中间某点时,事件非常不确定,此时该项值较大。因此说,越不确定,熵越大。

KL散度

以上说的都是某一个事件的信息量,如果我们另有一个独立的随机事件,我们该怎么计算事件A和事件B的差别?我们先介绍默认的计算方法:KL散度,有时又称相对熵、KL距离,实际上,虽然可以叫做KL距离,但它绝不是严格意义上的距离,因为KL散度不具有对称性,即A对B的KL散度 和 B对A的KL散度是不一样的。

KL散度的数学定义:对于离散事件

DKL=(A∣∣B)=∑iPA(xi)log(PA(xi)PB(xi))=∑iPA(xi)logPA(xi)−PA(xi)logPB(xi)D_{KL}=(A||B)=\sum_iP_A(x_i)log(\frac{P_A(x_i)}{P_B(x_i)})=\sum_iP_A(x_i)logP_A(x_i)-P_A(x_i)logP_B(x_i) DKL​=(A∣∣B)=i∑​PA​(xi​)log(PB​(xi​)PA​(xi​)​)=i∑​PA​(xi​)logPA​(xi​)−PA​(xi​)logPB​(xi​)

而对于连续事件,我们把求和改成积分即可。

可以看到,KL散度有这么几个特点:

当两个事件完全相同时,即 PA=PBP_A=P_BPA​=PB​ 时,DKL=0D_{KL}=0DKL​=0。如果把 A、BA、BA、B 的位置互换,则上式右边的值也会不同,KL散度不具有对称性,即 DKL(A∣∣B)≠DKL(B∣∣A)D_{KL}(A||B) \ne D_{KL}(B||A)DKL​(A∣∣B)=DKL​(B∣∣A)。可以发现,上式右边的左半部分 ∑iPA(xi)logPA(xi)\sum_i P_A(x_i)logP_A(x_i)∑i​PA​(xi​)logPA​(xi​) 其实就是事件 A 的熵,这点也是非常重要发现。

有这样的结论: A对B的KL散度由A自己的熵值和B在A上的期望共同决定,当使用A对B的KL散度来衡量两个分布的差异时,就是求A、B的对数差在A上的期望值。

交叉熵

终于来到了我们最熟悉的交叉熵,它是分类任务的最常用的损失函数,每个炼丹师在最开始拿MNIST手写数字分类做Hello World时,应该都使用的交叉熵损失函数。

可是我们上面已经介绍了:KL散度可以用来衡量两个分布的差异,那为什么不直接拿KL散度来做损失函数呢?我们先来对比一下A的熵、A对B的KL散度和A对B的交叉熵三者的公式:

A的熵:S(A)=−∑iPA(xi)logPA(xi)S(A)=-\sum_iP_A(x_i)logP_A(x_i)S(A)=−∑i​PA​(xi​)logPA​(xi​)A对B的KL散度:DKL(A∣∣B)=∑iPA(xi)logPA(xi)−PA(xi)logPB(xi)D_{KL}(A||B)=\sum_iP_A(x_i)logP_A(x_i)-P_A(x_i)logP_B(x_i)DKL​(A∣∣B)=∑i​PA​(xi​)logPA​(xi​)−PA​(xi​)logPB​(xi​)A对B的交叉熵:H(A,B)=−∑iPA(xi)logPB(xi)H(A,B)=-\sum_iP_A(x_i)logP_B(x_i)H(A,B)=−∑i​PA​(xi​)logPB​(xi​)

是不是越看越感觉这哥仨的某一部分有些像,它们之间应该是存在某种关系的。的确是这样的,有:

DKL(A∣∣B)=H(A,B)−I(A)D_{KL}(A||B)=H(A,B)-I(A) DKL​(A∣∣B)=H(A,B)−I(A)

即 KL散度=交叉熵-熵。

也就是说,KL散度与交叉熵的不同就体现在A本身的熵值上,如果A本身的熵值是一个常量,那么优化KL散度和优化交叉熵实质上就是等价的了。

再补充两个交叉熵的性质:

自己与自己的交叉熵即自己的熵值,即 H(A,A)=S(A)H(A,A)=S(A)H(A,A)=S(A)。与KL散度类似,交叉熵也不具有对称性,即 H(A,B)≠H(B,A)H(A,B)\ne H(B,A)H(A,B)=H(B,A)。

将交叉熵作为损失函数

在我们训练优化机器学习模型时,我们希望学到的是真实数据的分布 PReal(x)P_{Real}(x)PReal​(x),但这只是理想情况,我们不可能得到理想的真实分布。我们实际中一般都是通过在大规模的有标注训练数据分布 PLabel(x)P_{Label}(x)PLabel​(x) 上来近似 PReal(x)P_{Real}(x)PReal​(x)。

在训练中,如果我们用 KL 散度来表示分布之间的差异,那么我们希望的就是模型学习到的分布 PModel(x)P_{Model}(x)PModel​(x) 能够与标注训练数据的分布差异越小越好,也就是说我们要最小化这样的KL散度:DKL(Label∣∣Model)D_{KL}(Label||Model)DKL​(Label∣∣Model)。

可以看到,这里的 PLabelP_{Label}PLabel​ 就相当于上面的 PAP_APA​ ,而 PModelP_{Model}PModel​ 就相当于 PBP_BPB​。而我们的标签的分布 PLabelP_{Label}PLabel​ 的熵值还真就是个常量,更准确地说:标签的分布对于待更新的模型参数来说,是常量。因为标签值不对模型参数进行求导,它的分布是固定的,是不受模型参数变化的影响的。那么我们要最小化 DKL(Label∣∣Model)D_{KL}(Label||Model)DKL​(Label∣∣Model) 也就等价与计算交叉熵 $H(Label,Model) $了。而交叉熵算起来又更为方便,因此我们一般就将交叉熵作为损失函数了。

JS散度

JS散度又是怎么回事呢?我们看到,上面的KL散度和交叉熵都是不对称的形式,这在很多问题中是不合适的。因此JS散度的提出,解决了KL散度的不对称性的问题。

JS散度的公式如下:

JS(A∣∣B)=12KL(A∣∣A+B2)+12KL(B∣∣A+B2)JS(A||B)=\frac{1}{2}KL(A||\frac{A+B}{2})+\frac{1}{2}KL(B||\frac{A+B}{2}) JS(A∣∣B)=21​KL(A∣∣2A+B​)+21​KL(B∣∣2A+B​)

JS散度有这样的性质:

其值域范围是 [0,1][0,1][0,1]其具有对称性,即 JS(A∣∣B)=JS(B∣∣A))JS(A||B)=JS(B||A))JS(A∣∣B)=JS(B∣∣A)),这从公式里也能看出来,A、BA、BA、B 互换还是一样的。

代码实现及测试

以下根据公式手动实现了熵、KL 散度、JS 散度的计算,并与 scipy 库中的实现进行了对比。

import numpy as npfrom scipy.stats import entropyfrom scipy.spatial.distance import jensenshannondef calc_entropy(probs):# probs.shape: [n, ]log_probs = np.log2(probs)ent = -1 * np.sum(probs * log_probs)return entdef calc_KL_divergence(pa, pb):log_pa = np.log2(pa)log_pb = np.log2(pb)kl = np.sum(pa * log_pa - pa * log_pb)return kldef calc_JS_divergence(pa, pb):pc = 0.5 * (pa + pb)js = 0.5 * calc_KL_divergence(pa, pc) + 0.5 * calc_KL_divergence(pb, pc)return jsif __name__ == '__main__':a = np.array( [0.1, 0.2, 0.7] )b = np.array( [0.2, 0.3, 0.5] )sa = calc_entropy(a)sa_ = entropy(a, base=2)sb = calc_entropy(b)sb_ = entropy(b, base=2)print(f"entropy of a: {sa}, {sa_}")print(f"entropy of b: {sb}, {sb_}")kl_ab = calc_KL_divergence(a, b)kl_ab_ = entropy(a, b, base=2)kl_ba = calc_KL_divergence(b, a)kl_ba_ = entropy(b, a, base=2)print(f"kl of a->b: {kl_ab}, {kl_ab_}")print(f"kl of b->a: {kl_ba}, {kl_ba_}")js_ab = calc_JS_divergence(a, b)js_ab_ = jensenshannon(a, b, base=2)print(f"js of a, b: {js_ab}, {js_ab}")

PyTorch中的交叉熵实现

在 PyTorch 框架中,我们通常使用torch.nn.CrossEntropy这个类来计算损失函数。PyTorch中,该损失函数的底层实现实际上是由三部分组成:softmax,log,nll loss。其中 softmax 与 log 封装为 logsoftmax。在使用torch.nn.CrossEntropy计算损失值时,需要传入模型输出和标签。这里的两个传入参数需要注意:模型输出直接传入 logits 即可,不需要自己进行 softmax 处理;标签直接传入类别索引即可,不需要进行 one-hot 处理。以上两步在torch.nn.CrossEntropy都会自行进行。

上述一段阐述的 PyTorch 中各级封装的关系可由下图直观地表示:

对应测试代码如下:

import torchimport torch.nn as nn# x_input=torch.randn(3,3)#随机生成输入x_input = torch.tensor( [[-0.5, 0.3, 1.2]] )print('x_input:\t', x_input)y_target = torch.tensor( [2] )#设置输出具体值 print('y_target\n',y_target)#计算输入softmax,此时可以看到每一行加到一起结果都是1softmax_func = nn.Softmax(dim=1)soft_output = softmax_func(x_input)print('soft_output:\t', soft_output)#在softmax的基础上取loglog_output = torch.log(soft_output)print('log_output:\t', log_output)#对比softmax与log的结合与nn.LogSoftmaxloss(负对数似然损失)的输出结果,发现两者是一致的。logsoftmax_func = nn.LogSoftmax(dim=1)logsoftmax_output = logsoftmax_func(x_input)print('logsoftmax_output:\t', logsoftmax_output)#pytorch中关于NLLLoss的默认参数配置为:reducetion=True、size_average=Truenllloss_func = nn.NLLLoss()nlloss_output = nllloss_func(logsoftmax_output,y_target)print('nlloss_output:\t', nlloss_output)#直接使用pytorch中的loss_func=nn.CrossEntropyLoss()看与经过NLLLoss的计算是不是一样crossentropyloss = nn.CrossEntropyLoss()crossentropyloss_output = crossentropyloss(x_input, y_target)print('crossentropyloss_output:\t',crossentropyloss_output)

Ref

/qq_36552489/article/details/103793667

/question/65288314

/FrankieHello/article/details/80614422

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。