從ReLU到GELU,一文概覽神經網絡的激活函數

2020-12-04 機器之心Pro

選自mlfromscratch

作者:Casper Hansen

機器之心編譯

參與:熊貓、杜偉

激活函數對神經網絡的重要性自不必多言,機器之心也曾發布過一些相關的介紹文章,比如《一文概覽深度學習中的激活函數》。本文同樣關注的是激活函數。來自丹麥技術大學的 Casper Hansen 通過公式、圖表和代碼實驗介紹了 sigmoid、ReLU、ELU 以及更新的 Leaky ReLU、SELU、GELU 這些激活函數,並比較了它們的優勢和短板。

在計算每一層的激活值時,我們要用到激活函數,之後才能確定這些激活值究竟是多少。根據每一層前面的激活、權重和偏置,我們要為下一層的每個激活計算一個值。但在將該值發送給下一層之前,我們要使用一個激活函數對這個輸出進行縮放。本文將介紹不同的激活函數。

在閱讀本文之前,你可以閱讀我前一篇介紹神經網絡中前向傳播和反向傳播的文章,其中已經簡單地提及過激活函數,但還未介紹其實際所做的事情。本文的內容將建立在你已了解前一篇文章知識的基礎上。

前一篇文章地址:https://mlfromscratch.com/neural-networks-explained/

Casper Hansen

目錄

概述sigmoid 函數是什麼?梯度問題:反向傳播梯度消失問題梯度爆炸問題梯度爆炸的極端案例避免梯度爆炸:梯度裁剪/範數整流線性單元(ReLU)死亡 ReLU:優勢和缺點指數線性單元(ELU)滲漏型整流線性單元(Leaky ReLU)擴展型指數線性單元(SELU)SELU:歸一化的特例權重初始化+dropout高斯誤差線性單元(GELU)代碼:深度神經網絡的超參數搜索擴展閱讀:書籍與論文概述

激活函數是神經網絡中一個至關重要的部分。在這篇長文中,我將全面介紹六種不同的激活函數,並闡述它們各自的優缺點。我會給出激活函數的方程和微分方程,還會給出它們的圖示。本文的目標是以簡單的術語解釋這些方程以及圖。

我會介紹梯度消失和爆炸問題;對於後者,我將按照 Nielsen 提出的那個很贊的示例來解釋梯度爆炸的原因。

最後,我還會提供一些代碼讓你可以自己在 Jupyter Notebook 中運行。

我會在 MNIST 數據集上進行一些小型代碼實驗,為每個激活函數都獲得一張損失和準確度圖。

sigmoid 函數是什麼?

sigmoid 函數是一個 logistic 函數,意思就是說:不管輸入是什麼,得到的輸出都在 0 到 1 之間。也就是說,你輸入的每個神經元、節點或激活都會被縮放為一個介於 0 到 1 之間的值。

sigmoid 函數圖示。

sigmoid 這樣的函數常被稱為非線性函數,因為我們不能用線性的項來描述它。很多激活函數都是非線性或者線性和非線性的組合(有可能函數的一部分是線性的,但這種情況很少見)。

這基本上沒什麼問題,但值恰好為 0 或 1 的時候除外(有時候確實會發生這種情況)。為什麼這會有問題?

這個問題與反向傳播有關(有關反向傳播的介紹請參閱我的前一篇文章)。在反向傳播中,我們要計算每個權重的梯度,即針對每個權重的小更新。這樣做的目的是優化整個網絡中激活值的輸出,使其能在輸出層得到更好的結果,進而實現對成本函數的優化。

在反向傳播過程中,我們必須計算每個權重影響成本函數(cost function)的比例,具體做法是計算成本函數相對於每個權重的偏導數。假設我們不定義單個的權重,而是將最後一層 L 中的所有權重 w 定義為 w^L,則它們的導數為:

注意,當求偏導數時,我們要找到 a^L 的方程,然後僅微分 z^L,其餘部分保持不變。我們用撇號「'」來表示任意函數的導數。當計算中間項 a^L/z^L 的偏導數時,我們有:

則 sigmoid 函數的導數就為:

當我們向這個 sigmoid 函數輸入一個很大的 x 值(正或負)時,我們得到幾乎為 0 的 y 值——也就是說,當我們輸入 w×a+b 時,我們可能得到一個接近於 0 的值。

sigmoid 函數的導數圖示。

當 x 是一個很大的值(正或負)時,我們本質上就是用一個幾乎為 0 的值來乘這個偏導數的其餘部分。

如果有太多的權重都有這樣很大的值,那麼我們根本就沒法得到可以調整權重的網絡,這可是個大問題。如果我們不調整這些權重,那麼網絡就只有細微的更新,這樣算法就不能隨時間給網絡帶來多少改善。對於針對一個權重的偏導數的每個計算,我們都將其放入一個梯度向量中,而且我們將使用這個梯度向量來更新神經網絡。可以想像,如果該梯度向量的所有值都接近 0,那麼我們根本就無法真正更新任何東西。

這裡描述的就是梯度消失問題。這個問題使得 sigmoid 函數在神經網絡中並不實用,我們應該使用後面介紹的其它激活函數。

梯度問題

梯度消失問題

我的前一篇文章說過,如果我們想更新特定的權重,則更新規則為:

