天天看点

交叉熵损失分析交叉熵损失分析

交叉熵损失分析

分类任务是训练神经网络最常用的任务之一。对于分类任务来说,它的损失函数一般采用交叉熵损失函数。至于为什么这样做,本博客在此进行简单的分析。

平方损失函数

在分类任务上,类别往往属于离散的整形数据(integer)。最直观的想法就是直接使用平方损失函数: L = 1 2 ∑ i = 1 N ( y i − t i ) 2 L=\frac{1}{2}\sum_{i=1}^N(y_i-t_i)^2 L=21​∑i=1N​(yi​−ti​)2。这里假设 y i y_i yi​为第i个样本网络的输出结果, t i t_i ti​为该样本对应的标签。

使用平方损失虽然简单,但是缺点也很明显:

  • 例如:在二分类任务中,如果使用最后一层sigmoid激活以后的结果作为类别( y = s i g m o i d ( z ) , L = 1 2 ( y − t ) 2 y=sigmoid(z), L=\frac{1}{2}(y-t)^2 y=sigmoid(z),L=21​(y−t)2),如果网络输出了0.000001,那么在softmax这块( d L d z = ( y − t ) ∗ y ∗ ( 1 − y ) \frac{dL}{dz} = (y-t)*y*(1-y) dzdL​=(y−t)∗y∗(1−y)),导数基本很小,导致无法反向更新。对于logistic function都会存在这些问题。
  • 一般分类任务中,类别之间是互斥的。那么所有类别对应的概率之和应该等于1。平方损失无法体现出该点。

Softmax损失函数

Softmax函数解决了互斥类别中各个类的概率之和等于1的问题。该函数定义如下:

p i = e a i ∑ k = 1 N e a k p_i = \frac{e^{a_i}}{\sum_{k=1}^N e^{a_k}} pi​=∑k=1N​eak​eai​​

在numpy中,softmax函数可以通过如下实现:

def softmax(X):
    exps = np.exp(X)
    return exps / np.sum(exps)
           

但是这里有坑需要注意, e x e^x ex很容易出现数值溢出,返回nan的错误。

所以需要对softmax实现进行以下优化:

p i = e a i ∑ k = 1 N e a k = C e a i C ∑ k = 1 N e a k = e a i + l o g ( C ) ∑ k = 1 N e a k + l o g ( C ) p_i = \frac{e^{a_i}}{\sum_{k=1}^N e^{a_k}} = \frac{Ce^{a_i}}{C\sum_{k=1}^N e^{a_k}}=\frac{e^{a_i + log(C)}}{\sum_{k=1}^N e^{a_k+log(C)}} pi​=∑k=1N​eak​eai​​=C∑k=1N​eak​Ceai​​=∑k=1N​eak​+log(C)eai​+log(C)​

可以选取任意的值作为 l o g ( C ) log(C) log(C),通常来说选取 l o g ( C ) = − m a x ( a ) log(C) = -max(a) log(C)=−max(a),这样保证 e x e^x ex不会出现溢出。改变以后的softmax代码如下所示:

def softmax(X):
    exps = np.exp(X - np.max(X))
    return exps / np.sum(exps)
           

对softmax函数求导,如果i=j:

∂ e a i ∑ k = 1 N e a k ∂ a j = e a i ∑ k = 1 N e a k − e a j e a i ( ∑ k = 1 N e a k ) 2 = e a i ( ∑ k = 1 N e a k − e a j ) ( ∑ k = 1 N e a k ) 2 = e a j ∑ k = 1 N e a k × ( ∑ k = 1 N e a k − e a j ) ∑ k = 1 N e a k = p i ( 1 − p j ) \begin{aligned} \frac{\partial \frac{e^{a_i}}{\sum_{k=1}^N e^{a_k}}}{\partial a_j} &= \frac{e^{a_i} \sum_{k=1}^N e^{a_k} - e^{a_j}e^{a_i}}{\left( \sum_{k=1}^N e^{a_k}\right)^2} \\ &= \frac{e^{a_i} \left( \sum_{k=1}^N e^{a_k} - e^{a_j}\right )}{\left( \sum_{k=1}^N e^{a_k}\right)^2} \\ &= \frac{ e^{a_j} }{\sum_{k=1}^N e^{a_k} } \times \frac{\left( \sum_{k=1}^N e^{a_k} - e^{a_j}\right ) }{\sum_{k=1}^N e^{a_k} } \\ &= p_i(1-p_j) \end{aligned} ∂aj​∂∑k=1N​eak​eai​​​​=(∑k=1N​eak​)2eai​∑k=1N​eak​−eaj​eai​​=(∑k=1N​eak​)2eai​(∑k=1N​eak​−eaj​)​=∑k=1N​eak​eaj​​×∑k=1N​eak​(∑k=1N​eak​−eaj​)​=pi​(1−pj​)​

