Batchnorm原理詳解
前言:Batchnorm是深度網絡中經常用到的加速神經網絡訓練,加速收斂速度及穩定性的算法,可以說是目前深度網絡必不可少的一部分。 本文旨在用通俗易懂的語言,對深度學習的常用算法–batchnorm的原理及其代碼實現做一個詳細的解讀。本文主要包括以下幾個部分。
- Batchnorm主要解決的問題
- Batchnorm原理解讀
- Batchnorm的優點
- Batchnorm的源碼解讀
第一節:Batchnorm主要解決的問題
首先,此部分也即是講為什么深度網絡會需要batchnorm
,我們都知道,深度學習的話尤其是在CV上都需要對數據做歸一化,因為深度神經網絡主要就是為了學習訓練數據的分布,並在測試集上達到很好的泛化效果,但是,如果我們每一個batch輸入的數據都具有不同的分布,顯然會給網絡的訓練帶來困難。另一方面,數據經過一層層網絡計算后,其數據分布也在發生着變化,此現象稱為Internal Covariate Shift,接下來會詳細解釋,會給下一層的網絡學習帶來困難。batchnorm
直譯過來就是批規范化,就是為了解決這個分布變化問題。
1.1 Internal Covariate Shift
Internal Covariate Shift :此術語是google小組在論文Batch Normalizatoin 中提出來的,其主要描述的是:訓練深度網絡的時候經常發生訓練困難的問題,因為,每一次參數迭代更新后,上一層網絡的輸出數據經過這一層網絡計算后,數據的分布會發生變化,為下一層網絡的學習帶來困難(神經網絡本來就是要學習數據的分布,要是分布一直在變,學習就很難了),此現象稱之為Internal Covariate Shift。
Batch Normalizatoin之前的解決方案就是使用較小的學習率,和小心的初始化參數,對數據做白化處理,但是顯然治標不治本。
1.2 covariate shift
Internal
Covariate Shift 和Covariate Shift具有相似性,但並不是一個東西,前者發生在神經網絡的內部,所以是Internal,后者發生在輸入數據上。Covariate Shift
主要描述的是由於訓練數據和測試數據存在分布的差異性,給網絡的泛化性和訓練速度帶來了影響,我們經常使用的方法是做歸一化或者白化。想要直觀感受的話,看下圖:
舉個簡單線性分類栗子,假設我們的數據分布如a所示,參數初始化一般是0均值,和較小的方差,此時擬合的y=wx+b
如b圖中的橘色線,經過多次迭代后,達到紫色線,此時具有很好的分類效果,但是如果我們將其歸一化到0點附近,顯然會加快訓練速度,如此我們更進一步的通過變換拉大數據之間的相對差異性,那么就更容易區分了。
Covariate Shift 就是描述的輸入數據分布不一致的現象,對數據做歸一化當然可以加快訓練速度,能對數據做去相關性,突出它們之間的分布相對差異就更好了。Batchnorm做到了,前文已說過,Batchnorm是歸一化的一種手段,極限來說,這種方式會減小圖像之間的絕對差異,突出相對差異,加快訓練速度。所以說,並不是在深度學習的所有領域都可以使用BatchNorm
,下文會寫到其不適用的情況。
第二節:Batchnorm 原理解讀
本部分主要結合原論文部分,排除一些復雜的數學公式,對BatchNorm的原理做盡可能詳細的解釋。之前就說過,為了減小InternalCovariate Shift,對神經網絡的每一層做歸一化不就可以了,假設將每一層輸出后的數據都歸一化到0均值,1方差,滿足正太分布,但是,此時有一個問題,每一層的數據分布都是標准正太分布,導致其完全學習不到輸入數據的特征,因為,費勁心思學習到的特征分布被歸一化了,因此,直接對每一層做歸一化顯然是不合理的。 但是如果稍作修改,加入可訓練的參數做歸一化,那就是BatchNorm
之所以稱之為batchnorm是因為所norm的數據是一個batch的,假設輸入數據是β=x1...m
共m個數據,輸出是yi=BN(x),batchnorm的步驟如下:
1.先求出此次批量數據x
的均值,μβ=1m∑mi=1xi 2.求出此次batch的方差,σ2β=1m∑i=1m(xi−μβ)2 3.接下來就是對x做歸一化,得到x−i 4.最重要的一步,引入縮放和平移變量γ和β ,計算歸一化后的值,yi=γx−i +β
接下來詳細介紹一下這額外的兩個參數,之前也說過如果直接做歸一化不做其他處理,神經網絡是學不到任何東西的,但是加入這兩個參數后,事情就不一樣了,先考慮特殊情況下,如果γ和β分別等於此batch的方差和均值,那么yi不就還原到歸一化前的x了嗎,也即是縮放平移到了歸一化前的分布,相當於batchnorm沒有起作用,β 和γ分別稱之為 平移參數和縮放參數 。這樣就保證了每一次數據經過歸一化后還保留的有學習來的特征,同時又能完成歸一化這個操作,加速訓練。
先用一個簡單的代碼舉個小栗子:
def Batchnorm_simple_for_train(x, gamma, beta, bn_param):"""param:x : 輸入數據,設shape(B,L)param:gama : 縮放因子 γparam:beta : 平移因子 βparam:bn_param : batchnorm所需要的一些參數 eps : 接近0的數,防止分母出現0 momentum : 動量參數,一般為0.9, 0.99, 0.999 running_mean :滑動平均的方式計算新的均值,訓練時計算,為測試數據做准備 running_var : 滑動平均的方式計算新的方差,訓練時計算,為測試數據做准備""" running_mean = bn_param['running_mean'] #shape = [B] running_var = bn_param['running_var'] #shape = [B] results = 0. # 建立一個新的變量 x_mean=x.mean(axis=0) # 計算x的均值 x_var=x.var(axis=0) # 計算方差 x_normalized=(x-x_mean)/np.sqrt(x_var+eps) # 歸一化 results = gamma * x_normalized + beta # 縮放平移 running_mean = momentum * running_mean + (1 - momentum) * x_mean running_var = momentum * running_var + (1 - momentum) * x_var #記錄新的值 bn_param['running_mean'] = running_mean bn_param['running_var'] = running_var return results , bn_param
看完這個代碼是不是對batchnorm有了一個清晰的理解,首先計算均值和方差,然后歸一化,然后縮放和平移,完事!但是這是在訓練中完成的任務,每次訓練給一個批量,然后計算批量的均值方差,但是在測試的時候可不是這樣,測試的時候每次只輸入一張圖片,這怎么計算批量的均值和方差,於是,就有了代碼中下面兩行,在訓練的時候實現計算好mean, var測試的時候直接拿來用就可以了,不用計算均值和方差。
running_mean = momentum * running_mean + (1 - momentum) * x_meanrunning_var = momentum * running_var + (1 - momentum) * x_var
所以,測試的時候是這樣的:
def Batchnorm_simple_for_test(x, gamma, beta, bn_param):"""param:x : 輸入數據,設shape(B,L)param:gama : 縮放因子 γparam:beta : 平移因子 βparam:bn_param : batchnorm所需要的一些參數 eps : 接近0的數,防止分母出現0 momentum : 動量參數,一般為0.9, 0.99, 0.999 running_mean :滑動平均的方式計算新的均值,訓練時計算,為測試數據做准備 running_var : 滑動平均的方式計算新的方差,訓練時計算,為測試數據做准備""" running_mean = bn_param['running_mean'] #shape = [B] running_var = bn_param['running_var'] #shape = [B] results = 0. # 建立一個新的變量 x_normalized=(x-running_mean )/np.sqrt(running_var +eps) # 歸一化 results = gamma * x_normalized + beta # 縮放平移 return results , bn_param
第三節:Batchnorm源碼解讀
本節主要講解一段tensorflow中Batchnorm
def batch_norm_layer(x, train_phase, scope_bn): with tf.variable_scope(scope_bn): # 新建兩個變量,平移、縮放因子 beta = tf.Variable(tf.constant(0.0, shape=[x.shape[-1]]), name='beta', trainable=True) gamma = tf.Variable(tf.constant(1.0, shape=[x.shape[-1]]), name='gamma', trainable=True) # 計算此次批量的均值和方差 axises = np.arange(len(x.shape) - 1) batch_mean, batch_var = tf.nn.moments(x, axises, name='moments') # 滑動平均做衰減 ema = tf.train.ExponentialMovingAverage(decay=0.5) def mean_var_with_update(): ema_apply_op = ema.apply([batch_mean, batch_var]) with tf.control_dependencies([ema_apply_op]): return tf.identity(batch_mean), tf.identity(batch_var) # train_phase 訓練還是測試的flag # 訓練階段計算runing_mean和runing_var,使用mean_var_with_update()函數 # 測試的時候直接把之前計算的拿去用 ema.average(batch_mean) mean, var = tf.cond(train_phase, mean_var_with_update, lambda: (ema.average(batch_mean), ema.average(batch_var))) normed = tf.nn.batch_normalization(x, mean, var, beta, gamma, 1e-3) return normed
至於此行代碼tf.nn.batch_normalization()就是簡單的計算batchnorm過程啦,代碼如下: 這個函數所實現的功能就如此公式:γ(x−μ)σ+β
def batch_normalization(x, mean, variance, offset, scale, variance_epsilon, name=None): with ops.name_scope(name, "batchnorm", [x, mean, variance, scale, offset]): inv = math_ops.rsqrt(variance + variance_epsilon) if scale is not None: inv *= scale return x * inv + (offset - mean * inv if offset is not None else -mean * inv)
第四節:Batchnorm的優點
主要部分說完了,接下來對BatchNorm做一個總結:
- 沒有它之前,需要小心的調整學習率和權重初始化,但是有了BN可以放心的使用大學習率,就不用小心的調參了,較大的學習率極大的提高了學習速度,
- Batchnorm本身上也是一種正則的方式,可以代替其他正則方式如dropout等
- 另外,個人認為,batchnorm降低了數據之間的絕對差異,有一個去相關的性質,更多的考慮相對差異性,因此在分類任務上具有更好的效果。
注:或許大家都知道了,韓國團隊在2017NTIRE圖像超分辨率中取得了top1的成績,主要原因竟是去掉了網絡中的batchnorm層,由此可見,BN並不是適用於所有任務的,在image-to-image這樣的任務中,尤其是超分辨率上,圖像的絕對差異顯得尤為重要,所以batchnorm的scale並不適合。
參考文獻: 【1】http://blog.csdn.net/zhikangfu/article/details/53391840 【2】http://geek.csdn.net/news/detail/160906 【3】 https://www.zhihu.com/question/53133249