最近在訓練網路的時候注意到了一個引數

training

,這個引數出現在了Tensorflow的兩個函數里,tf。layers。batch_normalization和tf。layers。dropout。這個引數的出現,讓我們在使用這些方法的時候需要更加註意,這樣才能發揮出所訓練模型的最佳效果。我們從簡單的dropout開始,再看BN。

tf。layers。dropout

dropout的原理:

之前在我的總結CNN的文章中有寫到:

Dropout是一種Bagging的近似,Bagging定義k個不同的模型,從training set取樣出k個不同的資料集,在第i個模型上用第i個數據集進行訓練,最後綜合k個模型的結果,獲得最終的模型。但是需要的空間、時間都很大,在DNN中並不現實。

Dropout的目的是在指數級子網路的深度神經網路中近似Bagging。也就是說,在訓練時,每次Dropout後,訓練的網路是整個深度神經網路的其中一個子網路。在測試時,將dropout層取消,這樣得到的前向傳播結果其實就是若干個子網路前向傳播綜合結果的一種近似。

是減輕過擬合的大殺器。

dropout函式的引數:

dropout

inputs

rate

=

0。5

noise_shape

=

None

seed

=

None

training

=

False

name

=

None

在使用dropout時,需要控制好的兩個引數是rate和training。

rate:

表示inputs層的輸出單元被dropout的機率,如果rate=0。1,那麼就會有10%的單元會被dropout,而剩下的90%輸出都會被以1/(1-rate)的引數rescale,從而使得該層的輸出之和保持不變(保留一定的統計特徵,也會使得網路不會隨著傳播,輸出也變得越來越小)。

如果rate過大,那麼模型將會很難訓練,並且dropout過多的子網路能否cover任務所需的複雜度也是未知的;如果rate過小,那麼也很難起到減輕過擬合的效果。

training:

if

training

==

True

output

=

apply_dropout

input

# 對輸入進行dropout

elif

training

==

False

output

=

input

# 輸出與輸入保持一致

如果我們在使用dropout的時候不注意這個引數,從而在inference的時候也使用了dropout,那麼模型的能力就會大打折扣。

tf。layers。batch_normalization

batch normalization的原理:

由training引數想到的

大家都知道,Batch normalization解決的是一個Internal covariate shift問題,論文中將這個問題定義為在訓練過程中由於網路引數的改變而引起的網路啟用分佈的改變。做法就是將一個batch在網路中某一層的輸出,對batch取均值,算標準差(通俗解釋就是,batch有m=5個樣本,對每一個hidden node,由5個樣本在該個hidden node的輸出取均值,算標準差),進而對該層的輸出進行標準化,再進行scale and shift,這裡對scale and shift的解釋請參考問題裡魏秀參大神的回答,“而最後的“scale and shift”操作則是為了讓因訓練所需而“刻意”加入的BN能夠有可能還原最初的輸入(即當

\gamma^{(k)}=\sqrt{Var[x^{(k)}]}, \beta^{(k)}=E[x^{(k)}]

),從而保證整個network的capacity。”

(說起BN,想起去年在面試DJI的時候,我和麵試官講了一大通原理以後,面試官問我,那BN到底是怎麼解決Internal covariate shift問題的,你有沒有推導過公式?我沒有回答上來,因為BN的論文也沒有教我怎麼推導公式,我想他說的公式可能也不是上面BN操作的公式,BN的可解釋性我在自己CNN的總結文章裡也有說明。面試官和我說我建議你後面自己去推導一下,還是蠻有意思的,然後我們再討論。後來我就被刷了,hhhhhh:))

但是

,在使用Tensorflow框架裡的BN之前,必須要明白一件事:

BN在training和inference時使用的方法是不一樣的

training時:

我們需要逐個神經元逐個樣本地來計算,這個batch在某一層輸出的均值和標準差,然後再對該層的輸出進行標準化。同時還要學習gamma和beta兩個引數。

這是非常非常耗時的,顯然,我們不能在inference的時候使用這種方法。

解決方案就是,在訓練時使用滑動平均維護population均值和方差:

running_mean

=

momentum

*

running_mean

+

1

-

momentum

*

sample_mean

running_std

=

momentum

*

running_std

+

1

-

momentum

*

sample_std

在訓練結束儲存模型時,running_mean、running_var、trained_gamma和trained_beta一同被儲存下來。

inference時:

output

=

input

-

running_mean

/

running_std

output

=

trained_gamma

*

output

+

trained_beta

也就是說,在inference時,BN對應的操作不再是公式裡提到的那樣,計算該batch的各種統計量,而是直接使用在訓練時儲存下來的population均值和方差,進行一次線性變換。這樣效率提升了很多。但是缺點也顯而易見,如果訓練集和驗證集不平衡的時候,驗證的效果會一直一直很差,所以這樣看來深度學習還是資料的遊戲,誰的資料質量高誰就是贏家罷了。

當然你可以也用整個驗證集的mean和var來替代,但是,在真正使用的時候哪裡來驗證集呢?這是不是間接幫助了模型泛化呢?是不是有一點自欺欺人的味道呢?不過小蝦米其實也不用擔心這些,總有大佬開路。

值得提醒的一點是,需要在main里加入以下程式碼才能對running_mean和running_var進行更新:

update_ops

=

tf

get_collection

tf

GraphKeys

UPDATE_OPS

with

tf

control_dependencies

update_ops

):

train_op

=

optimizer

minimize

loss

batch_normalization函式的引數:

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

需要注意這裡的training引數,如果你在inference的時候training卻設定成了True,那網路是可能會存在一個學習過程的,使用的也不是之前moving average計算出來的均值和方差,效果可能會很差。

寫在最後的一點建議:

在使用Tensorflow構建網路時,我們應該這樣寫:

class

Network

object

):

def

__init__

self

。。。

is_training

=

False

。。。

):

# code block

self

is_training

=

is_training

# code block

self

setup

self

is_training

。。。

def

batch_normalization

self

input

。。。

):

# code block

output

=

tf

layers

batch_normalization

input

。。。

training

=

self

is_training

。。。

return

output

。。。

class

MyNet

Network

):

def

setup

self

is_training

。。。

):

# codes to construct the compute graph

。。。

def

main

():

# 訓練時

net

=

MyNet

。。。

is_training

=

True

。。。

# 測試時

net

=

MyNet

。。。

is_training

=

False

。。。

把“is_training”這個布林值在構建計算圖時就確定,從而更加清晰地呼叫batch normalization,確保不會出錯。使用tf。layers。dropout時也是一樣!

當然這只是建議的一種方式,寫法有很多種,只是一定要注意這些引數的設定,很重要。