batch normalization
20210702
深度學習中的五種歸一化(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)
此函數的作用是對輸入的每個batch數據做歸一化處理,目的是數據合理分布,加速計算過程,函數為:
num_features:為輸入的數據的通道數,
eps:使分母不為零,保持數據的穩定
momentum:用于在訓練時對均值和方差的估計
affine:為True時表示γ和β是可學習的參數,為False表示γ和β是不可學習的參數,此時γ=1,β=0;
track_running_stats=True;整個batch的方差和均值
https://blog.csdn.net/qq_39777550/article/details/108038677
重點
一、背景意義
本篇博文主要講解2015年深度學習領域,非常值得學習的一篇文獻:《Batch Normalization: Accelerating Deep Network Training by ?Reducing Internal Covariate Shift》,這個算法目前已經被大量的應用,最新的文獻算法很多都會引用這個算法,進行網絡訓練,可見其強大之處非同一般啊。
近年來深度學習捷報連連、聲名鵲起,隨機梯度下架成了訓練深度網絡的主流方法。盡管隨機梯度下降法對于訓練深度網絡簡單高效,但是它有個毛病,就是需要我們人為的去選擇參數,比如學習率、參數初始化、權重衰減系數、Drop out比例等。這些參數的選擇對訓練結果至關重要,以至于我們很多時間都浪費在這些的調參上。那么學完這篇文獻之后,你可以不需要那么刻意的慢慢調整參數。BN算法(Batch Normalization)其強大之處如下:
(1)你可以選擇比較大的初始學習率,讓你的訓練速度飆漲。以前還需要慢慢調整學習率,甚至在網絡訓練到一半的時候,還需要想著學習率進一步調小的比例選擇多少比較合適,現在我們可以采用初始很大的學習率,然后學習率的衰減速度也很大,因為這個算法收斂很快。當然這個算法即使你選擇了較小的學習率,也比以前的收斂速度快,因為它具有快速訓練收斂的特性;
(2)你再也不用去理會過擬合中drop out、L2正則項參數的選擇問題,采用BN算法后,你可以移除這兩項了參數,或者可以選擇更小的L2正則約束參數了,因為BN具有提高網絡泛化能力的特性;
(3)再也不需要使用使用局部響應歸一化層了(局部響應歸一化是Alexnet網絡用到的方法,搞視覺的估計比較熟悉),因為BN本身就是一個歸一化網絡層;
(4)可以把訓練數據徹底打亂(防止每批訓練的時候,某一個樣本都經常被挑選到,文獻說這個可以提高1%的精度,這句話我也是百思不得其解啊)。
原理開始講解算法前,先來思考一個問題:我們知道在神經網絡訓練開始前,都要對輸入數據做一個歸一化處理,那么具體為什么需要歸一化呢?歸一化后有什么好處呢?原因在于神經網絡學習過程本質(重點)就是為了學習數據分布,一旦訓練數據與測試數據的分布不同,那么網絡的泛化能力也大大降低;另外一方面,一旦每批訓練數據的分布各不相同(batch 梯度下降),那么網絡就要在每次迭代都去學習適應不同的分布,這樣將會大大降低網絡的訓練速度,這也正是為什么我們需要對數據都要做一個歸一化預處理的原因。
對于深度網絡的訓練是一個復雜的過程,只要網絡的前面幾層發生微小的改變,那么后面幾層就會被累積放大下去。一旦網絡某一層的輸入數據的分布發生改變,那么這一層網絡就需要去適應學習這個新的數據分布,所以如果訓練過程中,訓練數據的分布一直在發生變化,那么將會影響網絡的訓練速度。
我們知道網絡一旦train起來,那么參數就要發生更新,除了輸入層的數據外(因為輸入層數據,我們已經人為的為每個樣本歸一化),后面網絡每一層的輸入數據分布是一直在發生變化的,因為在訓練的時候,前面層訓練參數的更新將導致后面層輸入數據分布的變化。以網絡第二層為例:網絡的第二層輸入,是由第一層的參數和input計算得到的,而第一層的參數在整個訓練過程中一直在變化,因此必然會引起后面每一層輸入數據分布的改變。我們把網絡中間層在訓練過程中,數據分布的改變稱之為:“Internal ?Covariate?Shift”。Paper所提出的算法,就是要解決在訓練過程中,中間層數據分布發生改變的情況,于是就有了Batch??Normalization,這個牛逼算法的誕生。
二、初識BN(Batch??Normalization)
1、BN概述
就像激活函數層、卷積層、全連接層、池化層一樣,BN(Batch Normalization)也屬于網絡的一層。在前面我們提到網絡除了輸出層外,其它層因為低層網絡在訓練的時候更新了參數,而引起后面層輸入數據分布的變化。這個時候我們可能就會想,如果在每一層輸入的時候,再加個預處理操作那該有多好啊,比如網絡第三層輸入數據X3(X3表示網絡第三層的輸入數據)把它歸一化至:均值0、方差為1,然后再輸入第三層計算,這樣我們就可以解決前面所提到的“Internal?Covariate?Shift”的問題了。
而事實上,paper的算法本質原理就是這樣:在網絡的每一層輸入的時候,又插入了一個歸一化層,也就是先做一個歸一化處理,然后再進入網絡的下一層。不過文獻歸一化層,可不像我們想象的那么簡單,它是一個可學習、有參數的網絡層。既然說到數據預處理,下面就先來復習一下最強的預處理方法:白化。
2、預處理操作選擇
說到神經網絡輸入數據預處理,最好的算法莫過于白化預處理。然而白化計算量太大了,很不劃算,還有就是白化不是處處可微的,所以在深度學習中,其實很少用到白化。經過白化預處理后,數據滿足條件:a、特征之間的相關性降低,這個就相當于pca;b、數據均值、標準差歸一化,也就是使得每一維特征均值為0,標準差為1。如果數據特征維數比較大,要進行PCA,也就是實現白化的第1個要求,是需要計算特征向量,計算量非常大,于是為了簡化計算,作者忽略了第1個要求,僅僅使用了下面的公式進行預處理,也就是近似白化預處理:
公式簡單粗糙,但是依舊很牛逼。因此后面我們也將用這個公式,對某一個層網絡的輸入數據做一個歸一化處理。需要注意的是,我們訓練過程中采用batch 隨機梯度下降,上面的E(xk)指的是每一批訓練數據神經元xk的平均值;然后分母就是每一批數據神經元xk激活度的一個標準差了。
三、BN算法實現
1、BN算法概述
經過前面簡單介紹,這個時候可能我們會想當然的以為:好像很簡單的樣子,不就是在網絡中間層數據做一個歸一化處理嘛,這么簡單的想法,為什么之前沒人用呢?然而其實實現起來并不是那么簡單的。其實如果是僅僅使用上面的歸一化公式,對網絡某一層A的輸出數據做歸一化,然后送入網絡下一層B,這樣是會影響到本層網絡A所學習到的特征的。打個比方,比如我網絡中間某一層學習到特征數據本身就分布在S型激活函數的兩側,你強制把它給我歸一化處理、標準差也限制在了1,把數據變換成分布于s函數的中間部分,這樣就相當于我這一層網絡所學習到的特征分布被你搞壞了,這可怎么辦?于是文獻使出了一招驚天地泣鬼神的招式:變換重構,引入了可學習參數γ、β,這就是算法關鍵之處:
?
每一個神經元xk都會有一對這樣的參數γ、β。這樣其實當:
、
是可以恢復出原始的某一層所學到的特征的。因此我們引入了這個可學習重構參數γ、β,讓我們的網絡可以學習恢復出原始網絡所要學習的特征分布。(重點)最后Batch?Normalization網絡層的前向傳導過程公式就是:
?
上面的公式中m指的是mini-batch?size。
2、源碼實現
m = K.mean(X, axis=-1, keepdims=True)#計算均值std = K.std(X, axis=-1, keepdims=True)#計算標準差X_normed = (X - m) / (std + self.epsilon)#歸一化out = self.gamma * X_normed + self.beta#重構變換
3、實戰使用
(1)可能學完了上面的算法,你只是知道它的一個訓練過程,一個網絡一旦訓練完了,就沒有了min-batch這個概念了。測試階段我們一般只輸入一個測試樣本,看看結果而已。因此測試樣本,前向傳導的時候,上面的均值u、標準差σ?要哪里來?其實網絡一旦訓練完畢,參數都是固定的,這個時候即使是每批訓練樣本進入網絡,那么BN層計算的均值u、和標準差都是固定不變的。我們可以采用這些數值來作為測試樣本所需要的均值、標準差,于是最后測試階段的u和σ 計算公式如下:
上面簡單理解就是:對于均值來說直接計算所有batch u值的平均值;然后對于標準偏差采用每個batch?σB的無偏估計。最后測試階段,BN的使用公式就是:
(2)根據文獻說,BN可以應用于一個神經網絡的任何神經元上。文獻主要是把BN變換,置于網絡激活函數層的前面。在沒有采用BN的時候,激活函數層是這樣的:
z=g(Wu+b)
也就是我們希望一個激活函數,比如s型函數s(x)的自變量x是經過BN處理后的結果。因此前向傳導的計算公式就應該是:
z=g(BN(Wu+b))
其實因為偏置參數b經過BN層后其實是沒有用的,最后也會被均值歸一化,當然BN層后面還有個β參數作為偏置項,所以b這個參數就可以不用了。因此最后把BN層+激活函數層就變成了:
z=g(BN(Wu))
四、Batch Normalization在CNN中的使用
通過上面的學習,我們知道BN層是對于每個神經元做歸一化處理,甚至只需要對某一個神經元進行歸一化,而不是對一整層網絡的神經元進行歸一化。既然BN是對單個神經元的運算,那么在CNN中卷積層上要怎么搞?假如某一層卷積層有6個特征圖,每個特征圖的大小是100*100,這樣就相當于這一層網絡有6*100*100個神經元,如果采用BN,就會有6*100*100個參數γ、β,這樣豈不是太恐怖了。因此卷積層上的BN使用,其實也是使用了類似權值共享的策略,把一整張特征圖當做一個神經元進行處理。
卷積神經網絡經過卷積后得到的是一系列的特征圖,如果min-batch?sizes為m,那么網絡某一層輸入數據可以表示為四維矩陣(m,f,p,q),m為min-batch?sizes,f為特征圖個數,p、q分別為特征圖的寬高。在cnn中我們可以把每個特征圖看成是一個特征處理(一個神經元),因此在使用Batch?Normalization,mini-batch?size 的大小就是:m*p*q,于是對于每個特征圖都只有一對可學習參數:γ、β。說白了吧,這就是相當于求取所有樣本所對應的一個特征圖的所有神經元的平均值、方差,然后對這個特征圖神經元做歸一化。下面是來自于keras卷積層的BN實現一小段主要源碼:
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)
個人總結:2015年個人最喜歡深度學習的一篇paper就是Batch Normalization這篇文獻,采用這個方法網絡的訓練速度快到驚人啊,感覺訓練速度是以前的十倍以上,再也不用擔心自己這破電腦每次運行一下,訓練一下都要跑個兩三天的時間。另外這篇文獻跟空間變換網絡《Spatial Transformer Networks》的思想神似啊,都是一個變換網絡層。
參考文獻:
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.對小批量(mini-batch)的2d或3d輸入進行批標準化(Batch Normalization)操作2.num_features:來自期望輸入的特征數,該期望輸入的大小為'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:為保證數值穩定性(分母不能趨近或取0),給分母加上的值。默認為1e-5。momentum:動態均值和動態方差所使用的動量。默認為0.1。affine:一個布爾值,當設為true,給該層添加可學習的仿射變換參數。3.在每一個小批量(mini-batch)數據中,計算輸入各個維度的均值和標準差。gamma與beta是可學習的大小為C的參數向量(C為輸入大小)在訓練時,該層計算每次輸入的均值與方差,并進行移動平均。移動平均默認的動量值為0.1。在驗證時,訓練求得的均值/方差將用于標準化驗證數據。 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.對小批量(mini-batch)3d數據組成的4d輸入進行批標準化(Batch Normalization)操作2.num_features: 來自期望輸入的特征數,該期望輸入的大小為'batch_size x num_features x height x width'(輸入輸出相同)輸入Shape:(N, C,H, W)輸出Shape:(N, C, H, W)eps: 為保證數值穩定性(分母不能趨近或取0),給分母加上的值。默認為1e-5。momentum: 動態均值和動態方差所使用的動量。默認為0.1。affine: 一個布爾值,當設為true,給該層添加可學習的仿射變換參數。3.在每一個小批量(mini-batch)數據中,計算輸入各個維度的均值和標準差。gamma與beta是可學習的大小為C的參數向量(C為輸入大小)在訓練時,該層計算每次輸入的均值與方差,并進行移動平均。移動平均默認的動量值為0.1。在驗證時,訓練求得的均值/方差將用于標準化驗證數據。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.對小批量(mini-batch)4d數據組成的5d輸入進行批標準化(Batch Normalization)操作2.num_features: 來自期望輸入的特征數,該期望輸入的大小為'batch_size x num_features depth x height x width'(輸入輸出相同)輸入Shape:(N, C,D, H, W)輸出Shape:(N, C, D, H, W)eps: 為保證數值穩定性(分母不能趨近或取0),給分母加上的值。默認為1e-5。momentum: 動態均值和動態方差所使用的動量。默認為0.1。affine: 一個布爾值,當設為true,給該層添加可學習的仿射變換參數。3.在每一個小批量(mini-batch)數據中,計算輸入各個維度的均值和標準差。gamma與beta是可學習的大小為C的參數向量(C為輸入大小)在訓練時,該層計算每次輸入的均值與方差,并進行移動平均。移動平均默認的動量值為0.1。在驗證時,訓練求得的均值/方差將用于標準化驗證數據。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
對特征維進行歸一化
[batchsize feature] 用 BatchNorm1d
[batchsize feature H W] 用batchNorm2d
[batchsize feature X H W] x是什么 用 batchnorm3d
20201229
幾種batch normalization的使用
筆者近來在tensorflow中使用batch_norm時,由于事先不熟悉其內部的原理,因此將其錯誤使用,從而出現了結果與預想不一致的結果。事后對其進行了一定的調查與研究,在此進行一些總結。
一、錯誤使用及結果
筆者最先使用時只是了解到了在tensorflow中tf.layers.batch_normalization這個函數,就在函數中直接將其使用,該函數中有一個參數為training,在訓練階段賦值True,在測試階段賦值False。但是在訓練完成后,出現了奇怪的現象時,在training賦值為True時,測試的正確率正常,但是training賦值為False時,測試正確率就很低。上述錯誤使用過程可以精簡為下列代碼段
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,這種歸一化方法的示意圖和算法如下圖,
總的來說就是對于同一batch的input,假設輸入大小為[batch_num, height, width, channel],逐channel地計算同一batch中所有數據的mean和variance,再對input使用mean和variance進行歸一化,最后的輸出再進行線性平移,得到batch_norm的最終結果。偽代碼如下:
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
在實現的時候,會在訓練階段記錄下訓練數據中平均mean和variance,記為moving_mean和moving_variance,并在測試階段使用訓練時的moving_mean和moving_variance進行計算,這也就是參數training的作用。另外,在實現時一般使用一個decay系數來逐步更新moving_mean和moving_variance,moving_mean = moving_mean * decay + new_batch_mean * (1 - decay)
三、tensorflow中的三種實現
tensorflow中關于batch_norm現在有三種實現方式。
1、tf.nn.batch_normalization(最底層的實現)
tf.nn.batch_normalization(x,mean,variance,offset,scale,variance_epsilon,name=None
)
該函數是一種最底層的實現方法,在使用時mean、variance、scale、offset等參數需要自己傳遞并更新,因此實際使用時還需自己對該函數進行封裝,一般不建議使用,但是對了解batch_norm的原理很有幫助。
封裝使用的實例如下:
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中,首先先計算了x的逐通道的mean和var,然后將pop_mean和pop_var進行更新,并根據是在訓練階段還是測試階段選擇將當批次計算的mean和var或者訓練階段保存的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
)
該函數也就是筆者之前使用的函數,在官方文檔中寫道
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)
可以看到,與筆者之前的錯誤實現方法的差異主要在
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)with tf.control_dependencies(update_ops):
這兩句話,同時可以看到在第一種方法tf.nn.batch_normalization的封裝過程中也用到了類似的處理方法,具體會在下一段進行說明。
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的使用方法差不多,兩者最主要的差別在參數scale和centre的默認值上,這兩個參數即是我們之前介紹原理時所說明的對input進行mean和variance的歸一化之后采用的線性平移中的scale和offset,可以看到offset的默認值兩者都是True,但是scale的默認值前者為True后者為False,也就是說明在tf.contrib.layers.batch_norm中,默認不對處理后的input進行線性縮放,只是加一個偏移。
四、關于tf.GraphKeys.UPDATA_OPS
介紹到這里,還有兩個概念沒有介紹,一個是tf.GraphKeys.UPDATE_OPS,另一個是tf.control_dependencies。
1、tf.control_dependencies
首先我們先介紹tf.control_dependencies,該函數保證其轄域中的操作必須要在該函數所傳遞的參數中的操作完成后再進行。請看下面一個例子。
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
可以看到兩組加法進行的對比,正常的計算圖在計算add時是不會經過update_op操作的,因此在加法時a的值為1,但是采用tf.control_dependencies函數,可以控制在進行add前先完成update_op的操作,因此在加法時a的值為10,因此最后兩種加法的結果不同。
2、tf.GraphKeys.UPDATE_OPS
關于tf.GraphKeys.UPDATE_OPS,這是一個tensorflow的計算圖中內置的一個集合,其中會保存一些需要在訓練操作之前完成的操作,并配合tf.control_dependencies函數使用。
關于在batch_norm中,即為更新mean和variance的操作。通過下面一個例子可以看到tf.layers.batch_normalization中是如何實現的。
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>]
可以看到輸出的即為兩個batch_normalization中更新mean和variance的操作,需要保證它們在train_op前完成。
這兩個操作是在tensorflow的內部實現中自動被加入tf.GraphKeys.UPDATE_OPS這個集合的,在tf.contrib.layers.batch_norm的參數中可以看到有一項updates_collections的默認值即為tf.GraphKeys.UPDATE_OPS,而在tf.layers.batch_normalization中則是直接將兩個更新操作放入了上述集合。
五、關于最初的錯誤使用的思考
最后我對于一開始的使用方法為什么會導致錯誤進行了思考,tensorflow中具體實現batch_normalization的代碼在tensorflow\python\layers\normalization.py中,下面展示一些關鍵代碼。
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)
可以看到其內部邏輯和我在介紹tf.nn.batch_normalization一節中展示的封裝時所使用的方法類似。
如果不在使用時添加tf.control_dependencies函數,即在訓練時(training=True)每批次時只會計算當批次的mean和var,并傳遞給tf.nn.batch_normalization進行歸一化,由于mean_update和variance_update在計算圖中并不在上述操作的依賴路徑上,因為并不會主動完成,也就是說,在訓練時mean_update和variance_update并不會被使用到,其值一直是初始值。因此在測試階段(training=False)使用這兩個作為mean和variance并進行歸一化操作,這樣就會出現錯誤。而如果使用tf.control_dependencies函數,會在訓練階段每次訓練操作執行前被動地去執行mean_update和variance_update,因此moving_mean和moving_variance會被不斷更新,在測試時使用該參數也就不會出現錯誤。
總結
以上是生活随笔為你收集整理的batch normalization的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: xgboost重要参数2为主但不全要参照
- 下一篇: bert-as-service使用