TensorFlow損失函式專題

marsjhao發表於2020-04-06

一、分類問題損失函式——交叉熵(crossentropy)

交叉熵刻畫了兩個概率分佈之間的距離,是分類問題中使用廣泛的損失函式。給定兩個概率分佈p和q,交叉熵刻畫的是兩個概率分佈之間的距離:

我們可以通過Softmax迴歸將神經網路前向傳播得到的結果變成交叉熵要求的概率分佈得分。在TensorFlow中,Softmax迴歸的引數被去掉了,只是一個額外的處理層,將神經網路的輸出變成一個概率分佈。

程式碼實現:

import tensorflow as tf

y_ = tf.constant([[1.0, 0, 0]]) # 正確標籤
y1 = tf.constant([[0.9, 0.06, 0.04]]) # 預測結果1
y2 = tf.constant([[0.5, 0.3, 0.2]]) # 預測結果2
# 以下為未經過Softmax處理的類別得分
y3 = tf.constant([[10.0, 3.0, 2.0]])
y4 = tf.constant([[5.0, 3.0, 1.0]])

# 自定義交叉熵
cross_entropy1 = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y1, 1e-10, 1.0)))
cross_entropy2 = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y2, 1e-10, 1.0)))
# TensorFlow提供的整合交叉熵
# 注:該操作應該施加在未經過Softmax處理的logits上,否則會產生錯誤結果
# labels為期望輸出,且必須採用labels=y_, logits=y的形式將引數傳入
cross_entropy_v2_1 = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y3)
cross_entropy_v2_2 = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y4)

sess = tf.InteractiveSession()
print('[[0.9, 0.06, 0.04]]:', cross_entropy1.eval())
print('[[0.5, 0.3, 0.2]]:', cross_entropy2.eval())
print('v2_1', cross_entropy_v2_1.eval())
print('v2_2',cross_entropy_v2_2.eval())
sess.close()

'''
[[0.9, 0.06, 0.04]]: 0.0351202
[[0.5, 0.3, 0.2]]: 0.231049
v2_1 [ 0.00124651]
v2_2 [ 0.1429317]
'''

tf.clip_by_value()函式可將一個tensor的元素數值限制在指定範圍內,這樣可防止一些錯誤運算,起到數值檢查作用。

* 乘法操作符是元素之間直接相乘,tensor中是每個元素對應相乘,要去別去tf.matmul()函式的矩陣相乘。

tf.nn.softmax_cross_entropy_with_logits(labels=y_,logits=y)是TensorFlow提供的整合交叉熵函式。該操作應該施加在未經過Softmax處理的logits上,否則會產生錯誤結果;labels為期望輸出,且必須採用labels=y_, logits=y3的形式將引數傳入。

二、迴歸問題損失函式——均方誤差(MSE,mean squared error)

均方誤差亦可用於分類問題的損失函式,其定義為:


三、自定義損失函式

對於理想的分類問題和迴歸問題,可採用交叉熵或者MSE損失函式,但是對於一些實際的問題,理想的損失函式可能在表達上不能完全表達損失情況,以至於影響對結果的優化。例如:對於產品銷量預測問題,表面上是一個迴歸問題,可使用MSE損失函式。可實際情況下,當預測值大於實際值時,損失值應是正比於商品成本的函式;當預測值小於實際值,損失值是正比於商品利潤的函式,多數情況下商品成本和利潤是不對等的。自定義損失函式如下:

TensorFlow中,通過以下程式碼實現loss= tf.reduce_sum(tf.where(tf.greater(y, y_), (y-y_)*loss_more,(y_-y)*loss_less))。

tf.greater(x,y),返回x>y的判斷結果的bool型tensor,當tensor x, y的維度不一致時,採取廣播(broadcasting)機制。

tf.where(condition,x=None, y=None, name=None),根據condition選擇x (if true) or y (if false)。

程式碼實現:

import tensorflow as tf
from numpy.random import RandomState

batch_size = 8
x = tf.placeholder(tf.float32, shape=(None, 2), name='x-input')
y_ = tf.placeholder(tf.float32, shape=(None, 1), name='y-input')

w1 = tf.Variable(tf.random_normal([2,1], stddev=1, seed=1))
y = tf.matmul(x, w1)

# 根據實際情況自定義損失函式
loss_less = 10
loss_more = 1
# tf.select()在1.0以後版本中已刪除,tf.where()替代
loss = tf.reduce_sum(tf.where(tf.greater(y, y_),
                               (y-y_)*loss_more, (y_-y)*loss_less))
train_step = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)

