CNN 批歸一化(Batch Normalization)



批歸一化方法方法(Batch Normalization,BatchNorm)是由Ioffe和Szegedy於2015年提出的,已被廣泛應用在深度學習中,其目的是對神經網絡中間層的輸出進行標准化處理,使得中間層的輸出更加穩定。

通常我們會對神經網絡的數據進行標准化處理,處理后的樣本數據集滿足均值為0,方差為1的統計分布,這是因為當輸入數據的分布比較固定時,有利於算法的穩定和收斂。對於深度神經網絡來說,由於參數是不斷更新的,即使輸入數據已經做過標准化處理,但是對於比較靠后的那些層,其接收到的輸入仍然是劇烈變化的,通常會導致數值不穩定,模型很難收斂。BatchNorm能夠使神經網絡中間層的輸出變得更加穩定,並有如下三個優點:

使學習快速進行(能夠使用較大的學習率)

降低模型對初始值的敏感性

從一定程度上抑制過擬合

BatchNorm主要思路是在訓練時按mini-batch為單位,對神經元的數值進行歸一化,使數據的分布滿足均值為0,方差為1。具體計算過程如下:

1. 計算mini-batch內樣本的均值

 

 

 

其中x(i)表示mini-batch中的第i個樣本。

例如輸入mini-batch包含3個樣本,每個樣本有2個特征,分別是:

 

 

 

對每個特征分別計算mini-batch內樣本的均值:

 

 

 

則樣本均值是:

 

 

 

(說了那么多就是計算均值)

 

 

2. 計算mini-batch內樣本的方差

 

 

 

上面的計算公式先計算一個批次內樣本的均值μB和方差σ2B,然后再對輸入數據做歸一化,將其調整成均值為0,方差為1的分布。

對於上述給定的輸入數據x(1),x(2),x(3),可以計算出每個特征對應的方差:

 

 

 

則樣本方差是:

 

 

 

(一般以同屬性為一組,直接求方差,上述計算都是腦海中的高中數學)

 

 

3. 計算標准化之后的輸出

 

 

 

其中ϵ是一個微小值(例如1e−7),其主要作用是為了防止分母為0。

對於上述給定的輸入數據x(1),x(2),x(3),可以計算出標准化之后的輸出:

 

 

 

讀者可以自行驗證由x^(1),x^(2),x^(3)構成的mini-batch,是否滿足均值為0,方差為1的分布。
如果強行限制輸出層的分布是標准化的,可能會導致某些特征模式的丟失,所以在標准化之后,BatchNorm會緊接着對數據做縮放和平移。

 

 

 

其中γ和β是可學習的參數,可以賦初始值γ=1,β=0,在訓練過程中不斷學習調整。

上面列出的是BatchNorm方法的計算邏輯,下面針對兩種類型的輸入數據格式分別進行舉例。飛槳支持輸入數據的維度大小為2、3、4、5四種情況,這里給出的是維度大小為2和4的示例。

示例一: 當輸入數據形狀是[N,K]時,一般對應全連接層的輸出,示例代碼如下所示。
這種情況下會分別對K的每一個分量計算N個樣本的均值和方差,數據和參數對應如下:

輸入 x, [N, K]
輸出 y, [N, K]
均值 μB,[K, ]
方差 σ2B,[K, ]
縮放參數γ,[K, ]
平移參數β,[K, ]
 

# 輸入數據形狀是 [N, K]時的示例
import numpy as np

import paddle
import paddle.fluid as fluid
from paddle.fluid.dygraph.nn import BatchNorm
# 創建數據
data = np.array([[1,2,3], [4,5,6], [7,8,9]]).astype('float32')
# 使用BatchNorm計算歸一化的輸出
with fluid.dygraph.guard():
# 輸入數據維度[N, K],num_channels等於K
bn = BatchNorm(num_channels=3) 
x = fluid.dygraph.to_variable(data)
y = bn(x)
print('output of BatchNorm Layer: \n {}'.format(y.numpy()))

# 使用Numpy計算均值、方差和歸一化的輸出
# 這里對第0個特征進行驗證
a = np.array([1,4,7])
a_mean = a.mean()
a_std = a.std()
b = (a - a_mean) / a_std
print('std {}, mean {}, \n output {}'.format(a_mean, a_std, b))

# 建議讀者對第1和第2個特征進行驗證,觀察numpy計算結果與paddle計算結果是否一致
output of BatchNorm Layer: 
[[-1.2247438 -1.2247438 -1.2247438]
[ 0. 0. 0. ]
[ 1.2247438 1.2247438 1.2247438]]
std 4.0, mean 2.449489742783178, 
output [-1.22474487 0. 1.22474487]

 


示例二: 當輸入數據形狀是[N,C,H,W]時, 一般對應卷積層的輸出,示例代碼如下所示。
這種情況下會沿着C這一維度進行展開,分別對每一個通道計算N個樣本中總共N×H×W個像素點的均值和方差,數據和參數對應如下:

輸入 x, [N, C, H, W]
輸出 y, [N, C, H, W]
均值 μB,[C, ]
方差 σ2B​,  [C, ]
縮放參數γ, [C, ]
平移參數β, [C, ]
小竅門:

可能有讀者會問:“BatchNorm里面不是還要對標准化之后的結果做仿射變換嗎,怎么使用Numpy計算的結果與BatchNorm算子一致?” 這是因為BatchNorm算子里面自動設置初始值γ=1,β=0,這時候仿射變換相當於是恆等變換。在訓練過程中這兩個參數會不斷的學習,這時仿射變換就會起作用。

 

# 輸入數據形狀是[N, C, H, W]時的batchnorm示例
import numpy as np

