深度學習基礎系列(五)| 深刻理解交叉熵函數及其在tensorflow和keras中的實現

  在統計學中,損失函數是一種衡量損失和錯誤(這種損失與「錯誤地」估計有關,如費用或者設備的損失)程度的函數。假設某樣本的實際輸出爲a,而預計的輸出爲y,則y與a之間存在誤差,深度學習的目的便是經過不斷地訓練迭代,使得a愈來愈接近y,即 a - y →0,而訓練的本質就是尋找損失函數最小值的過程。html

  常見的損失函數爲兩種,一種是均方差函數,另外一種是交叉熵函數。對於深度學習而言,交叉熵函數要優於均方差函數,緣由在於交叉熵函數配合輸出層的激活函數如sigmoid或softmax函數能更快地加速深度學習的訓練速度。這是爲何呢?讓咱們來詳細瞭解下。python

 1、函數定義

1. 第一種損失函數:均方差函數,其定義爲:

  L(a, y) = ½ (a - y)git

  令y=0,則L(a, y) = ½ a2 ,當a處於(-1, 1)區間範圍時,其圖形特徵爲:算法

    

  能夠看出當y = 0, a =0時,L(a, y) →0,其值是最小的。api

  其求導公式爲:dL(a, y) / da = a - y, 從函數自己來看當a = y時,L(a, y) = 0,也即求得最小值。網絡

  

2. 第二種損失函數:交叉熵函數,其定義爲:

  L(a, y) = - [y * ln a + (1 - y) ln (1 - a)]函數

  爲何該函數能幫咱們找到最小值呢?假設a和y都是在(0, 1)這個區間內取值。學習

  令 y=0, 則 L(a, y) = - ln (1 - a), 當a →0時, L(a, y) ≈ - ln 1 = 0spa

                 當a →1時, L(a, y) ≈ - ln 0 = +∞code

  故y=0, a=0時,L(a, y) →0,符合找到最小值的目標。圖形解釋以下:

  

  反之,令 y=1, 則 L(a, y) = - ln a, 當a →0時, L(a, y) ≈ - ln 0 =  +∞

                     當a →1時, L(a, y) ≈ - ln 1 = 0

  故y=1, a=1時,L(a, y) →0,也符合找到最小值的目標。圖形解釋以下:

  

  固然,咱們也能夠令y=0.5,則 L(a, y) = -[0.5 * ln a + 0.5 * ln (1 - a)]。

  一樣能夠繪製出其圖形:

    

  能夠發現,當y=0.5, a=0.5時, L(a, y) →0,也符合找到最小值的目標。

  以上描述簡單地證實交叉熵函數是一個有效尋找最小值目標的損失函數。

  如下代碼能夠簡單地繪製出上述交叉熵函數的圖形特徵:  

import numpy as np
import matplotlib.pyplot as plt

def loss_5(x):
    return -0.5 * np.log(x) - 0.5 * np.log(1 - x)

def loss_0(x):
    return -1 * np.log(1 - x)

def loss_1(x):
    return -1 * np.log(x)

x = np.arange(0, 1, 0.01)
y = loss_5(x)
plt.plot(x, y)
plt.show()

  

  最後,咱們來求導下交叉熵函數,覺得後續證實用:

  dL(a, y)/da = (- [y * ln a + (1 - y) ln (1 - a)])

        = -(y / a + (-1) * (1 -y) / (1 - a))

           = - y / a + (1- y) / (1 - a )  

        =  (a - y) / [a * (1 - a)]

 

2、爲何交叉熵函數優於均方差函數?

  前面的內容已讓咱們瞭解兩種函數的特色,如今讓咱們看看爲何交叉熵函數要優於均方差函數。咱們知道深度學習的訓練過程是經過梯度降低法反向傳播更新參數值,具體來講,每一次前向傳播結束後,經過對損失函數+激活函數反向在每一層對每一個w和b參數進行求導,獲得dL/dw和dL/db的值,最後更新w和b,即 w → w - λ * dL/dw,b → b - λ * dL/db,其中λ爲學習率。

  咱們假設激活函數定義爲sigmoid函數,並有以下前向傳播路徑:

  z= wx + b

  a1= sigmoid(z1) 

  z2 = wa1 + b2  

  a2= sigmoid(z2)

  在此條件下,咱們看看均方差函數和交叉熵函數在反向傳播上的求導結果:

1. 均方差函數+sigmoid函數的反向求導:  

  令L(a, y) = ½ (a - y)2  ,由前述可知,其求導爲:L'(a, y) = a - y

  而激活函數sigmoid的求導爲:a' (z) = a (1 - a)

  咱們對w2進行求導,由鏈式法則可知:

  dL/dw2 = dL/da2  *  da2/dz2  * dz2/dw2

      = (a2 - y) * a2(1 - a2) * a1 