rdm = RandomState(seed=1) # 定義一個隨機數生成器並設定隨機種子
dataset_size = 128
X = rdm.rand(dataset_size, 2)
Y = [[x1 + x2 +rdm.rand()/10.0 - 0.05] for (x1, x2) in X] # 增加一個-0.05~0.05的噪聲

sess = tf.InteractiveSession()
tf.global_variables_initializer().run()
for i in range(5000):
    start = (i * batch_size) % dataset_size
    end = min(start+batch_size, dataset_size)
    train_step.run({x: X[start: end], y_: Y[start: end]})
    if i % 500 == 0:
        print('step%d:\n' % i, w1.eval())
print('final w1:\n', w1.eval())
sess.close()

'''
loss_less = 10
loss_more = 1
final w1:
 [[ 1.01934695]
 [ 1.04280889]]

loss_less = 1
loss_more = 10
final w1:
 [[ 0.95525807]
 [ 0.9813394 ]]

loss_less = 1
loss_more = 1
final w1:
 [[ 0.9846065 ]
 [ 1.01486754]]
'''

根據程式輸出可見,當我們將loss_less=10時,表明我們對預測值過小表徵的損失值更大,優化得到的引數均略大於1;當loss_more=10時,表明我們對預測值過大表徵的損失值更大,優化得到的引數均略小於1;當兩者均設為1時,得到的引數約等於1。

四、TensorFlow的Cross_Entropy實現

1. tf.nn.softmax_cross_entropy_with_logits(_sentinel=None,labels=None, logits=None, dim=-1, name=None)

該函式的功能是自動計算logits(未經過Softmax)與labels之間的cross_entropy交叉熵。

該操作應該施加在未經過Softmax處理的logits上,否則會產生錯誤結果;labels為期望輸出,且必須採用labels=y_,logits=y3的形式將引數傳入。

第一個引數logits:就是神經網路最後一層的輸出,如果有batch的話,它的大小就是[batchsize,num_classes],單樣本的話,大小就是num_classes

第二個引數labels:實際的標籤,大小同上。

注意:如果labels的每一行是one-hot表示,也就是隻有一個地方為1,其他地方為0,可以使用tf.sparse_softmax_cross_entropy_with_logits()

警告:(1)這個操作的輸入logits是未經縮放的,該操作內部會對logits使用softmax操作;(2)引數labels,logits必須有相同的形狀 [batch_size, num_classes] 和相同的型別(float16, float32,float64)中的一種。

該函式具體的執行過程分兩步:首先對logits做一個Softmax,

第二步就是將第一步的輸出與樣本的實際標籤labels做一個交叉熵。

注意!!!這個函式的返回值並不是一個數,而是一個向量,如果要求交叉熵,我們要再做一步tf.reduce_sum操作,就是對向量裡面所有元素求和,最後才得到交叉熵,如果求loss,則要做一步tf.reduce_mean操作,對向量求均值!

2. tf.nn.sparse_softmax_cross_entropy_with_logits(_sentinel=None,labels=None, logits=None, name=None)

該函式與tf.nn.softmax_cross_entropy_with_logits(_sentinel=None,labels=None, logits=None, dim=-1, name=None)十分相似,唯一的區別在於labels,該函式的標籤labels要求是排他性的即只有一個正確類別,labels的形狀要求是[batch_size] 而值必須是從0開始編碼的int32或int64,而且值範圍是[0, num_class),對比於tf.nn.softmax_cross_entropy_with_logits的[batchsize,num_classes]格式的得分編碼。

其他使用注意事項參見tf.nn.softmax_cross_entropy_with_logits的說明。

3. tf.nn.sigmoid_cross_entropy_with_logits(_sentinel=None,labels=None, logits=None, name=None)

sigmoid_cross_entropy_with_logits是TensorFlow最早實現的交叉熵演算法。這個函式的輸入是logits和labels,logits就是神經網路模型中的 W * X矩陣,注意不需要經過sigmoid,而labels的shape和logits相同,就是正確的標籤值,例如這個模型一次要判斷100張圖是否包含10種動物,這兩個輸入的shape都是[100, 10]。註釋中還提到這10個分類之間是獨立的、不要求是互斥,這種問題我們稱為多目標(多標籤)分類,例如判斷圖片中是否包含10種動物中的一種或幾種,標籤值可以包含多個1或0個1。

4. tf.nn.weighted_cross_entropy_with_logits(targets, logits, pos_weight, name=None)

weighted_sigmoid_cross_entropy_with_logits是sigmoid_cross_entropy_with_logits的擴充版,多支援一個pos_weight引數,在傳統基於sigmoid的交叉熵演算法上,正樣本算出的值乘以某個係數。

相關文章