什么是交叉熵?

什么是交叉熵?

Tags
Machine Learning
Published
February 24, 2019
Author
yanbc

常见的交叉熵

做深度学习的同学肯定对交叉熵不会感到陌生,因为我们在训练网络的时候经常会用到交叉熵作为损失函数,往大的方向看,大概有两种交叉熵函数:
  • 普通交叉熵函数:在TensorFlowKeras框架中又叫 Categorical Cross Entropy,主要用于多分类任务(Multi-Class)
  • 二元交叉熵函数:在TensorFlowKeras框架中又叫 Binary Cross Entropy,主要用于多标签任务(Multi-Label)
简单来说,多分类就是给你一张照片,里面可能是太阳,可能是月亮,可能是云,但它可以且只可以是其中的一种,也就是说各个标签之间是互斥的;而多标签任务,就是给你一张照片,要求检测其中存在的太阳月亮和云,这时候太阳、月亮、云在照片中的存在的相互独立的,比如,可能只有云,可能只有太阳,也可能两个都有。
notion image
但是,究竟什么是交叉熵?或者说,为什么叫交叉熵?这就涉及到信息论中的信息熵,这是 Claude Shannon 在1948年提出的概念,旨在对一个随机事件的信息量进行量化。香农老爷子人称信息论之父,如果你不是读通信的可能只是听说过他的名字,对他的工作并不熟悉,但他在信息论中的贡献对人类文明的进步是有重大意义的。我读大学的时候的学院院长是香农的小迷弟,他曾经说过香农是凭一己之力把人类文明推前了20年,现在想来毫不夸张。今天刚好是香农的死祭,我们也来了解一下香农的工作。

交叉熵与信息熵

前面提到,香农的许多重要贡献之一是1948年发表的一篇文章,他在其中定义了信息熵(Entropy)的概念。一个随机事件 X 的信息熵的数学表达式如下:
其中 是变量 X 的概率分布, 是 X 为 x 时的理论最小编码长度,当 log 函数的底数为2时,该物理量的单位为比特(Bit)。也就是说,一个随机事件的信息熵是它的理论最小编码长度的期望值。只要我们知道一个随机事件的概率分布,我们就可以计算它的信息熵。
我们来深入搞清楚一些概念,首先是信息熵的单位比特(Bit)。比特的意思是 Binary Digit,来自于计算信息熵时的 log 函数的底数2。事实上,你也可以用3或者4或者任意一个数作为底数,当然这样的话信息熵的单位就要对应的改变,上面的公式才有意义。比方说我们用3作为底数,套用公式算出来的信息熵单位就可以叫做 Tet,Tenary digit。我们之所以在日常生活中常见用 Bit 做单位而不是Tet,是因为我们的数字电路设计是基于布尔逻辑的,布尔逻辑中只有两种状态。当然了,用电子开关模拟布尔逻辑运算的现代电子电路的基本思路也是香农的研究结果。
其次,信息熵代表的是一个随机事件的理论最小编码长度的期望值。这句话是什么意思?举个例子,我们有一个随机事件 ,它的概率为 。由于它有三个可能,在二进制世界里至少要用两个二进制位来代表它,如果我们采用 的方式对其进行编码,那么它的平均编码长度为 。但是,根据香农的信息理论,该随机事件的最小平均编码长度为 。那么,怎么样编码才能达到最小平均编码长度呢?一种常见的方法是霍夫曼编码,具体编码方法详见wiki。在我们的例子中,我们可以把概率为0.5的情况用二进制 表示,其他两种情况分别用 表示,这样我们的平均编码长度就是 。香农的理论不仅告诉了我们随机事件 可以用平均编码长度为1.5的编码方式编码,还告诉了我们这就是它的最小平均编码长度,不能再小了,就问你服不服。
事件
发生概率
编码1
编码2
a_1
0.5
00
0
a_2
0.25
01
10
a_3
0.25
10
11
只要我们知道一个随机事件的概率分布,我们就能计算它的信息熵。但是,如果我们不知道它的概率分布呢?我们就只能估算它的概率分布,然后用估算的概率分布来设计它的编码方式了。还是用回我们的老朋友事件 。假设我们不知道它实际的概率分布,只能估测它的概率分布为 ,据此设计的最优编码如上所述,它的平均编码长度是1.5比特。
但是,如果实际上它的概率分布是 ,那么它的实际平均编码长度其实是 比特,比我们设想的平均编码长度多了0.1比特。这就是交叉熵的概念了。在现实生活中,对大部分有意义的随机事件我们不可能知道它的实际的概率分布 ,只能估测它的概率分布为 ,并根据 进行编码,那么实际的平均编码长度就是交叉熵
根据这个公式,我们可以证明估测的概率分布 越接近实际概率分布 ,交叉熵就越小,当 时,交叉熵达到最小值,也就是该随机事件的信息熵。
这和深度学习中的损失函数的意义是吻合的。在深度学习中,实际概率分布就是我们的实际标签(Ground Truth,gt),估测概率分布就是模型预测出来的标签(Prediction,pred),当pred越接近gt时,损失函数的值越小,模型性能越好。

