天天看點

從零實作深度學習架構——神經元與常見激活函數

引言

本着“凡我不能創造的,我就不能了解”的思想,本系列文章會基于純Python以及NumPy從零建立自己的深度學習架構,該架構類似PyTorch能實作自動求導。

要深入了解深度學習,從零開始建立的經驗非常重要,從自己可以了解的角度出發,盡量不使用外部完備的架構前提下,實作我們想要的模型。本系列​文章的宗旨就是通過這樣的過程,讓大家切實掌握深度學習底層實作,而不是僅做一個調包俠。

我們已經了解了線性回歸和邏輯回歸,本文來學習深度學習中神經網絡的基礎建構——神經元,以及常見的激活函數。

神經元

神經網絡和邏輯回歸很像,但神經網絡更強大。而神經網絡是由很多個神經元(Neuron)組成的。一個神經元将實數集作為輸入,然後應用某種運算,産生一個實數輸出。

從零實作深度學習架構——神經元與常見激活函數

在神經元内部,如上圖所示,神經元首先計算輸入的權重和,然後加上偏置項。給定輸入,每個輸入對應一個權重,得到權重和:

通常使用向量的形式描述更加友善。這樣由向量和标量,以及輸入向量來描述:

注意這裡得到的隻是一個實數(标量)。

最後,我們不是直接使用作為輸出,神經元内部應用一個非線性函數到​上:

這裡的非線性函數稱為激活函數,該函數的輸出值稱為激活值,我們已經見過的一種激活函數是Sigmoid函數:

這裡神經元的輸出和激活值相同,但在神經網絡中,我們通常用​表示整個網絡最終的輸出。把代入,得到神經元的輸出:

除了Sigmoid之外,還有很多其他比較常見的激活函數。

常見激活函數

激活函數(activation function)通過計算權重和并加上偏置來确定神經元是否應該被激活,大多數激活函數都是非線性的。所有

ReLU

最常用的激活函數是修正線性單元(Rectified linear unit,ReLU),提供了一種非常簡單的非線性變換。給定元素,ReLU函數被定義為該元素與的最大值:

ReLU函數通過将相應的激活值設為,僅保留正元素并丢棄所有負元素。我們可以畫出函數的圖形感受一下:

from metagrad.functions import *
from metagrad.utils import plot

if __name__ == '__main__':
    x = Tensor.arange(-8.0, 8.0, 0.1, requires_grad=True)
    y = relu(x)
    plot(x.numpy(), y.numpy(), 'x', 'relu(x)', random_fname=True, figsize=(5, 2.5))      
從零實作深度學習架構——神經元與常見激活函數

當輸入為負時,ReLU函數的導數為,當輸入為正時,ReLU函數的導數為。當輸入為時,我們讓其導數也為。

y.backward(Tensor.ones_like(x))
plot(x.numpy(), x.grad.numpy(), 'x', 'grad of relu', figsize=(5, 2.5))      
從零實作深度學習架構——神經元與常見激活函數

由于ReLU的簡單性,沒有包含,導緻它的計算效率極高。同時它的梯度要麼為,要麼為,這使得優化變現得更好,減輕了困擾神經網絡的梯度消息問題。

ReLU導數的函數圖形如上圖所示,我們可以看到,在的一側,梯度值永遠是。是以,在反向傳播的過程中,可能有些神經元的權重不會被更新(因為導數為)。這可能會導緻永不激活的死節點(神經元)。這個問題可以被ReLU的變種:Leaky ReLU解決。

Leaky ReLU

Leaky ReLU是ReLU的改進版本,主要用于解決上面跳到的死節點問題,通過給所有負值賦予一個小的正斜率來解決

通常這裡的,為了看出效果。在畫圖時讓,我們看一下它的圖形:

y = leaky_relu(x)
plot(x.numpy(), y.numpy(), 'x', 'leaky relu(x)', random_fname=True, figsize=(5, 2.5))      
從零實作深度學習架構——神經元與常見激活函數

Leaky ReLU的優點與ReLU相同,同時對于負輸入,其導數也變成了一個非零值(即)。

從零實作深度學習架構——神經元與常見激活函數

從上圖可以看到,對于的一側,它們也有非零的導數。不至于出現死節點,但是由于通常很小,導緻在在此側的模型參數學習緩慢。

除了Leaky ReLU外,類似地還有兩種變體,分别是Parametric ReLU和Randomized Leaky ReLU。

Parametric ReLU稱為參數化的ReLU,即令Leaky ReLU中的變成了一個可學習的參數。

而Randomized Leaky ReLU讓取自一個連續均勻機率分布。

Exponential Linear Unit

ELU(Exponential Linear Unit)也是ReLU的一種變體,類似Leaky ReLU修改在側的斜率,但在負區域不是一條直線,而是一條對數曲線。

通常,我們畫出ELU的圖像:

y = elu(x)
plot(x.numpy(), y.numpy(), 'x', 'elu(x)', random_fname=True, figsize=(5, 2.5))      
從零實作深度學習架構——神經元與常見激活函數

