1. 程式人生 > >batchnorm原理及程式碼詳解(筆記2)

batchnorm原理及程式碼詳解(筆記2)

Batchnorm原理詳解

前言:Batchnorm是深度網路中經常用到的加速神經網路訓練,加速收斂速度及穩定性的演算法,可以說是目前深度網路必不可少的一部分。
本文旨在用通俗易懂的語言,對深度學習的常用演算法–batchnorm的原理及其程式碼實現做一個詳細的解讀。本文主要包括以下幾個部分。

  • Batchnorm主要解決的問題
  • Batchnorm原理解讀
  • Batchnorm的優點
  • Batchnorm的原始碼解讀

第一節:Batchnorm主要解決的問題

首先,此部分也即是講為什麼深度網路會需要batchnorm

batchnorm,我們都知道,深度學習的話尤其是在CV上都需要對資料做歸一化,因為深度神經網路主要就是為了學習訓練資料的分佈,並在測試集上達到很好的泛化效果,但是,如果我們每一個batch輸入的資料都具有不同的分佈,顯然會給網路的訓練帶來困難。另一方面,資料經過一層層網路計算後,其資料分佈也在發生著變化,此現象稱為InternalInternal CovariateCovariate ShiftShift,接下來會詳細解釋,會給下一層的網路學習帶來困難。batchnorm

batchnorm直譯過來就是批規範化,就是為了解決這個分佈變化問題。

1.1 Internal Covariate Shift
Internal

Internal CovariateCovariate ShiftShift :此術語是google小組在論文BatchBatch NormalizatoinNormalizatoin 中提出來的,其主要描述的是:訓練深度網路的時候經常發生訓練困難的問題,因為,每一次引數迭代更新後,上一層網路的輸出資料經過這一層網路計算後,資料的分佈會發生變化,為下一層網路的學習帶來困難(神經網路本來就是要學習資料的分佈,要是分佈一直在變,學習就很難了),此現象稱之為InternalInternal CovariateCovariate Shift

Shift。

Batch

Batch Normalizatoin

Normalizatoin 之前的解決方案就是使用較小的學習率,和小心的初始化引數,對資料做白化處理,但是顯然治標不治本。

###1.2 covariate shift
Internal

Internal CovariateCovariate ShiftShift 和CovariateCovariate ShiftShift具有相似性,但並不是一個東西,前者發生在神經網路的內部,所以是InternalInternal,後者發生在輸入資料上。CovariateCovariate Shift

Shift主要描述的是由於訓練資料和測試資料存在分佈的差異性,給網路的泛化性和訓練速度帶來了影響,我們經常使用的方法是做歸一化或者白化。想要直觀感受的話,看下圖:

圖片來自網路

舉個簡單線性分類栗子,假設我們的資料分佈如a所示,引數初始化一般是0均值,和較小的方差,此時擬合的y=wx+b

y=wx+b如b圖中的橘色線,經過多次迭代後,達到紫色線,此時具有很好的分類效果,但是如果我們將其歸一化到0點附近,顯然會加快訓練速度,如此我們更進一步的通過變換拉大資料之間的相對差異性,那麼就更容易區分了。

Covariate

Covariate ShiftShift 就是描述的輸入資料分佈不一致的現象,對資料做歸一化當然可以加快訓練速度,能對資料做去相關性,突出它們之間的分佈相對差異就更好了。BatchnormBatchnorm做到了,前文已說過,BatchnormBatchnorm是歸一化的一種手段,極限來說,這種方式會減小影象之間的絕對差異,突出相對差異,加快訓練速度。所以說,並不是在深度學習的所有領域都可以使用BatchNorm

BatchNorm,下文會寫到其不適用的情況。

第二節:Batchnorm 原理解讀

本部分主要結合原論文部分,排除一些複雜的數學公式,對BatchNorm

BatchNorm的原理做盡可能詳細的解釋。

之前就說過,為了減小Internal

Internal CovariateCovariate ShiftShift,對神經網路的每一層做歸一化不就可以了,假設將每一層輸出後的資料都歸一化到0均值,1方差,滿足正太分佈,但是,此時有一個問題,每一層的資料分佈都是標準正太分佈,導致其完全學習不到輸入資料的特徵,因為,費勁心思學習到的特徵分佈被歸一化了,因此,直接對每一層做歸一化顯然是不合理的。
但是如果稍作修改,加入可訓練的引數做歸一化,那就是BatchNorm

BatchNorm實現的了,接下來結合下圖的虛擬碼做詳細的分析:
這裡寫圖片描述

之所以稱之為batchnorm是因為所norm的資料是一個batch的,假設輸入資料是β=x1...m

β=x1...m​共m個數據,輸出是yi=BN(x)yi​=BN(x),batchnorm

batchnorm的步驟如下:

1.先求出此次批量資料x

x的均值,μβ=1m∑mi=1xiμβ​=m1​∑i=1m​xi​
2.求出此次batch的方差,σ2β=1m∑i=1m(xi−μβ)2σβ2​=m1​∑i=1​m(xi​−μβ​)2
3.接下來就是對xx做歸一化,得到x−ixi−​
4.最重要的一步,引入縮放和平移變數$γ 和和\beta$ ,計算歸一化後的值,yi=γx−iyi​=γxi−​ +β

接下來詳細介紹一下這額外的兩個引數,之前也說過如果直接做歸一化不做其他處理,神經網路是學不到任何東西的,但是加入這兩個引數後,事情就不一樣了,先考慮特殊情況下,如果γ

γ和ββ分別等於此batch的標準差和均值,那麼yiyi​不就還原到歸一化前的xx了嗎,也即是縮放平移到了歸一化前的分佈,相當於batchnormbatchnorm沒有起作用,$ β$ 和γ

γ分別稱之為 平移引數和縮放參數 。這樣就保證了每一次資料經過歸一化後還保留的有學習來的特徵,同時又能完成歸一化這個操作,加速訓練。

先用一個簡單的程式碼舉個小栗子:

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

mean var

var測試的時候直接拿來用就可以了,不用計算均值和方差。

running_mean = momentum * running_mean + (1 - momentum) * x_mean
running_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

Batchnorm的可以使用的程式碼3

3,如下:
程式碼來自知乎,這裡加入註釋幫助閱讀。

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−μ)σ+β

σγ(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可以放心的使用大學習率,但是使用了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