但如果偏導數 C/w^(L) 很小,如同消失了一般,又該如何呢?這時我們就遇到了梯度消失問題,其中許多權重和偏置只能收到非常小的更新。

可以看到,如果權重的值為 0.2,則當出現梯度消失問題時,這個值基本不會變化。因為這個權重分別連接了第一層和第二層的首個神經元,所以我們可以用

的表示方式將其記為

假設這個權重的值為 0.2,給定一個學習率(具體多少不重要,這裡使用了 0.5),則新的權重為:

這個權重原來的值為 0.2,現在更新為了 0.199999978。很明顯,這是有問題的:梯度很小,如同消失了一樣,使得神經網絡中的權重幾乎沒有更新。這會導致網絡中的節點離其最優值相去甚遠。這個問題會嚴重妨礙神經網絡的學習。

人們已經觀察到,如果不同層的學習速度不同,那麼這個問題還會變得更加嚴重。層以不同的速度學習,前面幾層總是會根據學習率而變得更差。

出自 Nielsen 的書《Neural Networks and Deep Learning》。

在這個示例中,隱藏層 4 的學習速度最快,因為其成本函數僅取決於連接到隱藏層 4 的權重變化。我們看看隱藏層 1;這裡的成本函數取決於連接隱藏層 1 與隱藏層 2、3、4 的權重變化。如果你看過了我前一篇文章中關於反向傳播的內容,那麼你可能知道網絡中更前面的層會復用後面層的計算。

同時,如前面介紹的那樣,最後一層僅取決於計算偏導時出現的一組變化:

最終,這就是個大問題了,因為現在權重層的學習速度不同。這意味著網絡中更後面的層幾乎肯定會被網絡中更前面的層受到更多優化。

而且問題還在於反向傳播算法不知道應該向哪個方向傳遞權重來優化成本函數。

梯度爆炸問題

梯度爆炸問題本質上就是梯度消失問題的反面。研究表明,這樣的問題是可能出現的,這時權重處於「爆炸」狀態,即它們的值快速增長。

我們將遵照以下示例來進行說明:

http://neuralnetworksanddeeplearning.com/chap5.html#what's_causing_the_vanishing_gradient_problem_unstable_gradients_in_deep_neural_nets注意,這個示例也可用於展示梯度消失問題,而我是從更概念的角度選擇了它,以便更輕鬆地解釋。

本質上講,當 0<w<1 時,我們可能遇到梯度消失問題;當 w>1 時,我們可能遇到梯度爆炸問題。但是,當一個層遇到這個問題時,必然有更多權重滿足梯度消失或爆炸的條件。

我們從一個簡單網絡開始。這個網絡有少量權重、偏置和激活,而且每一層也只有一個節點。

這個網絡很簡單。權重表示為 w_j,偏置為 b_j,成本函數為 C。節點、神經元或激活表示為圓圈。

Nielsen 使用了物理學上的常用表示方式 Δ 來描述某個值中的變化(這不同於梯度符號 )。舉個例子,Δb_j 描述的是第 j 個偏置的值變化。

我前一篇文章的核心是我們要衡量與成本函數有關的權重和偏置的變化率。先不考慮層,我們看看一個特定的偏置,即第一個偏置 b_1。然後我們通過下式衡量變化率:

下面式子的論據和上面的偏導一樣。即我們如何通過偏置的變化率來衡量成本函數的變化率?正如剛才介紹的那樣,Nielsen 使用 Δ 來描述變化,因此我們可以說這個偏導能大致通過 Δ 來替代:

權重和偏置的變化可以進行如下可視化:

動圖出自 3blue1brown,視頻地址:https://www.youtube.com/watch?v=tIeHLnjs5U8。

我們先從網絡的起點開始,計算第一個偏置 b_1 中的變化將如何影響網絡。因為我們知道,在上一篇文章中,第一個偏置 b_1 會饋入第一個激活 a_1,我們就從這裡開始。我們先回顧一下這個等式:

如果 b_1 改變,我們將這個改變量表示為 Δb_1。因此,我們注意到當 b_1 改變時,激活 a_1 也會改變——我們通常將其表示為 a_1/b_1。

因此,我們左邊有偏導的表達式,這是 b_1 中與 a_1 相關的變化。但我們開始替換左邊的項,先用 z_1 的 sigmoid 替換 a_1:

上式表示當 b_1 變化時,激活值 a_1 中存在某個變化。我們將這個變化描述為 Δa_1。

我們變化 Δa_1 看作是與激活值 a_1 中的變化加上變化 Δb_1 近似一樣。

這裡我們跳過了一步,但本質上講,我們只是計算了偏導數,並用偏導的結果替代了分數部分。

a_1 的變化導致 z_2 的變化

所描述的變化 Δa_1 現在會導致下一層的輸入 z_2 出現變化。如果這看起來很奇怪或者你還不信服,我建議你閱讀我的前一篇文章。

表示方式和前面一樣,我們將下一個變化記為 Δz_2。我們又要再次經歷前面的過程,只是這次要得到的是 z_2 中的變化:

我們可以使用下式替代 Δa_1:

我們只計算這個式子。希望你清楚地明白到這一步的過程——這與計算 Δa_1 的過程一樣。

這個過程會不斷重複,直到我們計算完整個網絡。通過替換 Δa_j 值,我們得到一個最終函數,其計算的是成本函數中與整個網絡(即所有權重、偏置和激活)相關的變化。

基於此,我們再計算 C/b_1,得到我們需要的最終式:

梯度爆炸的極端案例

據此,如果所有權重 w_j 都很大,即如果很多權重的值大於 1,我們就會開始乘以較大的值。舉個例子,所有權重都有一些非常高的值,比如 100,而我們得到一些在 0 到 0.25 之間、 sigmoid 函數導數的隨機輸出:

最後一個偏導為

,可以合理地相信這會遠大於 1,但為了方便示例展示,我們將其設為 1。

使用這個更新規則,如果我們假設 b_1 之前等於 1.56,而學習率等於 0.5。

儘管這是一個極端案例,但你懂我的意思。權重和偏置的值可能會爆發式地增大,進而導致整個網絡爆炸。

現在花點時間想想網絡的權重和偏置以及激活的其它部分,爆炸式地更新它們的值。這就是我們所說的梯度爆炸問題。很顯然,這樣的網絡學不到什麼東西,因此這會完全毀掉你想要解決的任務。

避免梯度爆炸:梯度裁剪/規範

解決梯度爆炸問題的基本思路就是為其設定一個規則。這部分我不會深入進行數學解釋,但我會給出這個過程的步驟:

選取一個閾值——如果梯度超過這個值,則使用梯度裁剪或梯度規範;定義是否使用梯度裁剪或規範。如果使用梯度裁剪,你就指定一個閾值,比如 0.5。如果這個梯度值超過 0.5 或 -0.5,則要麼通過梯度規範化將其縮放到閾值範圍內,要麼就將其裁剪到閾值範圍內。但是要注意,這些梯度方法都不能避免梯度消失問題。所以我們還將進一步探索解決這個問題的更多方法。通常而言,如果你在使用循環神經網絡架構(比如 LSTM 或 GRU),那麼你就需要這些方法,因為這種架構常出現梯度爆炸的情況。

整流線性單元(ReLU)

整流線性單元是我們解決梯度消失問題的方法,但這是否會導致其它問題呢?請往下看。

ReLU 的公式如下:

ReLU 公式表明:

如果輸入 x 小於 0,則令輸出等於 0;如果輸入 x 大於 0,則令輸出等於輸入。儘管我們沒法用大多數工具繪製其圖形,但你可以這樣用圖解釋 ReLU。x 值小於零的一切都映射為 0 的 y 值,但 x 值大於零的一切都映射為它本身。也就是說,如果我們輸入 x=1,我們得到 y=1。

ReLU 激活函數圖示。

這很好,但這與梯度消失問題有什麼關係?首先,我們必須得到其微分方程:

其意思是:

如果輸入 x 大於 0,則輸出等於 1;如果輸入小於或等於 0,則輸出變為 0。用下圖表示:

已微分的 ReLU。

現在我們得到了答案:當使用 ReLU 激活函數時,我們不會得到非常小的值(比如前面 sigmoid 函數的 0.0000000438)。相反,它要麼是 0(導致某些梯度不返回任何東西),要麼是 1。

但這又催生出另一個問題:死亡 ReLU 問題。

如果在計算梯度時有太多值都低於 0 會怎樣呢?我們會得到相當多不會更新的權重和偏置,因為其更新的量為 0。要了解這個過程的實際表現,我們反向地看看前面梯度爆炸的示例。

我們在這個等式中將 ReLU 記為 R,我們只需要將每個 sigmoid σ 替換成 R:

現在,假如說這個微分後的 ReLU 的一個隨機輸入 z 小於 0——則這個函數會導致偏置「死亡」。假設是 R'(z_3)=0:

反過來,當我們得到 R'(z_3)=0 時,與其它值相乘自然也只能得到 0,這會導致這個偏置死亡。我們知道一個偏置的新值是該偏置減去學習率減去梯度,這意味著我們得到的更新為 0。

死亡 ReLU:優勢和缺點

當我們將 ReLU 函數引入神經網絡時,我們也引入了很大的稀疏性。那麼稀疏性這個術語究竟是什麼意思?

稀疏:數量少,通常分散在很大的區域。在神經網絡中,這意味著激活的矩陣含有許多 0。這種稀疏性能讓我們得到什麼?當某個比例(比如 50%)的激活飽和時,我們就稱這個神經網絡是稀疏的。這能提升時間和空間複雜度方面的效率——常數值(通常)所需空間更少,計算成本也更低。

Yoshua Bengio 等人發現 ReLU 這種分量實際上能讓神經網絡表現更好,而且還有前面提到的時間和空間方面的效率。

論文地址:https://www.utc.fr/~bordesan/dokuwiki/_media/en/glorot10nipsworkshop.pdf

優點:

相比於 sigmoid,由於稀疏性,時間和空間複雜度更低;不涉及成本更高的指數運算;能避免梯度消失問題。缺點:

引入了死亡 ReLU 問題,即網絡的大部分分量都永遠不會更新。但這有時候也是一個優勢;ReLU 不能避免梯度爆炸問題。指數線性單元(ELU)

指數線性單元激活函數解決了 ReLU 的一些問題,同時也保留了一些好的方面。這種激活函數要選取一個 α 值;常見的取值是在 0.1 到 0.3 之間。

如果你數學不好,ELU 的公式看起來會有些難以理解:

我解釋一下。如果你輸入的 x 值大於 0,則結果與 ReLU 一樣——即 y 值等於 x 值;但如果輸入的 x 值小於 0,則我們會得到一個稍微小於 0 的值。

所得到的 y 值取決於輸入的 x 值,但還要兼顧參數 α——你可以根據需要來調整這個參數。更進一步,我們引入了指數運算 e^x,因此 ELU 的計算成本比 ReLU 高。

下面繪出了 α 值為 0.2 的 ELU 函數的圖:

ELU 激活函數圖示。

上圖很直觀,我們應該還能很好地應對梯度消失問題,因為輸入值沒有映射到非常小的輸出值。

但 ELU 的導數又如何呢?這同樣也很重要。

看起來很簡單。如果輸入 x 大於 0,則 y 值輸出為 1;如果輸入 x 小於或等於 0,則輸出是 ELU 函數(未微分)加上 α 值。

可繪出圖為:

微分的 ELU 激活函數。

你可能已經注意到,這裡成功避開了死亡 ReLU 問題,同時仍保有 ReLU 激活函數的一些計算速度增益——也就是說,網絡中仍還有一些死亡的分量。

優點:

能避免死亡 ReLU 問題;能得到負值輸出,這能幫助網絡向正確的方向推動權重和偏置變化;在計算梯度時能得到激活,而不是讓它們等於 0。缺點:

由於包含指數運算,所以計算時間更長;無法避免梯度爆炸問題;神經網絡不學習 α 值。滲漏型整流線性單元激活函數(Leaky ReLU)

滲漏型整流線性單元激活函數也有一個 α 值,通常取值在 0.1 到 0.3 之間。Leaky ReLU 激活函數很常用,但相比於 ELU 它也有一些缺陷,但也比 ReLU 具有一些優勢。

Leaky ReLU 的數學形式如下:

因此,如果輸入 x 大於 0,則輸出為 x;如果輸入 x 小於或等於 0,則輸出為 α 乘以輸入。

這意味著能夠解決死亡 ReLU 問題,因為梯度的值不再被限定為 0——另外,這個函數也能避免梯度消失問題。儘管梯度爆炸的問題依然存在,但後面的代碼部分會介紹如何解決。

下面給出了 Leaky ReLU 的圖示,其中假設 α 值為 0.2:

Leaky ReLU 圖示。

和在公式中看到的一樣,如果 x 值大於 0,則任意 x 值都映射為同樣的 y 值;但如果 x 值小於 0,則會多一個係數 0.2。也就是說,如果輸入值 x 為 -5,則映射的輸出值為 -1。

因為 Leaky ReLU 函數是兩個線性部分組合起來的,所以它的導數很簡單:

第一部分線性是當 x 大於 0 時,輸出為 1;而當輸入小於 0 時,輸出就為 α 值,這裡我們選擇的是 0.2。

微分的 Leaky ReLU 圖示。

從上圖中也能明顯地看出來,輸入 x 大於或小於 0,微分的 Leaky ReLU 各為一個常量。

優點:

類似 ELU,Leaky ReLU 也能避免死亡 ReLU 問題,因為其在計算導數時允許較小的梯度;由於不包含指數運算,所以計算速度比 ELU 快。缺點:

無法避免梯度爆炸問題;神經網絡不學習 α 值;在微分時,兩部分都是線性的;而 ELU 的一部分是線性的,一部分是非線性的。擴展型指數線性單元激活函數(SELU)

擴展型指數線性單元激活函數比較新,介紹它的論文包含長達 90 頁的附錄(包括定理和證明等)。當實際應用這個激活函數時,必須使用 lecun_normal 進行權重初始化。如果希望應用 dropout,則應當使用 AlphaDropout。後面的代碼部分會更詳細地介紹。

論文作者已經計算出了公式的兩個值:α 和 λ;如下所示:

可以看到,它們的小數點後還有很多位,這是為了絕對精度。而且它們是預先確定的,也就是說我們不必擔心如何為這個激活函數選取合適的 α 值。

說實話,這個公式看起來和其它公式或多或少有些類似。所有新的激活函數看起來就像是其它已有的激活函數的組合。

SELU 的公式如下:

也就是說,如果輸入值 x 大於 0,則輸出值為 x 乘以 λ;如果輸入值 x 小於 0,則會得到一個奇異函數——它隨 x 增大而增大並趨近於 x 為 0 時的值 0.0848。本質上看,當 x 小於 0 時,先用 α 乘以 x 值的指數,再減去 α,然後乘以 λ 值。

SELU 函數圖示。

SELU 的特例

SELU 激活能夠對神經網絡進行自歸一化(self-normalizing)。這是什麼意思?

首先,我們先看看什麼是歸一化(normalization)。簡單來說,歸一化首先是減去均值,然後除以標準差。因此,經過歸一化之後,網絡的組件(權重、偏置和激活)的均值為 0,標準差為 1。而這正是 SELU 激活函數的輸出值。

均值為 0 且標準差為 1 又如何呢?在初始化函數為 lecun_normal 的假設下,網絡參數會被初始化一個正態分布(或高斯分布),然後在 SELU 的情況下,網絡會在論文中描述的範圍內完全地歸一化。本質上看,當乘或加這樣的網絡分量時,網絡仍被視為符合高斯分布。我們就稱之為歸一化。反過來,這又意味著整個網絡及其最後一層的輸出也是歸一化的。

