Softmax函數與交叉熵
Softmax函數
背景與定義
在Logistic regression二分類問題中,我們可以使用sigmoid函數將輸入映射到區間中,從而得到屬於某個類別的概率。將這個問題進行泛化,推廣到多分類問題中,我們可以使用softmax函數,對輸出的值歸一化為概率值。
這裡假設在進入softmax函數之前,已經有模型輸出值,其中是要預測的類別數,模型可以是全連接網路的輸出,其輸出個數為,即輸出為。
所以對每個樣本,它屬於類別的概率為:
通過上式可以保證,即屬於各個類別的概率和為1。
導數
對softmax函數進行求導,即求
第項的輸出對第項輸入的偏導。代入softmax函數表達式,可以得到:用我們高中就知道的求導規則:對於
它的導數為所以在我們這個例子中,
上面兩個式子只是代表直接進行替換,而非真的等式。(即)對進行求導,要分情況討論:
- 如果,則求導結果為
- 如果,則求導結果為
再來看對求導,結果為。
所以,當時:
當時:其中,為了方便,令
對softmax函數的求導,我在兩年前微信校招面試基礎研究崗位一面的時候,就遇到過,這個屬於比較基礎的問題。
softmax的計算與數值穩定性
在Python中,softmax函數為:
def softmax(x): exp_x = np.exp(x) return exp_x / np.sum(exp_x)
傳入[1, 2, 3, 4, 5]的向量
>>> softmax([1, 2, 3, 4, 5])array([ 0.01165623, 0.03168492, 0.08612854, 0.23412166, 0.63640865])
但如果輸入值較大時:
>>> softmax([1000, 2000, 3000, 4000, 5000])array([ nan, nan, nan, nan, nan])
這是因為在求exp(x)時候溢出了:
import mathmath.exp(1000)# Traceback (most recent call last):# File "<stdin>", line 1, in <module># OverflowError: math range error
一種簡單有效避免該問題的方法就是讓exp(x)中的x值不要那麼大或那麼小,在softmax函數的分式上下分別乘以一個非零常數:
這裡是個常數,所以可以令它等於。加上常數之後,等式與原來還是相等的,所以我們可以考慮怎麼選取常數。我們的想法是讓所有的輸入在0附近,這樣的值不會太大,所以可以讓的值為:這樣子將所有的輸入平移到0附近(當然需要假設所有輸入之間的數值上較為接近),同時,除了最大值,其他輸入值都被平移成負數,為底的指數函數,越小越接近0,這種方式比得到nan的結果更好。def softmax(x): shift_x = x - np.max(x) exp_x = np.exp(shift_x) return exp_x / np.sum(exp_x)>>> softmax([1000, 2000, 3000, 4000, 5000])array([ 0., 0., 0., 0., 1.])
當然這種做法也不是最完美的,因為softmax函數不可能產生0值,但這總比出現nan的結果好,並且真實的結果也是非常接近0的。
UPDATE(2017-07-07):
有同學問這種近似會不會影響計算結果,為了看原來的softmax函數計算結果怎麼樣,嘗試計算`softmax([1000, 2000, 3000, 4000, 5000])`的值。由於numpy是會溢出的,所以使用Python中的bigfloat庫。
import bigfloatdef softmax_bf(x): exp_x = [bigfloat.exp(y) for y in x] sum_x = sum(exp_x) return [y / sum_x for y in exp_x]res = softmax_bf([1000, 2000, 3000, 4000, 5000])print([%s] % , .join([str(x) for x in res]))
結果:
[6.6385371046556741e-1738, 1.3078390189212505e-1303, 2.5765358729611501e-869, 5.0759588975494576e-435, 1.0000000000000000]
可以看出,雖然前四項結果的量級不一樣,但都是無限接近於0,所以加了一個常數的softmax對原來的結果影響很小。
Loss function
對數似然函數
機器學習裡面,對模型的訓練都是對Loss function進行優化,在分類問題中,我們一般使用最大似然估計(Maximum likelihood estimation)來構造損失函數。對於輸入的,其對應的類標籤為,我們的目標是找到這樣的使得最大。在二分類的問題中,我們有:
其中,是模型預測的概率值,是樣本對應的類標籤。將問題泛化為更一般的情況,多分類問題:
由於連乘可能導致最終結果接近0的問題,一般對似然函數取對數的負數,變成最小化對數似然函數。
交叉熵
說交叉熵之前先介紹相對熵,相對熵又稱為KL散度(Kullback-Leibler Divergence),用來衡量兩個分布之間的距離,記為
這裡是的熵。假設有兩個分布和,它們在給定樣本集上的交叉熵定義為:
從這裡可以看出,交叉熵和相對熵相差了,而當已知的時候,是個常數,所以交叉熵和相對熵在這裡是等價的,反映了分布和之間的相似程度。關於熵與交叉熵等概念,可以參考該博客再做了解。回到我們多分類的問題上,真實的類標籤可以看作是分布,對某個樣本屬於哪個類別可以用One-hot的編碼方式,是一個維度為的向量,比如在5個類別的分類中,[0, 1, 0, 0, 0]表示該樣本屬於第二個類,其概率值為1。我們把真實的類標籤分布記為,該分布中,當屬於它的真實類別。同時,分類模型經過softmax函數之後,也是一個概率分布,因為,所以我們把模型的輸出的分布記為,它也是一個維度為的向量,如[0.1, 0.8, 0.05, 0.05, 0]。
對一個樣本來說,真實類標籤分布與模型預測的類標籤分布可以用交叉熵來表示:最終,對所有的樣本,我們有以下loss function:
其中是樣本屬於類別的概率,是模型對樣本預測為屬於類別的概率。Loss function求導
對單個樣本來說,loss function對輸入的導數為:
上面對求導結果已經算出:當時:
當時:
所以,將求導結果代入上式:
TensorFlow
方法1:手動實現(不建議使用)
在TensorFlow中,已經有實現好softmax函數,所以我們可以自己構造交叉熵損失函數:
import tensorflow as tfimport input_datax = tf.placeholder("float", shape=[None, 784])label = tf.placeholder("float", shape=[None, 10])w_fc1 = tf.Variable(tf.truncated_normal([784, 1024], stddev=0.1))b_fc1 = tf.Variable(tf.constant(0.1, shape=[1024]))h_fc1 = tf.matmul(x, w_fc1) + b_fc1w_fc2 = tf.Variable(tf.truncated_normal([1024, 10], stddev=0.1))b_fc2 = tf.Variable(tf.constant(0.1, shape=[10]))y = tf.nn.softmax(tf.matmul(h_fc1, w_fc2) + b_fc2)cross_entropy = -tf.reduce_sum(label * tf.log(y))
cross_entropy = -tf.reduce_sum(label * tf.log(y))是交叉熵的實現。先對所有的輸出用softmax進行轉換為概率值,再套用交叉熵的公式。
方法2:使用tf.nn.softmax_cross_entropy_with_logits(推薦使用)
import tensorflow as tfimport input_datax = tf.placeholder("float", shape=[None, 784])label = tf.placeholder("float", shape=[None, 10])w_fc1 = tf.Variable(tf.truncated_normal([784, 1024], stddev=0.1))b_fc1 = tf.Variable(tf.constant(0.1, shape=[1024]))h_fc1 = tf.matmul(x, w_fc1) + b_fc1w_fc2 = tf.Variable(tf.truncated_normal([1024, 10], stddev=0.1))b_fc2 = tf.Variable(tf.constant(0.1, shape=[10]))y = tf.matmul(h_fc1, w_fc2) + b_fc2cross_entropy = -tf.reduce_sum(tf.nn.softmax_cross_entropy_with_logits(labels=label, logits=y))
TensorFlow已經實現好函數,用來計算label和logits的softmax交叉熵。注意,該函數的參數logits在函數內會用softmax進行處理,所以傳進來時不能是softmax的輸出了。
區別
既然我們可以自己實現交叉熵的損失函數,為什麼TensorFlow還要再實現tf.nn.softmax_cross_entropy_with_logits函數呢?
這個問題在Stack overflow上已經有Google的人出來回答(傳送門),原話是:
If you want to do optimization to minimize the cross entropy, AND you』re softmaxing after your last layer, you should use tf.nn.softmax_cross_entropy_with_logits instead of doing it yourself, because it covers numerically unstable corner cases in the mathematically right way. Otherwise, you』ll end up hacking it by adding little epsilons here and there.
也就是說,方法1自己實現的方法會有在前文說的數值不穩定的問題,需要自己在softmax函數裡面加些trick。所以官方推薦如果使用的loss function是最小化交叉熵,並且,最後一層是要經過softmax函數處理,則最好使用tf.nn.softmax_cross_entropy_with_logits函數,因為它會幫你處理數值不穩定的問題。
總結
全文到此就要結束了,可以看到,前面介紹這麼多概念,其實只是為了解釋在具體實現時候要做什麼樣的選擇。可能會覺得有些小題大做,但對於NN這個黑盒子來說,我們現暫不能從理論上證明其有效性,那在工程實現上,我們不能再將它當作黑盒子來使用。
Reference
The Softmax function and its derivative Peter』s Notes CS231n Convolutional Neural Networks for Visual Recognition http://cs229.stanford.edu/notes/cs229-notes1.pdf 交叉熵(Cross-Entropy) - rtygbwwwerr的專欄 - 博客頻道 - CSDN.NET difference between tensorflow tf.nn.softmax and tf.nn.softmax_cross_entropy_with_logits
文章同時發在CSDN上:http://blog.csdn.net/behamcheung/article/details/71911133#
推薦閱讀:
※做出「狼人殺」的 AI 有哪些難點?
※深度學習中:」多層的好處是可以用較少的參數表示複雜的函數「這句話該怎麼理解?
※如何看待Jeff Dean&Hinton投到ICLR17的MoE的工作?
※NIPS 2016有什麼值得關注的呢?
※如何評價FAIR的最新工作Data Distillation?
TAG:深度学习DeepLearning | TensorFlow | 机器学习 |