[深度學習] 權重初始化--Weight Initialization

深度學習中的weight initialization對模型收斂速度和模型質量有重要影響!php



  • 在ReLU activation function中推薦使用Xavier Initialization的變種,暫且稱之爲He Initialization:
import numpy as np
W = np.random.randn(node_in, node_out) / np.sqrt(node_in / 2)
  • 使用Batch Normalization Layer能夠有效下降深度網絡對weight初始化的依賴:
import tensorflow as tf
# put this before nonlinear transformation
layer = tf.contrib.layers.batch_norm(layer, center=True, scale=True,
                                     is_training=True)

實驗代碼請參見個人Githubhtml

背景

深度學習模型訓練的過程本質是對weight(即參數 W)進行更新,這須要每一個參數有相應的初始值。有人可能會說:「參數初始化有什麼難點?直接將全部weight初始化爲0或者初始化爲隨機數!」 對一些簡單的機器學習模型,或當optimization function是convex function時,這些簡單的方法確實有效。然而對於深度學習而言,非線性函數被瘋狂疊加,產生如本文題圖所示的non-convex function,如何選擇參數初始值便成爲一個值得探討的問題 --- 其本質是初始參數的選擇應使得objective function便於被優化。事實上,在學術界這也是一個被actively研究的領域。node

TLDR裏已經涵蓋了本文的核心要點,下面在正文中,咱們來深刻了解一下來龍去脈。python

初始化爲0的可行性?

答案是不可行。 這是一道送分題 哈哈!爲何將全部W初始化爲0是錯誤的呢?是由於若是全部的參數都是0,那麼全部神經元的輸出都將是相同的,那在back propagation的時候同一層內全部神經元的行爲也是相同的 --- gradient相同,weight update也相同。這顯然是一個不可接受的結果。git

可行的幾種初始化方式

  • pre-training

pre-training是早期訓練神經網絡的有效初始化方法,一個便於理解的例子是先使用greedy layerwise auto-encoder作unsupervised pre-training,而後再作fine-tuning。具體過程能夠參見UFLDL的一個tutorial,由於這不是本文重點,就在這裏簡略的說一下:(1)pre-training階段,將神經網絡中的每一層取出,構造一個auto-encoder作訓練,使得輸入層和輸出層保持一致。在這一過程當中,參數得以更新,造成初始值(2)fine-tuning階段,將pre-train過的每一層放回神經網絡,利用pre-train階段獲得的參數初始值和訓練數據對模型進行總體調整。在這一過程當中,參數進一步被更新,造成最終模型。github

隨着數據量的增長以及activation function (參見個人另外一篇文章) 的發展,pre-training的概念已經漸漸發生變化。目前,從零開始訓練神經網絡時咱們也不多采用auto-encoder進行pre-training,而是直奔主題作模型訓練。不想從零開始訓練神經網絡時,咱們每每選擇一個已經訓練好的在任務A上的模型(稱爲pre-trained model),將其放在任務B上作模型調整(稱爲fine-tuning)。算法

  • random initialization

隨機初始化是不少人目前常用的方法,然而這是有弊端的,一旦隨機分佈選擇不當,就會致使網絡優化陷入困境。下面舉幾個例子。網絡

核心代碼見下方,完整代碼請參見個人Githubapp

data = tf.constant(np.random.randn(2000, 800))
layer_sizes = [800 - 50 * i for i in range(0,10)]
num_layers = len(layer_sizes)

fcs = []  # To store fully connected layers' output
for i in range(0, num_layers - 1):
    X = data if i == 0 else fcs[i - 1]
    node_in = layer_sizes[i]
    node_out = layer_sizes[i + 1]
    W = tf.Variable(np.random.randn(node_in, node_out)) * 0.01 
    fc = tf.matmul(X, W)
    fc = tf.nn.tanh(fc)
    fcs.append(fc)

這裏咱們建立了一個10層的神經網絡,非線性變換爲tanh,每一層的參數都是隨機正態分佈,均值爲0,標準差爲0.01。下圖給出了每一層輸出值分佈的直方圖。dom