均值 μ 為 0 且標準差 σ 為 1 的正態分布看起來是怎樣的?

SELU 的輸出是歸一化的,這可稱為內部歸一化(internal normalization),因此事實上其所有輸出都是均值為 0 且標準差為 1。這不同於外部歸一化(external normalization)——會用到批歸一化或其它方法。

很好,也就是說所有分量都會被歸一化。但這是如何做到的?

簡單解釋一下,當輸入小於 0 時,方差減小;當輸入大於 0 時,方差增大——而標準差是方差的平方根,這樣我們就使得標準差為 1。

我們通過梯度得到零均值。我們需要一些正值和負值才能讓均值為 0。我的上一篇文章介紹過,梯度可以調整神經網絡的權重和偏置,因此我們需要這些梯度輸出一些負值和正值,這樣才能控制住均值。

均值 μ 和方差 ν 的主要作用是使我們有某個域 Ω,讓我們總是能將均值和方差映射到預定義的區間內。這些區間定義如下:

∈ 符號表示均值和方差在這些預定義的區間之內。反過來,這又能避免網絡出現梯度消失和爆炸問題。

下面引述一段論文的解釋,說明了他們得到這個激活函數的方式,我認為這很重要:

SELU 允許構建一個映射 g,其性質能夠實現 SNN(自歸一化神經網絡)。SNN 不能通過(擴展型)修正線性單元(ReLU)、sigmoid 單元、tanh 單元和 Leaky ReLU 實現。這個激活函數需要有:(1)負值和正值,以便控制均值;(2)飽和區域(導數趨近於零),以便抑制更低層中較大的方差;(3)大於 1 的斜率,以便在更低層中的方差過小時增大方差;(4)連續曲線。後者能確保一個固定點,其中方差抑制可通過方差增大來獲得均衡。我們能通過乘上指數線性單元(ELU)來滿足激活函數的這些性質,而且 λ>1 能夠確保正值淨輸入的斜率大於 1。

我們再看看 SELU 的微分函數:

很好,不太複雜,我們可以簡單地解釋一下。如果 x 大於 0,則輸出值為 λ;如果 x 小於 0,則輸出為 α 乘以 x 的指數再乘 λ。

其圖形如下所示,看起來很特別:

微分的 SELU 函數。

注意 SELU 函數也需要 lecun_normal 進行權重初始化;而且如果你想使用 dropout,你也必須使用名為 Alpha Dropout 的特殊版本。

優點:

內部歸一化的速度比外部歸一化快,這意味著網絡能更快收斂;不可能出現梯度消失或爆炸問題,見 SELU 論文附錄的定理 2 和 3。缺點:

這個激活函數相對較新——需要更多論文比較性地探索其在 CNN 和 RNN 等架構中應用。這裡有一篇使用 SELU 的 CNN 論文:https://arxiv.org/pdf/1905.01338.pdfGELU

高斯誤差線性單元激活函數在最近的 Transformer 模型(谷歌的 BERT 和 OpenAI 的 GPT-2)中得到了應用。GELU 的論文來自 2016 年,但直到最近才引起關注。

這種激活函數的形式為:

看得出來,這就是某些函數(比如雙曲正切函數 tanh)與近似數值的組合。沒什麼過多可說的。有意思的是這個函數的圖形:

GELU 激活函數。

可以看出,當 x 大於 0 時,輸出為 x;但 x=0 到 x=1 的區間除外,這時曲線更偏向於 y 軸。

我沒能找到該函數的導數,所以我使用了 WolframAlpha 來微分這個函數。結果如下:

和前面一樣,這也是雙曲函數的另一種組合形式。但它的圖形看起來很有意思:

微分的 GELU 激活函數。

優點:

似乎是 NLP 領域的當前最佳;尤其在 Transformer 模型中表現最好;能避免梯度消失問題。缺點:

儘管是 2016 年提出的,但在實際應用中還是一個相當新穎的激活函數。用於深度神經網絡的代碼

假如說你想要嘗試所有這些激活函數,以便了解哪種最適合,你該怎麼做?通常我們會執行超參數優化——這可以使用 scikit-learn 的 GridSearchCV 函數實現。但是我們想要進行比較,所以我們的想法是選取一些超參數並讓它們保持恆定,同時修改激活函數。

說明一下我這裡要做的事情:

使用本文提及的激活函數訓練同樣的神經網絡模型;使用每個激活函數的歷史記錄,繪製損失和準確度隨 epoch 的變化圖。代碼也發布在了 GitHub 上,並且支持 colab,以便你能夠快速運行。地址:https://github.com/casperbh96/Activation-Functions-Search

我更偏好使用 Keras 的高級 API,所以這會用 Keras 來完成。

首先導入我們所需的一切。注意這裡使用了 4 個庫:tensorflow、numpy、matplotlib、 keras。

importtensorflowastfimportnumpyasnpimportmatplotlib.pyplotaspltfromkeras.datasetsimportmnistfromkeras.utils.np_utilsimportto_categoricalfromkeras.modelsimportSequentialfromkeras.layersimportDense,Dropout,Flatten,Conv2D,MaxPooling2D,Activation,LeakyReLUfromkeras.layers.noiseimportAlphaDropoutfromkeras.utils.generic_utilsimportget_custom_objectsfromkerasimportbackendasKfromkeras.optimizersimportAdam

