深度學習中的激活函數

  衆所周知神經網絡單元是由線性單元和非線性單元組成的,而非線性單元就是咱們今天要介紹的--激活函數,不一樣的激活函數得出的結果也是不一樣的。他們也各有各的優缺點,雖然激活函數有本身的發展歷史,不斷的優化,可是如何在衆多激活函數中作出選擇依然要看咱們所實現深度學習實驗的效果。算法

  這篇博客會分爲上下兩篇,上篇介紹一些經常使用的激活函數(Sigmoid、tanh、ReLU、LeakyReLU、maxout)。下篇介紹一下不經常使用的激活函數(PRelu、ELU、SELU)。網絡

sigmoid

sigmoid激活函數將輸入映射到(0,1)之間,他的數學函數爲:ide

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

歷史上sigmoid很是經常使用,可是因爲他的兩個缺點,實際不多用了,如今看到sigmoid激活函數,都是在新手教程中作一些簡單的實驗。學習

優勢

  它可以把輸入的連續實值變換爲0和1之間的輸出,特別的,若是是很是大的負數,那麼輸出就是0;若是是很是大的正數,輸出就是1.測試

缺點

一、梯度消失字體

 咱們先看sigmoid激活函數的導數圖像,優化

  從圖中咱們能夠看出,當$\sigma (x)$中的$x$較大或者較小時,導數接近0,然後向傳遞的數學依據是微積分求導的鏈式法則,當前層的導數須要以前各層導數的乘積,幾個小數的相乘,結果會很接近0。Sigmoid導數的最大值是0.25,這意味着導數在每一層至少會被壓縮爲原來的1/4,經過兩層後被變爲1/16,…,經過10層後爲1/1048576。這種狀況就是梯度消失。梯度一旦消失,參數不能沿着loss下降的方向優化,spa

二、不是原點中心對稱.net

  經過Sigmoid函數咱們能夠知道,Sigmoid的輸出值恆大於0,輸出不是0均值(既zero-centerde),這會致使後一層的神經元將獲得上一層輸出的非均值的輸入。

  舉例來說$\sigma (\sum_i w_ix_i+b)$,若是$x_i$恆大於0,那麼對其$w_i$的導數老是正數或老是負數,向傳播的過程當中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)解決了梯度消失問題。圖像數據是在(0~255)之間,即使歸一化處理值也大於0,可是音頻數據有正有負,不適合relu函數

缺點:

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

死掉:一個很是大的梯度通過一個 ReLU 神經元,更新過參數以後,這個神經元不再會被任何數據激活相應的權重永遠不會更新。有兩種緣由致使這種狀況:一、很是不幸的初始化。二、學習率設置的過高致使在訓練過程當中參數更新太大,解決方法是使用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。

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

即:

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

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博客:經常使用激活函數總結

相關文章
相關標籤/搜索