交叉熵损失函数
交叉熵(Cross Entropy)是一种广泛应用的熵损失度量工具,在分类问题中具有显著的应用价值。
一、分类的损失函数
采用交叉熵损失函数的原因是什么?相比于其他类型的损失函数,在哪些方面具有优势?以下通过实例分析其优势。
我们训练一个简单的神经网络用于分类任务:识别图像中的动物类别,并使用独热码表示最终结果。
模型一
| 正确答案 | 猫概率 | 狗概率 | 兔子概率 | 预测 |
|---|---|---|---|---|
| 猫(1 0 0) | 0.8 | 0.1 | 0.1 | 猫(1 0 0) |
| 狗(0 1 0) | 0.05 | 0.9 | 0.05 | 狗(0 1 0) |
| 兔子(0 0 1) | 0.4 | 0.3 | 0.3 | 猫(1 0 0) |
| 模型二 | ||||
| 正确答案 | 猫概率 | 狗概率 | 兔子概率 | 预测 |
| – | – | – | – | – |
| 猫(1 0 0) | 0.6 | 0.2 | 0.2 | 猫(1 0 0) |
| 狗(0 1 0) | 0.25 | 0.7 | 0.05 | 狗(0 1 0) |
| 兔子(0 0 1) | 0.8 | 0.15 | 0.05 | 猫(1 0 0) |
| 我们可以看到,尽管最后两个模型的输出完全一样,但从概率上看,模型一的表现要比模型二更好。 |
1.分类错误率
最基本的方式是直接计算分类错误的比例,
即 Classification\ Error = \frac{number\ of\ error}{total\ number}。
这两个模型的分类误差率均为三分之一,
这样的损失函数无法区分两者的性能差异。
2. 均方误差
均方误差一般用于回归,然而它也能用在分类中,我们把每个类别的独热码当作真实值,把概率当作预测值,来计算每个样本的误差,再求平均值,即MSE=\frac{1}{n}\sum_{i=1}^n(\hat y_i-y_i)^2模型一
样本一 MSE_{11}=\frac{1}{3}[(1-0.8)^2+(0-0.1)^2+(0-0.1)^2]=0.02
样本二 MSE_{12}=\frac{1}{3}[(0-0.05)^2+(1-0.95)^2+(0-0.05)^2]=0.0025
样本三 MSE_{13}=\frac{1}{3}[(0-0.4)^2+(0-0.3)^2+(1-0.3)^2]=0.247
模型总误差 MSE_1 = \frac{1}{3}(MSE_{11}+MSE_{12}+MSE_{13})=0.0897
模型二
样本一 MSE_{21}=\frac{1}{3}[(1-0.6)^2+(0-0.2)^2+(0-0.2)^2]=0.08
样本二 MSE_{22}=\frac{1}{3}[(0-0.25)^2+(1-0.7)^2+(0-0.05)^2]=0.0517
样本三 MSE_{23}=\frac{1}{3}[(0-0.8)^2+(0-0.15)^2+(1-0.05)^2]=0.5217
模型总误差 MSE_2 = \frac{1}{3}(MSE_{21}+MSE_{22}+MSE_{23})=0.2178
我们观察到,在引入概率计算之后应用了新的评估指标——均方误差指标后发现,在相同的训练时间内模型一的表现优于模型二。不过,在实际应用中我们并不推荐将MSE直接用于分类任务中,请您参考以下几点原因:首先,在模型刚开始训练时使用MSE作为损失函数会导致优化过程中的梯度下降速度非常缓慢;其次,并非所有基于MSE构建起来的分类问题都满足严格的凸优化条件这一前提假设。因此,在这类场景下传统优化算法的表现往往不尽如人意。下面我们将介绍一种在分类任务中表现更为优异的方法——交叉熵方法。
二、交叉熵的推导
交叉熵是信息论中的内容,我们从最基础的概念开始推导。
1. 信息量
一个事件能携带多少信息呢?显而易见的事情大家都知道它不会携带很多信息;相反地,在人们通常会预期到的事件中所包含的信息较少。然而,在一些超出人们的预期情况下发生的事件中所包含的信息却较多。举个例子:班上期中考试第一名被保送至其他班级这样的事情常人看来是理所当然的;但是某位同学却从班上最后一名变成了第一名这背后隐藏着许多因素——也许他付出了特别的努力或者是在课外学习和补习班上取得了优异的成绩等等这些都成为了一个新事件中的隐藏因素从而带来了更高的信息量。因此我们发现一个事件的信息量与其发生概率之间存在着密切的关系。
2.信息熵
为了给事件X设定一个取值范围。由于这件事可能有多种多样的可能性,即\chi代表了所有可能的情况。比如:我打开了电脑,
| 事件 | 概率 |
|---|---|
| 电脑正常开机 | 0.95 |
| 电脑黑屏,无法启动 | 0.04 |
| 电脑爆炸了 | 0.01 |
| 信息熵的定义是,所有事件的信息量乘上概率的期望,即 | |
| H(X)=-\sum_{i=1}^np(x_i)\log(p(x_i))在上面的例子中,我们的信息熵为H(X)=-(0.95\times \log(0.95)+0.05\times \log(0.05)+0.01 \times \log(0.01))。 | |
| 有的问题只有两个可能性(例如扔硬币),服从伯努利分布,可以将信息熵的公式简化为: | |
| 设p(0)=p,\ p(1)=1-p |
\begin{aligned} H(X) &=-\sum_{i=1}^np(x_i)\log(p(x_i))\\ &=-(p\log p+ (1-p)\log(1-p))\\ &= -p\log p-(1-p)\log(1-p) \end{aligned}
3. 相对熵/KL散度
不同事件在不同的条件下发生的概率是不同的,比如夏天和冬天买雪糕的概率是不同的。假设对于一个事件X,有两个概率分布P(X)和Q(X)。KL散度(Kullback-Leibler Divergence)指的就是在两个不同的概率分布下,该事件的信息熵的增量是多少。
在机器学习中,P(X)通常指代数据的真实分布,比如[1\ 0\ 0],我们也可以将其理解为概率,因为已经是真实发生的数据了,所以我们非常肯定因变量属于哪一个类型,可以给对应的类型1的概率。Q(X)通常指代的是我们的预测结果,比如[0.7\ 0.2\ 0.1],我们可以发现,我们的预测值不像真实值一样确定,所以我们需要更多的训练,来获取信息增量,才能让Q(X)到达P(X)的水平,当我们经过反复的训练,让Q变得和P一样完美,不再需要额外的信息增量了,此时Q与P等价;由此我们可以推导出KL散度的公式:
\begin{aligned} D_{KL}(p||q)&=\sum_{i=1}^np(x_i)\log(\frac{p(x_i)}{q(x_i)})\\ &=\sum_{i=1}^np(x_i)\log(p(x_i))-\sum_{i=1}^np(x_i)\log(q(x_i)) \end{aligned}由此我们发现,KL散度越小,P分布和Q分布越接近。
4. 交叉熵
基于KL散度的公式框架进行进一步推导:
\begin{aligned} D_{KL}(p||q) &= \sum_{i=1}^n p(x_i)\log p(x_i) - \sum_{i=1}^n p(x_i)\log q(x_i) \\ &= -H(p(X)) + [-\sum_{i=1}^n p(x_i)\log q(x_i)] \end{aligned}
其中前一部分即为p的信息熵项;
将后半部分定义为交叉熵:
H(p,q) = -\sum p(x_i)\log q(x_i)
在机器学习领域中,则采用该差异性指标作为衡量标准,并将其量化为损失函数的形式表示。
5. 交叉熵的计算
我们来计算一算上文两个模型的交叉熵分别是多少:
模型一
样本一 H(y,\hat y)_{11}=-[1\times \log0.8+0\times \log0.1 + 0\times \log0.1]=0.2231
样本二 H(y,\hat y)_{12}=-[0\times \log0.05+1\times \log0.9+ 0 \times \log0.05]=0.1054
样本三 H(y,\hat y)_{13}=-[0\times \log0.4 + 0\times \log0.3 + 1\times \log0.3]=1.204
模型总误差 H(y,\hat y)_1 = \frac{1}{3}(H(y,\hat y)_{11}+H(y,\hat y)_{12}+H(y,\hat y)_{13})=0.511
模型二
样本一 H(y,\hat y)_{21}=-[1\times \log0.6+0\times \log0.2 + 0\times \log0.2]=0.5108
样本二 H(y,\hat y)_{22}=-[0\times \log0.25+1\times \log0.7+ 0 \times \log0.05]=0.3567
样本三 H(y,\hat y)_{23}=-[0\times \log0.8 + 0\times \log0.15 + 1\times \log0.05]=2.9957
模型总误差 H(y,\hat y)_2 = \frac{1}{3}(H(y,\hat y)_{21}+H(y,\hat y)_{22}+H(y,\hat y)_{23})=1.288
可以看到,交叉熵可以捕获两个模型之间的差距。
三、反向传播
我们来深入分析一下模型最后两层的反向传播过程。由于二分类和多分类所使用的激活函数存在差异性特征,在这种情况下我们需要分别对它们进行求导运算。
1. 二分类
二元分类模型的最后一层激活函数采用了Sigmoid函数。为了更好地理解该模型的整体架构,请了解该模型的具体组成情况:

