在看Pytorch的交叉熵损失函数torch.nn.CrossEntropyLoss官方文档介绍中,给出的表达式如下。不免有点疑惑为何交叉熵损失的表达式是这个样子的
loss ( y , class ) = − log ( exp ( y [ class ] ) ∑ j exp ( y [ j ] ) ) = − y [ class ] + log ( ∑ j exp ( y [ j ] ) ) \operatorname{loss}(y, \text { class })=-\log \left(\frac{\exp (y[\text { class }])}{\sum_{j} \exp (y[j])}\right)=-y[\text { class }]+\log \left(\sum_{j} \exp (y[j])\right) loss(y, class )=−log(∑jexp(y[j])exp(y[ class ]))=−y[ class ]+log(j∑exp(y[j]))
这个表达式y是网络的输出向量,是一组实数值,衡量每个类别的score值,而class则是样本类别的标签值,也是是数值。为何 − y [ class ] + log ( ∑ j exp ( y [ j ] ) -y[\text { class }]+\log (\sum_{j} \exp (y[j]) −y[ class ]+log(∑jexp(y[j])就能算出损失值呢?
本文参考了相关资料,对这个表达式推导过程做了个总结,如果你有和我一样的困惑,希望本文对你有所帮助。
让我们先从损失函数,以及交叉熵推导过程说起。
损失函数的意义以及作用
在监督式学习的模型训练中,损失函数主要是用来计算在给定样本输入由模型的计算的输出 y ^ \hat{y} y^ 与真实的标签 y y y之间的距离,给模型的优化过程指明方向。在多分类任务重,经常用到的就是交叉熵损失函数。 要理解交叉熵,先从信息量,熵说起。
信息量
假设 X X X是一个离散型随机变量,其取值集合为 χ \chi χ,概率分布函数 p ( x ) = Pr ( X = x ) , x ∈ χ p(x)=\operatorname{Pr}(X=x), x \in \chi p(x)=Pr(X=x),x∈χ ,则定义事件 X = x 0 X=x0 X=x0 的信息量为:
I ( x 0 ) = − log ( p ( x 0 ) ) I\left(x_{0}\right)=-\log \left(p\left(x_{0}\right)\right) I(x0)=−log(p(x0))
由于是概率所以 p ( x 0 ) p(x0) p(x0)的取值范围是 [ 0 , 1 ] [0,1] [0,1],绘制为图形如下:
熵
有了信息量的定义,而熵用来表示所有信息量的期望,即:
H ( X ) = − ∑ i = 1 n p ( x i ) log ( p ( x i ) ) H(X)=-\sum_{i=1}^{n} p\left(x_{i}\right) \log \left(p\left(x_{i}\right)\right) H(X)=−i=1∑np(xi)log(p(xi))
其中n代表所有的n种可能性
对于n取值为2则表示实验结果只有两种可能性,因为两种可能性的概率之和为1,如果其中一个结果的的概率是 p ( x ) p(x) p(x)则另外一个结果是 ( 1 − p ( x ) ) (1-p(x)) (1−p(x))将上面所有的求和公式结果展开可以得到只有两个结果的熵的表达式为
H ( X ) = − ∑ i = 1 n p ( x i ) log ( p ( x i ) ) = − p ( x ) log ( p ( x ) ) − ( 1 − p ( x ) ) log ( 1 − p ( x ) ) \begin{aligned} H(X) &=-\sum_{i=1}^{n} p\left(x_{i}\right) \log \left(p\left(x_{i}\right)\right) \\ &=-p(x) \log (p(x))-(1-p(x)) \log (1-p(x)) \end{aligned} H(X)=−i=1∑np(xi)log(p(xi))=−p(x)log(p(x))−(1−p(x))log(1−p(x))
相对熵(KL散度)
相对熵又称KL散度,如果我们对于同一个随机变量 x 有两个单独的概率分布 P ( x ) P(x) P(x) 和 Q ( x ) Q(x) Q(x),我们可以使用 KL 散度(Kullback-Leibler (KL) divergence)来衡量这两个分布的差异
KL散度的计算公式:
D K L ( p ∥ q ) = ∑ i = 1 n p ( x i ) log ( p ( x i ) q ( x i ) ) D_{K L}(p \| q)=\sum_{i=1}^{n} p\left(x_{i}\right) \log \left(\frac{p\left(x_{i}\right)}{q\left(x_{i}\right)}\right) DKL(p∥q)=i=1∑np(xi)log(q(xi)p(xi))
交叉熵
对KL散度计算公式展开,可以得到
D K L ( p ∥ q ) = ∑ i = 1 n p ( x i ) log ( p ( x i ) ) − ∑ i = 1 n p ( x i ) log ( q ( x i ) ) = − H ( p ( x ) ) + [ − ∑ i = 1 n p ( x i ) log ( q ( x i ) ) ] \begin{aligned} D_{K L}(p \| q) &=\sum_{i=1}^{n} p\left(x_{i}\right) \log \left(p\left(x_{i}\right)\right)-\sum_{i=1}^{n} p\left(x_{i}\right) \log \left(q\left(x_{i}\right)\right) \\ &=-H(p(x))+\left[-\sum_{i=1}^{n} p\left(x_{i}\right) \log \left(q\left(x_{i}\right)\right)\right] \end{aligned} DKL(p∥q)=i=1∑np(xi)log(p(xi))−i=1∑np(xi)log(q(xi))=−H(p(x))+[−i=1∑np(xi)log(q(xi))]
等式的前一部分恰巧就是p的熵,等式的后一部分,就是交叉熵:
H ( p , q ) = − ∑ i = 1 n p ( x i ) log ( q ( x i ) ) H(p, q)=-\sum_{i=1}^{n} p\left(x_{i}\right) \log \left(q\left(x_{i}\right)\right) H(p,q)=−i=1∑np(xi)log(q(xi))
习惯上, y y y标记为网络的输出的话,n 表达为 C个种类,并且从0开始,那么交叉熵可以表达为:
H ( p , q ) = − ∑ i = 0 C − 1 p ( y i ) log ( q ( y i ) ) (公式1) H(p, q)=-\sum_{i=0}^{C-1} p\left(y_{i}\right) \log \left(q\left(y_{i}\right)\right) \tag{公式1} H(p,q)=−i=0∑C−1p(yi)log(q(yi))(公式1)
在机器学习中,我们需要评估label和predicts、之间的差距,使用KL散度刚刚好,即 D K L ( y ∥ y ^ ) D_{K L}(y \| \hat{y}) DKL(y∥y^),由于KL散度中的前一部分 − H ( y ) −H(y) −H(y)不变,故在优化过程中,只需要关注交叉熵就可以了。所以一般在机器学习中直接用用交叉熵做loss,评估模型。
机器学习分类如何使用交叉熵损失
假设我们模型的输出结果如图所示, y y y为模型对应的预测结果,一般情况下,对于多分类问题,有多少种种类就对应多少个输出值,记为 y 0 y_0 y0… y C − 1 y_{C-1} yC−1。
下面着重讲下这两个函数以及他们的相同点和不同点。
负对数似然函数(torch.nn.NLLLoss)
torch.nn.NLLLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
负对数似然损失函数,主要用于训练含有C个种类分类的训练任务中。
可选参数 weight 应该是为每个类分配权重的一维张量。当您的训练集不平衡时,这尤其有用。
参数input是网络输出值的每个类的概率的对数。input 是一个size为 ( m i n i b a t c h , C ) (minibatch, C) (minibatch,C)的张量。 通过在网络的最后一层添加 LogSoftmax 层,可以很容易地获得神经网络中的概率对数。
参数target是在 [ 0 , C − 1 ] [0, C-1] [0,C−1]取值范围内的类索引。
Target: 这个是真实的样本标签值,其size是 ( N ) (N) (N),分别表示每个样本的种类,这个大小和一个batch的训练样本的数量一致,其中Target的每个元素值满足, 0 ≤ targets [ i ] ≤ C − 1 0 \leq \operatorname{targets}[i] \leq C-1 0≤targets[i]≤C−1 ,在损失函数中用作样本标签值。
ℓ ( x , y ) = L = { l 1 , … , l N } ⊤ , l n = − w y n x n , y n , w c = weight [ c ] ⋅ 1 { c ≠ ignore_index } \ell(x, y)=L=\left\{l_{1}, \ldots, l_{N}\right\}^{\top},\quad l_{n}=-w_{y_{n}} x_{n, y_{n}}, \quad w_{c}= \operatorname{weight}[c] \cdot 1\{c \neq \text {ignore\_index}\} ℓ(x,y)=L={l1,…,lN}⊤,ln=−wynxn,yn,wc=weight[c]⋅1{c=ignore_index}
其中, x x x是 input, y y y 是 target, w w w 是 weight权重, N N N 一个batch 所包含的样本的数量.
ℓ ( x , y ) = { ∑ n = 1 N 1 ∑ n = 1 N w y n l n , if reduction = ’mean’ ∑ n = 1 N l n , if reduction = ’sum’ \ell(x, y)= \begin{cases}\sum_{n=1}^{N} \frac{1}{\sum_{n=1}^{N} w_{y_{n}}} l_{n}, & \text { if reduction }=\text { 'mean' } \\ \sum_{n=1}^{N} l_{n}, & \text { if reduction }=\text { 'sum' }\end{cases} ℓ(x,y)={∑n=1N∑n=1Nwyn1ln,∑n=1Nln, if reduction = ’mean’ if reduction = ’sum’
其它参数:
import torch
import torch.nn as nn
m = nn.LogSoftmax(dim=1)
loss = nn.NLLLoss()
# input is of size N x C = 3 x 5
input = torch.randn(3, 5, requires_grad=True)
print("Model ouput 3 samples and 5 class score for each sample: \n",input)
# each element in target has to have 0 <= value < C
target = torch.tensor([1, 0, 4])
print("Sample label value used as index, the label value in range(0, 5): \n",target)
output = loss(m(input), target)
print("Losses computed with nn.NLLLoss: ",output)
loss_formula = -1*(m(input)[range(input.shape[0]), target]).mean()
print("Losses computed with nn.NLLLoss formula: ",loss_formula)
运行结果:
Model ouput 3 samples and 5 class score for each sample:
tensor([[ 0.0727, -0.5552, -1.3034, 0.5908, 0.7858],
[-0.3638, 1.1226, -1.3350, 0.5818, -0.0582],
[ 1.3680, -0.7179, -0.3489, -0.5296, -0.1822]], requires_grad=True)
Sample label value used as index, the label value in range(0, 5):
tensor([1, 0, 4])
Losses computed with nn.NLLLoss: tensor(2.2232, grad_fn=<NllLossBackward>)
Losses computed with nn.NLLLoss formula: tensor(2.2232, grad_fn=<MulBackward0>)
Pytorch 中交叉熵损失函数torch.cross_entropy()
torch.nn.functional.cross_entropy(input, target, weight=None, size_average=None, \
ignore_index=-100, reduce=None, reduction='mean')
该接口将log_softmax 和 nll_loss 组合在一个函数中。在 C种分类的训练分类时很有用。
参数解读:
可选参数 weight 应该是为每个类分配权重的一维张量。当您的训练集不平衡时,这尤其有用。
input 应包含每个类的原始非标准化score值对应的是一组实数。是神经网络的输出的实数数值
input 必须是tensor, 其shape大小为 ( m i n i b a t c h , C ) (minibatch,C) (minibatch,C)。
该接口期望取值在 [ 0 , C − 1 ] [0,C−1] [0,C−1]范围内的类索引作为(大小为 minibatch 的)1D 张量的每个值的目标(target );如果指定了ignore_index,这个接口也接受这个类索引(这个索引不一定在类范围内)。
损失可以描述为:
loss ( y , class ) = − log ( exp ( y [ class ] ) ∑ j exp ( y [ j ] ) ) = − y [ class ] + log ( ∑ j exp ( y [ j ] ) ) \operatorname{loss}(y, \text { class })=-\log \left(\frac{\exp (y[\text { class }])}{\sum_{j} \exp (y[j])}\right)=-y[\text { class }]+\log \left(\sum_{j} \exp (y[j])\right) loss(y, class )=−log(∑jexp(y[j])exp(y[ class ]))=−y[ class ]+log(j∑exp(y[j]))
仔细观察下,官网中讲的这个公式不就是我们推导的的交叉熵公式4 吗,把 j j j的取值写完整,也就是
loss ( y , class ) = − log ( exp ( y [ class ] ) ∑ j exp ( y [ j ] ) ) = − y [ class ] + log ( ∑ j = 0 C − 1 exp ( y [ j ] ) ) \operatorname{loss}(y, \text { class })=-\log \left(\frac{\exp (y[\text { class }])}{\sum_{j} \exp (y[j])}\right)=-y[\text { class }]+\log \left(\sum_{j=0}^{C-1} \exp (y[j])\right) loss(y, class )=−log(∑jexp(y[j])exp(y[ class ]))=−y[ class ]+log(j=0∑C−1exp(y[j]))
对于样本不均衡情况下,需要指定 weight 参数,这种情况下,公式为:
loss ( y , class ) = weight [ class ] ( − y [ class ] + log ( ∑ j exp ( y [ j ] ) ) ) \operatorname{loss}(y, \text { class })=\text { weight }[\text { class }]\left(-y[\text { class }]+\log \left(\sum_{j} \exp (y[j])\right)\right) loss(y, class )= weight [ class ](−y[ class ]+log(j∑exp(y[j])))
损失在每个minibatch的观察中取平均值。如果指定了 weight 参数,那么这是一个加权平均值:
loss = ∑ i = 1 N loss ( i , class [ i ] ) ∑ i = 1 N weight [ class [ i ] ] \operatorname{loss}=\frac{\sum_{i=1}^{N} \operatorname{loss}(i, \text { class }[i])}{\sum_{i=1}^{N} \text { weight }[\text { class }[i]]} loss=∑i=1N weight [ class [i]]∑i=1Nloss(i, class [i])
除了上面的讲的input和target,其它参数,weight,size_average,ignore_index以及reduction,和torch.nn.NLLLoss基本一致。
举例说明:
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
loss = nn.CrossEntropyLoss(reduction="mean")
input = torch.randn(3, 5, requires_grad=True)
print("Model ouput 3 samples and 5 class score for each sample: \n",input)
target = torch.empty(3, dtype=torch.long).random_(5)
print("Sample label value, the label value in range(0, 5): \n",target)
#第一种方式,采用pytorch交叉熵函数接口计算loss
output = loss(input, target)
print("Losses computed with nn.CrossEntropyLoss: \n",output)
#第二种方式,采用交叉熵公式计算loss
f_2 = input.exp().sum(-1).log().unsqueeze(-1)
f_1 = -1*input[range(target.shape[0]), target]
loss0 = (f_1+f_2).mean()
print("Losses computed with loss formula: \n",output)
运行结果:
Model ouput 3 samples and 5 class score for each sample:
tensor([[-0.7193, -0.0853, 0.2175, -0.1100, 0.2599],
[-3.0053, -0.1658, -1.0710, 0.5690, 0.9577],
[-0.2433, -1.9273, 0.0165, -0.9162, 0.5135]], requires_grad=True)
Sample label value, the label value in range(0, 5):
tensor([4, 4, 2])
Losses computed with nn.CrossEntropyLoss:
tensor(1.1528, grad_fn=<NllLossBackward>)
Losses computed with loss formula:
tensor(1.1528, grad_fn=<NllLossBackward>)
Backend Qt5Agg is interactive backend. Turning interactive mode on.
从运行结果来看,采用pytorch交叉熵函数接口计算loss和采用交叉熵公式计算loss所得到的结果是一致的。
参考资料:
极大似然估计 —— Maximum Likelihood Estimation
Negative log likelihood explained
深度理解机器学习中常见损失函数
交叉熵、相对熵和负对数似然的理解
19个损失函数汇总,以Pytorch为例
Pytorch详解NLLLoss和CrossEntropyLoss
详解torch.nn.NLLLOSS
一文搞懂交叉熵在机器学习中的使用,透彻理解交叉熵背后的直觉