如果 i ≠ j i\neq j i̸​=j:

∂ e a i ∑ k = 1 N e a k ∂ a j = 0 − e a j e a i ( ∑ k = 1 N e a k ) 2 = − e a j ∑ k = 1 N e a k × e a i ∑ k = 1 N e a k = − p j . p i \begin{aligned} \frac{\partial \frac{e^{a_i}}{\sum_{k=1}^N e^{a_k}}}{\partial a_j}&= \frac{0 - e^{a_j}e^{a_i}}{\left( \sum_{k=1}^N e^{a_k}\right)^2} \\ &= \frac{- e^{a_j} }{\sum_{k=1}^N e^{a_k} } \times \frac{e^{a_i} }{\sum_{k=1}^N e^{a_k} } \\ &= - p_j.p_i \end{aligned} ∂aj​∂∑k=1N​eak​eai​​​​=(∑k=1N​eak​)20−eaj​eai​​=∑k=1N​eak​−eaj​​×∑k=1N​eak​eai​​=−pj​.pi​​

交叉熵损失函数

交叉熵损失定义如下:

p i = e a i ∑ k = 1 N e a k p_i = \frac{e^{a_i}}{\sum_{k=1}^N e^{a_k}} pi​=∑k=1N​eak​eai​​

L = − ∑ i y i l o g ( p i ) L = - \sum_i y_i log(p_i) L=−i∑​yi​log(pi​)

交叉熵损失同时解决了平方损失中的缺点1,和缺点2,是目前应用最广泛的分类损失。还是先从代码实现来说,使用numpy,交叉熵损失可以通过以下代码实现, epsilon防止log(0)的情况出现:

def cross_entropy(predictions, targets, epsilon=1e-12):
    """
    Computes cross entropy between targets (encoded as one-hot vectors)
    and predictions. 
    Input: predictions (N, k) ndarray
           targets (N, k) ndarray        
    Returns: scalar
    """
    predictions = np.clip(predictions, epsilon, 1. - epsilon)
    N = predictions.shape[0]
    ce = -np.sum(targets*np.log(predictions+1e-9))/N
    return ce

           

交叉熵损失求导如下:

∂ L ∂ a i = − [ ∂ y i l o g ( p i ) ∂ a i + ∂ ∑ k ≠ i y k l o g ( p k ) ∂ a i ] = − [ ∂ y i l o g ( p i ) ∂ p i ∗ ∂ p i ∂ a i + ∂ ∑ k ≠ i y k l o g ( p k ) ∂ p k ∗ ∂ p k ∂ a i ] = − [ y i p i ∗ p i ∗ ( 1 − p i ) + ∑ k ≠ i y k p k ∗ ( − p k ∗ p i ) ] = − [ y i − y i p i − ∑ k ≠ i y k p i ] = − [ y i − p i ∗ ∑ k y k ] = p i − y i \begin{aligned} \frac{\partial L}{\partial a_i} &=-[\frac{\partial{y_ilog(p_i)}}{\partial a_i} + \frac{\partial{\sum_{k\neq i}y_klog(p_k)}} {\partial a_i} ] \\ &= -[\frac{\partial{y_ilog(p_i)}}{\partial p_i} * \frac{\partial{p_i}}{\partial a_i} + \frac{\partial{\sum_{k\neq i}y_klog(p_k)}} {\partial p_k} * \frac{\partial{p_k}}{\partial a_i}] \\ &= -[\frac{y_i}{p_i} *{p_i}*({1-p_i}) + \sum_{k\neq i} \frac{y_k}{p_k} * (-p_k * p_i)] \\ &= -[y_i - y_ip_i - \sum_{k\neq i} y_k p_i] = -[y_i - p_i * \sum_k y_k] = p_i - y_i \end{aligned} ∂ai​∂L​​=−[∂ai​∂yi​log(pi​)​+∂ai​∂∑k̸​=i​yk​log(pk​)​]=−[∂pi​∂yi​log(pi​)​∗∂ai​∂pi​​+∂pk​∂∑k̸​=i​yk​log(pk​)​∗∂ai​∂pk​​]=−[pi​yi​​∗pi​∗(1−pi​)+k̸​=i∑​pk​yk​​∗(−pk​∗pi​)]=−[yi​−yi​pi​−k̸​=i∑​yk​pi​]=−[yi​−pi​∗k∑​yk​]=pi​−yi​​

这里 ∑ k y k = 1 \sum_k y_k = 1 ∑k​yk​=1。交叉熵函数的求导结果相当简洁,回到平方损失的缺点1。如果输出很小,label为1,那么梯度仍然很大(L has a very big gradient when the target value is 1 and the output is almost zero - neural network for machine learning, Geoffrey Hinton.)。

继续阅读