Sklearn中二分類問題的交叉熵計算


二分類問題的交叉熵

  在二分類問題中,損失函數(loss function)為交叉熵(cross entropy)損失函數。對於樣本點(x,y)來說,y是真實的標簽,在二分類問題中,其取值只可能為集合{0, 1}. 我們假設某個樣本點的真實標簽為yt, 該樣本點取yt=1的概率為yp, 則該樣本點的損失函數為

\[-log(yt|yp)=-(ytlog(yp)+(1-yt)log(1-yp)) \]

對於整個模型而言,其損失函數就是所有樣本點的損失函數的平均值。注意到,對於該損失函數,其值應該為非負值,因為yp的取值在(0,1)之間。

自己實現的方法有問題?

  在Python的sklearn模塊中,實現二分類問題的交叉熵損失函數為log_loss()。我們嘗試着運行官網中給出的例子,同時,用自己的方法實現該損失函數,其Python代碼如下:

from sklearn.metrics import log_loss
from math import log # 自然對數為底

# 二分類的交叉熵損失函數
# 利用sklearn模塊的計算結果
y_true = [0, 0, 1, 1]
y_pred = [[.9, .1], [.8, .2], [.3, .7], [.01, .99]]
sk_log_loss = log_loss(y_true, y_pred)
print('Loss by sklearn: %s.'%sk_log_loss)

# 利用公式計算得到的結果
Loss = 0
for label, prob in zip(y_true, y_pred):
    Loss -= (label*log(prob[0])+(1-label)*log(prob[1]))

Loss = Loss/len(y_true)
print('Loss by equation: %s.'% Loss)

在y_pred中,每個樣本點都對應一組概率,如果我們把第一個概率作為樣本分類為0的概率,第二個概率作為樣本分類為1的概率,我們就會得到以下的輸出結果:

Loss by sklearn: 0.1738073366910675.
Loss by equation: 2.430291498935543.

我們驚訝的發現,兩種方法得到的損失函數值竟然是不一樣的。可是,貌似我們的計算公式也沒有出問題啊,這到底是怎么回事呢?
  這時候,我們最好的辦法是借助源代碼的幫助,看看源代碼是怎么實現的,與我們的計算方法有什么不一樣。

研究sklearn中的log_loss()源代碼

  sklearn模塊中的log_loss()函數的源代碼地址為:https://github.com/scikit-learn/scikit-learn/blob/ed5e127b/sklearn/metrics/classification.py#L1576
  在具體分析源代碼之前,我們應該注意以下幾點(這也是從源代碼中發現的):

  • 損失函數中的對數以自然常數e為底;
  • 預測概率的值有可能會出現0或1的情形,這在公式中是無意義的。因此,該代碼使用了numpy中的clip()函數,將預測概率控制在[eps, 1-eps]范圍內,其中eps為一個很小的數,避免了上述問題的出現。

  在log_loss()函數中,參數為:y_true, y_pred, eps, normalize, sample_weight,labels,為了分析問題的方便,我們只考慮該函數在所有默認參數取默認值時的情形。y_true為樣本的真實標簽,y_pred為預測概率。
  對於樣本的真實標簽y_true, 源代碼中的處理代碼為:

    lb = LabelBinarizer()

    if labels is not None:
        lb.fit(labels)
    else:
        lb.fit(y_true)
        
    transformed_labels = lb.transform(y_true)

    if transformed_labels.shape[1] == 1:
        transformed_labels = np.append(1 - transformed_labels,
                                       transformed_labels, axis=1)

也就說,當我們的y_true為一維的時候,處理后的標簽應當為二維的,比如說,我們輸入的y_true為[0,0,1,1],那么處理后的標簽應當為:

[[1 0]
 [1 0]
 [0 1]
 [0 1]]

  對於預測概率,源代碼中的處理過程為

	# Clipping
    y_pred = np.clip(y_pred, eps, 1 - eps)

    # If y_pred is of single dimension, assume y_true to be binary
    # and then check.
    if y_pred.ndim == 1:
        y_pred = y_pred[:, np.newaxis]
    if y_pred.shape[1] == 1:
        y_pred = np.append(1 - y_pred, y_pred, axis=1)

也就說,當我們的y_true為一維的時候,處理后的標簽應當為二維的,這跟處理樣本的真實標簽y_true是一樣的。處理完y_true和y_pred后,之后就按照損失函數的公式得到計算值。

自己實現二分類問題的交叉熵計算

  在我們分析完log_loss的源代碼后,我們就能自己用公式來實現這個函數了,其Python代碼如下:

from sklearn.metrics import log_loss
from math import log # 自然對數為底

# 二分類的交叉熵損失函數的計算

# y_true為一維,y_pred為二維
# 用sklearn的log_loss函數計算損失函數
y_true = [0,0,1,1]
y_pred = [[0.1,0.9], [0.2,0.8], [0.3,0.7], [0.01, 0.99]]
sk_log_loss = log_loss(y_true,y_pred)
print('Loss by sklearn: %s.'%sk_log_loss)

# 用公式自己實現損失函數的計算
Loss = 0
for label, prob in zip(y_true, y_pred):
    Loss -= ((1-label)*log(prob[0])+label*log(prob[1]))

Loss = Loss/len(y_true)
print('Loss by equation: %s.'% Loss)

# y_true為一維,y_pred為二維
# 用sklearn的log_loss函數計算損失函數
y_true = [0,0,1,1]
y_pred = [0.1, 0.2, 0.3, 0.01]
sk_log_loss = log_loss(y_true,y_pred)
print('Loss by sklearn: %s.'%sk_log_loss)

# 用公式自己實現損失函數的計算
Loss = 0
for label, prob in zip(y_true, y_pred):
    Loss -= ((1-label)*log(1-prob)+label*log(prob))

Loss = Loss/len(y_true)
print('Loss by equation: %s.'% Loss)

運行該函數,輸出的結果為:

Loss by sklearn: 1.0696870713050948.
Loss by equation: 1.0696870713050948.
Loss by sklearn: 1.5344117643215158.
Loss by equation: 1.5344117643215158.

  這樣我們就用公式能自己實現二分類問題的交叉熵計算了,計算結果與sklearn的log_loss()函數一致。

感悟

  有空就得讀讀程序的源代碼,不僅有助於我們解決問題,還能給我們很多啟示,比如log_loss()函數中的np.clip()函數的應用,能很好地避免出現預測概率為0或1的情形。
  log_loss()函數的實現雖然簡單,但閱讀源代碼的樂趣是無窮的。以后也會繼續更新,希望大家多多關注。

注意:本人現已開通兩個微信公眾號: 因為Python(微信號為:python_math)以及輕松學會Python爬蟲(微信號為:easy_web_scrape), 歡迎大家關注哦~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM