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


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



  • 在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)

實驗代碼請參見我的Github

背景

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

TLDR里已經涵蓋了本文的核心要點,下面在正文中,我們來深入了解一下前因后果。

初始化為0的可行性?

答案是不可行。 這是一道送分題 哈哈!為什么將所有W初始化為0是錯誤的呢?是因為如果所有的參數都是0,那么所有神經元的輸出都將是相同的,那在back propagation的時候同一層內所有神經元的行為也是相同的 --- gradient相同,weight update也相同。這顯然是一個不可接受的結果。

可行的幾種初始化方式

  • 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階段得到的參數初始值和訓練數據對模型進行整體調整。在這一過程中,參數進一步被更新,形成最終模型。

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

  • random initialization

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

核心代碼見下方,完整代碼請參見我的Github

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。下圖給出了每一層輸出值分布的直方圖。



隨着層數的增加,我們看到輸出值迅速向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


免責聲明!

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



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