現在加載我們運行實驗所需的數據集;這裡選擇了 MNIST 數據集。我們可以直接從 Keras 導入它。

(x_train,y_train),(x_test,y_test)=mnist.load_data()

很好,但我們想對數據進行一些預處理,比如歸一化。我們需要通過很多函數來做這件事,主要是調整圖像大小(.reshape)並除以最大的 RGB 值 255(/= 255)。最後,我們通過 to_categorical() 對數據進行 one-hot 編碼。

defpreprocess_mnist(x_train,y_train,x_test,y_test):#Normalizingallimagesof28x28pixelsx_train=x_train.reshape(x_train.shape[0],28,28,1)x_test=x_test.reshape(x_test.shape[0],28,28,1)input_shape=(28,28,1)#Floatvaluesfordivisionx_train=x_train.astype('float32')x_test=x_test.astype('float32')#NormalizingtheRGBcodesbydividingittothemaxRGBvaluex_train/=255x_test/=255#Categoricalyvaluesy_train=to_categorical(y_train)y_test=to_categorical(y_test)returnx_train,y_train,x_test,y_test,input_shapex_train,y_train,x_test,y_test,input_shape=preprocess_mnist(x_train,y_train,x_test,y_test)

現在我們已經完成了數據預處理,可以構建模型以及定義 Keras 運行所需的參數了。首先從卷積神經網絡模型本身開始。SELU 激活函數是一個特殊情況,我們需要使用核初始化器 'lecun_normal' 和特殊形式的 dropout AlphaDropout(),其它一切都保持常規設定。

defbuild_cnn(activation,dropout_rate,optimizer):model=Sequential()if(activation=='selu'):model.add(Conv2D(32,kernel_size=(3,3),activation=activation,input_shape=input_shape,kernel_initializer='lecun_normal'))model.add(Conv2D(64,(3,3),activation=activation,kernel_initializer='lecun_normal'))model.add(MaxPooling2D(pool_size=(2,2)))model.add(AlphaDropout(0.25))model.add(Flatten())model.add(Dense(128,activation=activation,kernel_initializer='lecun_normal'))model.add(AlphaDropout(0.5))model.add(Dense(10,activation='softmax'))else:model.add(Conv2D(32,kernel_size=(3,3),activation=activation,input_shape=input_shape))model.add(Conv2D(64,(3,3),activation=activation))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Dropout(0.25))model.add(Flatten())model.add(Dense(128,activation=activation))model.add(Dropout(0.5))model.add(Dense(10,activation='softmax'))model.compile(loss='binary_crossentropy',optimizer=optimizer,metrics=['accuracy'])returnmodel

使用 GELU 函數有個小問題;Keras 中目前還沒有這個函數。幸好我們能輕鬆地向 Keras 添加新的激活函數。

#AddtheGELUfunctiontoKerasdefgelu(x):return0.5*x*(1+tf.tanh(tf.sqrt(2/np.pi)*(x+0.044715*tf.pow(x,3))))get_custom_objects().update({'gelu':Activation(gelu)})#Addleaky-relusowecanuseitasastringget_custom_objects().update({'leaky-relu':Activation(LeakyReLU(alpha=0.2))})act_func=['sigmoid','relu','elu','leaky-relu','selu','gelu']

現在我們可以使用 act_func 數組中定義的不同激活函數訓練模型了。我們會在每個激活函數上運行一個簡單的 for 循環,並將結果添加到一個數組:

result=[]foractivationinact_func:print('\nTrainingwith-->{0}<--activationfunction\n'.format(activation))model=build_cnn(activation=activation,dropout_rate=0.2,optimizer=Adam(clipvalue=0.5))history=model.fit(x_train,y_train,validation_split=0.20,batch_size=128,#128isfaster,butlessaccurate.16/32recommendedepochs=100,verbose=1,validation_data=(x_test,y_test))result.append(history)K.clear_session()delmodelprint(result)

基於此,我們可以為每個激活函數繪製從 model.fit() 得到的歷史圖,然後看看損失和準確度結果的變化情況。

現在我們可以為數據繪圖了,我用 matplotlib 寫了一小段代碼:

new_act_arr=act_func[1:]new_results=result[1:]defplot_act_func_results(results,activation_functions=[]):plt.figure(figsize=(10,10))plt.style.use('dark_background')#Plotvalidationaccuracyvaluesforact_funcinresults:plt.plot(act_func.history['val_acc'])plt.title('Modelaccuracy')plt.ylabel('TestAccuracy')plt.xlabel('Epoch')plt.legend(activation_functions)plt.show()#Plotvalidationlossvaluesplt.figure(figsize=(10,10))foract_funcinresults:plt.plot(act_func.history['val_loss'])plt.title('Modelloss')plt.ylabel('TestLoss')plt.xlabel('Epoch')plt.legend(activation_functions)plt.show()plot_act_func_results(new_results,new_act_arr)

這會得到如下圖表:

擴展閱讀

下面是四本寫得很贊的書:

Deep Learning,作者:Ian Goodfellow、Yoshua Bengio、Aaron CourvilleThe Hundred-Page Machine Learning Book,作者:Andriy BurkovHands-On Machine Learning with Scikit-Learn and TensorFlow,作者:Aurélien GéronMachine Learning: A Probabilistic Perspective,作者:Kevin P. Murphy下面是本文討論過的重要論文:

Leaky ReLU 論文:https://ai.stanford.edu/~amaas/papers/relu_hybrid_icml2013_final.pdfELU 論文:https://arxiv.org/pdf/1511.07289.pdfSELU 論文:https://arxiv.org/pdf/1706.02515.pdfGELU 論文:https://arxiv.org/pdf/1606.08415.pdf

相關焦點

  • ReLU到Sinc的26種神經網絡激活函數可視化大盤點
    在神經網絡中,激活函數決定來自給定輸入集的節點的輸出,其中非線性激活函數允許網絡複製複雜的非線性行為。正如絕大多數神經網絡藉助某種形式的梯度下降進行優化,激活函數需要是可微分(或者至少是幾乎完全可微分的)。此外,複雜的激活函數也許產生一些梯度消失或爆炸的問題。
  • Pytorch_第九篇_神經網絡中常用的激活函數
    神經網絡中常用的激活函數Introduce理論上神經網絡能夠擬合任意線性函數,其中主要的一個因素是使用了非線性激活函數(因為如果每一層都是線性變換,那有啥用啊,始終能夠擬合的都是線性函數啊)。本文主要介紹神經網絡中各種常用的激活函數。以下均為個人學習筆記,若有錯誤望指出。
  • 如何選擇神經網絡激活函數:有效的改善模型學習模式的能力
    激活函數是神經網絡中用於計算輸入和偏差的加權和的函數,用於確定神經元是否可以釋放。 它通常通過梯度下降法的某種梯度處理來操縱數據,然後產生神經網絡的輸出,該輸出包含數據中的參數。 有時這些激活函數通常稱為傳遞函數。激活函數具有改善數據學習模式的能力,從而實現了特徵檢測過程的自動化,並證明它們在神經網絡的隱藏層中的使用合理性,並且對於跨領域進行分類很有用。
  • 完勝ReLU!斯坦福的神經網絡用這種激活函數,高保真還原圖像視頻
    SIREN在這個方向上進行了突破,通過採用周期性激活函數Sine代替常見的非線性激活函數(如ReLU、TanH等),以連續的方式進行數據存儲。相比於ReLU、TanH等非周期性的激活函數來說,SIREN將正弦周期函數用作激活函數,相當於為神經網絡引入了周期性。
  • 超越ReLU卻鮮為人知3年後被挖掘:BERT、GPT-2等都在用的激活函數
    作為決定神經網絡是否傳遞信息的「開關」,激活函數對於神經網絡而言至關重要。不過今天被人們普遍採用的 ReLU 真的是最高效的方法嗎?最近在社交網絡上,人們找到了一個看來更強大的激活函數:GELU,這種方法早在 2016 年即被人提出,然而其論文迄今為止在 Google Scholar 上的被引用次數卻只有 34 次。
  • 神經網絡中的激活函數
    什麼是神經網絡激活函數?激活函數有助於決定我們是否需要激活神經元。如果我們需要發射一個神經元那麼信號的強度是多少。激活函數是神經元通過神經網絡處理和傳遞信息的機制為什麼在神經網絡中需要一個激活函數?在神經網絡中,z是輸入節點與節點權值加上偏差的乘積。
  • 一文讀懂圖神經網絡
    小聲:今天老闆說深度學習必須學習python,於是:一、介紹什麼是圖神經網絡圖神經網絡(Graph Neural Networks, GNNs)是基於圖結構的深度學習方法,近期被廣泛應用到各類圖像為什麼要使用圖神經網絡圖神經網絡有靈活的結構和更新方式,可以很好的表達一些數據本身的結構特性,除了一些自帶圖結構的數據集(如Cora,Citeseer等)以外,圖神經網絡目前也被應用在更多的任務上,比如文本摘要,文本分類和序列標註任務等,目前圖神經網絡以及其變種在很多任務上都取得了目前最好的結果
  • 人工神經網絡的驅動者:激活函數是什麼?
    圖源:unsplash激活函數是人工神經網絡的驅動因素,其位於神經元之上,並管理著神經元行為,比如是否需要處理某個輸入數據,若需要,又以何種程度處理等等。從技術上來講,某個節點的激活函數將其在神經網絡中前一個節點的數據輸入,並輸出一個確定值,這個值繼而指導後續節點如何發動來響應特定輸入信號。本文剖析由激活函數組成的神經網絡,以及神經網絡的生物類似物,並簡要介紹幾種常用的激活函數。神經網絡的結構作為一名數據科學愛好者,你一定見過上面的這張圖片或者與之相似的圖片。這張圖片是對雙層神經網絡工作流程的經典描述。
  • 神經網絡為何非激活函數不可?
    本文作者 Vandit Jain 對激活函數的相關知識進行了一個較為全面的總結,重點聚焦於以下幾個方面:一、激活函數是什麼?簡單地說,激活函數就是加入到人工神經網絡中的一個函數,目的在於幫助神經網絡從數據中學習複雜模式。
  • 性能優於ReLU,斯坦福用周期激活函數構建隱式神經表示,Hinton點讚
    機器之心報導參與:杜偉、小舟、魔王使用非線性周期函數構建的神經架構效果優於 ReLU?斯坦福的一項研究做出了嘗試。這個非線性激活函數效果比 ReLU 還好?這項研究提出利用周期性激活函數處理隱式神經表示,由此構建的正弦表示網絡(sinusoidal representation network,SIREN)非常適合表示複雜的自然信號及其導數。Geoffrey Hinton 轉發了這項研究,並表示該項目的講解視頻或許有助於理解網格單元。
  • 代碼詳解:基於Python建立任意層數的深度神經網絡 - 讀芯術
    圖1 神經網絡構造的例子(符號說明:上標[l]表示與第l層;上標(i)表示第i個例子;下標i表示矢量第i項)單層神經網絡圖2 單層神經網絡示例神經元模型是先計算一個線性函數(z=Wx+b),接著再計算一個激活函數。一般來說,神經元模型的輸出值是a=g(Wx+b),其中g是激活函數(sigmoid,tanh, ReLU, …)。
  • 人工智慧-深度學習-激活函數ReLU,Sigmoid,TanH,ELU,MaxOut
    激活函數:在多層神經網絡中,上層節點的輸出和下層節點的輸入之間具有一個函數關係,這個函數稱為激活函數(又稱激勵函數)激活函數的本質:激活函數是來向神經網絡中引入非線性因素的,通過激活函數,神經網絡就可以擬合各種曲線。
  • 通過函數圖像,了解26種神經網絡激活函數都長啥樣.
    在神經網絡中,激活函數決定來自給定輸入集的節點的輸出,其中非線性激活函數允許網絡複製複雜的非線性行為。正如絕大多數神經網絡藉助某種形式的梯度下降進行優化,激活函數需要是可微分(或者至少是幾乎完全可微分的)。此外,複雜的激活函數也許產生一些梯度消失或爆炸的問題。因此,神經網絡傾向於部署若干個特定的激活函數(identity、sigmoid、ReLU 及其變體)。
  • 代碼詳解:一文掌握神經網絡超參數調優
    該景觀類似於神經網絡的損失平面。訓練神經網絡的目的是通過某種形式的優化找到損失平面上的最小值——典型的隨機坡度減少。在學習使用高難度的優化功能後,本文讀者能充分應對施行神經網絡時遇到的實際問題場景。測試神經網絡前,首先需要給功能下定義能並找出最小值(否則無法確定為正確答案)。
  • 人工智慧算法:訓練神經網絡中的批量歸一化(附代碼)
    數據集的準備工作包括通過將每個像素值除以255.0來歸一化訓練圖像和測試圖像。這會將像素值置於0到1的範圍內。在此階段,還將創建數據集的驗證部分。在訓練期間利用該組數據集來評估網絡在各種迭代中的性能。這組操作涉及到進入BN層的輸入值的偏移量的標準化,歸一化,重新縮放和移位。激活層:這對神經網絡內的輸入執行指定的操作。該層在網絡內引入了非線性。本文實現的模型將利用激活函數:整流線性單元(ReLU)和softmax。
  • 神經網絡中的損失函數正則化和 Dropout 並手寫代碼實現
    在深度神經網絡中最常用的方法是Regularization和dropout。 在本文中,我們將一起理解這兩種方法並在python中實現它們Regularization 正則化正則化通過在損失函數的末尾添加額外的懲罰項來幫助防止模型過度擬合。
  • AI從入門到放棄:BP神經網絡算法推導及代碼實現筆記
    如果激活函數是飽和的,帶來的缺陷就是系統迭代更新變慢,系統收斂就慢,當然這是可以有辦法彌補的,一種方法是使用交叉熵函數作為損失函數,這裡不多說。ReLU是非飽和的,親測效果挺不錯,所以這貨最近挺火的。單調性:即導數符號不變。導出要麼一直大於0,要麼一直小於0,不要上躥下跳。導數符號不變,讓神經網絡訓練容易收斂。
  • 要想了解卷積神經網絡,首先需要了解三種基本架構
    通過卷積神經網絡提取數據表徵特徵。要想了解卷積神經網絡,首先需要了解三種基本架構:全連接,池化和卷積。基於全連接架構的卷積神經網絡用於圖像和3d立體視覺等識別問題,可直接使用relu激活函數。優點是穩定性好,容易訓練和理解。缺點是解決反射和色彩模式等問題時存在不穩定的問題,尤其是對平移圖像等。池化架構可用於文本檢測和圖像相似性匹配等圖像問題。
  • 理解神經網絡:從神經元到RNN、CNN、深度學習
    神經網絡到目前為止,我們已經介紹完了神經元和激活函數,它們一起是構建任意神經網絡的基本構件。現在,我們更深入的了解什麼是神經網絡,以及它們不同的種類。我強烈的建議你,如果對於神經元和激活函數有任何的疑惑,回過頭去複習一下它們。在理解一個神經網絡之前,有必要去理解神經網絡中的Layer(層),一層Layer是一組有輸入輸出的神經元。每一個神經元的輸入通過其所屬的激活函數處理,例如,這是一個小型神經網絡。
  • 吳恩達深度學習(20)-激活函數的導數和神經網絡的梯度下降
    激活函數的導數(Derivatives of activation functions)在神經網絡中使用反向傳播的時候,你真的需要計算激活函數的斜率或者導數。>在神經網絡中a=g(z);g(z)'=d/dz g(z)=a(1-a)2) Tanh activation function其具體的求導如下: 公式2: g(z)=tanh(z)=(e^z-e^(-z))/(e^z+e^(-z) )在神經網絡中;3)Rectified Linear Unit