神經網路初始化

十六呀發表於2019-03-19

本文通過對《Understanding the difficulty of training deep feedforward neural networks》文章翻譯和解讀,和知乎、CSDN幾位博主的文章總結、分析深度網路初始化方法。

首先是《Understanding the difficulty of training deep feedforward neural networks》

敲黑板:這裡有一個發文章技巧,行不行有待驗證大家應該感覺到一般的深度學習文章上來就是實驗,告訴讀者這個實驗結果好,然後由實驗結果再反向給出一些無從驗證的可能對可能不對的原因。而這篇文章雖然整體來看比較簡單,但結構非常嚴謹:首先通過實驗分析標準初始化方法的問題;然後根據兩個目標——狀態方差和梯度方差保持不變推匯出引數的特點,給出Xavier初始化方法的具體形式;最後通過實驗驗證Xavier初始化的效果確實不錯。

總結髮文章方法:

  • 實驗
  • 告訴讀者實驗結果好
  • 由結果反正無從驗證的可能對與不對的原因,設立目標
  • 根據目標驗證猜想效果不錯

文章翻譯解讀

下面之說一下我認為的重點:

分析的前提:

    1. 網路在初始化處於線性條件下,即啟用活函式的導數為1;
    1. 初始化的權值的mean 為0,且獨立同分布的;
  • 3.輸入特徵 x 的 variance是相同的。經過一系列推導,得到了下面這樣的結果:
    第一:
    神經網路初始化
    第二:公式5有用哦:

神經網路初始化
第三:公式6有用;

神經網路初始化
推出這玩意來了以後呢, 下面是關鍵:

1.前向傳播:用文中的話說:From a forward-propagation point of view, to keep information flowing we would like that:推出這玩意來了以後呢, 下面是關鍵:

1.前向傳播:用文中的話說:From a forward-propagation point of view, to keep information flowing we would like that:

神經網路初始化
就是說,為了在前向傳播過程中,可以讓資訊向前傳播,做法就是讓:啟用單元的輸出值的方差持不變。為什麼要這樣呢??有點小不理解。。

  1. 反向傳播:在反向傳播過程中,也是為了讓梯度可以反向傳播,讓:對啟用單元輸入值的梯度 保持不變,即:
    神經網路初始化
    最後得到的結論就是:

神經網路初始化
上面兩個式子折衷一下,為:

神經網路初始化
所以呢,權值初始化時,服從這樣的分佈:
神經網路初始化
這個方法就叫做: normalized initialization.

在訓練過程中,梯度問題:

這時,我們就不能單純地用梯度的 variance 去分析了,因為已經不滿足我們的假設條件了啊。

文章後面的一大堆基本沒有什麼重點的東西了吧,我覺得。寫幾個覺得有必要的總結吧:

  1. softsign啟用函式與雙曲正切函式相比,效果還 很不錯的,

  2. normalized initialization 的方法很不錯。

初始化比較

  • 把w初始化為0
  • 對w隨機初始化
  • Xavier initialization
  • He initialization

1.把w初始化為0

我們線上性迴歸,logistics迴歸的時候,基本上都是把引數初始化為0,我們的模型也能夠很好的工作。然後在神經網路中,把w初始化為0是不可以的。這是因為如果把w初始化0,那麼每一層的神經元學到的東西都是一樣的(輸出是一樣的),而且在bp的時候,每一層內的神經元也是相同的,因為他們的gradient相同。下面用一段程式碼來演示,當把w初始化為0:

def initialize_parameters_zeros(layers_dims):
    """
    Arguments:
    layer_dims -- python array (list) containing the size of each layer.
    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
                    W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
                    b1 -- bias vector of shape (layers_dims[1], 1)
                    ...
                    WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
                    bL -- bias vector of shape (layers_dims[L], 1)
    """
    parameters = {}
    np.random.seed(3)
    L = len(layers_dims)  # number of layers in the network
    for l in range(1, L):
        parameters['W' + str(l)] = np.zeros((layers_dims[l], layers_dims[l - 1]))
        parameters['b' + str(l)] = np.zeros((layers_dims[l], 1))
    return parameters
複製程式碼

我們可以看看cost function是如何變化的:

神經網路初始化
能夠看到代價函式降到0.64(迭代1000次)後,再迭代已經不起什麼作用了。

2.對w隨機初始化

目前常用的就是隨機初始化,即W隨機初始化。隨機初始化的程式碼如下:

def initialize_parameters_random(layers_dims):
    """
    Arguments:
    layer_dims -- python array (list) containing the size of each layer.
    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
                    W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
                    b1 -- bias vector of shape (layers_dims[1], 1)
                    ...
                    WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
                    bL -- bias vector of shape (layers_dims[L], 1)
    """
    np.random.seed(3)  # This seed makes sure your "random" numbers will be the as ours
    parameters = {}
    L = len(layers_dims)  # integer representing the number of layers
    for l in range(1, L):
        parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1])*0.01
        parameters['b' + str(l)] = np.zeros((layers_dims[l], 1))
    return parameters