隨着層數的增長,咱們看到輸出值迅速向0靠攏,在後幾層中,幾乎全部的輸出值x都很接近0!回憶優化神經網絡的back propagation算法,根據鏈式法則,gradient等於當前函數的gradient乘之後一層的gradient,這意味着輸出值x是計算gradient中的乘法因子,直接致使gradient很小,使得參數難以被更新!

讓咱們將初始值調大一些:

W = tf.Variable(np.random.randn(node_in, node_out))

均值仍然爲0,標準差如今變爲1,下圖是每一層輸出值分佈的直方圖:



幾乎全部的值集中在-1或1附近,神經元saturated了!注意到tanh在-1和1附近的gradient都接近0,這一樣致使了gradient過小,參數難以被更新。

  • Xavier initialization

Xavier initialization能夠解決上面的問題!其初始化方式也並不複雜。Xavier初始化的基本思想是保持輸入和輸出的方差一致,這樣就避免了全部輸出值都趨向於0。注意,爲了問題的簡便,Xavier初始化的推導過程是基於線性函數的,可是它在一些非線性神經元中也頗有效。讓咱們試一下:

W = tf.Variable(np.random.randn(node_in, node_out)) / np.sqrt(node_in)


Woohoo!輸出值在不少層以後依然保持着良好的分佈,這頗有利於咱們優化神經網絡!以前談到Xavier initialization是在線性函數上推導得出,這說明它對非線性函數並不具備普適性,因此這個例子僅僅說明它對tanh頗有效,那麼對於目前最經常使用的ReLU神經元呢(關於不一樣非線性神經元的比較請參考這裏)?繼續作一下實驗:

W = tf.Variable(np.random.randn(node_in, node_out)) / np.sqrt(node_in)
......
fc = tf.nn.relu(fc)

前面看起來還不錯,後面的趨勢倒是愈來愈接近0。幸運的是,He initialization能夠用來解決ReLU初始化的問題。

  • He initialization

He initialization的思想是:在ReLU網絡中,假定每一層有一半的神經元被激活,另外一半爲0,因此,要保持variance不變,只須要在Xavier的基礎上再除以2:

W = tf.Variable(np.random.randn(node_in,node_out)) / np.sqrt(node_in/2)
......
fc = tf.nn.relu(fc)


看起來效果很是好,推薦在ReLU網絡中使用!

Batch Normalization Layer

Batch Normalization是一種巧妙而粗暴的方法來削弱bad initialization的影響,其基本思想是:If you want it, just make it!

咱們想要的是在非線性activation以前,輸出值應該有比較好的分佈(例如高斯分佈),以便於back propagation時計算gradient,更新weight。Batch Normalization將輸出值強行作一次Gaussian Normalization和線性變換:


Batch Normalization中全部的操做都是平滑可導,這使得back propagation能夠有效運行並學到相應的參數\gamma\beta。須要注意的一點是Batch Normalization在training和testing時行爲有所差異。Training時\mu_\mathcal{B}\sigma_\mathcal{B}由當前batch計算得出;在Testing時\mu_\mathcal{B}\sigma_\mathcal{B}應使用Training時保存的均值或相似的通過處理的值,而不是由當前batch計算。

隨機初始化,無Batch Normalization:

W = tf.Variable(np.random.randn(node_in, node_out)) * 0.01
......
fc = tf.nn.relu(fc)

隨機初始化,有Batch Normalization:

W = tf.Variable(np.random.randn(node_in, node_out)) * 0.01
......
fc = tf.contrib.layers.batch_norm(fc, center=True, scale=True,
                                  is_training=True)
fc = tf.nn.relu(fc)


很容易看到,Batch Normalization的效果很是好,推薦使用!

參考資料

Xavier initialization是由Xavier Glorot et al.在2010年提出,He initialization是由Kaiming He et al.在2015年提出,Batch Normalization是由Sergey Ioffe et al.在2015年提出。

另有知乎網友在評論中提到了一些其餘相關工做:arxiv.org/abs/1511.0642arxiv.org/pdf/1702.0859

  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. Sergey Ioffe et al., Batch Normalization: Accelerating Deep Network Training byReducing Internal Covariate Shift
  4. Standord CS231n


原文: https://www.leiphone.com/news/201703/3qMp45aQtbxTdzmK.html

相關文章
相關標籤/搜索