基于链式法则,
\frac{\partial L}{\partial w_i}=\frac{\partial L}{\partial p}\cdot\frac{\partial p}{\partial z}\cdot\frac{\partial z}{\partial w_i}具体地,在计算每个导数时,
\begin{aligned} \frac{\partial L}{\partial p} &=\frac{\partial}{\partial p}[-(y\log p+(1-y)\log(1-p))]\\ &=-\frac{y}{p}+\frac{1-y}{1-p} \end{aligned}我们依次计算各部分的导数,
\begin{aligned} \frac{\partial p}{\partial z} &=\frac{\partial\sigma(z)}{\partial z}= \frac{\partial}{\partial z}\left(\frac{e^z}{1+e^{z}} \right)\\ &=e^z(1+e^{-z})^{-2}\\ &=σ(z)(1-σ(z)) \end{aligned}其中,
\begin{aligned} \frac{\partial z}{∂w_i}= \frac{∂}{∂w_i}(w_ix_i+b_i)=x_i \end{aligned}将各部分导数相乘,
\begin{aligned} \frac{\�L}{∂w_i}= &-\left(\frac{y}{σ(z)}+\frac{(1-y)}{(1-σ(z))}\right)\\ &×σ(z)(1-σ(z))x_i \\ =[\sigma(z)-y]x_i \end{aligned}最终得出结论,
\boxed{ \dfrac{\mathrm dL}{\mathrm dw_i}= [\sigma(z)-y]x_i }
2.多分类
多分类模型最后一层的激活函数使用的是Softmax函数,模型结构为:

和上面一样,根据链式法则,
\frac{\partial L}{\partial w_i}=\frac{\partial L}{\partial p}\cdot\frac{\partial p}{\partial z}\cdot\frac{\partial z}{\partial w_i}我们依次进行求导
\begin{aligned} \frac{\partial L}{\partial p} &=\frac{\partial}{\partial p}[-\sum_{i=1}^ny_i\log p]\\ &=-\sum_{i=1}^n\frac{y_i}{p} \end{aligned}
Softmax函数的公式为\sigma(z_i)=\frac{e^{z_i}}{\sum_{k=1}^n e^{z_k}},然而,Softmax在计算的过程中经常出现由于数据溢出导致的返回nan的情况,因此我们需要将其乘上一个系数,让数值大小下降下来,即:
\begin{aligned} \sigma(z_i) &=\frac{Ce^{z_i}}{C\sum_{k=1}^ne^{z_k}}\\ &=\frac{e^{z_i+\log C}}{\sum_{k=1}^ne^{z_k+\log C}} \end{aligned}
我们通常让\log C=-\max(z),让比较大的指数变小;由此,我们设z_i-\log C=z_i',并求其梯度
\begin{aligned} \frac{\partial p}{\partial z_j'} &=\frac{\partial\sigma(z_i')}{\partial z_j'}=\frac{\partial}{\partial z_j'}\frac{e^{z_i'}}{\sum_{k=1}^n e^{z_k'}} \end{aligned}
当i \ne j时
\begin{aligned} \frac{\partial p}{\partial z_j'} &=\frac{0-e^{z_i'}e^{z_j'}}{(\sum_{k=1}^n e^{z_k'})^2}\\ &=-\frac{e^{z_i'}}{(\sum_{k=1}^n e^{z_k'})}\cdot \frac{e^{z_j'}}{(\sum_{k=1}^n e^{z_k'})}\\ &=-\sigma(z_i)\cdot \sigma(z_j) \end{aligned}
当i = j时
\begin{aligned} \frac{\partial p}{\partial z_j'} &=\frac{e^{z_i'}\sum_{k=1}^n e^{z_k'}-(e^{z_i'})^2}{(\sum_{k=1}^n e^{z_k'})^2}\\ &=\frac{e^{z_i'}}{(\sum_{k=1}^n e^{z_k'})}\cdot \frac{\sum_{k=1}^n e^{z_k'}-e^{z_i'}}{(\sum_{k=1}^n e^{z_k'})}\\ &=\sigma(z_i')\cdot (1-\sigma(z_j')) \end{aligned}
由此可得:
\frac{\partial p}{\partial z_j'}= \begin{cases} -\sigma(z_i)\cdot \sigma(z_j), & i \ne j\\ \sigma(z_i)\cdot (1-\sigma(z_j)), & i = j \end{cases}
\begin{aligned} \frac{\partial z}{\partial w_i}=\frac{\partial}{\partial w_i}(w_ix_i+b_i)=x_i \end{aligned}
\begin{aligned} \frac{\partial L}{\partial w_i} &=\frac{\partial L}{\partial p}\cdot\frac{\partial p}{\partial z}\cdot\frac{\partial z}{\partial w_i}\\ &=-[\sum_{k\ne i}^n\frac{y_k}{p_k}\cdot [-\sigma(z_i)\cdot \sigma(z_k)]+\frac{y_i}{p_i}\cdot \sigma(z_i)\cdot (1-\sigma(z_i))]\cdot x_i\\ &=-[\sum_{k\ne i}^n\frac{y_k}{\sigma(z_k)}\cdot [-\sigma(z_i)\cdot \sigma(z_k)]+\frac{y_i}{\sigma(z_i)}\cdot \sigma(z_i)\cdot (1-\sigma(z_i))]\cdot x_i\\ &=-[-\sum_{k\ne i}^n y_k\cdot \sigma(z_i)+y_i(1-\sigma(z_i))]\cdot x_i\\ &=[\sum_{k\ne i}^n y_k\cdot \sigma(z_i)+y_i\cdot\sigma(z_i)-y_i]\cdot x_i\\ \end{aligned}
因为我们的向量y是独热码表示,所以\sum_{k\ne i}^n y_k + y_i = 1,因此上式可以化简为:
\frac{\partial L}{\partial w_i}=[\sigma(z_i)-y_i]\cdot x_i
四、交叉熵的实现
import numpy as np
class Loss:
def loss(self, predicted:Tensor, actual:Tensor) -> Tensor:
raise NotImplementedError
def grad(self, predicted:Tensor, actual:Tensor) -> Tensor:
raise NotImplementedError
class CrossEntropy(Loss):
def loss(self, predicted:Tensor, actual:Tensor) -> Tensor:
m = actual.shape[0]
log_likelihood = -np.log(predicted)
loss = np.sum(log_likelihood) / m
return loss
def grad(self, predicted:Tensor, actual:Tensor) -> Tensor:
m = actual.shape[0]
grad = predicted[range(m),actual.tolist()] - 1
grad /= m
return grad
五、参考资料
知乎专栏:损失函数 - 交叉熵损失函数
一文搞懂交叉熵在机器学习中的应用,透彻理解交叉熵背后的直觉
Softmax和交叉熵的深度解析和Python实现
Stanford University CS230 - Deep Learning
官方专栏:损失函数——深入解析交叉熵损失函数及其在实践中的应用
