深度学习之卷积神经网络(8)BatchNorm层
深度學習之卷積神經網絡(8)BatchNorm層
- BatchNorm層概念
- BatchNorm層實現
- 1. 向前傳播
- 2. 反向更新
- 3. BN層實現
- 4. 完整代碼
卷積神經網絡的出現,網絡參數量大大減低,使得幾十層的深層網絡稱為可能。然而,在殘差網絡出現之前,網絡的加深使得網絡訓練變得十分不穩定,甚至出現網絡長時間不更新甚至不收斂的現象,同時網絡對超參數比較敏感,超參數的微量擾動也會導致網絡的訓練軌跡完全改變。
?2015年,Google研究人員Sergey Ioffe等提出了一種參數標準化(Normalize)的手段,并基于參數標準化設計了Batch Normalization(簡寫為BatchNorm,或BN)層。BN層的提出,使得網絡的超參數的設定更加自由,比如更大的學習率、更隨意的網絡初始化等,同時網絡的收斂速度更快,性能也更好。BN層提出后便廣泛地應用在各種深度網絡模型上,卷積層、BN層、ReLU層、池化層一度成為網絡模型的標配單元塊,通過堆疊Conv-BN-ReLU-Pooling方式往往可以獲得不錯的模型性能。
BatchNorm層概念
?首先我們來探索,為什么需要對網絡中的數據進行標準化操作?這個問題很難從理論層面解釋透徹,即使是BN層的作者給出的解釋也未必讓所有人信服。與其糾結其緣由,不如通過具體問題來感受數據標準化后的好處。
?考慮Sigmoid激活函數和它的梯度分布,如下圖所示,Sigmoid函數在x∈[?2,2]x∈[-2,2]x∈[?2,2]區間的導數值在[0.1,0.25][0.1,0.25][0.1,0.25]區間分布; 當x>2x>2x>2或x<?2x<-2x<?2時,Sigmoid函數的導數變得很小,逼近于0,從而容易出現梯度彌散現象。為了避免因為輸入較大或者較小而導致Sigmoid函數出現梯度彌散現象,將函數輸入x標準化映射到0附近的一段較小區間將變得非常重要,可以從下圖看到,通過標準化重映射后,值被映射在0附近,此處的導數值不至于過小,從而不容易出現梯度彌散現象。這時使用標準化手段收益的一個例子。
?我們再看另一個例子??紤]2個輸入節點的線性模型,如圖所示:
L=a=x1w1+x2w2+b\mathcal L=a=x_1 w_1+x_2 w_2+bL=a=x1?w1?+x2?w2?+b
討論如下兩種輸入分布下的問題:
- 輸入x1∈[1,10],x2∈[1,10]x_1∈[1,10],x_2∈[1,10]x1?∈[1,10],x2?∈[1,10]
- 輸入x1∈[1,10],x2∈[100,1000]x_1∈[1,10],x_2∈[100,1000]x1?∈[1,10],x2?∈[100,1000]
由于模型相對簡單,可以繪制出兩種x1x_1x1?、x2x_2x2?下,函數的損失等高線圖,圖(b)是x1∈[1,10],x2∈[100,1000]x_1∈[1,10],x_2∈[100,1000]x1?∈[1,10],x2?∈[100,1000]時的某條優化軌跡線示意,圖(c)是x1∈[1,10],x2∈[1,10]x_1∈[1,10],x_2∈[1,10]x1?∈[1,10],x2?∈[1,10]時的某條優化軌跡線示意,圖中的圓環中心即為全局極值點。
考慮到:
?L?w1=x1?L?w2=x2\frac{?\mathcal L}{?w_1}=x_1\\ \frac{?\mathcal L}{?w_2}=x_2?w1??L?=x1??w2??L?=x2?
當x1x_1x1?、x2x_2x2?輸入分布相近時,?L?w1\frac{?\mathcal L}{?w_1}?w1??L?、?L?w2\frac{?\mathcal L}{?w_2}?w2??L?偏導數值相當,函數的優化軌跡如圖(c)所示; 當x1x_1x1?、x2x_2x2?輸入分布差距較大時,比如x1?x2x_1?x_2x1??x2?,則:
?L?w1??L?w2\frac{?\mathcal L}{?w_1}?\frac{?\mathcal L}{?w_2}?w1??L???w2??L?
損失函數等勢線在w2w_2w2?軸更加陡峭,某條可能的優化軌跡如圖(b)所示。對比兩條優化軌跡線可以觀察到,x1x_1x1?、x2x_2x2?分布相近時圖(c)中收斂更加快速,優化軌跡更理想。
?通過上述的兩個例子,我們能夠經驗性歸納出: 網絡層輸入xxx分布相近,并且分布在較小范圍內時(如0附近),更有利于函數的優化。那么如何保證輸入xxx分布相近呢?數據標準化可以實現此目的,通過數據標準化操作可以將數據xxx映射到x^\hat{x}x^:
x^=x?μrσr2+?\hat{x}=\frac{x-μ_r}{\sqrt{σ_r^2+?}}x^=σr2?+??x?μr??
其中μrμ_rμr?、σr2σ_r^2σr2?來自統計的所有數據的均值和方差,???是為防止出現除0錯誤而設置的較小的數字,如1e?81e-81e?8。
在基于Batch的訓練階段,如何獲取每個網絡層所有輸入的統計數據μrμ_rμr?、σr2σ_r^2σr2?呢?考慮Batch內部的均值μBμ_BμB?和方差σB2σ_B^2σB2?:
μB=1m∑i=1mxiμ_B=\frac{1}{m} \sum_{i=1}^mx_i μB?=m1?i=1∑m?xi?
σB2=1m∑i=1m(xi?μB)2σ_B^2=\frac{1}{m} \sum_{i=1}^m(x_i-μ_B)^2 σB2?=m1?i=1∑m?(xi??μB?)2
可以視為近似于μrμ_rμr?、σr2σ_r^2σr2?,其中mmm為Batch樣本數。因此,在訓練階段,通過
x^train=xtrain?μBσB2+?\hat{x}_{train}=\frac{x_{train}-μ_B}{\sqrt{σ_B^2+?}}x^train?=σB2?+??xtrain??μB??
標準化輸入,并記錄每個Batch的統計數據μBμ_BμB?、σB2σ_B^2σB2?,用于統計真實的全局μrμ_rμr?、σr2σ_r^2σr2?。
?在測試階段,根據記錄的每個Batch的μBμ_BμB?、σB2σ_B^2σB2?估計出所有訓練數據的μrμ_rμr?、σr2σ_r^2σr2?,按著
x^test=xtest?μrσr2+?\hat{x}_{test}=\frac{x_{test}-μ_r}{\sqrt{σ_r^2+?}}x^test?=σr2?+??xtest??μr??
將每層的輸入標準化。
?上述的標準化運算并沒有引入額外的待優化變量,μrμ_rμr?、σr2σ_r^2σr2?和μBμ_BμB?、σB2σ_B^2σB2?均由統計得到,不需要參與梯度更新。實際上為了提高BN層的表達能力,BN層作者引入了“scale and shift”技巧,將x^\hat{x}x^變量再次映射變換:
x~=x^?γ+β\tilde{x}=\hat{x}\cdotγ+βx~=x^?γ+β
其中γγγ參數實現對標準化后的x^\hat{x}x^再次進行縮放,βββ參數實現對標準化后的x^\hat{x}x^進行平移,不同的是,γγγ、βββ參數均由反向傳播算法自動優化,實現網絡層“按需”縮放平移數據的分布的目的。
?下面我們來學習在TensorFlow中實現的BN層的方法。
BatchNorm層實現
1. 向前傳播
?我們將BN層的輸入記為xxx,輸出記為x^\hat{x}x^。分訓練階段和測試階段來討論前向傳播過程。
?訓練階段: 首先計算當前Batch的μBμ_BμB?、σB2σ_B^2σB2?,根據
x^train=xtrain?μBσB2+??γ+β\hat{x}_{train}=\frac{x_{train}-μ_B}{\sqrt{σ_B^2+?}}\cdotγ+βx^train?=σB2?+??xtrain??μB???γ+β
計算BN層的輸出。
?同時按照
μr←momentum?μr+(1?momentum)?μBσr2←momentum?σr2+(1?momentum)?σB2μ_r←\text{momentum}\cdotμ_r+(1-\text{momentum})\cdotμ_B\\ σ_r^2←\text{momentum}\cdotσ_r^2+(1-\text{momentum})\cdotσ_B^2μr?←momentum?μr?+(1?momentum)?μB?σr2?←momentum?σr2?+(1?momentum)?σB2?
迭代更新全局訓練數據的統計值μrμ_rμr?和σr2σ_r^2σr2?,其中momentum\text{momentum}momentum是需要設置一個超參數,用于平衡μrμ_rμr?、σr2σ_r^2σr2?的更新幅度:
當momentum=0\text{momentum}=0momentum=0時,μrμ_rμr?和σr2σ_r^2σr2?直接被設置為最新一個Batch的μBμ_BμB?和σB2σ_B^2σB2?;
當momentum=1\text{momentum}=1momentum=1時,μrμ_rμr?和σr2σ_r^2σr2?保持不變,忽略最新一個Batch的μBμ_BμB?和σB2σ_B^2σB2?;
在TensorFlow中,momentum\text{momentum}momentum默認設置為0.99。
?測試階段: BN層根據
x~test=xtest?μrσr2+??γ+β\tilde{x}_{test}=\frac{x_{test}-μ_r}{\sqrt{σ_r^2+?}}\cdotγ+βx~test?=σr2?+??xtest??μr???γ+β
計算出x~test\tilde{x}_{test}x~test?,其中μrμ_rμr?、σr2σ_r^2σr2?、γγγ、βββ均來自訓練階段統計或優化的結果,在測試階段直接使用,并不會更新這些參數。
2. 反向更新
?在訓練模式下的反向更新階段,反向傳播算法根據損失L\mathcal LL求解梯度?L?γ\frac{?\mathcal L}{?γ}?γ?L?和?L?β\frac{?\mathcal L}{?β}?β?L?,并按著梯度更新法則自動優化γγγ、βββ參數。
?需要注意的是,對于2D特征圖輸入X:[b,h,w,c]\boldsymbol X:[b,h,w,c]X:[b,h,w,c],BN層并不是計算每個點的μBμ_BμB?、σB2σ_B^2σB2?,而是在通道軸ccc上面統計每個通道上面所有數據的μBμ_BμB?、σB2σ_B^2σB2?,因此μBμ_BμB?、σB2σ_B^2σB2?是每個通道上所有其它維度的均值和方差。以shape為[100,32,32,3][100,32,32,3][100,32,32,3]為例,在通道軸ccc上面的均值計算如下:
運行結果如下:
數據有ccc個通道數,則有ccc個均值產生。
?除了在ccc軸上面統計數據μBμ_BμB?、σB2σ_B^2σB2?的方式,我們也很容易將其推廣至其它維度計算均值的方式,如圖所示:
- Layer Norm: 統計每個樣本的所有特征的均值和方差
- Instance Norm: 統計每個樣本的每個通道上特征的均值和方差
- Group Norm: 將ccc通道分成若干組,統計每個樣本的通道組內的特征均值和方差
?上面提到的Normalization方法均由獨立的幾篇論文提出,并在某些應用上驗證了其相當于或者由于BatchNorm算法的效果。由此可見沒深度學習算法研究并非難于上青天,只要多思考、多鍛煉算法工程能力,人人都有機會發表創新性成果。
3. BN層實現
?在TensorFlow中,通過layers.BatchNormalization()類可以非常方便地實現BN層:
# 創建BN層 layer = layers.BatchNormalization()與全連接層、卷積層不同,BN層的訓練階段和測試階段的行為不同,需要通過設置training標志位來區分訓練模式還是測試模式。
?以LeNet-5的網絡模型為例,在卷積層后添加BN層,代碼如下:
在訓練階段,需要設置網絡的參數training=True以區分BN層是訓練還是測試模型,代碼如下:
在測試階段,需要設置training=False,避免BN層采用錯誤的行為,代碼如下:
4. 完整代碼
加入BN層的LeNet-5完整代碼如下:
import osfrom Chapter08 import metrics from Chapter08.metrics import loss_meteros.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers, Sequential, losses, optimizers, datasets# 加載MNIST數據集 def preprocess(x, y):# 預處理函數x = tf.cast(x, dtype=tf.float32) / 255y = tf.cast(y, dtype=tf.int32)return x, y# 加載MNIST數據集 (x, y), (x_test, y_test) = keras.datasets.mnist.load_data() # 創建數據集 batchsz = 128 train_db = tf.data.Dataset.from_tensor_slices((x, y)) train_db = train_db.map(preprocess).shuffle(60000).batch(batchsz).repeat(10) test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)) test_db = test_db.batch(batchsz)network = Sequential([ # 網絡容器layers.Conv2D(6, kernel_size=3, strides=1), # 第一個卷積層,6個3×3卷積核# 插入BN層layers.BatchNormalization(),layers.MaxPooling2D(pool_size=2, strides=2), # 高寬各減半的池化層layers.ReLU(), # 激活函數layers.Conv2D(16, kernel_size=2, strides=1), # 第二個卷積層,16個3×3卷積核# 插入BN層layers.BatchNormalization(),layers.MaxPooling2D(pool_size=2, strides=2), # 高寬各減半的池化層layers.ReLU(), # 激活函數layers.Flatten(), # 打平層,方便全連接層處理layers.Dense(120, activation='relu'), # 全連接層,120個節點# 此處也可以插入BN層layers.Dense(84, activation='relu'), # 全連接層,84個節點# 此處也可以插入BN層layers.Dense(10), # 全連接層,10個節點 ])# build一次網格模型,給輸入x的形狀,其中4為隨意給的batchsize network.build(input_shape=(4, 28, 28, 1)) # 統計網絡信息 network.summary()# 創建損失函數的類,在實際計算時直接調用實例即可 criteon = losses.CategoricalCrossentropy(from_logits=True)optimizer = optimizers.Adam(lr=0.01)# 訓練部分實現如下 # 構建梯度記錄環境 # 訓練20個epochdef train_epoch(epoch):for step, (x, y) in enumerate(train_db): # 循環優化with tf.GradientTape() as tape:# 插入通道維度,=>[b,28,28,1]x = tf.expand_dims(x, axis=3)# 向前計算,獲得10類別的概率分布,[b,784] => [b,10]out = network(x, training=True)# 真實標簽one-hot編碼,[b] => [b,10]y_onehot = tf.one_hot(y, depth=10)# 計算交叉熵損失函數,標量loss = criteon(y_onehot, out)# 自動計算梯度grads = tape.gradient(loss, network.trainable_variables)# 自動更新參數optimizer.apply_gradients(zip(grads, network.trainable_variables))if step % 100 == 0:print(step, 'loss:', loss_meter.result().numpy())loss_meter.reset_states()# 計算準確度if step % 100 == 0:# 記錄預測正確的數量,總樣本數量correct, total = 0, 0for x, y in test_db: # 遍歷所有訓練集樣本# 插入通道維度,=>[b,28,28,1]x = tf.expand_dims(x, axis=3)# 向前計算,獲得10類別的概率分布,[b,784] => [b,10]out = network(x, training=False)# 真實的流程時先經過softmax,再argmax# 但是由于softmax不改變元素的大小相對關系,故省去pred = tf.argmax(out,axis=-1)y = tf.cast(y, tf.int64)# 統計預測樣本總數correct += float(tf.reduce_sum(tf.cast(tf.equal(pred, y), tf.float32)))# 統計預測樣本總數total += x.shape[0]# 計算準確率print('test acc:', correct/total)def train():for epoch in range(30):train_epoch(epoch)if __name__ == '__main__':train()總結
以上是生活随笔為你收集整理的深度学习之卷积神经网络(8)BatchNorm层的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深度学习之卷积神经网络(7)池化层
- 下一篇: 保险中的几个“时间”及生日单