1. 程式人生 > >深度學習中的啟用函式

深度學習中的啟用函式

  眾所周知神經網路單元是由線性單元和非線性單元組成的,而非線性單元就是我們今天要介紹的--啟用函式,不同的啟用函式得出的結果也是不同的。他們也各有各的優缺點,雖然啟用函式有自己的發展歷史,不斷的優化,但是如何在眾多啟用函式中做出選擇依然要看我們所實現深度學習實驗的效果。

  這篇部落格會分為上下兩篇,上篇介紹一些常用的啟用函式(Sigmoid、tanh、ReLU、LeakyReLU、maxout)。下篇介紹一下不常用的啟用函式(PRelu、ELU、SELU)。

sigmoid

sigmoid啟用函式將輸入對映到(0,1)之間,他的數學函式為:

$$\sigma (z)=\frac{1}{1+e^{-z}}$$

歷史上sigmoid非常常用,但是由於他的兩個缺點,實際很少用了,現在看到sigmoid啟用函式,都是在新手教程中做一些簡單的實驗。

優點

  它能夠把輸入的連續實值變換為0和1之間的輸出,特別的,如果是非常大的負數,那麼輸出就是0;如果是非常大的正數,輸出就是1.

缺點

函式飽和使梯度消失

 我們先看sigmoid啟用函式的導數影象,

  如果我們初始化神經網路的權值為$[0,1]$之間的隨機值,由反向傳播演算法的數學推導可知,梯度從後向前傳播時,每傳遞一層梯度值都會減小為原來的0.25倍,如果神經網路隱層特別多,那麼梯度在穿過多層後將變得非常小接近於0,即出現梯度消失現象;當網路權值初始化為$(1,+\infty )$區間內的值,則會出現梯度爆炸情況。

不是原點中心對稱

  輸出不是0均值(既zero-centerde),這會導致後一層的神經元將得到上一層輸出的非均值的輸入。產生的結果就是:如$x>0,f=W^Tx_b$,那麼對w求區域性梯度則都為正,這樣在反向傳播的過程中w要麼都往正方向更新,要麼都往負方向更新,導致有一種捆綁的效果,使得收斂緩慢。

  這個特性會導致後面網路層的輸入不是零中心的,如果輸入都是正數的話(如 x>0 中 ),那麼關於$W$的梯度在反向傳播過程中,權重要麼全往正方向更新,要麼全往負方向更新,這樣很可能導致陷入區域性最小值。當然了,如果按batch去訓練,那麼那個batch可能得到不同的訊號,所以這個問題還是可以緩解一下的。

運算量大:

  解析式中含有冪運算,計算機求解時相對來講比較耗時。對於規模比較大的深度網路,這會較大地增加訓練時間。

def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))
View Code

tanh

tanh函式它的輸出是zero-centered的,但是它同樣存在梯度消失和冪指數問題。數學函式為:

$$f(z)=tanh(z)=\frac{e^{z}-e^{-z}}{e^z}+e^{-z}$$

def tanh(x):
    return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))
View Code

  tanh函式相比於Sigmoid函式往往更具有優越性,這主要是因為Sigmoid函式在輸入處於[-1,1]之間時,函式值變化敏感,一旦接近或者超出區間就失去敏感性,處於飽和狀態。

ReLU

這才是一個目前主流論文中非常常用的啟用函式,它的數學公式為:

$$f(x)=max(0,x)$$

def relu(x):
    return np.where(x<0,0,x)
View Code

優點

  1. ReLU的計算量小,收斂速度很快,因為sigmoid和tanh,ReLU有指數運算
  2. 在正區間(x>0)解決了梯度消失問題。

缺點:

  1. ReLU的輸出不是zero-centered
  2. RuLU在訓練的時候很容易導致神經元“死掉”

死掉:一個非常大的梯度經過一個 ReLU 神經元,更新過引數之後,這個神經元再也不會被任何資料啟用相應的權重永遠不會更新。有兩種原因導致這種情況:1、非常不幸的初始化。2、學習率設定的太高導致在訓練過程中引數更新太大,解決方法是使用Xavier初始化方法,合理設定學習率,會降低這種情況的發生概率。或使用Adam等自動調節學習率的演算法。

