深度學習:批歸一化Batch Normalization

        深度神經網絡模型訓練難,其中一個重要的現象就是 Internal Covariate Shift. Batch Norm 自 2015 年由Google 提出之後, Layer Norm / Weight Norm / Cosine Norm 等也橫空出世。BN,Batch Normalization,就是在深度神經網絡訓練過程中使得每一層神經網絡的輸入保持相近的分佈。

BN的來源Motivation

1.1 獨立同分布與白化

1.2 深度學習中的 Internal Covariate Shift

        作者認爲:網絡訓練過程中參數不斷改變導致後續每一層輸入的分佈也發生變化,而學習的過程又要使每一層適應輸入的分佈,因此我們不得不降低學習率、小心地初始化。作者將分佈發生變化稱之爲 internal covariate shift。

        大家應該都知道,我們一般在訓練網絡的時會將輸入減去均值,還有些人甚至會對輸入做白化等操作,目的是爲了加快訓練。爲什麼減均值、白化可以加快訓練呢,這裏做一個簡單地說明:首先,圖像數據是高度相關的,假設其分佈如下圖a所示(簡化爲2維)。由於初始化的時候,我們的參數一般都是0均值的,因此開始的擬合y=Wx+b,基本過原點附近,如圖b紅色虛線。因此,網絡需要經過多次學習才能逐步達到如紫色實線的擬合,即收斂的比較慢。如果我們對輸入數據先作減均值操作,如圖c,顯然可以加快學習。更進一步的,我們對數據再進行去相關操作,使得數據更加容易區分,這樣又會加快訓練,如圖d。
這裏寫圖片描述

        白化的方式有好幾種,常用的有PCA白化:即對數據進行PCA操作之後,在進行方差歸一化。這樣數據基本滿足0均值、單位方差、弱相關性。作者首先考慮,對每一層數據都使用白化操作,但分析認爲這是不可取的。因爲白化需要計算協方差矩陣、求逆等操作,計算量很大,此外,反向傳播時,白化操作不一定可導。於是,作者採用下面的Normalization方法。

Normalization via Mini-Batch Statistics

        數據歸一化方法很簡單,就是要讓數據具有0均值和單位方差,如下式:
這裏寫圖片描述
        但是如果簡單的這麼幹,會降低層的表達能力。比如下圖,在使用sigmoid激活函數的時候,如果把數據限制到0均值單位方差,那麼相當於只使用了激活函數中近似線性的部分,這顯然會降低模型表達能力。
這裏寫圖片描述

爲此,作者又爲BN增加了2個參數,用來保持模型的表達能力:
這裏寫圖片描述
上述公式中用到了均值E和方差Var,需要注意的是理想情況下E和Var應該是針對整個數據集的,但顯然這是不現實的。因此,作者做了簡化,用一個Batch的均值和方差作爲對整個數據集均值和方差的估計。
整個BN的算法如下:
這裏寫圖片描述
求導的過程也非常簡單,有興趣地可以自己再推導一遍或者直接參見原文。

[Batch Normalization詳解]

總結一下:

Normalization 的通用公式

BN與常用的數據歸一化最大的區別就是加了後面這兩個參數,這兩個參數主要作用是在加速收斂和表徵破壞之間做的trade off

BN訓練和測試時的差異

        對於BN,在訓練時,是對每一批的訓練數據進行歸一化,也即用每一批數據的均值和方差。而在測試時,比如進行一個樣本的預測,就並沒有batch的概念,因此用的是全量訓練數據的均值和方差,可以通過移動平均法求得。

BN訓練時爲什麼不用全量訓練集的均值和方差呢?

        因爲用全量訓練集的均值和方差容易過擬合,對於BN,其實就是對每一批數據進行歸一化到一個相同的分佈,而每一批數據的均值和方差會有一定的差別,而不是用固定的值,這個差別實際上能夠增加模型的魯棒性,也會在一定程度上減少過擬合。也正是因此,BN一般要求將訓練集完全打亂,並用一個較大的batch值,否則,一個batch的數據無法較好得代表訓練集的分佈,會影響模型訓練的效果。

        具體在tf的解釋看下面tf函數說明。

[BN和Dropout在訓練和測試時的差別]

BN的dropout共同使用時的要注意的問題

[深度學習:正則化]

Batch Normalized的作用

1)改善流經網絡的梯度

2)允許更大的學習率,大幅提高訓練速度:你可以選擇比較大的初始學習率,讓你的訓練速度飆漲。以前還需要慢慢調整學習率,甚至在網絡訓練到一半的時候,還需要想着學習率進一步調小的比例選擇多少比較合適,現在我們可以採用初始很大的學習率,然後學習率的衰減速度也很大,因爲這個算法收斂很快。當然這個算法即使你選擇了較小的學習率,也比以前的收斂速度快,因爲它具有快速訓練收斂的特性;

3)減少對初始化的強烈依賴

4)改善正則化策略:作爲正則化的一種形式,輕微減少了對dropout的需求你再也不用去理會過擬閤中drop out、L2正則項參數的選擇問題,採用BN算法後,你可以移除這兩項了參數,或者可以選擇更小的L2正則約束參數了,因爲BN具有提高網絡泛化能力的特性;

5)再也不需要使用使用局部響應歸一化層了(局部響應歸一化是Alexnet網絡用到的方法,搞視覺的估計比較熟悉),因爲BN本身就是一個歸一化網絡層;

6)可以把訓練數據徹底打亂(防止每批訓練的時候,某一個樣本都經常被挑選到,文獻說這個可以提高1%的精度)。

[cs231n學習筆記-激活函數-BN-參數優化]

Batch-normalized對於優化的作用

How Does Batch Normalization Help Optimization?

[https://arxiv.org/pdf/1805.11604.pdf]
BN解決梯度消失的原因:

考慮一些具體的激活函數。我們看到,無論是tanh還是sigmoid

函數圖像的兩端,相對於x的變化,y的變化都很小(這其實很正常,畢竟tanh就是拉伸過的sigmoid),也就是說,容易出現梯度衰減的問題。那麼,如果在tanh或sigmoid之前,進行一些normalization處理,就可以緩解梯度衰減的問題。所以最初的BN論文選擇把BN層放在非線性激活之前。

但是ReLU的畫風和它們完全不一樣啊。

實際上,最初的BN論文雖然也在使用ReLU的Inception上進行了試驗,但首先研究的是sigmoid激活。因此,試驗ReLU的,我猜想作者可能就順便延續了之前把BN放前面的配置,而沒有單獨針對ReLU進行處理。

[Batch-normalized 應該放在非線性激活層的前面還是後面?]

所以Batch-normalized 應該放在非線性激活層的前面還是後面?

You have the choice of applying batch normalization either before or after the non-linearity, depending on your definition of the “activation distribution of interest” that you wish to normalize. It will probably end up being a hyperparameter that you’ll just have to tinker with.BN放在非線性函數前還是後取決於你想要normalize的對象,更像一個超參數。

[Batch-normalized 應該放在非線性激活層的前面還是後面?]

-柚子皮-

 

 

Batch Normalization —— 縱向規範化

Batch Normalization 針對單個神經元進行,利用網絡訓練時一個 mini-batch 的數據來計算該神經元 x_i 的均值和方差,因而稱爲 Batch Normalization。

相對上面的 Normalization 通用公式:

BN 比較適用的場景是:每個 mini-batch 比較大,數據分佈比較接近。在進行訓練之前,要做好充分的 shuffle,否則效果會差很多。

另外,由於 BN 需要在運行過程中統計每個 mini-batch 的一階統計量和二階統計量,因此不適用於 動態的網絡結構 和 RNN 網絡。不過,也有研究者專門提出了適用於 RNN 的 BN 使用方法,這裏先不展開了。

-柚子皮-

Layer Normalization —— 橫向規範化

層規範化就是針對 BN 的上述不足而提出的。與 BN 不同,LN 是一種橫向的規範化,如圖所示。它綜合考慮一層所有維度的輸入,計算該層的平均輸入值和輸入方差,然後用同一個規範化操作來轉換各個維度的輸入。

相對上面的 Normalization 通用公式:

LN 針對單個訓練樣本進行,不依賴於其他數據,因此可以避免 BN 中受 mini-batch 數據分佈影響的問題,可以用於 小mini-batch場景、動態網絡場景和 RNN,特別是自然語言處理領域。此外,LN 不需要保存 mini-batch 的均值和方差,節省了額外的存儲空間。

但是,BN 的轉換是針對單個神經元可訓練的——不同神經元的輸入經過再平移和再縮放後分布在不同的區間,而 LN 對於一整層的神經元訓練得到同一個轉換——所有的輸入都在同一個區間範圍內。如果不同輸入特徵不屬於相似的類別(比如顏色和大小),那麼 LN 的處理可能會降低模型的表達能力。

-柚子皮-

 

 

TensorFlow批歸一化函數

tensorflow中batch normalization的實現主要有下面三個:

tf.nn.batch_normalization

tf.layers.batch_normalization

tf.contrib.layers.batch_norm

封裝程度逐個遞進,建議使用tf.layers.batch_normalization或tf.contrib.layers.batch_norm,因爲在tensorflow官網的解釋比較詳細。

tf.layers.batch_normalization公式如下:

y=γ(x−μ)/σ+β

其中x是輸入,y是輸出,μ是均值,σ是方差,γ和β是縮放(scale)、偏移(offset)係數。

tf.keras.layers.BatchNormalization(...):

使用keras的話,是不需且不能In particular, tf.control_dependencies(tf.GraphKeys.UPDATE_OPS) should not be used。

tf.layers.batch_normalization(  #將被tf.keras.layers.BatchNormalization(...)取代
    inputs,
    axis=-1,
    momentum=0.99,
    epsilon=0.001,
    center=True,
    scale=True,
    beta_initializer=tf.zeros_initializer(),
    gamma_initializer=tf.ones_initializer(),
    moving_mean_initializer=tf.zeros_initializer(),
    moving_variance_initializer=tf.ones_initializer(),
    beta_regularizer=None,
    gamma_regularizer=None,
    beta_constraint=None,
    gamma_constraint=None,
    training=False,
    trainable=True,
    name=None,
    reuse=None,
    renorm=False,
    renorm_clipping=None,
    renorm_momentum=0.99,
    fused=None,
    virtual_batch_size=None,
    adjustment=None
)

參數:

inputs:張量輸入。
axis:一個int,應該被規範化的軸(通常是特徵軸)。例如,在使用data_format=“channels_first”的Convolution2D層之後,在BatchNormalization中設置axis=1。  理解的話可能參考示例及tf.contrib.layers.layer_norm中參數inputs的說明。
momentum:滑動平均值的動量。
epsilon:小浮點數加上方差以避免被零除。
center:如果爲True,則將beta的偏移量添加到標準化張量。如果爲False,則忽略beta。
scale:如果爲True,則乘以gamma。如果爲False,則不使用gamma。當下一層是線性的(例如,nn.relu)時,可以禁用此選項,因爲可以由下一層進行縮放。
beta_initializer:beta權重的初始值設定項。
gamma_initializer:gamma權重的初始值設定項。
moving_mean_initializer:滑動平均值的初始化器。
moving_variance_initializer:滑動方差的初始值設定項。
beta_regularizer:可選的beta權重正則化器。
gamma_regularizer:gamma權重的可選調節器。
beta_constraint:由Optimizer更新後應用於beta權重的可選投影函數(例如,用於實現層權重的規範約束或值約束)。函數必須將未投影的變量作爲輸入,並且必須返回投影的變量(必須具有相同的形狀)。在進行異步分佈式訓練時,使用約束是不安全的。
gamma_constraint:由Optimizer更新後應用於gamma權重的可選投影函數。
training:要麼是Python布爾值,要麼是TensorFlow布爾值標量張量(例如佔位符)。是以訓練模式(使用當前批的統計數據進行規範化)還是以推理模式(使用滑動統計數據進行規範化)返回輸出。注意:請確保正確設置此參數,否則您的訓練 / 驗證將無法正常工作。
trainable:布爾值,如果爲True,還將變量添加到圖形集合GraphKeys.TRAINABLE_VARIABLES(請參見tf.variable)。
name:字符串,層的名稱。
reuse:布爾值,是否以相同的名稱重用前一層的權重。
`renorm:是否使用批量再規範化(https://arxiv.org/abs/1702.03275)。 這會在訓練期間增加額外的變量。這個參數的任何一個值的推斷都是相同的。
renorm_clipping:一種字典,可以將關鍵字“rmax”、“rmin”、“dmax”映射到用於剪裁renorm校正的標量張量。校正(r,d)用作corrected_value = normalized_value * r + d,其中r被剪裁爲[rmin,rmax],d被剪裁爲[-dmax,dmax]。缺少的rmax、rmin和dmax分別設置爲inf、0和inf。
renorm_momentum:用renorm更新滑動方式和標準偏差的動量。與動量不同,這會影響訓練,既不應太小(會增加噪音),也不應太大(會給出過時的估計)。注意,momentum仍然被用來得到均值和方差來進行推理。
fused:如果False或者True,儘可能使用更快的融合實現。如果爲False,則使用系統建議的實現。
virtual_batch_size:一個int。默認情況下,virtual_batch_size爲None,這意味着在整個批次中執行批次規範化。當virtual_batch_size不是None時,改爲執行“Ghost Batch Normalization”,創建每個單獨規範化的虛擬子批(使用共享gamma、beta和滑動統計)。必須在執行期間劃分實際批大小。
adjustment:僅在訓練期間,採用包含輸入張量(動態)形狀的張量並返回一對(scale、bias)以應用於標準化值(γ和β之前)的函數。例如,如果axis=-1,adjustment = lambda shape: ( tf.random_uniform(shape[-1:], 0.93, 1.07),tf.random_uniform(shape[-1:], -0.1, 0.1))將標準化值向上或向下縮放7%,然後將結果向上滑動0.1(每個功能都有獨立的縮放和偏移,但在所有示例中都有共享),最後應用gamma 和/或 beta。如果沒有,則不應用調整。如果指定了virtual_batch_size,則無法指定。

訓練的時候需要特別注意的兩點

(1)輸入參數設置training=True。同使用tf.layers.dropout時一樣!

(2)計算loss時,要添加以下代碼(即添加update_ops到最後的train_op中)。

net_optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
with tf.control_dependencies(tf.get_collection(tf.GraphKeys.UPDATE_OPS)):
    train_op = net_optimizer.minimize(loss)

或者等價地:

  x_norm = tf.compat.v1.layers.batch_normalization(x, training=training)

  # ...

  update_ops = tf.compat.v1.get_collection(tf.GraphKeys.UPDATE_OPS)
  train_op = optimizer.minimize(loss)
  train_op = tf.group([train_op, update_ops])

其原因:

        這樣才能計算μ和σ的滑動平均(訓練時,需要更新滑動平均值(moving_mean)和滑動方差(moving_variance)。默認情況下,更新操作放置在tf.GraphKeys.UPDATE_OPS中,因此需要將它們作爲對train_ops的依賴項添加。此外,在獲取update_ops集合之前,請確保添加任何批處理標準化(batch_normalization)操作。否則,update_ops將爲空,訓練 / 驗證將無法正常工作。)

根據前面的理論,BN在training和inference時使用的方法是不一樣的:

training時:

我們需要逐個神經元逐個樣本地來計算,這個batch在某一層輸出的均值和標準差,然後再對該層的輸出進行標準化。同時還要學習gamma和beta兩個參數。這是非常非常耗時的,顯然,我們不能在inference的時候使用這種方法。

解決方案就是,在訓練時使用滑動平均維護population均值和方差:

running_mean = momentum * running_mean + (1 - momentum) * sample_mean
running_std = momentum * running_std + (1 - momentum) * sample_std
在訓練結束保存模型時,running_mean、running_var、trained_gamma和trained_beta一同被保存下來。

inference時:

output = (input - running_mean) / running_std
output = trained_gamma * output + trained_beta
也就是說,在inference時,BN對應的操作不再是公式裏提到的那樣,計算該batch的各種統計量,而是直接使用在訓練時保存下來的population均值和方差,進行一次線性變換。這樣效率提升了很多。但是缺點也顯而易見,如果訓練集和驗證集不平衡的時候,驗證的效果會一直一直很差。

[TensorFlow中批歸一化的實現——tf.layers.batch_normalization()函數]

[由training參數想到的]

tf.layers.batch_normalization示例

output = tf.Variable([[[0.3, 0.0, 0.5, 0.2],
                       [0.44, 0.32, 0.23, 0.01],
                       [-0.2, 0.6, 0.5, 0.1]],
                      [[0.4, 0.0, 0.0, 0.2],
                       [0.4, 0.2, 0.3, 0.01],
                       [0.2, -0.6, -0.5, 0.15]]])
print(output.shape)

axis = [0, 1]
output_n = tf.layers.batch_normalization(output, axis=axis, training=True, trainable=False)
print(output_n.shape)
output_n_mean = tf.reduce_mean(output_n, axis=[2])with tf.Session() as sess:

    sess.run(tf.global_variables_initializer())
    print('\n')
    print(output_n.eval())
    print('\n')
    print(output_n_mean.eval())

(2, 3, 4)

(2, 3, 4)
[[[ 0.2731793  -1.365896    1.365896   -0.27317917]
  [ 1.1840361   0.43622375 -0.12463534 -1.4956245 ]
  [-1.3987572   1.0879223   0.77708733 -0.4662524 ]]

 [[ 1.4808724  -0.8885234  -0.8885234   0.29617447]
  [ 1.1691556  -0.18638718  0.49138427 -1.4741528 ]
  [ 1.0586927  -1.1269956  -0.85378444  0.92208725]]]


[[ 2.9802322e-08  0.0000000e+00  1.4901161e-08]
 [ 1.4901161e-08 -2.9802322e-08 -1.4901161e-08]] #誤差範圍內可以認爲是0

如果batch_nomalization中axis改成axis = [2],且output_n_mean = tf.reduce_mean(output_n, axis=[0,1]),則輸出:

[[[ 0.19564903 -0.23395906  0.9464483   1.0328814 ]
  [ 0.8277463   0.6298898   0.16815075 -1.1887878 ]
  [-2.061841    1.3857577   0.9464483  -0.13641822]]

 [[ 0.64714706 -0.23395906 -0.49484357  1.0328814 ]
  [ 0.64714706  0.30594647  0.3699316  -1.1887878 ]
  [-0.255849   -1.8536758  -1.9361355   0.4482317 ]]]


[-9.934107e-08  0.000000e+00  0.000000e+00  9.934107e-08] #誤差範圍內可以認爲是0

也就是說batch_nomalization中axis是指定哪幾個維度,則對另外幾個維度進行nomalization。

其它示例:

cnn後加batch_normalization[使用tf.layers高級函數來構建帶有BatchNormalization的神經網絡]

TensorFlow層歸一化函數

tf.contrib.layers.layer_norm(
    inputs,
    center=True,
    scale=True,
    activation_fn=None,
    reuse=None,
    variables_collections=None,
    outputs_collections=None,
    trainable=True,
    begin_norm_axis=1,
    begin_params_axis=-1,
    scope=None
)

By default, begin_norm_axis = 1 and begin_params_axis = -1, meaning that normalization is performed over all but the first axis (the HWC if inputs is NHWC), while the beta and gamma trainable parameters are calculated for the rightmost axis (the C if inputs is NHWC). Scaling and recentering is performed via broadcast of the beta and gamma parameters with the normalized tensor.

參數:

inputs: A tensor having rank R. The normalization is performed over axes begin_norm_axis ... R - 1 and centering and scaling parameters are calculated over begin_params_axis ... R - 1.

lstm中使用layer_norm

[tf.contrib.rnn.LayerNormBasicLSTMCell]

直接在lstm後面使用layer_norm或者batch_normalization還不清楚怎麼搞。

from: -柚子皮-

ref: [詳解深度學習中的Normalization,BN/LN/WN]**

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章