談到激活函數,首先我們應該明白基本的概念,什麼是神經網絡算法?爲什麼需要激活函數?在周志華老師的西瓜書中“神經網絡是由具有適應性的簡單單元組成的廣泛並行互聯的網絡”,如下圖可以抽象爲一個神經網絡的層, 神經元收到多個其他神經元傳來的信號,通過激活函數進行激活輸出
如果沒有激活函數f,那麼y接手到的結果仍然是線性結果,無法接收到非線性的信息,這對特徵提取是非常不利的。接下來通過一個實例來講述這件事 ,對於輸入y = wx+b 假w=3 ,b=1 則y1= 3x+1,後續神經元爲y2=2x+2兩個神經元獨立都是線性函數,如果將y1當參數傳遞給y2 則
**所以說簡單的堆疊網絡層,而不經過非線性激活函數激活,並不能學習到新的特徵學到的仍然是線性關係。
# -*- coding: utf-8 -*-
"""
reference the code from 墨氳
"""
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
x = np.arange(-10,10, step=1)
def non_activation_function_model(x):
y_1 = x * 3 + 1
y_2 = y_1 * 2 + 2
print(y_2)
return y_2
def activation_function_model(x):
y_1 = x * 3 + 1
y_relu =np.where( y_1 > 0, y_1, 0)
# print(y_relu)
y_2 = y_relu * 2 + 1
print(y_2)
return y_2
y_non = non_activation_function_model(x)
y_ = activation_function_model(x)
plt.plot(x, y_non, label='non_activation_function')
plt.plot(x, y_, label='activation_function')
plt.legend()
plt.show()
輸出:
從實例中可以知道經過激活函數的神經元會學習到非線性的結果數據,這也是多維特徵提取的基礎。
ReLU
Relu 是比較常見的激活函數,f=max(0,x),y' = 0?x<0:1
# -*- coding: utf-8 -*-
"""
actor:haiqiang
"""
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
x = np.arange(-10,10, step=1)
def ReLU(x):
return np.where(x>0,x,0)
def dao_ReLU(x):
return np.where(x>0,1,0)
print type(x)
relu = ReLU(x)
drelu = dao_ReLU(x)
plt.plot(x, relu, label='ReLU')
plt.plot(x, drelu, label='dao_ReLU')
plt.legend()
plt.show()
輸出:
優點: 這部分內容引用
- 收斂速度明顯快於sigmoid和tanh
- 不存在梯度消失問題
- 計算複雜度低,不需要指數運算
缺點:
- Relu輸出的均值非0
- 存在神經元壞死現象(Dead ReLU Problem),某些神經元可能永遠不會被激活,導致相應參數永遠不會被更新(在負數部分,梯度爲0)
- 產生這種現象的原因 有兩個:
- 參數初始化問題: 採用Xavier初始化方法,使的輸入值和輸出值的方差一致
- 學習率learning_rate太大,導致參數更新變化太大,很容易使得輸出值從正輸出變成負輸出。:設置較小的learning_rate,或者使用學習率自動調節的優化算法
- relu不會對數據做規範化(壓縮)處理,使得數據的幅度隨模型層數的增加而不斷增大
Pytorch 爲我們封裝好了一個函數,可以直接調用
layer = nn.ReLU(inplace=True)
x = layer.forward(x)
一般會將inplace設置爲True,代表輸入和輸出的x公用一片內存單元。源碼參考
Sigmoid
常見的激活函數除了ReLU還有Sigmod,這個相對複雜一些。
導數是dσ(x)/dx=σ(x)(1−σ(x))
#代碼參考墨氳
import numpy as np
import matplotlib.pyplot as plt
def sigma(x):
return 1 / (1 + np.exp(-x))
def sigma_diff(x):
return sigma(x) * (1 - sigma(x))
x = np.arange(-6, 6, step=0.5)
y_sigma = sigma(x)
y_sigma_diff = sigma_diff(x)
axes = plt.subplot(111)
axes.plot(x, y_sigma, label='sigma')
axes.plot(x, y_sigma_diff, label='sigma_diff')
axes.spines['bottom'].set_position(('data',0))
axes.spines['left'].set_position(('data',0))
axes.legend()
plt.show()
輸出
由圖像我們可以看出當輸入的值小於-4或者>4的時候,sigmoid函數的導數趨緊0,也就是權值很久得不到更新,我們可以使用BatchNorm 來把輸入值限定在一定區間內,避免梯度消失 .
優點:
- 是便於求導的平滑函數;
- 能壓縮數據,保證數據幅度不會趨於+∞或−∞
- +∞或−∞
缺點:
- 容易出現梯度消失(gradient vanishing)的現象:當激活函數接近飽和區時,變化太緩慢,導數接近0,根據後向傳遞的數學依據是微積分求導的鏈式法則,當前導數需要之前各層導數的乘積,幾個比較小的數相乘,導數結果很接近0,從而無法完成深層網絡的訓練。
- Sigmoid的輸出均值不是0(zero-centered)的:這會導致後層的神經元的輸入是非0均值的信號,這會對梯度產生影響。以 f=sigmoid(wx+b)爲例, 假設輸入均爲正數(或負數),那麼對w的導數總是正數(或負數),這樣在反向傳播過程中要麼都往正方向更新,要麼都往負方向更新,使得收斂緩慢。
- 指數運算相對耗時
Pytorch中調用函數接口:torch.sigmoid
(input, out=None) → Tensor 源碼參考
堅持一件事或許很難,但堅持下來一定很酷!^_^