1. 程式人生 > >自定義損失函式的兩種應用場景!

自定義損失函式的兩種應用場景!

第一種場景,希望預測出來的類別有著不同的側重點

這個自定義損失函式的背景:(一般迴歸用的損失函式是MSE, 但要看實際遇到的情況而有所改變)

我們現在想要做一個迴歸,來預估某個商品的銷量,現在我們知道,一件商品的成本是1元,售價是10元。

如果我們用均方差來算的話,如果預估多一個,則損失一塊錢,預估少一個,則損失9元錢(少賺的)。

顯然,我寧願預估多了,也不想預估少了。

所以,我們就自己定義一個損失函式,用來分段地看,當yhat 比 y大時怎麼樣,當yhat比y小時怎麼樣。(yhat沿用吳恩達課堂中的叫法)

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

#在windows下,下面用這個where替代,因為呼叫tf.select會報錯

loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_)*loss_more, (y_-y)*loss_less))

train_step = tf.train.AdamOptimizer(0.001).minimize(loss)

#通過隨機數生成一個模擬資料集

rdm = RandomState(1)

dataset_size = 128

X = rdm.rand(dataset_size, 2)

"""

設定迴歸的正確值為兩個輸入的和加上一個隨機量,之所以要加上一個隨機量是

為了加入不可預測的噪音,否則不同損失函式的意義就不大了,因為不同損失函式

都會在能完全預測正確的時候最低。一般來說,噪音為一個均值為0的小量,所以

這裡的噪音設定為-0.05, 0.05的隨機數。

"""

Y = [[x1 + x2 + rdm.rand()/10.0-0.05] for (x1, x2) in X]

with tf.Session() as sess:

    init = tf.global_variables_initializer()

    sess.run(init)

    steps = 5000

    for i in range(steps):

        start = (i * batch_size) % dataset_size

        end = min(start + batch_size, dataset_size)

        sess.run(train_step, feed_dict={x:X[start:end], y_:Y[start:end]})

    print(sess.run(w1))

[[ 1.01934695]

[ 1.04280889]

最終結果如上面所示。

因為我們當初生成訓練資料的時候,y是x1 + x2,所以迴歸結果應該是1,1才對。

但是,由於我們加了自己定義的損失函式,所以,傾向於預估多一點。

如果,我們將loss_less和loss_more對調,我們看一下結果: 

[[ 0.95525807]

[ 0.9813394 ]]

通過這個例子,我們可以看出,對於相同的神經網路,不同的損失函式會對訓練出來的模型產生重要的影響。 

第二種應用場景,針對於樣本不均衡的應用場景

自定義損失函式是損失函式章節的結尾,學習自定義損失函式,對於提高分類分割等問題的準確率很有幫助,同時探索新型的損失函式也可以讓你文章多多。這裡我們介紹構建自定義損失函式的方法,並且介紹可以均衡正負例的loss,以及在多分類中可以解決樣本數量不均衡的loss的方法。

首先為了有足夠的知識學會自定義損失函式,我們需要知道tensorflow都能實現什麼樣的操作。其實答案是你常見的數學運算都可以,所以說只要你能把心中的損失函式表達為數學式的形式,那麼你就能夠將其轉變為損失函式的形式。下面介紹一些常見的函式:

四則運算:tf.add(Tensor1,Tensor2),tf.sub(Tensor1,Tensor2), tf.mul(Tensor1,Tensor2),tf.div(Tensor1,Tensor2)這裡的操作也可以被正常的加減乘除的負號所取代。這裡想要指出的是乘法和除法的規則和numpy庫是一樣的,是matlab中的點乘而不是矩陣的乘法,矩陣的乘法是tf.matmul(Tensor1, Tensor2)

基礎運算: 取模運算(tf.mod()),絕對值(tf.abs()),平方(tf.square()),四捨五入取整(tf.round()),取平方根(tf.sqrt()) ,自然對數的冪(tf.exp()) ,取對數(tf.log()) ,冪(tf.pow()) ,正弦運算(tf.sin())。以上的這些數學運算以及很多沒有被提及的運算在tensorflow中都可以自己被求導,所以大家不用擔心還需要自己寫反向傳播的問題,只要你的操作是由tensorflow封裝的基礎操作實現的,那麼反向傳播就可以自動的實現。

條件判斷,通過條件判斷語句我們就可以實現分段的損失函式,利用tf.where(condition, tensor_x, tensor_y) 如果說條件condition是True那麼就會返回tensor_x,如果是False則返回tensor_y。注:舊版本的tensorflow可以用tf.select實現這個操作。

比較操作,為了獲得condition這個引數,我們可以用tf.greater( tensor_x, tensor_y)如果說tensor_x大於tensor_y則返回True。

tf.reduce_sum(),tf.reduce_mean()這兩個操作是重要的loss操作,因為loss是一個數字,而通常計算得到的是一個高維的矩陣,因此用降維加法和降維取平均,可以將一個高維的矩陣變為一個數字。

有了上面的這些操作,我們就可以實現基本的損失函式的構建了,比如我們構建交叉熵損失函式:

cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y)+(y_-1)* tf.log(1-y)))

再介紹一個均衡正負樣本數量的loss,首先在訓練資料中,通常來講正例比較少,負例比較多,如疾病篩查中,有病的佔少數而絕大部分人是健康的,這種數量不均衡的資料可能會讓分類器傾向於將所有的示例都分為健康人,因為這樣整體的準確率可能就能達到90%以上,為此,可以用調整loss權重的方式來緩解樣本數量不均衡的問題,如:

pos_ratio=num_of_positive/num_all # 病人佔總體的比例,較小如0.1

neg_ratio=num_of_negative/num_all # 正常人佔總體的比例,較大如0.9

cross_entropy = tf.reduce_mean(-neg_ratio*tf.reduce_sum(y_ * tf.log(y)+pos_ratio*(y_-1)* tf.log(1-y)))

在這裡我們給病人的損失項乘了一個較大的係數,使得一旦佔少數的病人被錯分為健康人的時候,代價就非常的大。同樣的給正常人的損失項乘了一個較小的係數,使其診斷錯誤時對網路的影像較小。

這也符合實際情況,即使健康人在篩查時被通知可能患病,只要再進一步檢查就可以。但是如果在篩查的時候將病人誤分為健康人,那麼付出的就可能是生命的代價了。

以上是二分類的例子,那麼在多分類的時候應該如何做呢?我們也可以通過乘係數這樣的方式解決問題,這裡我們認為標籤是one_hot形式的如:

class1_weight=0.2 # 第一類的權重係數

class2_weight=0.5 # 第二類的權重係數

class3_weight=0.3 # 第三類的權重係數

cross_entropy=tf.reduce_mean(-class1_weight*tf.reduce_sum(y_[:,0]*tf.log(y[:,0])

-class2_weight*tf.reduce_sum(y_[:,1] * tf.log(y[:,1])

-class3_weight*tf.reduce_sum(y_[:,2] * tf.log(y[:,2]))

因為標籤和預測的結果都是one_hot的形式,因此在這裡y[:,0]就是第一類的概率值,其中第一個維度的長度是minibatch的大小。同理y[:,0]就是第二類的概率值,我們在不同的項上乘上不同類別的權重係數,就可以一定程度上解決樣本數量不均衡所帶來的困擾。