補充:ReLU相比sigmoid和tanh的一個缺點是沒有對上界設限,在實際使用中,可以設定一個上限,如ReLU6經驗函式: f(x)=min(6,max(0,x))

LeakyReLU

LeakyReLU也有人稱為PReLU,但是還是不太一樣的,LeakyReLU中的斜率a是自定義的,pReLU中的a是通過訓練學習得到的,LeakyReLU是為了解決“ReLU死亡”問題的嘗試

$$f(x)=\left\{\begin{matrix}
x&&x>0\\
0.01x&&其他
\end{matrix}\right.$$

ReLU 中當 x<0 時,函式值為 0 。而 Leaky ReLU 則是給出一個很小的負數梯度值,比如 0.01 。

有些研究者的論文指出這個啟用函式表現很不錯,但是其效果並不是很穩定。

def prelu(x,a):
    return np.where(x<0,a*x,x)
View Code

雖然Leaky ReLU修復了ReLU的神經元死亡問題,但是在實際的使用並沒有完全證明Leaky ReLU完全優於ReLU。

softmax

softmax用於多分類神經網路輸出,如果某一個$a_i$打過其他z,那這個對映的分量就逼近1,其他就逼近0,主要應用於“分類”。

$$SOFTMAX:a_i=\sigma_i(z)=\frac{e^{z_i}}{\sum_{j=1}^{m}e^{z_j}},z_i=w_ix+b$$

作用:把神經元中線性部分輸出的得分值(score),轉換為概率值。softmax輸出的是(歸一化)概率,

  含有softmax啟用函式的網路層有這樣一個性質:$\sum_{i=1}^{j}\sigma _i(z)=1$,可以解釋為每個節點的輸出值小於等於1。softmax激勵函式通常在神經網路的最後一層作為分類器的輸出,輸出值(概率)最大的即為分類結果。

$$貓:\begin{pmatrix}0.05\\ 0.05\\ 0.7\\ 0.2\end{pmatrix} 狗:\begin{pmatrix}0.8\\ 0.06\\ 0.01\\ 0.04\end{pmatrix}$$

PReLU

在RReLU中,負值的斜率$a_i$在訓練中是隨機的,$a_i$是可學習的,如果$a_i=0$,那麼 PReLU 退化為ReLU;如果$a_i$是一個很小的固定值(如$a_i=0.01$),則 PReLU 退化為 Leaky ReLU。

  $a_i$在之後的測試中就變成了固定的了。RReLU的亮點在於,在訓練環節中,$a_i$是從一個均勻的分佈$U(I,u)$中隨機抽取的數值。形式上來說,我們能得到以下數學表示式:

$$f(x)=\left\{\begin{matrix}
x&&x>0\\
a_ix&&x\leqslant 0
\end{matrix}\right.$$

其中$$a_i\sim U(x,y),區間(x,y)上的均勻分佈;x,y\in [0,1]$$

優點

(1)PReLU只增加了極少量的引數,也就意味著網路的計算量以及過擬合的危險性都只增加了一點點。特別的,當不同channels使用相同的$a$時,引數就更少了。

(2)BP更新$a$時,採用的是帶動量的更新方式,如下:

$$\Delta a_i=\mu \Delta a_i+\epsilon \frac{\partial \varepsilon }{\partial a_i}$$

ELU

ELU也是為了解決ReLU存在的問題而提出的,ELU有ReLU的基本所有優點,以及不會有Dead ReLU問題,和輸出的均值接近0(zero-certered),它的一個小問題在於計算量稍大。類似於Leaky ReLU,理論上雖然好於ReLU,但在實際使用中目前並沒有好的證據ELU總是優於ReLU。

$$f(x)=\left\{\begin{matrix}
x&&x>0\\
\alpha (e^x-1)&&x\leq 0
\end{matrix}\right.$$

$$f'(x)=\left\{\begin{matrix}
1&&x>0\\
f(x)+a&&x\leq 0
\end{matrix}\right.$$

def elu(x, a):
    return np.where(x < 0, a*(np.exp(x)-1), a*x)
View Code

 

其中$\alpha$是一個可調整的引數,它控制著ELU負值部分在何時飽和。右側線性部分使得ELU能夠緩解梯度消失,而左側軟飽能夠讓ELU對輸入變化或噪聲更魯棒。ELU的輸出均值接近於零,所以收斂速度更快 

SELU

$$SELU(x)=\lambda \left\{\begin{matrix}
x&&x>0\\
\alpha e^x-\alpha &&x\leq 0
\end{matrix}\right.$$

  經過該啟用函式後使得樣本分佈自動歸一化到0均值和單位方差(自歸一化,保證訓練過程中梯度不會爆炸或消失,效果比Batch Normalization 要好)

  其實就是ELU乘了個$\alpha$,關鍵在於這個$\alpha$是大於1的。以前relu,prelu,elu這些啟用函式,都是在負半軸坡度平緩,這樣在啟用函式的方差過大的時候可以讓它減小,防止了梯度爆炸,但是正半軸坡度簡單的設成了1。而selu的正半軸大於1,在方差過小的的時候可以讓它增大,同時防止了梯度消失。這樣啟用函式就有一個不動點,網路深了以後每一層的輸出都是均值為0方差為1。

def selu(x):
    alpha = 1.6732632423543772848170429916717
    scale = 1.0507009873554804934193349852946
    return scale*np.where(x>=0.0, x, alpha*(np.exp(x)-1))
View Code

 

其中超參 α 和 λ 的值是 證明得到 的(而非訓練學習得到):

α = 1.6732632423543772848170429916717
λ = 1.0507009873554804934193349852946

即:

  • 不存在死區
  • 存在飽和區(負無窮時, 趨於 - αλ
  • 輸入大於零時,啟用輸出對輸入進行了放大

如何選擇合適的啟用函式

這個問題目前沒有確定的方法,憑一些經驗吧。

1)深度學習往往需要大量時間來處理大量資料,模型的收斂速度是尤為重要的。所以,總體上來講,訓練深度學習網路儘量使用zero-centered資料 (可以經過資料預處理實現) 和zero-centered輸出。所以要儘量選擇輸出具有zero-centered特點的啟用函式以加快模型的收斂速度。

2)如果使用 ReLU,那麼一定要小心設定 learning rate,而且要注意不要讓網路出現很多 “dead” 神經元,如果這個問題不好解決,那麼可以試試 Leaky ReLU、PReLU。

3)最好不要用 sigmoid,你可以試試 tanh,不過可以預期它的效果會比不上 ReLU 和 Maxout.

最後來一張全家照

import math
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
plt.rcParams['font.sans-serif']=['SimHei']  # 指定預設字型
plt.rcParams['axes.unicode_minus']=False    # 用來正常顯示符號

def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))
def tanh(x):
    return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))
def relu(x):
    return np.where(x<0,0,x)
def prelu(x,a):
    return np.where(x<0,a*x,x)
def elu(x, a):
    return np.where(x < 0, a*(np.exp(x)-1), a*x)
def selu(x):
    alpha = 1.6732632423543772848170429916717
    scale = 1.0507009873554804934193349852946
    return scale*np.where(x>=0.0, x, alpha*(np.exp(x)-1))


fig = plt.figure(figsize=(6,4))
ax = fig.add_subplot(111)

x = np.linspace(-10, 10)
y_sigmoid = sigmoid(x)
y_tanh = tanh(x)
y_relu = relu(x)
y_LeakyReLU = prelu(x, 0.05)
y_elu = elu(x, 0.25)
y_selu = selu(x)

plt.xlim(-11,11)
plt.ylim(-1.1,1.1)


ax.spines['top'].set_color('none')
ax.spines['right'].set_color('none')

ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.set_xticks([-10,-5,0,5,10])
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))
ax.set_yticks([-1,-0.5,0.5,1])


plt.plot(x,y_sigmoid,label="Sigmoid",color = "blue")    # 藍色
plt.plot(2*x,y_tanh,label="tanh", color = "red")        # 紅色
plt.plot(2*x,y_relu,label="relu", color = "c")     # 青色
plt.plot(2*x,y_LeakyReLU, '-.', label="LeakyReLU", color = "Violet")    # 紫色
plt.plot(2*x,y_elu, ":", label="elu", color = "green")        # 綠色
plt.plot(2*x,y_selu, "--", label="selu", color = "k")      # 黑色

plt.legend()
plt.show()
View Code

參考文獻

hn_ma的CSDN部落格

SELU論文地址:【Self-Normalizing Neural Networks】.

StevenSun2014的CSDN部落格:常用啟用函式總結