2. 交叉熵函數+sigmoid函數的反向求導:

   令L(a, y) = - [y * ln a + (1 - y) ln (1 - a)],其求導爲:L'(a, y)  = (a - y) / [a * (1 - a)]

   一樣激活函數sigmoid的求導爲:a' (z) = a (1 - a)

   咱們依舊對w2進行求導,由鏈式法則可知:

   dL/dw2 = dL/dσ2  *  dσ2/dz2  * dz2/dw2

     = (a2 - y) / [a2 * (1 - a2)] * a2(1 - a2) * a1 

        = (a2 - y) * a1

  

   比較上述兩個反向求導w2的結果,有沒有發現交叉熵函數的求導結果要更簡潔,由於它沒有 a2(1 - a2) 這一項,即輸出層激活函數a2的自身求導。沒有它很是重要,經過前文深度學習基礎系列(三)| sigmoid、tanh和relu激活函數的直觀解釋, 咱們可知激活函數的自身斜率在趨近兩端時變得很平滑,這也意味着反向求導時迭代值dL/dw會比較小,從而使得訓練速度變慢,這便是交叉熵函數優於均方差函數的緣由。

   也許你們會問,在sigmoid函數上交叉熵函數佔優,那在softmax函數上又表現如何呢?咱們繼續驗證,前向傳播圖做以下改造:

  z= wx + b

  a1= sigmoid(z1) 

  z2 = w2  a1 + b2  

  a2= softmax(z2)

  假設softmax最後的輸出結果a2是由三個分類值組成,即y1  +  y +  y = 1,三種分類的機率和爲1,其中y1 機率爲0.9,y2 機率爲0.05,y3 的機率爲0.05。天然,咱們但願某個分類的機率最接近1,以說明這個分類就是咱們的預測值,所以可認爲y = 1。 而激活函數softmax,參考前文深度學習基礎系列(四)| 理解softmax函數

  令y1 = ex1 / (ex1 + ex2 + ex3 ) 

     y2 = ex2 / (ex1 + ex2 + ex3 ) 

     y3  = ex3 / (ex1 + ex2 + ex3 ) 

  可知其求導爲:

  dy1/dx1 = y1 (1 - y1

       dy2/dx1 = - y1 * y

  dy3/dx1 = - y1 * y3     

  在此條件下,咱們看看均方差函數和交叉熵函數在反向傳播上的求導結果: 

3. 均方差函數+softmax函數的反向求導: 

  若在y1進行反向求導,令L(y1, y) = ½ (y1 - y)2  ,其求導爲:L'(y1, y) = y1 - y = y- 1 

  咱們對w2進行求導,由鏈式法則可知:

  dL/dw2 = dL/dy1  *  dy1/dz2  * dz2/dw2

      = (y1 - 1) * y1(1 - y1) * a1

  

  另外一方面,若在y2進行反向求導,令L(y2, y) = ½ (y2 - y)2  ,其求導爲:L'(y2, y) = y2 - y = y- 1

  咱們對w2進行求導,由鏈式法則可知:

  dL/dw2 = dL/dy2  *  dy2/dz2  * dz2/dw2

      = (y2 - 1) *  (- y1 * y2) * a1 

 

4. 交叉熵函數+softmax函數的反向求導: 

  若在y1進行反向求導,令 - [y * ln y1 + (1 - y) ln (1 - y1)],其求導爲:L'(y1, y)  = (y1 - y) / [y1 * (1 - y1)] =  (y1 - 1) / [y1 * (1 - y1)] = -1/y1

  咱們對w2進行求導,由鏈式法則可知:

  dL/dw2 = dL/dy1  *  dy1/dz2  * dz2/dw2

      = -1/y1 * y1(1 - y1) * a1 

      = - (1 - y1) * a1 

  

  另外一方面,若在y2進行反向求導,令 - [y * ln y2 + (1 - y) ln (1 - y2)],其求導爲:L'(y2, y)  = (y2 - y) / [y2 * (1 - y2)] =  (y2 - 1) / [y2 * (1 - y2)] = -1/y2

  咱們對w2進行求導,由鏈式法則可知:

  dL/dw2 = dL/dy2  *  dy2/dz2  * dz2/dw2

      = -1/y2 *  (- y1 * y2) * a1 

      =  y1 * a1 

       綜上比較,交叉熵函數在反向求導中,依舊完美地避開了softmax函數自身導數的影響,仍是優於均方差函數。

  還需補充的是,在反向傳播過程當中,每一層對w和b求導,經過鏈式法則可知,依舊會受到該層的激活函數影響,若爲sigmoid函數,依然不可避免地下降學習速度。所以深層網絡中隱藏層的激活函數目前大都爲relu函數,能夠確保其導數要麼爲0,要麼爲1。  

 

3、交叉熵函數在tensorflow和keras的實現說明 

1.  sigmoid + 交叉熵函數

  在tensorflow上體現爲sigmoid_cross_entropy_with_logits,對應的官網地址爲:https://www.tensorflow.org/api_docs/python/tf/nn/sigmoid_cross_entropy_with_logits

  說明:可應用於二元分類,如判斷一張圖片是否爲貓;但若是圖片裏有多個分類,也並不互斥,好比一張圖片裏既包含狗,也包含貓,但若是咱們給了貓和狗兩個標籤,那判斷這張圖片,既能夠認爲是狗,也能夠認爲是貓。 

  官網已給出數學推導後的公式:

  L = max(x, 0) - x * z + log(1 + exp(-abs(x))),其中x可認爲是logits,z可認爲是labels

  舉一個簡單的例子:

import keras.backend as K
from tensorflow.python.ops import nn

y_target = K.constant(value=[1])
y_output = K.constant(value=[1])
print("y_target: ", K.eval(y_target))
print("y_output: ", K.eval(y_output))
print("the loss is: ", K.eval(nn.sigmoid_cross_entropy_with_logits(labels=y_target, logits=y_output)))

  結果爲:

y_target:  [1.]
y_output:  [1.]
the loss is:  [0.3132617]

  能夠把參數帶入公式驗證:max(1, 0) - 1 * 1 + log(1 + exp(-abs(1))) = ln(1  + e-1) = 0.3132617

  

  在keras上體現爲binary_crossentropy,用途與上面的sigmoid_cross_entropy_with_logits是同樣的,但二者有差異:

  sigmoid_cross_entropy_with_logits還原了預計值和輸出值在數學公式中的差值,但不是一種機率差。上述例子中,輸出值和預計值明明同樣,機率差應該爲0,但結果卻爲0.3132617

  而binary_crossentropy則先把輸出值進行機率包裝後,再帶入sigmoid_cross_entropy_with_logits數學公式中。

  舉一個例子:

import keras.backend as K
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import clip_ops
from tensorflow.python.framework import ops

y_target = K.constant(value=[1])
y_output = K.constant(value=[1])
print("y_target: ", K.eval(y_target))
print("y_output: ", K.eval(y_output))

epsilon_ = ops.convert_to_tensor(K.epsilon(), y_output.dtype.base_dtype)
print("epsilon: ", K.eval(epsilon_))
output = clip_ops.clip_by_value(y_output, epsilon_, 1 - epsilon_)
print("output: ", K.eval(output))
output = math_ops.log(output / (1 - output))
print("output: ", K.eval(output))
print("the loss is: ", K.eval(nn.sigmoid_cross_entropy_with_logits(labels=y_target, logits=output)))

  結果爲:

y_target:  [1.]
y_output:  [1.]
epsilon:  1e-07
output:  [0.9999999]
output:  [15.942385]
the loss is:  [1.1920933e-07]

  最後的結果1.1920933e-07至關於0.00000012,符合咱們對預測值和輸出值差異不大的認知。所以能夠理解keras的損失函數輸出值更加人性化。

 

2. softmax + 交叉熵函數

  在tensorflow上體現爲softmax_cross_entropy_with_logits_v2,對應的官網地址爲:https://www.tensorflow.org/api_docs/python/tf/nn/softmax_cross_entropy_with_logits_v2

  說明:可用於多元分類,如判斷一張圖片是否爲貓;但若是圖片裏多個類別則是互斥的,好比一張圖片裏既包含狗,也包含貓,那咱們也只能給惟一的標籤,要麼是狗,要麼是貓。

  其數學公式爲:L = ∑ -(y * lna) 

  舉個例子說明:  

import keras.backend as K
from tensorflow.python.ops import nn

y_target = K.constant(value=[1, 2])
y_output = K.constant(value=[1, 2])
print("y_target: ", K.eval(y_target))
print("y_output: ", K.eval(y_output))

print("the loss is: ", K.eval(nn.softmax_cross_entropy_with_logits_v2(labels=y_target, logits=y_output)))

  結果爲:

y_target:  [1. 2.]
y_output:  [1. 2.]
the loss is:  1.939785

  說明:y_output有兩個輸出值1和2,經過softmax函數轉化後,咱們可知1的對應softmax值爲0.268941422,而2的對應softmax值爲0.731058581;同時1對應的預測值爲1,2對應的預測值爲2。將上述值帶入公式驗證:

  L = ∑ -(y * lna) 

     = -(1 * ln (e / (e + e2)) ) + -(2 * ln (e/ (e + e2)))

     = -(1* ln0.268941422) + -(2 * ln0.731058581)

     = 1.939785

  

  而在keras中,與此對應的是categorical_crossentropy,採用的算法和上述基本一致,只是須要把y_output按分類值作機率轉換。

  咱們來舉例說明:

import keras.backend as K
from tensorflow.python.ops import nn
from tensorflow.python.ops import math_ops
from tensorflow.python.ops import clip_ops
from tensorflow.python.framework import ops

y_target = K.constant(value=[1, 2])
y_output = K.constant(value=[1, 2])
print("y_target: ", K.eval(y_target))
print("y_output: ", K.eval(y_output))

output = y_output / math_ops.reduce_sum(y_output, -1, True)
print("output: ", K.eval(output))
epsilon_ = ops.convert_to_tensor(K.epsilon(), output.dtype.base_dtype)
output = clip_ops.clip_by_value(output, epsilon_, 1. - epsilon_)
print("output: ", K.eval(output))
print("the loss is: ", K.eval(-math_ops.reduce_sum(y_target * math_ops.log(output), -1)))

  結果爲:

y_target:  [1. 2.]
y_output:  [1. 2.]
output:  [0.33333334 0.6666667 ]
output:  [0.33333334 0.6666667 ]
the loss is:  1.9095424

  其計算公式爲:

  L = ∑ -(y * lna) 

     = -(1 * ln 0.33333334 ) + -(2 * ln 0.66666667)

     = 1.9095424

 

3. sparse + softmax + 交叉熵函數: 

  在tensorflows中體現爲sparse_softmax_cross_entropy_with_logits,對應的官網地址爲:https://www.tensorflow.org/api_docs/python/tf/nn/sparse_softmax_cross_entropy_with_logits

  說明:可用於多元分類,如判斷一張圖片是否爲貓;但若是圖片裏多個類別則是互斥的,好比一張圖片裏既包含狗,也包含貓,那咱們也只能給惟一的標籤,要麼是狗,要麼是貓。

  加上sparse的區別在於:不加sparse給定的labels是容許有機率分佈的,如[0.3, 0.4, 0.2, 0.05, 0.05],而加sparse,則如其名sparse稀疏矩陣,機率只容許要麼存在,要麼不存在,如[0, 0, 0, 1, 0]

  咱們常見的mnist數據集、cifar-10數據集等都是這種類型。

  其數學公式爲:L = -y * ln a,其中a爲機率爲1的輸出值

  依舊舉例說明: 

import keras.backend as K
from tensorflow.python.ops import nn

y_target = K.constant(value=1, dtype='int32')
y_output = K.constant(value=[1, 2])
print("y_target: ", K.eval(y_target))
print("y_output: ", K.eval(y_output))

print("the loss is: ", K.eval(nn.sparse_softmax_cross_entropy_with_logits(labels=y_target, logits=y_output)))

  結果爲:

y_target:  1
y_output:  [1. 2.]
the loss is:  0.31326166

  說明:本例中,輸出值y_output爲1和2,表示兩種分類,而y_target的值爲1,表示爲分類的index,也就是對應了輸出值2;更直白地說,輸出值1的機率爲0,輸出值2的機率爲1。

  具體計算方式爲:a = e/ (e + e2) = 0.731058581

          L = - ln a = 0.313261684

   

  在keras中,與此對應的爲sparse_categorical_crossentropy,該函數會調用上述函數,最大的不一樣在於:1. 其參數y_output被設定爲通過softmax以後的值;2. y_output還會被log處理下

  舉個例子:

import keras.backend as K

y_target = K.constant(value=1, dtype='int32')
y_output = K.constant(value=[0.2, 0.2, 0.6])
print("y_target: ", K.eval(y_target))
print("y_output: ", K.eval(y_output))

print("the loss is: ", K.eval(K.sparse_categorical_crossentropy(y_target, y_output)))

  結果爲:

y_target:  1
y_output:  [0.2 0.2 0.6]
the loss is:  [1.609438]

  說明:本例中y_output的值已經假設按照softmax處理後的值:0.2,0.2和0.6

  其計算方式爲:

  a = eln0.2/(eln0.2 + eln0.2 + eln0,6) = 0.2/(0.2 + 0.2 +0.6) = 0.2

  L = - ln 0.2 = 1.609438

 

  以上便是交叉熵函數做爲損失函數的使用理由,及其在tensorflow和keras中的實現說明。  

相關文章
相關標籤/搜索