吳恩達《優化深度神經網路》課程筆記(3)– 超引數除錯、Batch正則化和程式設計框架

紅色石頭發表於2018-07-31

上節課我們主要介紹了深度神經網路的優化演算法。包括對原始資料集進行分割,使用mini-batch gradient descent。然後介紹了指數加權平均(Exponentially weighted averages)的概念以及偏移校正(bias correction)方法。接著,我們著重介紹了三種常用的加速神經網路學習速度的三種演算法:動量梯度下降、RMSprop和Adam演算法。其中,Adam結合了動量梯度下降和RMSprop各自的優點,實際應用中表現更好。然後,我們介紹了另外一種提高學習速度的方法:learning rate decay,通過不斷減小學習因子,減小步進長度,來減小梯度振盪。最後,我們對深度學習中local optima的概念作了更深入的解釋。本節課,我們將重點介紹三個方面的內容:超引數除錯、Batch正則化和深度學習程式設計框架。

1. Tuning Process

深度神經網路需要除錯的超引數(Hyperparameters)較多,包括:

  • \alpha:學習因子
  • \beta:動量梯度下降因子

  • \beta_1,\beta_2,\varepsilon:Adam演算法引數

  • #layers:神經網路層數

  • #hidden units:各隱藏層神經元個數

  • learning rate decay:學習因子下降引數

  • mini-batch size:批量訓練樣本包含的樣本個數

超引數之間也有重要性差異。通常來說,學習因子\alpha是最重要的超引數,也是需要重點除錯的超引數。動量梯度下降因子\beta、各隱藏層神經元個數#hidden units和mini-batch size的重要性僅次於\alpha。然後就是神經網路層數#layers和學習因子下降引數learning rate decay。最後,Adam演算法的三個引數\beta_1,\beta_2,\varepsilon一般常設定為0.9,0.999和10^{-8},不需要反覆除錯。當然,這裡超引數重要性的排名並不是絕對的,具體情況,具體分析。

如何選擇和除錯超引數?傳統的機器學習中,我們對每個引數等距離選取任意個數的點,然後,分別使用不同點對應的引數組合進行訓練,最後根據驗證集上的表現好壞,來選定最佳的引數。例如有兩個待除錯的引數,分別在每個引數上選取5個點,這樣構成了5×5=25中引數組合,如下圖所示:

這種做法在引數比較少的時候效果較好。但是在深度神經網路模型中,我們一般不採用這種均勻間隔取點的方法,比較好的做法是使用隨機選擇。也就是說,對於上面這個例子,我們隨機選擇25個點,作為待除錯的超引數,如下圖所示:

隨機化選擇引數的目的是為了儘可能地得到更多種引數組合。還是上面的例子,如果使用均勻取樣的話,每個引數只有5種情況;而使用隨機取樣的話,每個引數有25種可能的情況,因此更有可能得到最佳的引數組合。

這種做法帶來的另外一個好處就是對重要性不同的引數之間的選擇效果更好。假設hyperparameter1為\alpha,hyperparameter2為\varepsilon,顯然二者的重要性是不一樣的。如果使用第一種均勻取樣的方法,\varepsilon的影響很小,相當於只選擇了5個\alpha值。而如果使用第二種隨機取樣的方法,\varepsilon\alpha都有可能選擇25種不同值。這大大增加了\alpha除錯的個數,更有可能選擇到最優值。其實,在實際應用中完全不知道哪個引數更加重要的情況下,隨機取樣的方式能有效解決這一問題,但是均勻取樣做不到這點。

在經過隨機取樣之後,我們可能得到某些區域模型的表現較好。然而,為了得到更精確的最佳引數,我們應該繼續對選定的區域進行由粗到細的取樣(coarse to fine sampling scheme)。也就是放大表現較好的區域,再對此區域做更密集的隨機取樣。例如,對下圖中右下角的方形區域再做25點的隨機取樣,以獲得最佳引數。

2. Using an appropriate scale to pick hyperparameters

上一部分講的除錯引數使用隨機取樣,對於某些超引數是可以進行尺度均勻取樣的,但是某些超引數需要選擇不同的合適尺度進行隨機取樣。

什麼意思呢?例如對於超引數#layers和#hidden units,都是正整數,是可以進行均勻隨機取樣的,即超引數每次變化的尺度都是一致的(如每次變化為1,猶如一個刻度尺一樣,刻度是均勻的)。

