使用 TensorFlow with Keras,按照《Python 深度學習》(《Deep Learning with Python》)文本和序列處理那章,使用一維卷積Conv1D進行IMBD電影評論情感分類,下面是書里的代碼:
結果,運行的時候,出現了訓練誤差和精度、驗證誤差和精度都保持不變的情況:
我驚呆了!訓練誤差一直是 7.7364,訓練精度一直是0.4985,驗證誤差一直是7.6168,驗證精度一直是 0.5062 。你說你要是上升,或者下降又上升,或者 xxxx ,怎么着都比這種固定不動的情況好理解啊,這 xxxx。
既不是過擬合,也不是性能不佳,不是學習率太大或太小,也不是網絡層次復雜,更不是訓練過程中 batch大小、epoch數或者validation 數據的比例。。。
最后的最后,終於讓我找到了原因,並且引出了一個大問題,這波很重要~~
原因:二至交叉熵損失函數 。
注意,模型 compile 的時候,書里的代碼傳入的是 loss='binary_crossentropy',這是以字符串形式傳入的損失函數,經過查閱文檔、百度搜索、VS Code里點來點去找模塊源碼,我終於明白這是個在keras 的 losses 模塊里定義的一個函數(它雖然與同模塊下 BinaryCrossentropy 類有相同的功能——計算二值交叉熵,但是二者相對獨立地存在,而字符串指定 'binary_crossentropy' 時,指的就是前者 this function,而不是后者 that class)。
而不管是身為函數的 binary_crossentropy,還是身為類的 BinaryCrossentropy (雖然它的對象可以當作正常函數來訪問,因為實現了 __call__ 這個成員方法),它們使用時都會涉及一個叫做 from_logits 的關鍵字參數。當 from_logits = False 時,該損失函數接收概率值作為輸入,也就是說接收 [0, 1] 的數,計算損失時直接帶入熵的公式,而 False 也是默認參數值;當
from_logits=True 時,該損失函數接收 logit 值作為輸入(即 logistic 值,亦即 ),是個 ( - INF, INF) 的值,計算損失時,可以理解為:先計算概率 ,然后再帶入交叉熵公式得到結果。
那么問題來了,書中代碼構建的網絡,最后一層全連接層,並沒有指定使用 activation 函數,也就是說,一個線性轉換的結果直接輸出作為預測結果,它是一個從負無窮到正無窮的數,是個logit數值,並不是個概率值,所以不能直接當成 from=False 的 binary_crossentropy 函數參數,來計算損失,因為這樣會使得 log 傳入負值而沒有數學意義,代碼層面不會報錯,則不知道使用了什么機制,可能是不予考慮,也可能是別的什么,反正沒有正常計算,更沒有正常 backward。所以就出現了精度、誤差都是固定值的情況。
=========================================================================
可怕的是,使用書中不很正確的代碼,並不會每次都重現精度、誤差保持不變的情況,這是因為最后一層參數初始化隨機的問題。如果運氣好,那么很少會出現交叉熵的 log 計算無意義的情況,即不會有對負數求 log 的情況。陰差陽錯的,也會進行梯度下降,而且還真就能提高精度。
但畢竟不嚴謹。
現在看來,解決辦法:要么設定二值交叉熵函數的 from_logits=True,要么網絡最后一層添加 activation='sigmoid' 作為非線性壓縮轉換。
第二種辦法——給網絡加上最后的 sigmoid函數,肯定是對的(這應該是書中這段代碼的主要錯誤了,加上這個就完全沒問題了)。
至於第一種辦法,雖然使得 loss 計算合理了,但是仍然有問題,因為 編譯的時候制定了 metrics 為 'acc',即 accuracy 精度,要知道我們輸出的並不是 label,而是個連續值,所以又出現過很多其他的問題,比如 loss 不斷下降,但是精度卻一直保持一個固定值,這種情況也是偶爾地出現,還是依靠於網絡權重的初始化,運氣不好就 GG。所以參數隨機初始化真是個好東西,可以彌補理論上的錯誤,無腦地迭代、梯度下降、參數更新,最后竟然還能學到不錯的結果 :)
===============================================================
我的實驗怎么做的?
As mentioned above,精度、損失值固定不變是隨機出現的,參數初始化的好就不會出現,為了研究這個問題,讓它必然出現這種情況,我在訓練開始前,使用
將最后一個全連接層的所有權重設置為 -1.0,這樣,必然得到負數,必然使 log 無意義,必然出現那種情況(此時這層依然是書上的代碼,沒有非線性轉換),然后我就換用不同的 loss 設置,一個是直接 from_logits=False 的 'binary_crossentropy',這時出現了上述的精度、誤差全都不變的情況,原因很簡單,數學意義錯誤,代碼層面的捕捉異常我就沒有深入考慮了;另一個是使用loss=BinaryCrossentropy(from_logits=True),這時,更有趣了,誤差不斷下降,但是精度像上述一樣,訓練時、驗證時都保持一個固定值不變,原因上面也說了,網絡輸出的是連續值,不可能跟0 or 1作為離散標記來比對。
====================================================================
上面的都是為了滿足我的好學心(好奇心)瞎做的,瞎扯的。
其實。。。
從 model 定義(架構)、optimizer的學習率、fit 時傳入的超參數設置等進行考慮,逐步地(循環遞歸地(皮))排查,最后發現 model 的輸出不太對勁,因為這是個文本分類的問題,結果輸出了既不是 like 0,又不是 like 1 的輸出,就知道,書里把上一章的溫度檢測回歸模型的思路帶進來了,於是:最后一個全連接(或者密集連接)層沒有進行非線性的轉換。
因為使用的是交叉熵損失,所以最后輸出網絡的時候要給個非線性變換,把輸出壓縮到 [0, 1]范圍內,不然直接輸出的數值沒有意義,或者不能直接進行解釋。
直接老老實實加個 activation='sigmoid'就好。
完。
抓主要矛盾,主要矛盾,主要矛盾。下次一定 :)