【白話深度學習】輕鬆瞭解幾種常用的激活函數

 

如圖所示,表示神經元的O中明確顯示了激活函數的計算過程,即信號的加權總和爲節點a,然後節點a被激活函數h()轉換成節點y。簡單的說,我們可以將上圖用以下兩個函數表示:

  1. a = b + w_{1}x_{1} + w_{2}x_{2}
  2. y = h(a)

表示先計算輸入信號的加權總和,然後用激活函數轉換這一總和。因此,登場的h(a)函數會將輸入信號的總和轉換爲輸出信號,這種函數一般稱爲激活函數(activation function)。

 

二、神經網絡的激活函數爲什麼必須使用非線性函數

換句話說,激活函數不能使用線性函數。爲什麼不能使用線性函數呢?

因爲使用線性函數的話,加深神經網絡的層數就沒有意義了。 線性函數的問題在於,不管如何加深層數,總是存在與之等效的“無隱藏層的神經網絡”。爲了具體地(稍微直觀地)理解這一點,我們來思考下面這個簡單的例子。

這裏我們考慮把線性函數h(x) = cx作爲激活函數,把y(x) = h(h(h(x)))的運算對應3層神經網絡A。這個運算會進行y(x) = c × c × c × x 的乘法運算,但是同樣的處理可以由y(x) = ax(注意,a = c^{^{3}})這一次乘法運算(即沒有隱藏層的神經網絡)來表示。如本例所示, 使用線性函數時,無法發揮多層網絡帶來的優勢。因此,爲了發揮疊加層所帶來的優勢,激活函數必須使用非線性函數。

如果使用非線性函數的話,激活函數給神經元引入了非線性因素,使得神經網絡可以任意逼近任何非線性函數,這樣神經網絡就可以應用到衆多的非線性模型中。

 

三、幾種激活函數

3.1 階躍函數

公式:

h(x) = \left\{\begin{matrix} 0 \quad (x\leqslant 0)\\ 1\quad (x> 0) \end{matrix}\right.

1. 階躍函數的實現

當輸入超過0時,輸出1, 否則輸出0。可以像下面這樣簡單地實現階躍函數。

def step_function(x):
    if x > 0:
        return 1
    else:
        return 0

這個實現簡單、易於理解,但是參數x只能接受實數(浮點數)。也就是說,允許形如step_function(3.0)的調用,但不允許參數取NumPy數組,例如step_function(np.array([1.0, 2.0]))。爲了便於後面的操作,因此我們把它修改爲支持NumPy數組的實現。

def step_function(x):
    y = x > 0
    return y.astype(np.int)

上述函數的內容只有兩行。由於使用了NumPy中的“技巧”,可能會有點難理解。因此將用幾行代碼讓你更好地理解這種“技巧”。

import numpy as np
x = np.array([-1.0, 1.0, 2.0])
print(x)   # [-1., 1., 2.]
y = x > 0
print(y)   # [False, True, True]
Y = y.astype(np.int)
print(Y)   # [0, 1, 1]
[-1.  1.  2.]
[False  True  True]
[0 1 1]

其中astype()方法通過參數指定期望的類型,這個例子中是np.int型。Python中將布爾型轉換爲int型後,True會轉換爲1,False會轉換爲0。以上就是階躍函數的實現中所用到的NumPy的“技巧”。

2. 階躍函數的圖形

# 導包
import numpy as np
import matplotlib.pylab as plt

# 定義階躍函數
def step_function(x):
    return np.array(x > 0, dtype=np.int)

# 模擬數據
x = np.arange(-5.0, 5.0, 0.1)
y = step_function(x)

# 繪圖
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # 指定y軸的範圍
plt.show()

階躍函數以0爲界,輸出從0切換爲1(或者從1切換爲0)。它的值呈階梯式變化,所以稱爲階躍函數。

3.2 sigmoid函數

公式:

h(x) = \frac{1}{1 + e^{-x}}

1. sigmoid函數的實現

下面,用Python可以像下面代碼表示的sigmoid函數。

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

如果在這個sigmoid函數中輸入一個NumPy數組,結果會如何呢?

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

x = np.array([-1.0, 1.0, 2.0])
print(sigmoid(x))
[0.26894142 0.73105858 0.88079708]

之所以sigmoid函數的實現能支持NumPy數組,祕密就在於NumPy的廣播功能。根據NumPy 的廣播功能,如果在標量和NumPy數組 之間進行運算,則標量會和NumPy數組的各個元素進行運算。

2. sigmoid函數的圖形

# 導包
import numpy as np
import matplotlib.pylab as plt

# 定義sigmoid函數
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

# 模擬數據
x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)

# 繪圖
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # 指定y軸的範圍
plt.show()

縱軸的取值範圍(0, 1)。

3.3 ReLU函數

公式:

h(x) = \left\{\begin{matrix} x \quad (x > 0)\\ 0\quad (x\leqslant 0) \end{matrix}\right.

1. ReLu函數的實現

ReLU(Rectified Linear Unit)函數在輸入大於0時,直接輸出該值;在輸入小於等於0時,輸出0。因此,ReLU函數的實現也很簡單,可以寫成如下形式。

def relu(x):
    return np.maximum(0, x)

這裏使用了NumPy的maximum函數。maximum函數會從輸入的數值中選擇較大的那個值進行輸出。

2. ReLu函數的圖形

# 導包
import numpy as np
import matplotlib.pylab as plt

# 定義relu函數
def relu(x):
    return np.maximum(0, x)

# 模擬數據
x = np.arange(-5.0, 5.0, 0.1)
y = relu(x)

# 繪圖
plt.plot(x, y)
plt.ylim(-1, 5) # 指定y軸的範圍
plt.show()

3.4 softmax函數

公式:

y^{_{k}} = \frac{e^{a_{k}}}{\sum_{i=1}^{n}e^{a_{i}}}

softmax函數的分子是輸入信號a_{k}的指數函數,分母是所有輸入信號的指數函數的和。

1. softmax函數的實現

用圖表示softmax函數的話,如圖所示,softmax函數的輸出通過箭頭與所有的輸入信號相連。輸出層的各個神經元都受到所有輸入信號的影響。

下面,用Python可以像下面代碼表示的softmax函數。

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    
    return y

2. 實現softmax函數時的注意事項

上面的softmax函數的實現雖然正確描述了公式,但在計算機的運算上有一定的缺陷。這個缺陷就是溢出問題。softmax函數的實現中要進行指數函數的運算,但是此時指數函數的值很容易變得非常大。比如,e^{10}的值 會超過20000,e^{100}會變成一個後面有40多個0的超大值,e^{1000}的結果會返回一個表示無窮大的inf。如果在這些超大值之間進行除法運算,結果會出現“不確定”的情況。

softmax函數的實現可以像下式這樣進行改進。

y^{_{k}} = \frac{e^{a_{k}}}{\sum_{i=1}^{n}e^{a_{i}}}= \frac{Ce^{a_{k}}}{C \sum_{i=1}^{n}e^{a_{i}}} = \frac{e^{a_{k}+logC}}{\sum_{i=1}^{n}e^{a_{i}+logC}} = \frac{e^{a_{k}+C'}}{\sum_{i=1}^{n}e^{a_{i}+C'}}

 

這裏的C'可以使用任何值,但是爲了防止溢出,一般會使用輸入信號中的最大值。我們來看一個具體的例子。

 
a = np.array([1010, 1000, 990])
print(np.exp(a) / np.sum(np.exp(a))) # softmax函數的運算

c = np.max(a) # 1010
print(a - c)
print(np.exp(a - c) / np.sum(np.exp(a - c)))
[nan nan nan]
[  0 -10 -20]
[9.99954600e-01 4.53978686e-05 2.06106005e-09]

我們發現原本爲nan(not a number,不確定)的地方,現在被正確計算了。綜上,我們可以像下面這樣實現softmax函數。

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c) # 溢出對策
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

3. softmax函數的特徵

使用softmax()函數,可以按如下方式計算神經網絡的輸出。

import numpy as np

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c) # 溢出對策
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

a = np.array([0.3, 2.9, 4.0])
y = softmax(a)

print(y)
print(np.sum(y))
[0.01821127 0.24519181 0.73659691]
1.0

如上所示,softmax函數的輸出是0.0到1.0之間的實數。並且softmax函數的輸出值的總和是1。輸出總和爲1是softmax函數的一個重要性質。

正因爲有了這個性質,我們纔可以把softmax函數的輸出解釋爲“概率”。 比如,上面的例子可以解釋成y[0]的概率是0.018(1.8%),y[1]的概率是0.245(24.5%),y[2]的概率是0.737(73.7%)。從概率的結果來看,可以說“因爲第2個元素的概率最高,所以答案是第2個類別”。而且,還可以回答“有74%的概率是第2個類別,有25%的概率是第1個類別,有1%的概率是第0個類別”。也就是說,通過使用softmax函數,我們可以用概率的(統計的)方法處理問題。

 
 
 
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章