但是,對於某些超引數,可能需要非均勻隨機取樣(即非均勻刻度尺)。例如超引數\alpha,待調範圍是[0.0001, 1]。如果使用均勻隨機取樣,那麼有90%的取樣點分佈在[0.1, 1]之間,只有10%分佈在[0.0001, 0.1]之間。這在實際應用中是不太好的,因為最佳的\alpha值可能主要分佈在[0.0001, 0.1]之間,而[0.1, 1]範圍內\alpha值效果並不好。因此我們更關注的是區間[0.0001, 0.1],應該在這個區間內細分更多刻度。

通常的做法是將linear scale轉換為log scale,將均勻尺度轉化為非均勻尺度,然後再在log scale下進行均勻取樣。這樣,[0.0001, 0.001],[0.001, 0.01],[0.01, 0.1],[0.1, 1]各個區間內隨機取樣的超引數個數基本一致,也就擴大了之前[0.0001, 0.1]區間內取樣值個數。

一般解法是,如果線性區間為[a, b],令m=log(a),n=log(b),則對應的log區間為[m,n]。對log區間的[m,n]進行隨機均勻取樣,然後得到的取樣值r,最後反推到線性區間,即10^r10^r就是最終取樣的超引數。相應的Python語句為:

m = np.log10(a)
n = np.log10(b)
r = np.random.rand()
r = m + (n-m)*r
r = np.power(10,r)

除了\alpha之外,動量梯度因子\beta也是一樣,在超引數除錯的時候也需要進行非均勻取樣。一般\beta的取值範圍在[0.9, 0.999]之間,那麼1-\beta的取值範圍就在[0.001, 0.1]之間。那麼直接對1-\beta在[0.001, 0.1]區間內進行log變換即可。

這裡解釋下為什麼\beta也需要向\alpha那樣做非均勻取樣。假設\beta從0.9000變化為0.9005,那麼\frac{1}{1-\beta}基本沒有變化。但假設\beta從0.9990變化為0.9995,那麼\frac{1}{1-\beta}前後差別1000。\beta越接近1,指數加權平均的個數越多,變化越大。所以對\beta接近1的區間,應該採集得更密集一些。

3. Hyperparameters tuning in practice: Pandas vs. Caviar

經過除錯選擇完最佳的超引數並不是一成不變的,一段時間之後(例如一個月),需要根據新的資料和實際情況,再次除錯超引數,以獲得實時的最佳模型。

在訓練深度神經網路時,一種情況是受計算能力所限,我們只能對一個模型進行訓練,除錯不同的超引數,使得這個模型有最佳的表現。我們稱之為Babysitting one model。另外一種情況是可以對多個模型同時進行訓練,每個模型上除錯不同的超引數,根據表現情況,選擇最佳的模型。我們稱之為Training many models in parallel。

因為第一種情況只使用一個模型,所以類比做Panda approach;第二種情況同時訓練多個模型,類比做Caviar approach。使用哪種模型是由計算資源、計算能力所決定的。一般來說,對於非常複雜或者資料量很大的模型,使用Panda approach更多一些。

4. Normalizing activations in a network

Sergey Ioffe和Christian Szegedy兩位學者提出了Batch Normalization方法。Batch Normalization不僅可以讓除錯超引數更加簡單,而且可以讓神經網路模型更加“健壯”。也就是說較好模型可接受的超引數範圍更大一些,包容性更強,使得更容易去訓練一個深度神經網路。接下來,我們就來介紹什麼是Batch Normalization,以及它是如何工作的。

之前,我們在吳恩達《優化深度神經網路》課程筆記(1)– 深度學習的實用層面中提到過在訓練神經網路時,標準化輸入可以提高訓練的速度。方法是對訓練資料集進行歸一化的操作,即將原始資料減去其均值\mu後,再除以其方差\sigma^2。但是標準化輸入只是對輸入進行了處理,那麼對於神經網路,又該如何對各隱藏層的輸入進行標準化處理呢?

其實在神經網路中,第l層隱藏層的輸入就是第l-1層隱藏層的輸出A^{[l-1]}。對A^{[l-1]}進行標準化處理,從原理上來說可以提高W^{[l]}b^{[l]}的訓練速度和準確度。這種對各隱藏層的標準化處理就是Batch Normalization。值得注意的是,實際應用中,一般是對Z^{[l-1]}進行標準化處理而不是A^{[l-1]},其實差別不是很大。

Batch Normalization對第l層隱藏層的輸入Z^{[l-1]}做如下標準化處理,忽略上標[l-1]

\mu=\frac1m\sum_iz^{(i)}

\sigma^2=\frac1m\sum_i(z_i-\mu)^2

