batch normalization
20210702
深度學(xué)習(xí)中的五種歸一化(BN、LN、IN、GN和SN)方法簡介
https://blog.csdn.net/u013289254/article/details/99690730
https://cloud.tencent.com/developer/article/1500846
常用的 Normalization 方法:BN、LN、IN、GN
常用的 Normalization 方法:BN、LN、IN、GN(附代碼&鏈接)
https://mp.weixin.qq.com/s/j4LS4rDE5nfRy3CcWAre4A
https://blog.csdn.net/kyle1314608/article/details/118422495
白化
https://mp.weixin.qq.com/s/eeIF9zWf-dWmvXvbXmfhCg
深入理解Batch Normalization
20210614
https://www.cnblogs.com/wlk12580/p/13651673.html
pytorch torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
此函數(shù)的作用是對(duì)輸入的每個(gè)batch數(shù)據(jù)做歸一化處理,目的是數(shù)據(jù)合理分布,加速計(jì)算過程,函數(shù)為:
num_features:為輸入的數(shù)據(jù)的通道數(shù),
eps:使分母不為零,保持?jǐn)?shù)據(jù)的穩(wěn)定
momentum:用于在訓(xùn)練時(shí)對(duì)均值和方差的估計(jì)
affine:為True時(shí)表示γ和β是可學(xué)習(xí)的參數(shù),為False表示γ和β是不可學(xué)習(xí)的參數(shù),此時(shí)γ=1,β=0;
track_running_stats=True;整個(gè)batch的方差和均值
https://blog.csdn.net/qq_39777550/article/details/108038677
重點(diǎn)
一、背景意義
本篇博文主要講解2015年深度學(xué)習(xí)領(lǐng)域,非常值得學(xué)習(xí)的一篇文獻(xiàn):《Batch Normalization: Accelerating Deep Network Training by ?Reducing Internal Covariate Shift》,這個(gè)算法目前已經(jīng)被大量的應(yīng)用,最新的文獻(xiàn)算法很多都會(huì)引用這個(gè)算法,進(jìn)行網(wǎng)絡(luò)訓(xùn)練,可見其強(qiáng)大之處非同一般啊。
近年來深度學(xué)習(xí)捷報(bào)連連、聲名鵲起,隨機(jī)梯度下架成了訓(xùn)練深度網(wǎng)絡(luò)的主流方法。盡管隨機(jī)梯度下降法對(duì)于訓(xùn)練深度網(wǎng)絡(luò)簡單高效,但是它有個(gè)毛病,就是需要我們?nèi)藶榈娜ミx擇參數(shù),比如學(xué)習(xí)率、參數(shù)初始化、權(quán)重衰減系數(shù)、Drop out比例等。這些參數(shù)的選擇對(duì)訓(xùn)練結(jié)果至關(guān)重要,以至于我們很多時(shí)間都浪費(fèi)在這些的調(diào)參上。那么學(xué)完這篇文獻(xiàn)之后,你可以不需要那么刻意的慢慢調(diào)整參數(shù)。BN算法(Batch Normalization)其強(qiáng)大之處如下:
(1)你可以選擇比較大的初始學(xué)習(xí)率,讓你的訓(xùn)練速度飆漲。以前還需要慢慢調(diào)整學(xué)習(xí)率,甚至在網(wǎng)絡(luò)訓(xùn)練到一半的時(shí)候,還需要想著學(xué)習(xí)率進(jìn)一步調(diào)小的比例選擇多少比較合適,現(xiàn)在我們可以采用初始很大的學(xué)習(xí)率,然后學(xué)習(xí)率的衰減速度也很大,因?yàn)檫@個(gè)算法收斂很快。當(dāng)然這個(gè)算法即使你選擇了較小的學(xué)習(xí)率,也比以前的收斂速度快,因?yàn)樗哂锌焖儆?xùn)練收斂的特性;
(2)你再也不用去理會(huì)過擬合中drop out、L2正則項(xiàng)參數(shù)的選擇問題,采用BN算法后,你可以移除這兩項(xiàng)了參數(shù),或者可以選擇更小的L2正則約束參數(shù)了,因?yàn)锽N具有提高網(wǎng)絡(luò)泛化能力的特性;
(3)再也不需要使用使用局部響應(yīng)歸一化層了(局部響應(yīng)歸一化是Alexnet網(wǎng)絡(luò)用到的方法,搞視覺的估計(jì)比較熟悉),因?yàn)锽N本身就是一個(gè)歸一化網(wǎng)絡(luò)層;
(4)可以把訓(xùn)練數(shù)據(jù)徹底打亂(防止每批訓(xùn)練的時(shí)候,某一個(gè)樣本都經(jīng)常被挑選到,文獻(xiàn)說這個(gè)可以提高1%的精度,這句話我也是百思不得其解啊)。
原理開始講解算法前,先來思考一個(gè)問題:我們知道在神經(jīng)網(wǎng)絡(luò)訓(xùn)練開始前,都要對(duì)輸入數(shù)據(jù)做一個(gè)歸一化處理,那么具體為什么需要?dú)w一化呢?歸一化后有什么好處呢?原因在于神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)過程本質(zhì)(重點(diǎn))就是為了學(xué)習(xí)數(shù)據(jù)分布,一旦訓(xùn)練數(shù)據(jù)與測試數(shù)據(jù)的分布不同,那么網(wǎng)絡(luò)的泛化能力也大大降低;另外一方面,一旦每批訓(xùn)練數(shù)據(jù)的分布各不相同(batch 梯度下降),那么網(wǎng)絡(luò)就要在每次迭代都去學(xué)習(xí)適應(yīng)不同的分布,這樣將會(huì)大大降低網(wǎng)絡(luò)的訓(xùn)練速度,這也正是為什么我們需要對(duì)數(shù)據(jù)都要做一個(gè)歸一化預(yù)處理的原因。
對(duì)于深度網(wǎng)絡(luò)的訓(xùn)練是一個(gè)復(fù)雜的過程,只要網(wǎng)絡(luò)的前面幾層發(fā)生微小的改變,那么后面幾層就會(huì)被累積放大下去。一旦網(wǎng)絡(luò)某一層的輸入數(shù)據(jù)的分布發(fā)生改變,那么這一層網(wǎng)絡(luò)就需要去適應(yīng)學(xué)習(xí)這個(gè)新的數(shù)據(jù)分布,所以如果訓(xùn)練過程中,訓(xùn)練數(shù)據(jù)的分布一直在發(fā)生變化,那么將會(huì)影響網(wǎng)絡(luò)的訓(xùn)練速度。
我們知道網(wǎng)絡(luò)一旦train起來,那么參數(shù)就要發(fā)生更新,除了輸入層的數(shù)據(jù)外(因?yàn)檩斎雽訑?shù)據(jù),我們已經(jīng)人為的為每個(gè)樣本歸一化),后面網(wǎng)絡(luò)每一層的輸入數(shù)據(jù)分布是一直在發(fā)生變化的,因?yàn)樵谟?xùn)練的時(shí)候,前面層訓(xùn)練參數(shù)的更新將導(dǎo)致后面層輸入數(shù)據(jù)分布的變化。以網(wǎng)絡(luò)第二層為例:網(wǎng)絡(luò)的第二層輸入,是由第一層的參數(shù)和input計(jì)算得到的,而第一層的參數(shù)在整個(gè)訓(xùn)練過程中一直在變化,因此必然會(huì)引起后面每一層輸入數(shù)據(jù)分布的改變。我們把網(wǎng)絡(luò)中間層在訓(xùn)練過程中,數(shù)據(jù)分布的改變稱之為:“Internal ?Covariate?Shift”。Paper所提出的算法,就是要解決在訓(xùn)練過程中,中間層數(shù)據(jù)分布發(fā)生改變的情況,于是就有了Batch??Normalization,這個(gè)牛逼算法的誕生。
二、初識(shí)BN(Batch??Normalization)
1、BN概述
就像激活函數(shù)層、卷積層、全連接層、池化層一樣,BN(Batch Normalization)也屬于網(wǎng)絡(luò)的一層。在前面我們提到網(wǎng)絡(luò)除了輸出層外,其它層因?yàn)榈蛯泳W(wǎng)絡(luò)在訓(xùn)練的時(shí)候更新了參數(shù),而引起后面層輸入數(shù)據(jù)分布的變化。這個(gè)時(shí)候我們可能就會(huì)想,如果在每一層輸入的時(shí)候,再加個(gè)預(yù)處理操作那該有多好啊,比如網(wǎng)絡(luò)第三層輸入數(shù)據(jù)X3(X3表示網(wǎng)絡(luò)第三層的輸入數(shù)據(jù))把它歸一化至:均值0、方差為1,然后再輸入第三層計(jì)算,這樣我們就可以解決前面所提到的“Internal?Covariate?Shift”的問題了。
而事實(shí)上,paper的算法本質(zhì)原理就是這樣:在網(wǎng)絡(luò)的每一層輸入的時(shí)候,又插入了一個(gè)歸一化層,也就是先做一個(gè)歸一化處理,然后再進(jìn)入網(wǎng)絡(luò)的下一層。不過文獻(xiàn)歸一化層,可不像我們想象的那么簡單,它是一個(gè)可學(xué)習(xí)、有參數(shù)的網(wǎng)絡(luò)層。既然說到數(shù)據(jù)預(yù)處理,下面就先來復(fù)習(xí)一下最強(qiáng)的預(yù)處理方法:白化。
2、預(yù)處理操作選擇
說到神經(jīng)網(wǎng)絡(luò)輸入數(shù)據(jù)預(yù)處理,最好的算法莫過于白化預(yù)處理。然而白化計(jì)算量太大了,很不劃算,還有就是白化不是處處可微的,所以在深度學(xué)習(xí)中,其實(shí)很少用到白化。經(jīng)過白化預(yù)處理后,數(shù)據(jù)滿足條件:a、特征之間的相關(guān)性降低,這個(gè)就相當(dāng)于pca;b、數(shù)據(jù)均值、標(biāo)準(zhǔn)差歸一化,也就是使得每一維特征均值為0,標(biāo)準(zhǔn)差為1。如果數(shù)據(jù)特征維數(shù)比較大,要進(jìn)行PCA,也就是實(shí)現(xiàn)白化的第1個(gè)要求,是需要計(jì)算特征向量,計(jì)算量非常大,于是為了簡化計(jì)算,作者忽略了第1個(gè)要求,僅僅使用了下面的公式進(jìn)行預(yù)處理,也就是近似白化預(yù)處理:
公式簡單粗糙,但是依舊很牛逼。因此后面我們也將用這個(gè)公式,對(duì)某一個(gè)層網(wǎng)絡(luò)的輸入數(shù)據(jù)做一個(gè)歸一化處理。需要注意的是,我們訓(xùn)練過程中采用batch 隨機(jī)梯度下降,上面的E(xk)指的是每一批訓(xùn)練數(shù)據(jù)神經(jīng)元xk的平均值;然后分母就是每一批數(shù)據(jù)神經(jīng)元xk激活度的一個(gè)標(biāo)準(zhǔn)差了。
三、BN算法實(shí)現(xiàn)
1、BN算法概述
經(jīng)過前面簡單介紹,這個(gè)時(shí)候可能我們會(huì)想當(dāng)然的以為:好像很簡單的樣子,不就是在網(wǎng)絡(luò)中間層數(shù)據(jù)做一個(gè)歸一化處理嘛,這么簡單的想法,為什么之前沒人用呢?然而其實(shí)實(shí)現(xiàn)起來并不是那么簡單的。其實(shí)如果是僅僅使用上面的歸一化公式,對(duì)網(wǎng)絡(luò)某一層A的輸出數(shù)據(jù)做歸一化,然后送入網(wǎng)絡(luò)下一層B,這樣是會(huì)影響到本層網(wǎng)絡(luò)A所學(xué)習(xí)到的特征的。打個(gè)比方,比如我網(wǎng)絡(luò)中間某一層學(xué)習(xí)到特征數(shù)據(jù)本身就分布在S型激活函數(shù)的兩側(cè),你強(qiáng)制把它給我歸一化處理、標(biāo)準(zhǔn)差也限制在了1,把數(shù)據(jù)變換成分布于s函數(shù)的中間部分,這樣就相當(dāng)于我這一層網(wǎng)絡(luò)所學(xué)習(xí)到的特征分布被你搞壞了,這可怎么辦?于是文獻(xiàn)使出了一招驚天地泣鬼神的招式:變換重構(gòu),引入了可學(xué)習(xí)參數(shù)γ、β,這就是算法關(guān)鍵之處:
?
每一個(gè)神經(jīng)元xk都會(huì)有一對(duì)這樣的參數(shù)γ、β。這樣其實(shí)當(dāng):
、
是可以恢復(fù)出原始的某一層所學(xué)到的特征的。因此我們引入了這個(gè)可學(xué)習(xí)重構(gòu)參數(shù)γ、β,讓我們的網(wǎng)絡(luò)可以學(xué)習(xí)恢復(fù)出原始網(wǎng)絡(luò)所要學(xué)習(xí)的特征分布。(重點(diǎn))最后Batch?Normalization網(wǎng)絡(luò)層的前向傳導(dǎo)過程公式就是:
?
上面的公式中m指的是mini-batch?size。
2、源碼實(shí)現(xiàn)
m = K.mean(X, axis=-1, keepdims=True)#計(jì)算均值std = K.std(X, axis=-1, keepdims=True)#計(jì)算標(biāo)準(zhǔn)差X_normed = (X - m) / (std + self.epsilon)#歸一化out = self.gamma * X_normed + self.beta#重構(gòu)變換
3、實(shí)戰(zhàn)使用
(1)可能學(xué)完了上面的算法,你只是知道它的一個(gè)訓(xùn)練過程,一個(gè)網(wǎng)絡(luò)一旦訓(xùn)練完了,就沒有了min-batch這個(gè)概念了。測試階段我們一般只輸入一個(gè)測試樣本,看看結(jié)果而已。因此測試樣本,前向傳導(dǎo)的時(shí)候,上面的均值u、標(biāo)準(zhǔn)差σ?要哪里來?其實(shí)網(wǎng)絡(luò)一旦訓(xùn)練完畢,參數(shù)都是固定的,這個(gè)時(shí)候即使是每批訓(xùn)練樣本進(jìn)入網(wǎng)絡(luò),那么BN層計(jì)算的均值u、和標(biāo)準(zhǔn)差都是固定不變的。我們可以采用這些數(shù)值來作為測試樣本所需要的均值、標(biāo)準(zhǔn)差,于是最后測試階段的u和σ 計(jì)算公式如下:
上面簡單理解就是:對(duì)于均值來說直接計(jì)算所有batch u值的平均值;然后對(duì)于標(biāo)準(zhǔn)偏差采用每個(gè)batch?σB的無偏估計(jì)。最后測試階段,BN的使用公式就是:
(2)根據(jù)文獻(xiàn)說,BN可以應(yīng)用于一個(gè)神經(jīng)網(wǎng)絡(luò)的任何神經(jīng)元上。文獻(xiàn)主要是把BN變換,置于網(wǎng)絡(luò)激活函數(shù)層的前面。在沒有采用BN的時(shí)候,激活函數(shù)層是這樣的:
z=g(Wu+b)
也就是我們希望一個(gè)激活函數(shù),比如s型函數(shù)s(x)的自變量x是經(jīng)過BN處理后的結(jié)果。因此前向傳導(dǎo)的計(jì)算公式就應(yīng)該是:
z=g(BN(Wu+b))
其實(shí)因?yàn)槠脜?shù)b經(jīng)過BN層后其實(shí)是沒有用的,最后也會(huì)被均值歸一化,當(dāng)然BN層后面還有個(gè)β參數(shù)作為偏置項(xiàng),所以b這個(gè)參數(shù)就可以不用了。因此最后把BN層+激活函數(shù)層就變成了:
z=g(BN(Wu))
四、Batch Normalization在CNN中的使用
通過上面的學(xué)習(xí),我們知道BN層是對(duì)于每個(gè)神經(jīng)元做歸一化處理,甚至只需要對(duì)某一個(gè)神經(jīng)元進(jìn)行歸一化,而不是對(duì)一整層網(wǎng)絡(luò)的神經(jīng)元進(jìn)行歸一化。既然BN是對(duì)單個(gè)神經(jīng)元的運(yùn)算,那么在CNN中卷積層上要怎么搞?假如某一層卷積層有6個(gè)特征圖,每個(gè)特征圖的大小是100*100,這樣就相當(dāng)于這一層網(wǎng)絡(luò)有6*100*100個(gè)神經(jīng)元,如果采用BN,就會(huì)有6*100*100個(gè)參數(shù)γ、β,這樣豈不是太恐怖了。因此卷積層上的BN使用,其實(shí)也是使用了類似權(quán)值共享的策略,把一整張?zhí)卣鲌D當(dāng)做一個(gè)神經(jīng)元進(jìn)行處理。
卷積神經(jīng)網(wǎng)絡(luò)經(jīng)過卷積后得到的是一系列的特征圖,如果min-batch?sizes為m,那么網(wǎng)絡(luò)某一層輸入數(shù)據(jù)可以表示為四維矩陣(m,f,p,q),m為min-batch?sizes,f為特征圖個(gè)數(shù),p、q分別為特征圖的寬高。在cnn中我們可以把每個(gè)特征圖看成是一個(gè)特征處理(一個(gè)神經(jīng)元),因此在使用Batch?Normalization,mini-batch?size 的大小就是:m*p*q,于是對(duì)于每個(gè)特征圖都只有一對(duì)可學(xué)習(xí)參數(shù):γ、β。說白了吧,這就是相當(dāng)于求取所有樣本所對(duì)應(yīng)的一個(gè)特征圖的所有神經(jīng)元的平均值、方差,然后對(duì)這個(gè)特征圖神經(jīng)元做歸一化。下面是來自于keras卷積層的BN實(shí)現(xiàn)一小段主要源碼:
input_shape = self.input_shapereduction_axes = list(range(len(input_shape)))del reduction_axes[self.axis]broadcast_shape = [1] * len(input_shape)broadcast_shape[self.axis] = input_shape[self.axis]if train:m = K.mean(X, axis=reduction_axes)brodcast_m = K.reshape(m, broadcast_shape)std = K.mean(K.square(X - brodcast_m) + self.epsilon, axis=reduction_axes)std = K.sqrt(std)brodcast_std = K.reshape(std, broadcast_shape)mean_update = self.momentum * self.running_mean + (1-self.momentum) * mstd_update = self.momentum * self.running_std + (1-self.momentum) * stdself.updates = [(self.running_mean, mean_update),(self.running_std, std_update)]X_normed = (X - brodcast_m) / (brodcast_std + self.epsilon)else:brodcast_m = K.reshape(self.running_mean, broadcast_shape)brodcast_std = K.reshape(self.running_std, broadcast_shape)X_normed = ((X - brodcast_m) /(brodcast_std + self.epsilon))out = K.reshape(self.gamma, broadcast_shape) * X_normed + K.reshape(self.beta, broadcast_shape)
個(gè)人總結(jié):2015年個(gè)人最喜歡深度學(xué)習(xí)的一篇paper就是Batch Normalization這篇文獻(xiàn),采用這個(gè)方法網(wǎng)絡(luò)的訓(xùn)練速度快到驚人啊,感覺訓(xùn)練速度是以前的十倍以上,再也不用擔(dān)心自己這破電腦每次運(yùn)行一下,訓(xùn)練一下都要跑個(gè)兩三天的時(shí)間。另外這篇文獻(xiàn)跟空間變換網(wǎng)絡(luò)《Spatial Transformer Networks》的思想神似啊,都是一個(gè)變換網(wǎng)絡(luò)層。
參考文獻(xiàn):
1、《Batch Normalization: Accelerating Deep Network Training by ?Reducing Internal Covariate Shift》
2、《Spatial Transformer Networks》
3、https://github.com/fchollet/keras
20201203 1.nn.BatchNorm1d(num_features) 1.對(duì)小批量(mini-batch)的2d或3d輸入進(jìn)行批標(biāo)準(zhǔn)化(Batch Normalization)操作2.num_features:來自期望輸入的特征數(shù),該期望輸入的大小為'batch_size x num_features [x width]'意思即輸入大小的形狀可以是'batch_size x num_features' 和 'batch_size x num_features x width' 都可以。(輸入輸出相同)輸入Shape:(N, C)或者(N, C, L)輸出Shape:(N, C)或者(N,C,L)eps:為保證數(shù)值穩(wěn)定性(分母不能趨近或取0),給分母加上的值。默認(rèn)為1e-5。momentum:動(dòng)態(tài)均值和動(dòng)態(tài)方差所使用的動(dòng)量。默認(rèn)為0.1。affine:一個(gè)布爾值,當(dāng)設(shè)為true,給該層添加可學(xué)習(xí)的仿射變換參數(shù)。3.在每一個(gè)小批量(mini-batch)數(shù)據(jù)中,計(jì)算輸入各個(gè)維度的均值和標(biāo)準(zhǔn)差。gamma與beta是可學(xué)習(xí)的大小為C的參數(shù)向量(C為輸入大小)在訓(xùn)練時(shí),該層計(jì)算每次輸入的均值與方差,并進(jìn)行移動(dòng)平均。移動(dòng)平均默認(rèn)的動(dòng)量值為0.1。在驗(yàn)證時(shí),訓(xùn)練求得的均值/方差將用于標(biāo)準(zhǔn)化驗(yàn)證數(shù)據(jù)。 4.例子>>> # With Learnable Parameters>>> m = nn.BatchNorm1d(100) #num_features指的是randn(20, 100)中(N, C)的第二維C>>> # Without Learnable Parameters>>> m = nn.BatchNorm1d(100, affine=False)>>> input = autograd.Variable(torch.randn(20, 100)) #輸入Shape:(N, C)>>> output = m(input) #輸出Shape:(N, C)
2.nn.BatchNorm2d(num_features)
1.對(duì)小批量(mini-batch)3d數(shù)據(jù)組成的4d輸入進(jìn)行批標(biāo)準(zhǔn)化(Batch Normalization)操作2.num_features: 來自期望輸入的特征數(shù),該期望輸入的大小為'batch_size x num_features x height x width'(輸入輸出相同)輸入Shape:(N, C,H, W)輸出Shape:(N, C, H, W)eps: 為保證數(shù)值穩(wěn)定性(分母不能趨近或取0),給分母加上的值。默認(rèn)為1e-5。momentum: 動(dòng)態(tài)均值和動(dòng)態(tài)方差所使用的動(dòng)量。默認(rèn)為0.1。affine: 一個(gè)布爾值,當(dāng)設(shè)為true,給該層添加可學(xué)習(xí)的仿射變換參數(shù)。3.在每一個(gè)小批量(mini-batch)數(shù)據(jù)中,計(jì)算輸入各個(gè)維度的均值和標(biāo)準(zhǔn)差。gamma與beta是可學(xué)習(xí)的大小為C的參數(shù)向量(C為輸入大小)在訓(xùn)練時(shí),該層計(jì)算每次輸入的均值與方差,并進(jìn)行移動(dòng)平均。移動(dòng)平均默認(rèn)的動(dòng)量值為0.1。在驗(yàn)證時(shí),訓(xùn)練求得的均值/方差將用于標(biāo)準(zhǔn)化驗(yàn)證數(shù)據(jù)。4.例子>>> # With Learnable Parameters>>> m = nn.BatchNorm2d(100) #num_features指的是randn(20, 100, 35, 45)中(N, C,H, W)的第二維C>>> # Without Learnable Parameters>>> m = nn.BatchNorm2d(100, affine=False)>>> input = autograd.Variable(torch.randn(20, 100, 35, 45)) #輸入Shape:(N, C,H, W)>>> output = m(input)
3.nn.BatchNorm3d(num_features)
1.對(duì)小批量(mini-batch)4d數(shù)據(jù)組成的5d輸入進(jìn)行批標(biāo)準(zhǔn)化(Batch Normalization)操作2.num_features: 來自期望輸入的特征數(shù),該期望輸入的大小為'batch_size x num_features depth x height x width'(輸入輸出相同)輸入Shape:(N, C,D, H, W)輸出Shape:(N, C, D, H, W)eps: 為保證數(shù)值穩(wěn)定性(分母不能趨近或取0),給分母加上的值。默認(rèn)為1e-5。momentum: 動(dòng)態(tài)均值和動(dòng)態(tài)方差所使用的動(dòng)量。默認(rèn)為0.1。affine: 一個(gè)布爾值,當(dāng)設(shè)為true,給該層添加可學(xué)習(xí)的仿射變換參數(shù)。3.在每一個(gè)小批量(mini-batch)數(shù)據(jù)中,計(jì)算輸入各個(gè)維度的均值和標(biāo)準(zhǔn)差。gamma與beta是可學(xué)習(xí)的大小為C的參數(shù)向量(C為輸入大小)在訓(xùn)練時(shí),該層計(jì)算每次輸入的均值與方差,并進(jìn)行移動(dòng)平均。移動(dòng)平均默認(rèn)的動(dòng)量值為0.1。在驗(yàn)證時(shí),訓(xùn)練求得的均值/方差將用于標(biāo)準(zhǔn)化驗(yàn)證數(shù)據(jù)。4.例子>>> # With Learnable Parameters>>> m = nn.BatchNorm3d(100) #num_features指的是randn(20, 100, 35, 45, 10)中(N, C, D, H, W)的第二維C>>> # Without Learnable Parameters>>> m = nn.BatchNorm3d(100, affine=False) #num_features指的是randn(20, 100, 35, 45, 10)中(N, C, D, H, W)的第二維C>>> input = autograd.Variable(torch.randn(20, 100, 35, 45, 10)) #輸入Shape:(N, C, D, H, W) >>> output = m(input)
20201203
對(duì)特征維進(jìn)行歸一化
[batchsize feature] 用 BatchNorm1d
[batchsize feature H W] 用batchNorm2d
[batchsize feature X H W] x是什么 用 batchnorm3d
20201229
幾種batch normalization的使用
筆者近來在tensorflow中使用batch_norm時(shí),由于事先不熟悉其內(nèi)部的原理,因此將其錯(cuò)誤使用,從而出現(xiàn)了結(jié)果與預(yù)想不一致的結(jié)果。事后對(duì)其進(jìn)行了一定的調(diào)查與研究,在此進(jìn)行一些總結(jié)。
一、錯(cuò)誤使用及結(jié)果
筆者最先使用時(shí)只是了解到了在tensorflow中tf.layers.batch_normalization這個(gè)函數(shù),就在函數(shù)中直接將其使用,該函數(shù)中有一個(gè)參數(shù)為training,在訓(xùn)練階段賦值True,在測試階段賦值False。但是在訓(xùn)練完成后,出現(xiàn)了奇怪的現(xiàn)象時(shí),在training賦值為True時(shí),測試的正確率正常,但是training賦值為False時(shí),測試正確率就很低。上述錯(cuò)誤使用過程可以精簡為下列代碼段
is_traing = tf.placeholder(dtype=tf.bool)
input = tf.ones([1, 2, 2, 3])
output = tf.layers.batch_normalization(input, training=is_traing)
loss = ...
train_op = optimizer.minimize(loss)with tf.Session() as sess:sess.run(tf.global_variables_initializer())sess.run(train_op)
二、batch_normalization
下面首先粗略的介紹一下batch_normalization,這種歸一化方法的示意圖和算法如下圖,
總的來說就是對(duì)于同一batch的input,假設(shè)輸入大小為[batch_num, height, width, channel],逐channel地計(jì)算同一batch中所有數(shù)據(jù)的mean和variance,再對(duì)input使用mean和variance進(jìn)行歸一化,最后的輸出再進(jìn)行線性平移,得到batch_norm的最終結(jié)果。偽代碼如下:
for i in range(channel):x = input[:,:,:,i]mean = mean(x)variance = variance(x)x = (x - mean) / sqrt(variance)x = scale * x + offsetinput[:,:,:,i] = x
在實(shí)現(xiàn)的時(shí)候,會(huì)在訓(xùn)練階段記錄下訓(xùn)練數(shù)據(jù)中平均mean和variance,記為moving_mean和moving_variance,并在測試階段使用訓(xùn)練時(shí)的moving_mean和moving_variance進(jìn)行計(jì)算,這也就是參數(shù)training的作用。另外,在實(shí)現(xiàn)時(shí)一般使用一個(gè)decay系數(shù)來逐步更新moving_mean和moving_variance,moving_mean = moving_mean * decay + new_batch_mean * (1 - decay)
三、tensorflow中的三種實(shí)現(xiàn)
tensorflow中關(guān)于batch_norm現(xiàn)在有三種實(shí)現(xiàn)方式。
1、tf.nn.batch_normalization(最底層的實(shí)現(xiàn))
tf.nn.batch_normalization(x,mean,variance,offset,scale,variance_epsilon,name=None
)
該函數(shù)是一種最底層的實(shí)現(xiàn)方法,在使用時(shí)mean、variance、scale、offset等參數(shù)需要自己傳遞并更新,因此實(shí)際使用時(shí)還需自己對(duì)該函數(shù)進(jìn)行封裝,一般不建議使用,但是對(duì)了解batch_norm的原理很有幫助。
封裝使用的實(shí)例如下:
import tensorflow as tfdef batch_norm(x, name_scope, training, epsilon=1e-3, decay=0.99):""" Assume nd [batch, N1, N2, ..., Nm, Channel] tensor"""with tf.variable_scope(name_scope):size = x.get_shape().as_list()[-1]scale = tf.get_variable('scale', [size], initializer=tf.constant_initializer(0.1))offset = tf.get_variable('offset', [size])pop_mean = tf.get_variable('pop_mean', [size], initializer=tf.zeros_initializer(), trainable=False)pop_var = tf.get_variable('pop_var', [size], initializer=tf.ones_initializer(), trainable=False)batch_mean, batch_var = tf.nn.moments(x, list(range(len(x.get_shape())-1)))train_mean_op = tf.assign(pop_mean, pop_mean * decay + batch_mean * (1 - decay))train_var_op = tf.assign(pop_var, pop_var * decay + batch_var * (1 - decay))def batch_statistics():with tf.control_dependencies([train_mean_op, train_var_op]):return tf.nn.batch_normalization(x, batch_mean, batch_var, offset, scale, epsilon)def population_statistics():return tf.nn.batch_normalization(x, pop_mean, pop_var, offset, scale, epsilon)return tf.cond(training, batch_statistics, population_statistics)is_traing = tf.placeholder(dtype=tf.bool)
input = tf.ones([1, 2, 2, 3])
output = batch_norm(input, name_scope='batch_norm_nn', training=is_traing)with tf.Session() as sess:sess.run(tf.global_variables_initializer())saver = tf.train.Saver()saver.save(sess, "batch_norm_nn/Model")
在batch_norm中,首先先計(jì)算了x的逐通道的mean和var,然后將pop_mean和pop_var進(jìn)行更新,并根據(jù)是在訓(xùn)練階段還是測試階段選擇將當(dāng)批次計(jì)算的mean和var或者訓(xùn)練階段保存的mean和var與新定義的變量scale和offset一起傳遞給tf.nn.batch_normalization
2、tf.layers.batch_normalization
tf.layers.batch_normalization(inputs,axis=-1,momentum=0.99,epsilon=0.001,center=True,scale=True,beta_initializer=tf.zeros_initializer(),gamma_initializer=tf.ones_initializer(),moving_mean_initializer=tf.zeros_initializer(),moving_variance_initializer=tf.ones_initializer(),beta_regularizer=None,gamma_regularizer=None,beta_constraint=None,gamma_constraint=None,training=False,trainable=True,name=None,reuse=None,renorm=False,renorm_clipping=None,renorm_momentum=0.99,fused=None,virtual_batch_size=None,adjustment=None
)
該函數(shù)也就是筆者之前使用的函數(shù),在官方文檔中寫道
Note: when training, the moving_mean and moving_variance need to be updated. By default the update ops are placed in tf.GraphKeys.UPDATE_OPS, so they need to be added as a dependency to the train_op. Also, be sure to add any batch_normalization ops before getting the update_ops collection. Otherwise, update_ops will be empty, and training/inference will not work properly. For example:
x_norm = tf.layers.batch_normalization(x, training=training)# ...update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)with tf.control_dependencies(update_ops):train_op = optimizer.minimize(loss)
可以看到,與筆者之前的錯(cuò)誤實(shí)現(xiàn)方法的差異主要在
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)with tf.control_dependencies(update_ops):
這兩句話,同時(shí)可以看到在第一種方法tf.nn.batch_normalization的封裝過程中也用到了類似的處理方法,具體會(huì)在下一段進(jìn)行說明。
3、tf.contrib.layers.batch_norm(slim)
tf.contrib.layers.batch_norm(inputs,decay=0.999,center=True,scale=False,epsilon=0.001,activation_fn=None,param_initializers=None,param_regularizers=None,updates_collections=tf.GraphKeys.UPDATE_OPS,is_training=True,reuse=None,variables_collections=None,outputs_collections=None,trainable=True,batch_weights=None,fused=None,data_format=DATA_FORMAT_NHWC,zero_debias_moving_mean=False,scope=None,renorm=False,renorm_clipping=None,renorm_decay=0.99,adjustment=None
)
這種方法與tf.layers.batch_normalization的使用方法差不多,兩者最主要的差別在參數(shù)scale和centre的默認(rèn)值上,這兩個(gè)參數(shù)即是我們之前介紹原理時(shí)所說明的對(duì)input進(jìn)行mean和variance的歸一化之后采用的線性平移中的scale和offset,可以看到offset的默認(rèn)值兩者都是True,但是scale的默認(rèn)值前者為True后者為False,也就是說明在tf.contrib.layers.batch_norm中,默認(rèn)不對(duì)處理后的input進(jìn)行線性縮放,只是加一個(gè)偏移。
四、關(guān)于tf.GraphKeys.UPDATA_OPS
介紹到這里,還有兩個(gè)概念沒有介紹,一個(gè)是tf.GraphKeys.UPDATE_OPS,另一個(gè)是tf.control_dependencies。
1、tf.control_dependencies
首先我們先介紹tf.control_dependencies,該函數(shù)保證其轄域中的操作必須要在該函數(shù)所傳遞的參數(shù)中的操作完成后再進(jìn)行。請(qǐng)看下面一個(gè)例子。
import tensorflow as tf
a_1 = tf.Variable(1)
b_1 = tf.Variable(2)
update_op = tf.assign(a_1, 10)
add = tf.add(a_1, b_1)a_2 = tf.Variable(1)
b_2 = tf.Variable(2)
update_op = tf.assign(a_2, 10)
with tf.control_dependencies([update_op]):add_with_dependencies = tf.add(a_2, b_2)with tf.Session() as sess:sess.run(tf.global_variables_initializer())ans_1, ans_2 = sess.run([add, add_with_dependencies])print("Add: ", ans_1)print("Add_with_dependency: ", ans_2)輸出:
Add: 3
Add_with_dependency: 12
可以看到兩組加法進(jìn)行的對(duì)比,正常的計(jì)算圖在計(jì)算add時(shí)是不會(huì)經(jīng)過update_op操作的,因此在加法時(shí)a的值為1,但是采用tf.control_dependencies函數(shù),可以控制在進(jìn)行add前先完成update_op的操作,因此在加法時(shí)a的值為10,因此最后兩種加法的結(jié)果不同。
2、tf.GraphKeys.UPDATE_OPS
關(guān)于tf.GraphKeys.UPDATE_OPS,這是一個(gè)tensorflow的計(jì)算圖中內(nèi)置的一個(gè)集合,其中會(huì)保存一些需要在訓(xùn)練操作之前完成的操作,并配合tf.control_dependencies函數(shù)使用。
關(guān)于在batch_norm中,即為更新mean和variance的操作。通過下面一個(gè)例子可以看到tf.layers.batch_normalization中是如何實(shí)現(xiàn)的。
import tensorflow as tfis_traing = tf.placeholder(dtype=tf.bool)
input = tf.ones([1, 2, 2, 3])
output = tf.layers.batch_normalization(input, training=is_traing)update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
print(update_ops)
# with tf.control_dependencies(update_ops):# train_op = optimizer.minimize(loss)with tf.Session() as sess:sess.run(tf.global_variables_initializer())saver = tf.train.Saver()saver.save(sess, "batch_norm_layer/Model")輸出:[<tf.Tensor 'batch_normalization/AssignMovingAvg:0' shape=(3,) dtype=float32_ref>, <tf.Tensor 'batch_normalization/AssignMovingAvg_1:0' shape=(3,) dtype=float32_ref>]
可以看到輸出的即為兩個(gè)batch_normalization中更新mean和variance的操作,需要保證它們在train_op前完成。
這兩個(gè)操作是在tensorflow的內(nèi)部實(shí)現(xiàn)中自動(dòng)被加入tf.GraphKeys.UPDATE_OPS這個(gè)集合的,在tf.contrib.layers.batch_norm的參數(shù)中可以看到有一項(xiàng)updates_collections的默認(rèn)值即為tf.GraphKeys.UPDATE_OPS,而在tf.layers.batch_normalization中則是直接將兩個(gè)更新操作放入了上述集合。
五、關(guān)于最初的錯(cuò)誤使用的思考
最后我對(duì)于一開始的使用方法為什么會(huì)導(dǎo)致錯(cuò)誤進(jìn)行了思考,tensorflow中具體實(shí)現(xiàn)batch_normalization的代碼在tensorflow\python\layers\normalization.py中,下面展示一些關(guān)鍵代碼。
if self.scale:self.gamma = self.add_variable(name='gamma',shape=param_shape,dtype=param_dtype,initializer=self.gamma_initializer,regularizer=self.gamma_regularizer,constraint=self.gamma_constraint,trainable=True)
else:self.gamma = Noneif self.center:self.beta = self.add_variable(name='beta',shape=param_shape,dtype=param_dtype,initializer=self.beta_initializer,regularizer=self.beta_regularizer,constraint=self.beta_constraint,trainable=True)
else:self.beta = Nonescale, offset = _broadcast(self.gamma), _broadcast(self.beta)self.moving_mean = self._add_tower_local_variable(name='moving_mean',shape=param_shape,dtype=param_dtype,initializer=self.moving_mean_initializer,trainable=False)self.moving_variance = self._add_tower_local_variable(name='moving_variance',shape=param_shape,dtype=param_dtype,initializer=self.moving_variance_initializer,trainable=False)def _assign_moving_average(self, variable, value, momentum):with ops.name_scope(None, 'AssignMovingAvg', [variable, value, momentum]) as scope:decay = ops.convert_to_tensor(1.0 - momentum, name='decay')if decay.dtype != variable.dtype.base_dtype:decay = math_ops.cast(decay, variable.dtype.base_dtype)update_delta = (variable - value) * decayreturn state_ops.assign_sub(variable, update_delta, name=scope)def _do_update(var, value):return self._assign_moving_average(var, value, self.momentum)# Determine a boolean value for `training`: could be True, False, or None.
training_value = utils.constant_value(training)
if training_value is not False:mean, variance = nn.moments(inputs, reduction_axes, keep_dims=keep_dims)moving_mean = self.moving_meanmoving_variance = self.moving_variancemean = utils.smart_cond(training,lambda: mean,lambda: moving_mean)variance = utils.smart_cond(training,lambda: variance,lambda: moving_variance)
else:new_mean, new_variance = mean, variancemean_update = utils.smart_cond(training,lambda: _do_update(self.moving_mean, new_mean),lambda: self.moving_mean)
variance_update = utils.smart_cond(training,lambda: _do_update(self.moving_variance, new_variance),lambda: self.moving_variance)
if not context.executing_eagerly():self.add_update(mean_update, inputs=inputs)self.add_update(variance_update, inputs=inputs)
outputs = nn.batch_normalization(inputs,_broadcast(mean),_broadcast(variance),offset,scale,self.epsilon)
可以看到其內(nèi)部邏輯和我在介紹tf.nn.batch_normalization一節(jié)中展示的封裝時(shí)所使用的方法類似。
如果不在使用時(shí)添加tf.control_dependencies函數(shù),即在訓(xùn)練時(shí)(training=True)每批次時(shí)只會(huì)計(jì)算當(dāng)批次的mean和var,并傳遞給tf.nn.batch_normalization進(jìn)行歸一化,由于mean_update和variance_update在計(jì)算圖中并不在上述操作的依賴路徑上,因?yàn)椴⒉粫?huì)主動(dòng)完成,也就是說,在訓(xùn)練時(shí)mean_update和variance_update并不會(huì)被使用到,其值一直是初始值。因此在測試階段(training=False)使用這兩個(gè)作為mean和variance并進(jìn)行歸一化操作,這樣就會(huì)出現(xiàn)錯(cuò)誤。而如果使用tf.control_dependencies函數(shù),會(huì)在訓(xùn)練階段每次訓(xùn)練操作執(zhí)行前被動(dòng)地去執(zhí)行mean_update和variance_update,因此moving_mean和moving_variance會(huì)被不斷更新,在測試時(shí)使用該參數(shù)也就不會(huì)出現(xiàn)錯(cuò)誤。
總結(jié)
以上是生活随笔為你收集整理的batch normalization的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: xgboost重要参数2为主但不全要参照
- 下一篇: bert-as-service使用