二分類問題的交叉熵
??在二分類問題中揭绑,損失函數(shù)(loss function)為交叉熵(cross entropy)損失函數(shù)坦报。對(duì)于樣本點(diǎn)(x,y)來說上煤,y是真實(shí)的標(biāo)簽休玩,在二分類問題中,其取值只可能為集合{0, 1}. 我們假設(shè)某個(gè)樣本點(diǎn)的真實(shí)標(biāo)簽為yt, 該樣本點(diǎn)取yt=1的概率為yp, 則該樣本點(diǎn)的損失函數(shù)為
-log(yt|yp)=-(ytlog(yp)+(1-yt)log(1-yp))
對(duì)于整個(gè)模型而言劫狠,其損失函數(shù)就是所有樣本點(diǎn)的損失函數(shù)的平均值拴疤。注意到,對(duì)于該損失函數(shù)独泞,其值應(yīng)該為非負(fù)值呐矾,因?yàn)閥p的取值在(0,1)之間。
自己實(shí)現(xiàn)的方法有問題懦砂?
??在Python的sklearn模塊中蜒犯,實(shí)現(xiàn)二分類問題的交叉熵?fù)p失函數(shù)為log_loss()。我們嘗試著運(yùn)行官網(wǎng)中給出的例子荞膘,同時(shí)罚随,用自己的方法實(shí)現(xiàn)該損失函數(shù),其Python代碼如下:
from sklearn.metrics import log_loss
from math import log # 自然對(duì)數(shù)為底
# 二分類的交叉熵?fù)p失函數(shù)
# 利用sklearn模塊的計(jì)算結(jié)果
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)
# 利用公式計(jì)算得到的結(jié)果
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中羽资,每個(gè)樣本點(diǎn)都對(duì)應(yīng)一組概率淘菩,如果我們把第一個(gè)概率作為樣本分類為0的概率,第二個(gè)概率作為樣本分類為1的概率屠升,我們就會(huì)得到以下的輸出結(jié)果:
Loss by sklearn: 0.1738073366910675.
Loss by equation: 2.430291498935543.
我們驚訝的發(fā)現(xiàn)潮改,兩種方法得到的損失函數(shù)值竟然是不一樣的「古可是汇在,貌似我們的計(jì)算公式也沒有出問題啊,這到底是怎么回事呢微服?
??這時(shí)候趾疚,我們最好的辦法是借助源代碼的幫助缨历,看看源代碼是怎么實(shí)現(xiàn)的,與我們的計(jì)算方法有什么不一樣糙麦。
研究sklearn中的log_loss()源代碼
??sklearn模塊中的log_loss()函數(shù)的源代碼地址為:https://github.com/scikit-learn/scikit-learn/blob/ed5e127b/sklearn/metrics/classification.py#L1576 辛孵。
??在具體分析源代碼之前,我們應(yīng)該注意以下幾點(diǎn)(這也是從源代碼中發(fā)現(xiàn)的):
- 損失函數(shù)中的對(duì)數(shù)以自然常數(shù)e為底赡磅;
- 預(yù)測(cè)概率的值有可能會(huì)出現(xiàn)0或1的情形魄缚,這在公式中是無意義的。因此焚廊,該代碼使用了numpy中的clip()函數(shù)冶匹,將預(yù)測(cè)概率控制在[eps, 1-eps]范圍內(nèi),其中eps為一個(gè)很小的數(shù),避免了上述問題的出現(xiàn)咆瘟。
??在log_loss()函數(shù)中嚼隘,參數(shù)為:y_true, y_pred, eps, normalize, sample_weight,labels,為了分析問題的方便袒餐,我們只考慮該函數(shù)在所有默認(rèn)參數(shù)取默認(rèn)值時(shí)的情形飞蛹。y_true為樣本的真實(shí)標(biāo)簽,y_pred為預(yù)測(cè)概率灸眼。
??對(duì)于樣本的真實(shí)標(biāo)簽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)
也就說卧檐,當(dāng)我們的y_true為一維的時(shí)候,處理后的標(biāo)簽應(yīng)當(dāng)為二維的焰宣,比如說霉囚,我們輸入的y_true為[0,0,1,1],那么處理后的標(biāo)簽應(yīng)當(dāng)為:
[[1 0]
[1 0]
[0 1]
[0 1]]
??對(duì)于預(yù)測(cè)概率匕积,源代碼中的處理過程為
# 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)
也就說盈罐,當(dāng)我們的y_true為一維的時(shí)候,處理后的標(biāo)簽應(yīng)當(dāng)為二維的闪唆,這跟處理樣本的真實(shí)標(biāo)簽y_true是一樣的暖呕。處理完y_true和y_pred后,之后就按照損失函數(shù)的公式得到計(jì)算值苞氮。
自己實(shí)現(xiàn)二分類問題的交叉熵計(jì)算
??在我們分析完log_loss的源代碼后,我們就能自己用公式來實(shí)現(xiàn)這個(gè)函數(shù)了瓤逼,其Python代碼如下:
from sklearn.metrics import log_loss
from math import log # 自然對(duì)數(shù)為底
# 二分類的交叉熵?fù)p失函數(shù)的計(jì)算
# y_true為一維笼吟,y_pred為二維
# 用sklearn的log_loss函數(shù)計(jì)算損失函數(shù)
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)
# 用公式自己實(shí)現(xiàn)損失函數(shù)的計(jì)算
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函數(shù)計(jì)算損失函數(shù)
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)
# 用公式自己實(shí)現(xiàn)損失函數(shù)的計(jì)算
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)
運(yùn)行該函數(shù)霸旗,輸出的結(jié)果為:
Loss by sklearn: 1.0696870713050948.
Loss by equation: 1.0696870713050948.
Loss by sklearn: 1.5344117643215158.
Loss by equation: 1.5344117643215158.
??這樣我們就用公式能自己實(shí)現(xiàn)二分類問題的交叉熵計(jì)算了贷帮,計(jì)算結(jié)果與sklearn的log_loss()函數(shù)一致。
感悟
??有空就得讀讀程序的源代碼诱告,不僅有助于我們解決問題撵枢,還能給我們很多啟示,比如log_loss()函數(shù)中的np.clip()函數(shù)的應(yīng)用,能很好地避免出現(xiàn)預(yù)測(cè)概率為0或1的情形锄禽。
??log_loss()函數(shù)的實(shí)現(xiàn)雖然簡(jiǎn)單潜必,但閱讀源代碼的樂趣是無窮的。以后也會(huì)繼續(xù)更新沃但,希望大家多多關(guān)注磁滚。
注意:本人現(xiàn)已開通兩個(gè)微信公眾號(hào): 因?yàn)镻ython(微信號(hào)為:python_math)以及輕松學(xué)會(huì)Python爬蟲(微信號(hào)為:easy_web_scrape), 歡迎大家關(guān)注哦~~