z^{(i)}_{norm}=\frac{z^{(i)}-\mu}{\sqrt{\sigma^2+\varepsilon}}

其中,m是單個mini-batch包含樣本個數,\varepsilon是為了防止分母為零,可取值10^{-8}。這樣,使得該隱藏層的所有輸入z^{(i)}均值為0,方差為1。

但是,大部分情況下並不希望所有的z^{(i)}均值都為0,方差都為1,也不太合理。通常需要對z^{(i)}進行進一步處理:

\tilde z^{(i)}=\gamma\cdot z^{(i)}_{norm}+\beta

上式中,\gamma\beta是learnable parameters,類似於W和b一樣,可以通過梯度下降等演算法求得。這裡,\gamma\beta的作用是讓\tilde z^{(i)}的均值和方差為任意值,只需調整其值就可以了。例如,令:

\gamma=\sqrt{\sigma^2+\varepsilon},\ \ \beta=u

\tilde z^{(i)}=z^{(i)},即identity function。可見,設定\gamma\beta為不同的值,可以得到任意的均值和方差。

這樣,通過Batch Normalization,對隱藏層的各個z^{[l](i)}進行標準化處理,得到\tilde z^{[l](i)},替代z^{[l](i)}

值得注意的是,輸入的標準化處理Normalizing inputs和隱藏層的標準化處理Batch Normalization是有區別的。Normalizing inputs使所有輸入的均值為0,方差為1。而Batch Normalization可使各隱藏層輸入的均值和方差為任意值。實際上,從啟用函式的角度來說,如果各隱藏層的輸入均值在靠近0的區域即處於啟用函式的線性區域,這樣不利於訓練好的非線性神經網路,得到的模型效果也不會太好。這也解釋了為什麼需要用\gamma\beta來對z^{[l](i)}作進一步處理。

5. Fitting Batch Norm into a neural network

我們已經知道了如何對某單一隱藏層的所有神經元進行Batch Norm,接下來將研究如何把Bath Norm應用到整個神經網路中。

對於L層神經網路,經過Batch Norm的作用,整體流程如下:

實際上,Batch Norm經常使用在mini-batch上,這也是其名稱的由來。

值得注意的是,因為Batch Norm對各隱藏層Z^{[l]}=W^{[l]}A^{[l-1]}+b^{[l]}有去均值的操作,所以這裡的常數項b^{[l]}可以消去,其數值效果完全可以由\tilde Z^{[l]}中的\beta來實現。因此,我們在使用Batch Norm的時候,可以忽略各隱藏層的常數項b^{[l]}。在使用梯度下降演算法時,分別對W^{[l]}\beta^{[l]}\gamma^{[l]}進行迭代更新。除了傳統的梯度下降演算法之外,還可以使用我們之前介紹過的動量梯度下降、RMSprop或者Adam等優化演算法。

6. Why does Batch Norm work?

我們可以把輸入特徵做均值為0,方差為1的規範化處理,來加快學習速度。而Batch Norm也是對隱藏層各神經元的輸入做類似的規範化處理。總的來說,Batch Norm不僅能夠提高神經網路訓練速度,而且能讓神經網路的權重W的更新更加“穩健”,尤其在深層神經網路中更加明顯。比如神經網路很後面的W對前面的W包容性更強,即前面的W的變化對後面W造成的影響很小,整體網路更加健壯。

舉個例子來說明,假如用一個淺層神經網路(類似邏輯迴歸)來訓練識別貓的模型。如下圖所示,提供的所有貓的訓練樣本都是黑貓。然後,用這個訓練得到的模型來對各種顏色的貓樣本進行測試,測試的結果可能並不好。其原因是訓練樣本不具有一般性(即不是所有的貓都是黑貓),這種訓練樣本(黑貓)和測試樣本(貓)分佈的變化稱之為covariate shift。

對於這種情況,如果實際應用的樣本與訓練樣本分佈不同,即發生了covariate shift,則一般是要對模型重新進行訓練的。在神經網路,尤其是深度神經網路中,covariate shift會導致模型預測效果變差,重新訓練的模型各隱藏層的W^{[l]}B^{[l]}均產生偏移、變化。而Batch Norm的作用恰恰是減小covariate shift的影響,讓模型變得更加健壯,魯棒性更強。Batch Norm減少了各層W^{[l]}B^{[l]}之間的耦合性,讓各層更加獨立,實現自我訓練學習的效果。也就是說,如果輸入發生covariate shift,那麼因為Batch Norm的作用,對個隱藏層輸出Z^{[l]}進行均值和方差的歸一化處理,W^{[l]}B^{[l]}更加穩定,使得原來的模型也有不錯的表現。針對上面這個黑貓的例子,如果我們使用深層神經網路,使用Batch Norm,那麼該模型對花貓的識別能力應該也是不錯的。