import paddle
import paddle.fluid as fluid
from paddle.fluid.dygraph.nn import BatchNorm

# 設置隨機數種子,這樣可以保證每次運行結果一致
np.random.seed(100)
# 創建數據
data = np.random.rand(2,3,3,3).astype('float32')
# 使用BatchNorm計算歸一化的輸出
with fluid.dygraph.guard():
# 輸入數據維度[N, C, H, W],num_channels等於C
bn = BatchNorm(num_channels=3)
x = fluid.dygraph.to_variable(data)
y = bn(x)
print('input of BatchNorm Layer: \n {}'.format(x.numpy()))
print('output of BatchNorm Layer: \n {}'.format(y.numpy()))

# 取出data中第0通道的數據,
# 使用numpy計算均值、方差及歸一化的輸出
a = data[:, 0, :, :]
a_mean = a.mean()
a_std = a.std()
b = (a - a_mean) / a_std
print('channel 0 of input data: \n {}'.format(a))
print('std {}, mean {}, \n output: \n {}'.format(a_mean, a_std, b))

# 提示:這里通過numpy計算出來的輸出
# 與BatchNorm算子的結果略有差別,
# 因為在BatchNorm算子為了保證數值的穩定性,
# 在分母里面加上了一個比較小的浮點數epsilon=1e-05
input of BatchNorm Layer: 
[[[[0.54340494 0.2783694 0.4245176 ]
[0.84477615 0.00471886 0.12156912]
[0.67074907 0.82585275 0.13670659]]

[[0.5750933 0.89132196 0.20920213]
[0.18532822 0.10837689 0.21969749]
[0.9786238 0.8116832 0.17194101]]

[[0.81622475 0.27407375 0.4317042 ]
[0.9400298 0.81764936 0.33611196]
[0.17541045 0.37283206 0.00568851]]]


[[[0.25242636 0.7956625 0.01525497]
[0.5988434 0.6038045 0.10514768]
[0.38194343 0.03647606 0.89041156]]

[[0.98092085 0.05994199 0.89054596]
[0.5769015 0.7424797 0.63018394]
[0.5818422 0.02043913 0.21002658]]

[[0.5446849 0.76911515 0.25069523]
[0.2858957 0.8523951 0.9750065 ]
[0.8848533 0.35950786 0.59885895]]]]
output of BatchNorm Layer: 
[[[[ 0.4126078 -0.46198368 0.02029109]
[ 1.4071034 -1.3650038 -0.97940934]
[ 0.832831 1.344658 -0.9294571 ]]

[[ 0.2520175 1.2038351 -0.84927964]
[-0.9211378 -1.1527538 -0.8176896 ]
[ 1.4666051 0.96413004 -0.961432 ]]

[[ 0.9541142 -0.9075856 -0.36629617]
[ 1.37925 0.9590063 -0.6945517 ]
[-1.2463869 -0.5684581 -1.8291974 ]]]


[[[-0.5475932 1.2450331 -1.3302356 ]
[ 0.5955492 0.6119205 -1.0335984 ]
[-0.12019944 -1.2602081 1.5576957 ]]

[[ 1.473519 -1.2985382 1.2014993 ]
[ 0.25745988 0.7558342 0.41783488]
[ 0.27233088 -1.4174379 -0.8467981 ]]

[[ 0.02166975 0.79234385 -0.98786545]
[-0.86699003 1.0783203 1.4993572 ]
[ 1.1897788 -0.6142123 0.20769882]]]]
channel 0 of input data: 
[[[0.54340494 0.2783694 0.4245176 ]
[0.84477615 0.00471886 0.12156912]
[0.67074907 0.82585275 0.13670659]]

[[0.25242636 0.7956625 0.01525497]
[0.5988434 0.6038045 0.10514768]
[0.38194343 0.03647606 0.89041156]]]
std 0.4183686077594757, mean 0.3030227720737457, 
output: 
[[[ 0.41263014 -0.46200886 0.02029219]
[ 1.4071798 -1.3650781 -0.9794626 ]
[ 0.8328762 1.3447311 -0.92950773]]

[[-0.54762304 1.2451009 -1.3303081 ]
[ 0.5955816 0.61195374 -1.0336547 ]
[-0.12020606 -1.2602768 1.5577804 ]]]
 

 

- 預測時使用BatchNorm

(預測用訓練時保存的相應結果)

上面介紹了在訓練過程中使用BatchNorm對一批樣本進行歸一化的方法,但如果使用同樣的方法對需要預測的一批樣本進行歸一化,則預測結果會出現不確定性。

例如樣本A、樣本B作為一批樣本計算均值和方差,與樣本A、樣本C和樣本D作為一批樣本計算均值和方差,得到的結果一般來說是不同的。那么樣本A的預測結果就會變得不確定,這對預測過程來說是不合理的。解決方法是在訓練過程中將大量樣本的均值和方差保存下來,預測時直接使用保存好的值而不再重新計算。實際上,在BatchNorm的具體實現中,訓練時會計算均值和方差的移動平均值。在飛槳中,默認是采用如下方式計算:

 

 

 

在訓練過程的最開始將saved_μB和saved_σ2B​設置為0,每次輸入一批新的樣本,計算出μB和σ2B,然后通過上面的公式更新saved_μB​和saved_σ2B,在訓練的過程中不斷的更新它們的值,並作為BatchNorm層的參數保存下來。預測的時候將會加載參數saved_μB​和saved_σ2B​,用他們來代替μB​和σ2B​。
————————————————
版權聲明:本文為CSDN博主「aiAIman」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/coolyoung520/article/details/109075379


免責聲明!

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



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