複製程式碼

乘0.01是因為要把W隨機初始化到一個相對較小的值,因為如果X很大的話,W又相對較大,會導致Z非常大,這樣如果啟用函式是sigmoid,就會導致sigmoid的輸出值1或者0,然後會導致一系列問題(比如cost function計算的時候,log裡是0,這樣會有點麻煩)。
隨機初始化後,cost function隨著迭代次數的變化示意圖為:

神經網路初始化
能夠看出,cost function的變化是比較正常的。但是隨機初始化也有缺點,np.random.randn()其實是一個均值為0,方差為1的高斯分佈中取樣。當神經網路的層數增多時,會發現越往後面的層的啟用函式(使用tanH)的輸出值幾乎都接近於0,如下圖所示:
神經網路初始化

import numpy as np
import matplotlib.pyplot as plt

def initialize_parameters(layer_dims):
    """
    :param layer_dims: list,每一層單元的個數(維度)
    :return:dictionary,儲存引數w1,w2,...,wL,b1,...,bL
    """
    np.random.seed(3)
    L = len(layer_dims)#the number of layers in the network
    parameters = {}
    for l in range(1,L):
        parameters["W" + str(l)] = np.random.randn(layer_dims[l],layer_dims[l-1])*0.01
        parameters["b" + str(l)] = np.zeros((layer_dims[l],1))
    return parameters

def forward_propagation():
    data = np.random.randn(1000, 100000)
    # layer_sizes = [100 - 10 * i for i in range(0,5)]
    layer_sizes = [1000,800,500,300,200,100,10]
    num_layers = len(layer_sizes)
    parameters = initialize_parameters(layer_sizes)
    A = data
    for l in range(1,num_layers):
        A_pre = A
        W = parameters["W" + str(l)]
        b = parameters["b" + str(l)]
        z = np.dot(W,A_pre) + b #計算z = wx + b
        A = np.tanh(z)
        #畫圖
        plt.subplot(2,3,l)
        plt.hist(A.flatten(),facecolor='g')
        plt.xlim([-1,1])
        plt.yticks([])
    plt.show()

複製程式碼

3.Xavier initialization Xavier initialization是 Glorot 等人為了解決隨機初始化的問題提出來的另一種初始化方法,他們的思想倒也簡單,就是儘可能的讓輸入和輸出服從相同的分佈,這樣就能夠避免後面層的啟用函式的輸出值趨向於0。他們的初始化方法為:

def initialize_parameters_he(layers_dims):
    """
    Arguments:
    layer_dims -- python array (list) containing the size of each layer.

    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
                    W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
                    b1 -- bias vector of shape (layers_dims[1], 1)
                    ...
                    WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
                    bL -- bias vector of shape (layers_dims[L], 1)
    """
    np.random.seed(3)
    parameters = {}
    L = len(layers_dims)  # integer representing the number of layers
    for l in range(1, L):
        parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) * np.sqrt(1 / layers_dims[l - 1])
        parameters['b' + str(l)] = np.zeros((layers_dims[l], 1))
    return parameters

複製程式碼

來看下Xavier initialization後每層的啟用函式輸出值的分佈:

神經網路初始化
能夠看出,深層的啟用函式輸出值還是非常漂亮的服從標準高斯分佈。雖然Xavier initialization能夠很好的 tanH 啟用函式,但是對於目前神經網路中最常用的ReLU啟用函式,還是無能能力,請看下圖:

神經網路初始化

4.He initialization

為了解決上面的問題,提出了一種針對ReLU的初始化方法,一般稱作 He initialization。初始化方式為:

def initialize_parameters_he(layers_dims):
    """
    Arguments:
    layer_dims -- python array (list) containing the size of each layer.

    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
                    W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
                    b1 -- bias vector of shape (layers_dims[1], 1)
                    ...
                    WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
                    bL -- bias vector of shape (layers_dims[L], 1)
    """
    np.random.seed(3)
    parameters = {}
    L = len(layers_dims)  # integer representing the number of layers
    for l in range(1, L):
        parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) * np.sqrt(2 / layers_dims[l - 1])
        parameters['b' + str(l)] = np.zeros((layers_dims[l], 1))
    return parameters

複製程式碼

來看看經過He initialization後,當隱藏層使用ReLU時,啟用函式的輸出值的分佈情況:

神經網路初始化

全文(廢話)總結:

1.啟用函式:
tanh、softsign好於sigmoid用

神經網路初始化
初始化 2.rule:
用He initialization初始化

感謝

參考文獻

  1. Xavier Glorot et al., Understanding the Difficult of Training Deep Feedforward Neural Networks
  2. Kaiming He et al., Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classfication
  3. Andrew ng coursera 《deep learning》課
  4. 夏飛 《聊一聊深度學習的weight initialization》
    5.blog.csdn.net/victoriaw/a…
    6.blog.csdn.net/u012328159/…

相關文章