深度學習中的激活函數


本節內容比較簡單,通過python的matplotlib模塊畫出深度學習中常用的激活函數

import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['axes.labelsize'] = 14
plt.rcParams['xtick.labelsize'] = 12
plt.rcParams['ytick.labelsize'] = 12
z = np.linspace(-5,5,200)

sigmoid###

首先是sigmoid相信大家都不陌生,大家學習邏輯回歸和神經網絡的時候經常遇到。

def logit(z):
    return 1 / (1 + np.exp(-z))
plt.plot([-5,5],[0,0],'k-')
plt.plot([-5,5],[1,1],'k--')
plt.plot([0, 0], [-0.2, 1.2], 'k-')
plt.plot(z, logit(z), "b-",linewidth=2)
props = dict(facecolor='black', shrink=0.1)
plt.annotate('Saturating', xytext=(3.5, 0.7), xy=(5, 1), arrowprops=props, fontsize=14, ha="center")
plt.annotate('Saturating', xytext=(-3.5, 0.3), xy=(-5, 0), arrowprops=props, fontsize=14, ha="center")
plt.annotate('Linear', xytext=(2, 0.2), xy=(0, 0.5), arrowprops=props, fontsize=14, ha="center")
plt.axis([-5, 5, -0.2, 1.2])
plt.title("Sigmoid activation function", fontsize=14)
plt.grid(True)
plt.show()

效果:

從上面的圖可以看出,當輸入的值比較大或者比較小的時候值會保持在0和1,常被稱為saturates 。當對這些位置進行求導的時候,值接近於0。再進行BP對的時候,回傳的信號會越來越弱,這種情況被稱作 梯度消失(vanishing gradients)。

tanh###

plt.plot([-5,5],[0,0],'k-')#
plt.plot([-5,5],[1,1],'k--')
plt.plot([-5,5],[-1,-1],'k--')#(-5,-1),(5,-1)
plt.plot([0, 0], [-2, 2], 'k-')
plt.plot(z, np.tanh(z), "b-",linewidth=2)
plt.axis([-5, 5, -2, 2])
plt.title("Tanh activation function", fontsize=14)
plt.grid(True)
plt.show()

從圖中可以看出,tanh和sigmod非常類似,只是取值范圍為[-1,1],因此tanh也面臨這梯度消失的問題。

ReLU和LeakyReLU###

選擇一個好的激活函數就能夠很大程度上避免梯度消失問題,例如Relu,但這種方法會面臨一個問題,在訓練過程中部分神經元掛掉了,也就是這些神經元只會輸出0,特別是當學習率比較大的時候,一旦出現這種情況,這些神經元就不會再復活。為了解決這一問題,有一種方法被提出,叫做LeakyReLU,公式很簡單\(LeakyReLU_a(z)=max(az,z)\)

def leaky_relu(z, alpha=0.01):
    return np.maximum(alpha*z, z)
plt.plot(z, leaky_relu(z, 0.05), "b-", linewidth=2)
plt.plot([-5, 5], [0, 0], 'k-')
plt.plot([0, 0], [-0.5, 4.2], 'k-')
plt.grid(True)
props = dict(facecolor='black', shrink=0.1)
plt.annotate('Leak', xytext=(-3.5, 0.5), xy=(-5, -0.2), arrowprops=props, fontsize=14, ha="center")
plt.title("Leaky ReLU activation function", fontsize=14)
plt.axis([-5, 5, -0.5, 4.2])
plt.show()


上圖激活函數有一個參數\(a\),控制着leaks的數量,斜率非常的小,但是能夠保證神經元不會掛掉,相關研究發現,LeakyReLu的效果要優於ReLU。常用的LeakyReLU變體有

  • 1.Randomized leaky ReLU(RReLU):在訓練過程中\(a\)是從一組選項中隨機選擇,在測試過程中使用它們的平均值。
  • 2.Parametric leaky ReLU(PReLU):\(a\)是從訓練集中學習得到,在較大的數據集上面效果較好,在較小的數據集上面有overfitting的風險。

