《Deep Learning》(Ian Goodfellow & Yoshua Bengio & Aaron Courville)第四章「數值計算」中,談到了上溢出(overflow)和下溢出(underflow)對數值計算的影響,並以softmax函數和log softmax函數為例進行了講解。這里我再詳細地把它總結一下。
『1』什么是下溢出(underflow)和上溢出(overflow)
實數在計算機內用二進制表示,所以不是一個精確值,當數值過小的時候,被四舍五入為0,這就是下溢出。此時如果對這個數再做某些運算(例如除以它)就會出問題。反之,當數值過大的時候,情況就變成了上溢出。
『2』softmax函數是什么
softmax函數如下:
從公式上看含義不是特別清晰,所以借用知乎上的一幅圖來說明(感謝原作者):
這幅圖極其清晰地表明了softmax函數是什么,一圖勝千言。
『3』計算softmax函數值的問題
通常情況下,計算softmax函數值不會出現什么問題,例如,當softmax函數表達式里的所有 xi 都是一個“一般大小”的數值 c 時——也就是上圖中, z1=z2=z3=c 時,那么,計算出來的函數值y1=y2=y3=1/3 。
但是,當某些情況發生時,計算函數值就出問題了:
- c 極其大,導致分子計算 ec 時上溢出
- c 為負數,且 |c| 很大,此時分母是一個極小的正數,有可能四舍五入為0,導致下溢出
『4』如何解決
所以怎樣規避這些問題呢?我們可以用同一個方法一口氣解決倆:
令 M=max(xi),i=1,2,⋯,n ,即 M 為所有 xi 中最大的值,那么我們只需要把計算 f(xi)的值,改為計算 f(xi−M) 的值,就可以解決上溢出、下溢出的問題了,並且,計算結果理論上仍然和 f(xi)保持一致。
舉個實例:還是以前面的圖為例,本來我們計算 f(z2) ,是用“常規”方法來算的:
現在我們改成:
其中, M=3 是 z1,z2,z3 中的最大值。可見計算結果並未改變。這是怎么做到的呢?通過簡單的代數運算就可以參透其中的“秘密”:
通過這樣的變換,對任何一個 xi,減去M之后,e 的指數的最大值為0,所以不會發生上溢出;同時,分母中也至少會包含一個值為1的項,所以分母也不會下溢出(四舍五入為0)。所以這個技巧沒什么高級的技術含量。
『5』延伸問題
看似已經結案了,但仍然有一個問題:如果softmax函數中的分子發生下溢出,也就是前面所說的 c 為負數,且 |c| 很大,此時分母是一個極小的正數,有可能四舍五入為0的情況,此時,如果我們把softmax函數的計算結果再拿去計算 log,即 log softmax,其實就相當於計算 log(0) ,所以會得到 −∞ ,但這實際上是錯誤的,因為它是由舍入誤差造成的計算錯誤。所以,有沒有一個方法,可以把這個問題也解決掉呢?
答案還是采用和前面類似的策略來計算 log softmax 函數值:
大家看到,在最后的表達式中,會產生下溢出的因素已經被消除掉了——求和項中,至少有一項的值為1,這使得log后面的值不會下溢出,也就不會發生計算 log(0) 的悲劇。在很多數值計算的library中,都采用了此類方法來保持數值穩定。