從另一個方面來說,Batch Norm也起到輕微的正則化(regularization)效果。具體表現在:

  • 每個mini-batch都進行均值為0,方差為1的歸一化操作
  • 每個mini-batch中,對各個隱藏層的Z^{[l]}新增了隨機噪聲,效果類似於Dropout

  • mini-batch越小,正則化效果越明顯

但是,Batch Norm的正則化效果比較微弱,正則化也不是Batch Norm的主要功能。

7. Batch Norm at test time

訓練過程中,Batch Norm是對單個mini-batch進行操作的,但在測試過程中,如果是單個樣本,該如何使用Batch Norm進行處理呢?

首先,回顧一下訓練過程中Batch Norm的主要過程:

\mu=\frac1m\sum_iz^{(i)}

\sigma^2=\frac1m\sum_i(z^{(i)}-\mu)^2

z_{norm}^{(i)}=\frac{z^{(i)}-\mu}{\sqrt{\sigma^2+\varepsilon}}

\tilde z^{(i)}=\gamma\cdot z^{(i)}_{norm}+\beta

其中,\mu\sigma^2是對單個mini-batch中所有m個樣本求得的。在測試過程中,如果只有一個樣本,求其均值和方差是沒有意義的,就需要對\mu\sigma^2進行估計。估計的方法有很多,理論上我們可以將所有訓練集放入最終的神經網路模型中,然後將每個隱藏層計算得到的\mu^{[l]}\sigma^{2[l]}直接作為測試過程的\mu\sigma^2來使用。但是,實際應用中一般不使用這種方法,而是使用我們之前介紹過的指數加權平均(exponentially weighted average)的方法來預測測試過程單個樣本的\mu\sigma^2

指數加權平均的做法很簡單,對於第l層隱藏層,考慮所有mini-batch在該隱藏層下的\mu^{[l]}\sigma^{2[l]},然後用指數加權平均的方式來預測得到當前單個樣本的\mu^{[l]}\sigma^{2[l]}。這樣就實現了對測試過程單個樣本的均值和方差估計。最後,再利用訓練過程得到的\gamma\beta值計算出各層的\tilde z^{(i)}值。

8. Softmax Regression

目前我們介紹的都是二分類問題,神經網路輸出層只有一個神經元,表示預測輸出\hat y是正類的概率P(y=1|x)\hat y>0.5則判斷為正類,\hat y<0.5則判斷為負類。

對於多分類問題,用C表示種類個數,神經網路中輸出層就有C個神經元,即n^{[L]}=C。其中,每個神經元的輸出依次對應屬於該類的概率,即P(y=c|x)。為了處理多分類問題,我們一般使用Softmax迴歸模型。Softmax迴歸模型輸出層的啟用函式如下所示:

z^{[L]}=W^{[L]}a^{[L-1]}+b^{[L]}

a^{[L]}_i=\frac{e^{z^{[L]}_i}}{\sum_{i=1}^Ce^{z^{[L]}_i}}

輸出層每個神經元的輸出a^{[L]}_i對應屬於該類的概率,滿足:

\sum_{i=1}^Ca^{[L]}_i=1

所有的a^{[L]}_i,即\hat y,維度為(C, 1)。

下面給出幾個簡單的線性多分類的例子:

如果使用神經網路,特別是深層神經網路,可以得到更復雜、更精確的非線性模型。

9. Training a softmax classifier

Softmax classifier的訓練過程與我們之前介紹的二元分類問題有所不同。先來看一下softmax classifier的loss function。舉例來說,假如C=4,某個樣本的預測輸出\hat y和真實輸出y為:

\hat y值來看,P(y=4|x)=0.4,概率最大,而真實樣本屬於第2類,因此該預測效果不佳。我們定義softmax classifier的loss function為:

L(\hat y,y)=-\sum_{j=1}^4y_j\cdot log\ \hat y_j

然而,由於只有當j=2時,y_2=1,其它情況下,y_j=0。所以,上式中的L(\hat y,y)可以簡化為:

L(\hat y,y)=-y_2\cdot log\ \hat y_2=-log\ \hat y_2

要讓L(\hat y,y)更小,就應該讓\hat y_2越大越好。\hat y_2反映的是概率,完全符合我們之前的定義。

所有m個樣本的cost function為:

J=\frac1m\sum_{i=1}^mL(\hat y,y)

