? ? ? ?在學(xué)習(xí)源碼的過程中,發(fā)現(xiàn)在搭建網(wǎng)絡(luò)架構(gòu)的時候,經(jīng)常會用到bn算法(即batch_normalization,批標(biāo)準(zhǔn)化),所以有必要深入去研究其中的奧妙。bn算法的提出在2015年的論文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》。
? ? ? ?正如論文開始所說:由于訓(xùn)練過程中各層輸入的分布隨著前幾層參數(shù)的變化而變化,使得訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)變得復(fù)雜。這通過要求較低的學(xué)習(xí)速率和仔細(xì)的參數(shù)初始化來減慢訓(xùn)練,并且使得訓(xùn)練具有飽和非線性的模型變得非常困難。我們將這種現(xiàn)象稱為 internal covariate shift。顯而易見,為了解決這個問題,作者提出了bn,所以前提,我們得先理解何為internal covariate shift。
將在訓(xùn)練過程中深度網(wǎng)絡(luò)內(nèi)部節(jié)點(diǎn)分布的變化稱為internal covariate shift。這個internal可以看作隱層
作者以sigmoid為例,z = g(wu+b),其中u為輸入層,w和b為權(quán)重矩陣和偏移向量(即網(wǎng)絡(luò)層里需要學(xué)習(xí)的參數(shù))
隨著|x|的增加,g'(x)會趨向于0,這意味著,在所有維數(shù)上,除了那些絕對值較小的 x=wu+b ,下降到 u 的梯度將消失,模型將緩慢訓(xùn)練。
x 受 W,b 和前面所有層的參數(shù)的影響,在訓(xùn)練過程中這些參數(shù)的變化可能會將 x 的許多維度移入非線性的飽和狀態(tài)并減慢收斂速度。這種影響隨著網(wǎng)絡(luò)深度的增加而放大。
我們都知道在train網(wǎng)絡(luò)之前,會對數(shù)據(jù)進(jìn)行歸一化處理,為的是保持訓(xùn)練和測試數(shù)據(jù)的分布相同,而在神經(jīng)網(wǎng)絡(luò)內(nèi)部,每一層我們都需要有輸出和輸出,除了對原始數(shù)據(jù)的標(biāo)準(zhǔn)化處理,在經(jīng)過網(wǎng)絡(luò)每一層計(jì)算后的數(shù)據(jù),它們的分布是不同的。網(wǎng)絡(luò)的訓(xùn)練,需要去學(xué)習(xí)適應(yīng)不同的數(shù)據(jù)分布,明顯造成的后果就是收斂慢,效果不佳。
另一方面,網(wǎng)絡(luò)前面的參數(shù)變更,會隨著網(wǎng)絡(luò)的深度,其影響不斷累積增大,所以說只要有某一層的數(shù)據(jù)分布發(fā)生變化,后面層的數(shù)據(jù)輸入分布也會不同,結(jié)合前面說的,為了解決中間層數(shù)據(jù)分布改變的情況。
總的來說,bn的操作很簡單,也很容易理解。就是在網(wǎng)絡(luò)的每一層輸入之前,做了一個歸一化處理,就是作用于(wu+b),即bn(wu+b),然后再接激活函數(shù)(非線性映射)。而且,很多論文的代碼里,bn算作了獨(dú)立的一層。
公式如下:
E[x]為均值,sqrt(var)為標(biāo)準(zhǔn)差,然后加上scale和shift兩個可訓(xùn)練的變量
而Batch Normalization可使各隱藏層輸入的均值和方差為任意值。實(shí)際上,從激活函數(shù)的角度來說,如果各隱藏層的輸入均值在靠近0的區(qū)域即處于激活函數(shù)的線性區(qū)域,這樣不利于訓(xùn)練好的非線性神經(jīng)網(wǎng)絡(luò),得到的模型效果也不會太好。這也解釋了為什么需要用 γ 和 β 來做進(jìn)一步處理
y(k)就是經(jīng)過bn處理后的輸出了,論文里提到,當(dāng)
可以恢復(fù)原始的激活,也就是這一層所學(xué)到的原始特征
對于批處理的推理如下,整個思路也很清晰:
到了這里,其實(shí)腦子還是很懵,一知半解,還是屬于抽象的理解,對于其中還是很多疑惑
想要更好的理解,還是得回到數(shù)學(xué)的層面去看
一般來說,如果模型的輸入特征不相關(guān)且滿足標(biāo)準(zhǔn)正態(tài)分布時,模型的表現(xiàn)一般較好。
在訓(xùn)練神經(jīng)網(wǎng)絡(luò)模型時,我們可以事先將特征去相關(guān)并使得它們滿足一個比較好的分布,
這樣,模型的第一層網(wǎng)絡(luò)一般都會有一個比較好的輸入特征,
但是隨著模型的層數(shù)加深,網(wǎng)絡(luò)的非線性變換使得每一層的結(jié)果變得相關(guān)了,且不再滿足分布。
甚至,這些隱藏層的特征分布或許已經(jīng)發(fā)生了偏移。
在經(jīng)過激活層之前,就是x = wu+b,也就是激活函數(shù)的輸入,隨著網(wǎng)絡(luò)加深,x的會逐漸向兩端靠攏(紅色箭頭的地方),那么會造成什么后果,我們可以看下這兩個函數(shù)的導(dǎo)數(shù):
Sigmoid' = sigmoid*(1-sigmoid)
Tanh' = 1-tanh^2
不難看出,在函數(shù)的兩側(cè)梯度變化趨向于0,且變化很慢,這會導(dǎo)致在Back propagation的時候梯度消失,也就是說收斂越來越慢。 在這里感覺也可以理解為:
至于為什么要這么做最后的公式(scale,offset),論文里有提到
簡單地對圖層的每個輸入進(jìn)行規(guī)范化可能會更改圖層可以表示的內(nèi)容。
例如,正則化一個 sigmoid 的輸入將限制他們非線性的線性狀態(tài)
作者也沒具體說明,在我也是不太明白其原理,只能理解為上面的變換會改變原來學(xué)習(xí)到的特征分布,因此加入了可學(xué)習(xí)的γ和β,為什么是可學(xué)習(xí)的,感覺應(yīng)該是讓網(wǎng)絡(luò)自己找到一個在正態(tài)變換后不破壞原特征分布的平衡狀態(tài)。
好吧,很玄,希望有小伙伴能給我解答一下。
測試
? ? ? ?到這里,大致的原理就這樣了,至于后面如何反向傳播,以及推理過程中 均值mean 和 方差var 的設(shè)置,就不再寫下去,網(wǎng)上也很多解讀的資源。
理解了理論之后,結(jié)合實(shí)戰(zhàn)操作才有意思,先附一張圖.
在cnn中,batch_normalization就是取同一個channel上所有批次做處理,粗略畫了這個示意圖
代表batch = 3,channel = 2 , W和H = 2
下面用了numpy,pytorch以及tensorflow的函數(shù)計(jì)算batch_normalization
先看一下pytorch的函數(shù)以及描述
nn.batchnorm2d / tf.layers.batch_normalization
最需要注意的是pytorch和tensorflow 的公式不太一樣,所以結(jié)果會有稍微差異,在測試的時候,我們只需要把其他因素統(tǒng)一就好:
# 導(dǎo)庫
import numpy as np
import tensorflow as tf
import torch.nn as nn
# 創(chuàng)建一個隨機(jī)矩陣
test1 = np.random.rand(4,3,2,2)
test1 = test1.astype(np.float32)
test =test1
先算numpy均值,方差與pytorch(注意設(shè)置momentum = 1,affine=False)
a1 = 0
v = 0
d = []
std = []
for i in range(3):
a1 = test1[:,i,:,:]
d.append(a1.sum())
d = np.array(d)
mean = d/16
m = nn.BatchNorm2d(3,affine=False,momentum=1)
input = torch.from_numpy(test1)
output = m(input)
# 均值mean
print('torch 尺寸:',input.shape)
print('pytorch 均值:',m.running_mean.data[0],m.running_mean.data[1],m.running_mean.data[2])
print('手算 均值:',mean)
#numpy 計(jì)算: np.mean(np.mean(np.mean(test1,axis=0),axis=1),axis=1)
# 方差var = (x-mean) / n
for i in range(3):
v = test1[:,i,:,:]-mean[i]
v = ((v**2).sum()/16)**0.5
std.append(v)
std = np.array(std)
# numpy計(jì)算 :np.std(test1[1,2,3])
print('標(biāo)準(zhǔn)差:',std)
#bathnorm
for i in range(3):
test[:,i,:,:] = (test1[:,i,:,:]-mean[i])/(std[i]+1e-5)
print(test[1])
print(output[1])
輸出:
torch 尺寸: torch.Size([4, 3, 2, 2])
pytorch 均值: tensor(0.4218) tensor(0.5399) tensor(0.3418)
手算 均值: [0.42182693 0.5398935 0.34179664]
標(biāo)準(zhǔn)差: [0.26444062 0.2462885 0.22490181]
numpy結(jié)果:
[[[-1.0535254 -0.4905532 ]
[-1.3194345 -0.22114275]]
[[ 0.5717635 1.1570975 ]
[-1.1665905 -1.5158345 ]]
[[ 1.6828372 0.8369611 ]
[ 0.32095668 -0.89949685]]]
pytorch結(jié)果:
tensor([[[-1.0535, -0.4905],
[-1.3194, -0.2211]],
[[ 0.5717, 1.1570],
[-1.1665, -1.5158]],
[[ 1.6827, 0.8369],
[ 0.3209, -0.8994]]])
可以看出bn的計(jì)算是一樣的,接下來看一下tf版本的,在這里我加上了tf.nn.batch_normalitization
需要將test維度轉(zhuǎn)成(N, H, W, C)
x = test1
b = test
x = np.transpose(x,(0,2,3,1))
b = np.transpose(b,(0,2,3,1))
axis = list(range(len(x)-1))
x = tf.convert_to_tensor(x)
wb_mean, wb_var = tf.nn.moments(x,axis)
scale = tf.Variable(tf.ones([3]))
offset = tf.Variable(tf.zeros([3]))
variance_epsilon = 1e-5
Wx_plus_b = tf.nn.batch_normalization(x, wb_mean, wb_var, offset, scale, variance_epsilon)
Wx_plus_b1 = (x - wb_mean) / tf.sqrt(wb_var + variance_epsilon)
Wx_plus_b1 = Wx_plus_b1 * scale + offset
Wx_plus_b2 = tf.layers.batch_normalization(x,momentum=1,scale=False,epsilon= 1e-5)
? 最后對比各種計(jì)算結(jié)果
with tf.Session() as sess:
tf.global_variables_initializer().run()
print('nn.bn: \n',sess.run(Wx_plus_b[1]))
print('手算bn:\n',sess.run(Wx_plus_b1[1]))
print('layers.bn: \n',sess.run(Wx_plus_b2[1]))
print('numpy bn: \n',b[1])
print('output: \n',output[1])
輸出:
nn.bn:
[[[-1.0535603 0.57178396 1.6829035 ]
[-0.49056947 1.1571388 0.83699405]]
[[-1.319478 -1.1666319 0.32096925]
[-0.22115016 -1.5158883 -0.8995324 ]]]
手算bn:
[[[-1.0535601 0.57178396 1.6829034 ]
[-0.49056944 1.1571388 0.836994 ]]
[[-1.319478 -1.1666319 0.32096922]
[-0.22115014 -1.5158883 -0.8995324 ]]]
layers.bn:
[[[-1.0535202 0.57176065 1.6828288 ]
[-0.49055076 1.1570916 0.8369569 ]]
[[-1.319428 -1.1665846 0.32095507]
[-0.22114165 -1.5158268 -0.8994923 ]]]
numpy bn:
[[[-1.0535254 0.5717635 1.6828372 ]
[-0.4905532 1.1570975 0.8369611 ]]
[[-1.3194345 -1.1665905 0.32095668]
[-0.22114275 -1.5158345 -0.89949685]]]
output:
tensor([[[-1.0535, -0.4905],
[-1.3194, -0.2211]],
[[ 0.5717, 1.1570],
[-1.1665, -1.5158]],
[[ 1.6827, 0.8369],
[ 0.3209, -0.8994]]])
結(jié)語
對于batchnorm,還是有很多地方不懂或者不理解的。
文章寫得比較亂,也并不嚴(yán)謹(jǐn),有需要改正的也請指出