Batch Normalization
簡介
BN在2015年由Google提出(Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift),主要來解決上面描述的問題,幫助模型加速收斂。
先來看一下paper中提到的BN algorithm,再來試著從兩個不同的角度來看待為何要使用BN:
其實單看algo的話並不難理解,不過為何要這樣做?下面會試著針對幾個角度來探討:
- Internal Covariate Shift
- Gradient Vanishing/Exploding
- Feature Scaling
從Internal Covariate Shift的角度看BN
Internal Covariate Shift
在訓練Deep learning model時,每一層的forward propagation都會造成參數分布的變化,進而後面的layer都要重新去適應這個變動,造成Internal Covariate Shift(ICS)。
- 實際上,BN之所以有用,可能和減少ICS無關,關於細節可參考延伸討論: BN真的跟ICS有關係?,不過這裡的介紹仍然會先以2015年這篇的觀點來敘述。
簡單介紹一下什麼是Covariate Shift:假設$q_1$是testing set中某個樣本點的機率密度,$q_0$是training set中一個樣本點的機率密度。ML/DL的任務就是找到夠小的$loss(\theta)$使得$P(y|x, \theta)$最大。
當我們在$q_0(x)$上找到一組最好的參數$\theta’$時,是否能夠保證$q_1(x)$上也會是最好呢?
- 傳統ML問題會假設訓練集和測試集是$i.i.d$的,不過現實上往往這個假設不成立,這個testing set分布和training set分布不同的現象就稱之Covariate Shift
- 而在deep learning task中,由於這個現象會發生在每一層內,所以稱之Internal Covariate Shift(ICS)
如何解決Internal Covariate Shift(ICS)?
要減少ICS的問題,就得先理解他產生的原因:由於weights更新導致模型中每一層的input distribution改變
既然如此,我們就可以透過固定每一層間的分布來減少ICS的問題。
傳統上ICS的問題,可以透過PCA Whitening(或ZCA weighting)來針對每一層的input進行變換(關於PCA/ZCA whitening的細節這裡就不討論,只是讓大家知道有這種方法),使得輸入的特徵之間具有相同的mean和variance,並去除特徵之間的相關性,但是PCA whitening有著一些缺點:
- 對於DL的task,每個epoch都要對每一層做一次PCA whitening,cost太大
- PCA whitening改變了每一層參數之間的分布,可能改變了layer中數據的表達能力,造成喪失一些資訊
既然cost太大,那就試著來簡化一下:
- 只單獨對每個feature來進行normalization,使得每個feature的mean為0,variance為1
- 加個線性變換,讓數據盡可能地恢復本身的表達能力
- 深度學習的task通常是以mini-batch為單位,所以做的時候也是基於mini-batch的基礎上進行計算
好的,以上三點合起來就是Batch Normalization,對照一下應該不難理解一開始提供的algo。而其中的$\gamma$、$\beta$,就是為了讓分布盡可能地回復本身的表達能力而做的線性變換
延伸討論: BN真的跟ICS有關係?
其實前面講了這麼多,關於BN減少了ICS的看法在2018年的How Does Batch Normalization Help Optimization?這篇就被被打臉了,這篇文章的觀點認為BN之所以有用是因為Normalization而不是減少ICS。
论文中提到并没有发现BN跟internal covariate shift有半毛钱关系,实验方法是加入了一个“noise batch normalization”的对照实验,在batch normalization中加入噪声,使得z发生协变量偏移,但是这样的noise batch normalization的效果也远优于未经BN的网络,由此反驳了ICS的观点。
论文中认为BN使得优化问题更加平滑,由此梯度的预测性更强,所以可以允许使用更大学习率,并且加快网络收敛。在使用BN后loss和gradient的Lipschitzness都提升了(也就是更平滑了)。
從Gradient Vanishing/Exploding的角度看BN
Saturating Non-linearity & Non-Saturating Nonlinearitiy
根據2012年的一篇paper: ImageNet Classification with Deep Convolutional Neural Networks將DL模型內常被使用的activation function分成了非飽和非線性(non-saturating nonlinearities) 和 飽和非線性(saturating nonlinearities) 兩種,定義如下:
- f is non-staurating iff
- f is staurating iff f is non-staurating
簡單來說,飽和函數會壓縮input的value,所以DL常用的activation function裡:
- ReLU是非飽和非線性函數
- Sigmoid、Tanh是飽和非線性函數
Saturating Non-linearity如何影響Training?
這邊參考莫煩的Normalization介紹,考慮activation function是tanh的情況
上圖是activation function的圖,中間那張是數值沒有經過normalization的分布,最下面那張則是數值經過normalization後的分布。此時會有什麼影響?
- 如果沒有做normalization,大部分的值經過tanh後就會趨近於+1或-1,也就是進入了所謂的飽和區域,造成neuron對於輸入的變化不再敏感
- 進入飽和區也就代表Gradient Vanishing(因為沒有斜率),降低訓練速度
如果做了Normalization,就可以大幅地避免值都進入飽和區的狀況,如下圖:
- 上兩張是進入tanh前有/沒有Normalization的數據分布;而下兩張是進入tanh後有/沒有Normalization的數據分布,可以看到有Normalization的數據能有更好的分布
從數學公式來看呢?
用數學式子來解釋一下Gradient Vanishing/Exploding,實際上,在back propagation時,該層的gradient是要乘上該層的weights的,這會造成什麼問題?
假設第l層的forward propagation為:
那麼該層的backward將會是
延伸一下,考慮從l層到k層的情況,backward propagation就會是
針對$\prod_{i=k+1}^l{w_i}$來討論一下:
- 如果$w_i$大部分很小,那整體的值就會變成0(Ex: $0.9^{100}$)
- 如果$w_i$大部分很大,那整體的值就會爆炸(Ex: $1.1^{100}$)
那如果加上了BN呢?
此時backward就會是
透過BN,各層的輸出和backward的值都會先做了縮放,減少了Gradient Vanishing/Exploding的狀況
從Feature Scaling的角度看BN
如果不同的feature之間的值域相差很大時,數值大的feature的參數會對結果的影響更大,導致訓練過程中的結果往往被數值大的feature影響。要解決這個問題就必須在訓練過程中,為不同的方向設置不同的學習率,可是這樣很不方便,此時就會做feature scaling。
上圖取自李宏毅老師的BN介紹,其中w1和w2是兩種不同的feature,如果直接拿來訓練會因為數值本身的range造成loss cruve圖呈現橢圓狀(左圖)。透過BN做feature scaling後,可期望loss curve呈現圓形狀(右圖),這樣會有助於幫助模型更快收斂。
延伸討論: BN在實務上的設計
在實務上,training時會以batch為單位去算平均跟標準差,但在testing時資料是一筆一筆進來的,沒辦法這樣做,所以通常會採用training時的平均跟變異數。
所以,在pytorch中提供了2種mode來區別這件事: train()
以及eval()
- 當有使用BN的模型在training時前面就會加上
train()
- 而在testing時,透過在前面加上
eval()
告訴模型不要再重新去取平均跟變異數,而是使用training好的值- 如果沒有加上的話,一旦batch_size過小,可能就會造成data產生大量的誤差
延伸討論: BN到底要加在activation function前還是後面?
實際上有放在前面效果比較好的,也有放在後面效果比較好的。
這是個萬年不解謎題,大家都在吵吵吵,各自提出各自的看法,到現在我也還不知道正確答案。
總之,我採用了知乎上面某位網友的回答並稍微整理了一下內容給大家參考(比較令我信服的回答):
在BN的原始论文中,BN是放在非线性激活层前面的(arXiv:1502.03167v3,第5页)
> We add the BN transform immediately before the nonlinearity(注意:before的黑体是我加的,为了突出重点)
但是,François Chollet爆料说BN论文的作者之一Christian把BN放在ReLU后面
> I can guarantee that recent code written by Christian applies relu before BN.
另外,Jeremy Howard直接主张把BN放在非线性激活后面
> You want the batchnorm after the non-linearity, and before the dropout.
“应该”放在前面还是后面?这个“应该”其实有两种解释:
- 放在前面还是后面比较好?
- 为什么要放在前面还是后面?
对于第一问,目前在实践上,倾向于把BN放在ReLU后面。也有评测表明BN放ReLU后面效果更好。
对于第二问,实际上,我们目前对BN的机制仍然不是特别清楚,这里只能尝试做些(玄学)解释,不一定正确。
考虑一些飽和激活函数(tanh、sigmoid)。函数图像的两端,相对于x的变化,y的变化都很小。也就是说,容易出现梯度衰减的问题。那么,如果在tanh或sigmoid之前,进行一些normalization处理,就可以缓解梯度衰减的问题。我想这可能也是最初的BN论文选择把BN层放在非线性激活之前的原因。
但是ReLU和它们完全不一样啊(因為是非飽和激活函数,比較沒有上述梯度衰减問題)。
实际上,最初的BN论文虽然也在使用ReLU的Inception上进行了试验,但首先研究的是sigmoid激活。因此,试验ReLU的,我猜想作者可能就顺便延续了之前把BN放前面的配置,而没有单独针对ReLU进行处理。
总结一下,BN层的作用机制也许是通过平滑隐藏层输入的分布,帮助随机梯度下降的进行,缓解随机梯度下降权重更新对后续层的负面影响。因此,实际上,无论是放非线性激活之前,还是之后,也许都能发挥这个作用。只不过,取决于具体激活函数的不同,效果也许有一点差别(比如,对sigmoid和tanh而言,放非线性激活之前,也许顺便还能缓解sigmoid/tanh的梯度衰减问题,而对ReLU而言,这个平滑作用经ReLU“扭曲”之后也许有所衰弱)。
延伸討論: BN的batch size應該要多大/多小?
這個問題本質上和batch size對於training的影響是一樣的問題,所以直接從探討batch size大小對於訓練的影響這個角度來回答:
- batch size太小,訓練時的震盪會很嚴重,有機會跳出local min,但不利於收斂;而對於BN來說,找到的平均和變異則不能代表全局平均和變異,因此效果會不好
- batch size太大,震盪會比較小,但容易接近全局(這裡的全局是指training set而不是數據母體)的結果,導致不同batch的梯度方向没有任何變化,容易陷入local min
所以結論就是,只探討batch_size的話,答案是不能太大也不能太小(廢話)。
但如果一併探討BN的話,過小會造成效果不好,大一點其實不影響BN,但會有local min問題。
結論
綜上所述,BN之所以有用是因為:
- 減緩了梯度消失/爆炸的問題
- 改善了非飽和模型不易訓練的問題
- 減少weight initialization的影響(因為假設初始化的不好,可以透過後面的BN來進行改善)
- 起到對資料做normalization的作用
(這裡並沒有提到”減緩ICS”這項特性是因為根據2018年提出的paper提到,實際上BN之所以有用可能和減緩ICS無關)
Normalization的各種變形
常用的Normalization方法除了上面講了這麼多的Batch Normalization外,還有
- Layer Normalization(LN,2016年)
- Instance Normalization(IN,2017年)
- Group Normalization(GN,2018年)
簡單用一張圖來理解他們之間的差異:
現在假設feature map的維度為: $(N, C, H, W)$
- N: batch size
- C: channel
- H, W: height & width
- BN是在batch方向上,对N、H、W做normalization,保留C的维度。
- 對於較小的batch_size效果較差(因為數量不夠代表平均跟變異)
- BN適用於固定深度的Network,Ex:CNN;不適用於RNN
- LN在channel方向上,对C、H、W做normalization,主要用在RNN
- IN在image pixel上,对H、W做normalization,用在style transfer
- GN先將channel分组,然後再做normalization。
打到這邊也累了所以提個大概就好,所以下面只介紹LN,其餘的有興趣的在自己去看xD
Layer Normalization
根據上面BN介紹了那麼一大坨,可以知道BN的缺點在於: 當batch_size過小時,batch的平均和變異並不能代表全局的平均和變異,此時BN的效果就會很差。
這個情形在RNN上尤其明顯,由於RNN是的input長度是動態的,所以考慮以下的情形:
- 當N過小的時候,對於T>4的數據只有一筆(淺藍色),無法代表全局資訊
- 當在testing時遇到比training set裡面任何一筆都還要長的input時,此時並沒有任何一個平均和變異可以拿來做BN
所以,LN的主要想法是以單一的input為單位,去計算平均和變異(BN是以batch為單位),假設$H$是一層中隱藏節點的數量,$l$是Model的layer數,$\alpha$是每一個neuron:
然後就可以做Normaluization,注意到這裡的計算和batch_size完全無關,所以避開了BN在batch_size過小時會遇到的問題。
此外,LN裡面也提供了類似BN的兩個trainable parameters: gain(g)和bias(b)來盡可能地還原被破壞的訊息。
總結
這篇文章主要從不同的角度來探討為何BN對於Training有幫助,以及延伸探討實務上應該如何使用BN,最後也簡介了BN的各種變形及應用。
Reference
- Batch Normalization原理与实战
- 【机器学习】covariate shift现象的解释
- PCA Whitening
- Batch Normalization的通俗解释
- How Does Batch Normalization Help Optimization?
- ImageNet Classification with Deep Convolutional Neural Networks
- 李宏毅老師的BN介紹
- 莫煩的Normalization介紹
- Batch-normalized 应该放在非线性激活层的前面还是后面?
- 常用的 Normalization 方法:BN、LN、IN、GN