其預測輸出向量A^{[L]}\hat Y的維度為(4, m)。

softmax classifier的反向傳播過程仍然使用梯度下降演算法,其推導過程與二元分類有一點點不一樣。因為只有輸出層的啟用函式不一樣,我們先推導dZ^{[L]}

da^{[L]}=-\frac{1}{a^{[L]}}

\frac{\partial a^{[L]}}{\partial z^{[L]}}=\frac{\partial}{\partial z^{[L]}}\cdot (\frac{e^{z^{[L]}_i}}{\sum_{i=1}^Ce^{z^{[L]}_i}})=a^{[L]}\cdot (1-a^{[L]})

dz^{[L]}=da^{[L]}\cdot \frac{\partial a^{[L]}}{\partial z^{[L]}}=a^{[L]}-1=a^{[L]}-y

對於所有m個訓練樣本:

dZ^{[L]}=A^{[L]}-Y

可見dZ^{[L]}的表示式與二元分類結果是一致的,雖然推導過程不太一樣。然後就可以繼續進行反向傳播過程的梯度下降演算法了,推導過程與二元分類神經網路完全一致。

10. Deep learning frameworks

深度學習框架有很多,例如:

  • Caffe/Caffe2
  • CNTK

  • DL4J

  • Keras

  • Lasagne

  • mxnet

  • PaddlePaddle

  • TensorFlow

  • Theano

  • Torch

一般選擇深度學習框架的基本準則是:

  • Ease of programming(development and deployment)
  • Running speed

  • Truly open(open source with good governance)

實際應用中,我們應該根據自己的需求選擇最合適的深度學習框架。

11. TensorFlow

這裡簡單介紹一下最近幾年比較火的一個深度學習框架:TensorFlow。

舉個例子來說明,例如cost function是引數w的函式:

J=w^2-10w+25

如果使用TensorFlow對cost function進行優化,求出最小值對應的w,程式如下:

import numpy as np
import tensorflow as tf

w = tf.Variable(0,dtype=tf.float32)
#cost = tf.add(tf.add(w**2,tf.multiply(-10,w)),25)
cost = w**2 - 10*w +25
train = tf.train.GradientDescentOptimizer(0.01).minimize(cost)

init = tf.global_variables_initializer()
session = tf.Session()
session.run(init)
print(session.run(w))

>>0.0

session.run(train)
print(session.run(w))

>>0.1

for i in range(1000):
    session.run(train)
print(session.run(w))

>>4.99999

TensorFlow框架內可以直接呼叫梯度下降優化演算法,不需要我們自己再寫程式了,大大提高了效率。在執行1000次梯度下降演算法後,w的解為4.99999,已經非常接近w的最優值5了。

針對上面這個例子,如果對w前的係數用變數x來代替,程式如下:

import numpy as np
import tensorflow as tf

cofficients = np.array([[1.],[-10.],[25.]])

w = tf.Variable(0,dtype=tf.float32)
x = tf.placeholder(tf.float32,[3,1])
#cost = tf.add(tf.add(w**2,tf.multiply(-10,w)),25)
#cost = w**2 - 10*w +25
cost = x[0][0]*w**2 + x[1][0]*w + x[2][0]
train = tf.train.GradientDescentOptimizer(0.01).minimize(cost)

init = tf.global_variables_initializer()
session = tf.Session()
session.run(init)
print(session.run(w))

>>0.0

session.run(train, feed_dict=(x:coefficients))
print(session.run(w))

>>0.1

for i in range(1000):
    session.run(train, feed_dict=(x:coefficients))
print(session.run(w))

>>4.99999

結果跟之前是一樣的。除此之外,我們還可以更改x即cofficients的值,而得到不同的優化結果w。

另外,上段程式中的:

session = tf.Session()
session.run(init)
print(session.run(w))

有另外一種寫法:

with tf.Session() as session:
    session.run(init)
    print(session.run(w))

TensorFlow的最大優點就是採用資料流圖(data flow graphs)來進行數值運算。圖中的節點(Nodes)表示數學操作,圖中的線(edges)則表示在節點間相互聯絡的多維資料陣列,即張量(tensor)。而且它靈活的架構讓你可以在多種平臺上展開計算,例如臺式計算機中的一個或多個CPU(或GPU),伺服器,移動裝置等等。

關於TensorFlow更多的原理和程式設計技巧這裡就不在贅述了,感興趣的朋友可以關注更詳細的TensorFlow相關文件。


更多AI資源請關注公眾號:AI有道(ID:redstonewill)

相關文章