ELU###

在2015年一篇名為《Fast and Accurate Deep Network Learning by Exponential Linear Units》,提出了一種新的激活函數ELU,不過和之前的ReLU相似,公式如下

def elu(z,alpha=1):
    return np.where(z<0,alpha*(np.exp(z) - 1),z)
plt.plot(z, elu(z), "b-", linewidth=2)
plt.plot([-5, 5], [0, 0], 'k-')#(x)
plt.plot([0, 0], [-2, 5], 'k-')#(y)
plt.plot([-5,5],[-1,-1],'k--') #(虛線)
plt.show()

ELU被提出,必定存在它的優越之處:

  • 1.在z<0時候 能夠讓神經元的輸出均值接近0,\(a\)控制着在z<0時,激活函數的趨勢,
  • 2.整個函數處處可導,當z<0時,梯度的值不為0,避免了神經元掛掉的問題

SELU###

計算及技術發展日新月異,一項牛逼的技術剛出來還沒火幾天,就可能會有一個更牛逼的出來,下面有請scaled exponential linear units,selu,滿篇幅的公式。

def selu(z,
         scale=1.0507009873554804934193349852946,
         alpha=1.6732632423543772848170429916717):
    return scale * elu(z, alpha)

這個公式的牛逼之處在於在100層的神經網絡訓練過程中,能夠保證均值接近0,方差為1。能夠有效的避免梯度消失問題。這個激活函數和Batch Normalization目的類似,都是避免神經元的輸出值發生飄移。下面模擬一個100層的網絡,

np.random.seed(42)
Z = np.random.normal(size=(500, 100))#模擬的輸入x
for layer in range(100):
    W = np.random.normal(size=(100, 100), scale=np.sqrt(1/100))
    Z = selu(np.dot(Z, W))
    means = np.mean(Z, axis=1)
    stds = np.std(Z, axis=1)
    if layer % 10 == 0:
        print("Layer {}: {:.2f} < mean < {:.2f}, {:.2f} < std deviation < {:.2f}".format(
            layer, means.min(), means.max(), stds.min(), stds.max()))

輸出,讀者可以將上面的激活函數換成其他的看一下效果

Layer 0: -0.26 < mean < 0.27, 0.74 < std deviation < 1.27
Layer 10: -0.24 < mean < 0.27, 0.74 < std deviation < 1.27
Layer 20: -0.17 < mean < 0.18, 0.74 < std deviation < 1.24
Layer 30: -0.27 < mean < 0.24, 0.78 < std deviation < 1.20
Layer 40: -0.38 < mean < 0.39, 0.74 < std deviation < 1.25
Layer 50: -0.27 < mean < 0.31, 0.73 < std deviation < 1.27
Layer 60: -0.26 < mean < 0.43, 0.74 < std deviation < 1.35
Layer 70: -0.19 < mean < 0.21, 0.75 < std deviation < 1.21
Layer 80: -0.18 < mean < 0.16, 0.72 < std deviation < 1.19
Layer 90: -0.19 < mean < 0.16, 0.75 < std deviation < 1.20

TF實現SELU的方法

def selu(z,
         scale=1.0507009873554804934193349852946,
         alpha=1.6732632423543772848170429916717):
    return scale * tf.where(z >= 0.0, z, alpha * tf.nn.elu(z))

總結###

一下子講了這么多的激活函數,那么到底該怎么選擇呢?一般來說的選擇順序為

ELU(SELU) > leaky ReLU(變體) > ReLU > tanh > logistic 

如果想要快,那就選用RELU,如果不想調節參數那就使用默認的參數(0.01 for Leaky ReLU,1 for ELU),如果時間和計算力充足,那就RRELU或PReLU來個cross-validation。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM