衆所周知神經網絡單元是由線性單元和非線性單元組成的,一般神經網絡的計算時線性的,而非線性單元就是我們今天要介紹的--激活函數,不同的激活函數得出的結果也是不同的。他們也各有各的優缺點,雖然激活函數有自己的發展曆史,不斷的優化,但是如何在衆多激活函數中做出選擇依然要看我們所實作深度學習實驗的效果。
這篇部落格會介紹一些常用的激活函數:Sigmoid、tanh、ReLU、LeakyReLU、maxout。以及一些較為冷門的激活函數:PRelu、ELU、SELU
sigmoid
sigmoid激活函數将輸入映射到(0,1)之間,他的數學函數為:
$$\sigma (z)=\frac{1}{1+e^{-z}}$$
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-x))
View Code
曆史上sigmoid非常常用,但是由于他的兩個缺點,實際很少用了,現在看到sigmoid激活函數,都是在新手教程中做一些簡單的實驗。
優點
- 它能夠把輸入的連續實值變換為0和1之間的輸出,适合做機率值的處理。
- 如果是非常大的負數,那麼輸出就是0
- 如果是非常大的正數,輸出就是1
缺點
1、梯度消失
我們從上圖可以看出,當x較大或者較小時,sigmoid輸出趨近0或1,導數接近0,而後向傳遞的數學依據是微積分求導的鍊式法則,目前層的導數需要之前各層導數的乘積,幾個小數的相乘,結果會很接近0。Sigmoid導數的最大值是0.25,這意味着導數在每一層至少會被壓縮為原來的1/4,通過兩層後被變為1/16,…,通過10層後為1/1048576。這種情況就是梯度消失。梯度一旦消失,參數不能沿着loss降低的方向優化,
2、不是以零為中心
通過Sigmoid函數我們可以知道,Sigmoid的輸出值恒大于0,輸出不是0均值(既zero-centerde),這會導緻後一層的神經元将得到上一層輸出的非均值的輸入。
舉例來講$\sigma (\sum_i w_ix_i+b)$,如果$x_i$恒大于0,那麼對其$w_i$的導數總是正數或總是負數,向傳播的過程中w要麼都往正方向更新,要麼都往負方向更新,導緻有一種捆綁的效果,使得收斂緩慢。且可能導緻陷入局部最小值。當然了,如果按batch去訓練,那麼那個batch可能得到不同的信号,是以這個問題還是可以緩解一下的。
3、運算量大:
解析式中含有幂運算,計算機求解時相對來講比較耗時。對于規模比較大的深度網絡,這會較大地增加訓練時間。
tanh
Tanh 激活函數又叫作雙曲正切激活函數(hyperbolic tangent activation function)。與 Sigmoid 函數類似,但 Tanh 函數将其壓縮至-1 到 1 的區間内,輸出是zero-centered的(零為中心),在實踐中,Tanh 函數的使用優先性高于 Sigmoid 函數。負數輸入被當作負值,零輸入值的映射接近零,正數輸入被當作正值。
數學函數為:
$$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
優點
- sigmoid的優點他都有,另外 tanh的輸出是zero-centered,以0為中心
缺點
1、特殊情況存在梯度消失問題
當輸入值過大或者過小,提取趨近于0,失去敏感性,處于飽和狀态。
ReLU
這才是一個目前主流論文中非常常用的激活函數,它的數學公式為:
$$f(x)=max(0,x)$$
def relu(x):
return np.where(x<0,0,x)
View Code
優點
- ReLU的計算量小,收斂速度很快,因為sigmoid和tanh,ReLU有指數運算
- 在正區間(x>0)解決了梯度消失問題。圖像資料是在(0~255)之間,即便歸一化處理值也大于0,但是音頻資料有正有負,不适合relu函數
缺點:
- ReLU的輸出不是zero-centered
- RuLU在訓練的時候很容易導緻神經元“死掉”
死掉:一個非常大的梯度經過一個 ReLU 神經元,更新過參數之後,這個神經元再也不會被任何資料激活相應的權重永遠不會更新。有兩種原因導緻這種情況:1、非常不幸的初始化。2、學習率設定的太高導緻在訓練過程中參數更新太大,解決方法是使用Xavier初始化方法,合理設定學習率,會降低這種情況的發生機率。或使用Adam等自動調節學習率的算法。
補充:ReLU相比sigmoid和tanh的一個缺點是沒有對上界設限,在實際使用中,可以設定一個上限,如ReLU6經驗函數: f(x)=min(6,max(0,x))
LeakyReLU
Leaky ReLU(洩露型線性整流函數),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。
PReLU
Parameterised ReLU(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
Exponential Linear Unit(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
即:
- 不存在死區
- 存在飽和區(負無窮時, 趨于 -
)αλ
- 輸入大于零時,激活輸出對輸入進行了放大
Swish
Swish 激活函數,該函數又叫作自門控激活函數,它近期由谷歌的研究者釋出,數學公式為:
$$\sigma (x)=\frac{x}{1+e^{-x}}$$
根據論文(https://arxiv.org/abs/1710.05941v1),Swish 激活函數的性能優于 ReLU 函數。
根據上圖,我們可以觀察到在 x 軸的負區域曲線的形狀與 ReLU 激活函數不同,是以,Swish 激活函數的輸出可能下降,即使在輸入值增大的情況下。大多數激活函數是單調的,即輸入值增大的情況下,輸出值不可能下降。而 Swish 函數為 0 時具備單側有界(one-sided boundedness)的特性,它是平滑、非單調的。更改一行代碼再來檢視它的性能,似乎也挺有意思。
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}$$
如何選擇合适的激活函數
這個問題目前沒有确定的方法,憑一些經驗吧。
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部落格:常用激活函數總結
26種神經網絡激活函數可視化(留着以後看,原文更加精彩)