ELU在負值部分緩慢變得平滑,直到輸出等于,且是一個可調整的參數,它控制着ELU負值部分在何時飽和。但是引入了。其導數的圖像為:

從零實作深度學習架構——神經元與常見激活函數

SoftPlus

SoftPlus函數與ReLU函數接近,但比較平滑,也是單邊抑制的。

其中預設為,随着的增加,該函數越來越像ReLU。

我們看一下預設情況下的函數圖像:

y = softplus(x, beta=10)
plot(x.numpy(), y.numpy(), 'x', 'softplus(x)', random_fname=True, figsize=(5, 2.5))      
從零實作深度學習架構——神經元與常見激活函數

當時,我們看一下函數圖像:

y = softplus(x, beta=10)
plot(x.numpy(), y.numpy(), 'x', r'softplus(x) with $\beta$ = 10', random_fname=True, figsize=(5, 2.5))      
從零實作深度學習架構——神經元與常見激活函數

其導數為:

當,其導數就是。我們來看下其導數圖像:

從零實作深度學習架構——神經元與常見激活函數

Swish

Swish在更深層次的模型上顯示出比ReLU更好的性能。Swish的輸入從負無窮到正無窮。函數定義為

相當于是對輸入進行了門控(通過函數),我們看一下它的圖像:

y = swish(x)
plot(x.numpy(), y.numpy(), 'x', 'swish(x)', random_fname=True, figsize=(5, 2.5))      
從零實作深度學習架構——神經元與常見激活函數

它的曲線都是光滑的,且處處可導。當增大時,函數值趨于無窮大;當減小時,函數值趨于常數。

Swish函數的導數為下面的公式:

其導數的圖像為:

從零實作深度學習架構——神經元與常見激活函數

Swish的特性:

  • 無上邊界:不像sigmoid和tanh函數,Swish沒有上邊界的,因為它避免了在接近零的梯度中緩慢的訓練時間——像sigmoid或tanh這樣的函數是有界的,是以需要小心地初始化網絡,以保持在這些函數的界限内。
  • 曲線的平滑性:平滑性在泛化和優化中起着重要的作用。與ReLU不同,Swish是一個平滑的函數,這使得它對初始化權值和學習率不那麼敏感。
  • 有下邊界:這有助于增強正則化效果(左側慢慢接近于,一定程度過濾掉一部分資訊,起到正則化的效果)。

Sigmoid

Sigmoid函數将輸入壓縮為區間上的輸出。是以,Sigmoid函數通常稱為擠壓函數(squashing function):

當我們想要将輸出看成二分類的機率時,sigmoid此時最常用。然而,sigmoid在隐藏層中較少使用,它通常被更簡單、更容易訓練的ReLU所取代。

下面我們畫出sigmoid函數。

y = sigmoid(x)
plot(x.numpy(), y.numpy(), 'x', 'sigmoid(x)', random_fname=True, figsize=(5, 2.5))      
從零實作深度學習架構——神經元與常見激活函數

其導數計算如下:

其導數的圖像為:

從零實作深度學習架構——神經元與常見激活函數

在深層網絡中,Sigmoid存在三個問題:

  • 飽和的神經元會讓梯度消失,即在較大的正數或負數作為輸入的時候,梯度就會變成零,使得神經元基本不能更新。
  • 其輸出不是以為中心的,而是。我們知道,在深層網絡中,由于上一層使用的是Sigmoid激活函數,導緻該層的輸入都是正數。即該層的梯度取決于,要麼都是正的,要麼都是負的,出現了zig zag問題。如下圖所示,假設最佳更新路線是藍線所示,由于zig zag問題,使其優化變成緩慢。
  • 指數計算耗時
從零實作深度學習架構——神經元與常見激活函數

Tanh

tanh是sigmoid函數的變種,它的函數值變成範圍從到,即變成了以為中心的。

我們來畫出該函數的圖像:

y = tanh(x)
plot(x.numpy(), y.numpy(), 'x', 'tanh(x)', random_fname=True, figsize=(5, 2.5))      
從零實作深度學習架構——神經元與常見激活函數

tanh函數具有平滑可微性,和将離群值映射到均值的良好性質。

為什麼說tanh函數是sigmoid函數的變種呢?我們來推導一下:

是以,我們可以看到tanh隻是sigmoid函數的縮放版本。

tanh函數的導數是:

其中;

其導數圖像如下所示:

從零實作深度學習架構——神經元與常見激活函數

可以看到,當輸入接近于時,tanh函數的導數接近于最大值。而輸入在任一方向上越遠離點,導數越接近。

Tanh的缺點類似Sigmoid,不過它是以為中心的,避免了zig zag問題。

如何選擇激活函數

我們看了這麼多激活函數,到底要如何選擇呢?

在深層網絡中,首先要嘗試ReLU,它具有速度快的優點,如果效果欠佳;

那麼嘗試Leaky ReLU;

或者tanh這種以零為中心的;

完整代碼

References

  1. DIVE INTO DEEP LEARNING
  2. Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification

繼續閱讀