Keras中的两种交叉熵损失函数

知道了交叉熵的定义,我们来看一下交叉熵的分类。前面提到,常见的有两种交叉熵损失函数,Categorical Cross EntropyBinary Cross Entropy,一般Categorical Cross Entropy用于多分类任务,并且网络最后一层的输出一般使用softmax激活函数,因为softmax保证了最后一层输出的数值在0到1之间且和为1;而Binary Cross Entropy一般用于多标签任务,网络最后一层输出使用sigmoid激活函数,因为sigmoid保证了最后一层输出的数值在0到1之间且相互独立。
这两种损失函数在 TensorFlow 和 Keras 中的定义如下:
import tensorflow as tf import tensorflow.keras.backend as K import numpy as np def sigmoid(x): return 1 / (1 + np.exp(-1 * x)) def softmax(x): d = np.exp(x) return d / np.sum(d)
Binary cross-entropy的定义,用 numpy 实现
def binary_crossentropy(y_true, y_pred, from_logits=False): if from_logits: y_pred = sigmoid(y_pred) b1 = -1 * y_true * np.log(y_pred) b2 = -1 * (1 - y_true) * np.log(1 - y_pred) return b1 + b2
Categorical cross-entropy的定义,用 numpy 实现
def categorical_crossentropy(y_true, y_pred, from_logits=False): if from_logits: ce = -1 * y_true * np.log(softmax(y_pred)) else: ce = -1 * y_true * np.log(y_pred / np.sum(y_pred)) return np.sum(ce)
对比确认上面的两个函数和框架里面的是一样的
if __name__ == '__main__': pred = np.array([0.1, 0.3, 0.5, 0.7, 0.85, 0.97], dtype='float32') gt = np.array([0.2, 0.3, 0.6, 0.67, 0.88, 0.65], dtype='float32') graph1 = tf.Graph() with graph1.as_default(): pred_t = tf.constant(pred) gt_t = tf.constant(gt) # note from_logits bce_loss = K.binary_crossentropy(gt_t, pred_t, from_logits=True) graph2 = tf.Graph() with graph2.as_default(): pred_t = tf.constant(pred) gt_t = tf.constant(gt) # note from_logits cce_loss = K.categorical_crossentropy(gt_t, pred_t, from_logits=True) # keras's definition of binary and categorical cross entropy with tf.Session(graph=graph1) as sess: keras_bce = sess.run(bce_loss) with tf.Session(graph=graph2) as sess: keras_cce = sess.run(cce_loss) # binary and categorical cross entropy calculated with numpy numpy_bce = binary_crossentropy(gt, pred, from_logits=True) numpy_cce = categorical_crossentropy(gt, pred, from_logits=True)