說明:
本系列文章翻譯斯坦福大學的課程:Convolutional Neural Networks for Visual Recognition的課程講義 原文地址:http://cs231n.github.io/。 最好有Python基礎(但不是必要的),Python的介紹見該課程的module0。
本節的code見地址:
https://github.com/anthony123/cs231n/tree/master/module1-6(working on)如果在code中發現bug或者有什么不清楚的地方,可以及時給我留言,因為code沒有經過很嚴格的測試。
這節課的主要內容:
- 設置數據和模型
- 數據預處理
- 權重初始化
- 批量標準化
- 正則化(L2/L1/Maxnorm/Dropout)
- 損失函數
- 總結
設置數據和模型
在上一節課中,我們介紹了神經元的模型,它計算點積,然后計算一個非線性函數。神經網絡將神經元組織成層級結構。總之,這種選擇定義了一種新的分數函數,它 從我們在線性分類那節中的線性分類擴展而來。特別地, 神經網絡計算一系列的線性映射,并和非線性操作交替出現。在這節中,我們會討論數據預處理,權重初始化及損失函數的設計選擇。
數據預處理
有三種常見的數據預處理的方法,我們假設數據矩陣X的大小是[NxD] (其中N是數據的數量, D是它們的維度)。
平均數減法 是一種非常常見的數據預處理方法,具體做法為減去所有特性的平均數。在幾何上的解釋,就是將所有的數據放在原點的周圍,在numpy中,計算公式為X -= np.mean(X, axis = 0),以圖像為例,可以對所有的像素都減去同一個值(比如,X -= np.mean(X)),也可以針對每一個通道單獨處理。
標準化 是指將數據標準化,使得所有數據的變化范圍都一樣。有兩種標準化的方法。一種是在數據中心化后,再除以它們的標準差。另一種方法是使得最小值和最大值為-1和+1。你使用標準化操作的理由是你認為不同的特征有不同的范圍,但是它們對學習算法的重要性都是一樣的。但是,對于圖像數據而言,像素的范圍都一樣(0~255),所以這個預處理操作并不是必須的。
PCA和Whitening 是另外一種預處理的方法。首先將數據中心化,然后我們計算協方差矩陣,它告訴我們數據之間的相關性:
#假設輸入數據矩陣X為[NxD]
X -= np.mean(X, axis = 0) #零中心化(重要)
cov = np.dot(x.T, x)/X.shape[0] #計算數據的協方差矩陣
協方差矩陣中(i,j)元素表示數據中第i個維度與第j個維度的協方差。特別地,這個矩陣的對角線為數據的方差。而且,協方差矩陣的對稱和半正定的。我們可以通過下面的方法計算協方差矩陣的SVD分解:
U,S,V = np.linalg.svd(cov)
其中,U的列向量為特征向量,S是1維的奇異值向量(為特征值的平方)。為了對數據去相關化,我們把原始數據(已經零中心化)映射到特征坐標軸:
Xrot = np.dot(X,U)
我們可以注意到U的每一列都是正交標準向量,所以它們可以作為坐標向量。所以這個映射對應于數據X的一個旋轉,以便所有的新軸都是特征向量。如果我們現在計算Xrot的特征向量,那么我們可以發現結果是一個對角線矩陣。np.lialg.svd的一個非常好的特性是它的返回值U的特征向量是按照特征值的大小排列的。我們使用這個特性,通過只使用最開始的幾個特征向量來減少數據的維度,這也被稱之為主成分分析(Principal Component Analysis PCA):
Xrot_reduced = np.dot(X,U[:,:100]) #xrot_reduced 為 [Nx100]
這個操作之后,我們把原始矩陣從[NxD]減少為[Nx100],只保存了100個方差最大的數據。在訓練線性分類器或神經網絡的過程中,使用PCA處理后的數據,可以獲得很好的結果,并且可以節省大量的時間和空間。
最后一個在實踐中經常使用的操作是白化(whitening)。白化操作將特征坐標系下的數據除以特征值,來標準化它的返回。這種轉變的幾何解釋就是,如果輸入數據是一個多變量的高斯分布,那么白化后的數據便是以零為平均值,單位協方差矩陣為標準差的高斯分布。白化的代碼為:
Xwhite = Xrot / np.sqrt(S + 1e-5)
警告:加大雜質
我們加入1e-5(或一個小的常量)來防止除零操作。這個操作的一個弱點是它們增加了數據中的雜質,它在數據的所有維度都增加了一個微小值。在實踐中,它能夠通過增加一個更大的平滑量(比如,將1e-5增加為一個更大的值)來緩解。
我們可以通過CIFAR-10圖片來可視化這些操作。CIFAR-10的大小為50,000x3072,我們可以計算出[3072x3072]的協方差矩陣,并計算SVD分解(計算花費會比較大)。計算的特征向量看出去是什么樣子的呢?下面的圖片可以幫助你:
在實踐中 我們介紹PCA/白化只是為了完整性,但是這些操作并不經常應用在卷積神經網絡中。但是,零中心化非常重要,我們經常會看到將每個像素點做這種標準化。
常見的錯誤 預處理非常重要的一點是任何預處理(比如 計算數據的平均值)必須在測試數據上計算,然后應用到驗證/測試集中。直接計算每張圖片的平均值,然后將每張圖片減去這個平均值的做法是錯誤的。平均值必須在訓練集上測試,然后在所有的圖片中減去這個值。
權重初始化
我們已經知道如何構造一個神經網絡架構,及如何對數據進行預處理。在開始訓練網絡之前,我們必須要初始化這些參數。
錯誤的做法:零初始化
我們先了解什么做法是錯誤的。我們并不知道每個權重的最終值是多少,但是我們可以假設其中有一半的權重是正數,一般的權重為負數。一個聽起來不錯的初始化方法便是設置所有的權重為零,因為至少我們對平均值的猜測非常好。但是實踐表明,這個做法是錯誤的,因為如果每個神經元的輸出都一樣,那么它們在反向傳播過程中都計算出相同的梯度,從而使得參數更新都一樣。也就是說,如果權重都初始化為相同的話,那么所有的神經元都是對稱的。
小的隨機數字
我們還是需要非常小的隨機數字,但是就像我們討論的那樣,不能都等于零。一個解決方案是,把所有的權重都初始化為小的數字,從而可以打破這種對稱性。其思想為:所有的神經元都是隨機和不同的,所以不同的神經元有不同的更新,并最后將這些神經元整合成一個網絡。一個可能的實現方式為:W = 0.01*np.random.randn(D,H),其中randn產生的數字為以零為平均值,單位標準差的高斯分布。使用這種方式,每個神經元的權重矩陣都從多維度高斯分布中隨機初始化。所以神經元在輸入空間內指向不同的方向。我們也可以從一個分布中隨機生成數據,但是在實踐中,它對最后的效果并沒有很大的影響。
警告 更小的數字不一定會產生更好的結果。比如,擁有小權重初始值的神經網絡在反向傳播過程中,會生成比較小的梯度(因為梯度值和權重值成正比)。這回導致梯度信號減少。所以,對于一個深度的網絡,這是需要考慮的一個因素。
使用1/sqrt(n)校準方差 上面建議的一個問題在于隨機初始化神經元的輸出分布方差會隨著輸入規模的增加而變大。我們可以通過除以輸入數目的平方根來標準化每個神經元的輸出方差。這可以確保神經網絡中的所有神經元輸出有相同的分布,而且實踐證明,它也會提高聚合的速率。
我們可以推導一下分數函數的方差。我們考慮內積
,我們可以計算s的方差
在前兩步中,我們使用了方差的特性。在第三步中,我們假設輸入和權重的平均值為零。所以,E[xi] = e[wi] = 0。注意:有的時候并不能做這種假設,對于ReLU,我們會有一個正的平均值。在最后一步中,我們假設所有的Wi和Xi的分布都一樣。從這個推導我們可以看出,如果我們想要s和輸入x的分布一樣,那么在初始化過程中,我們需要保證權重的方差為1/n。因為Var(aX) = a^2Var(X),所以a=1/sqrt(n)。所以w的初始化為w=np.random.randn(n)/sqrt(n)。
在Glorot的論文 Understanding the difficult of training deep feedforward neural networks 有過類似地分析。在這篇論文中, 作者推薦另外一種初始化的方法。一篇更近的關于這個主題的論文是Delving Deep into Rectifiers: Suepassing Human-Level Performance on ImageNet Classification. 在這篇論文中,推導了一種針對于ReLu神經元的初始化。最終得到的結論是w應該初始化為 w = np.random.randn(n)*sqrt(2.0/n)。
稀疏初始化 另外一種解決這種非校準方差問題的方法是先把所有的權重矩陣都初始化為0, 但是為了破壞對稱性,每個神經元都隨機和下一層的固定的神經元連接。一般,神經元為個數10個。
初始化偏置值 我們通常將偏置值初始化為零。因為在權重隨機化過程中已經破壞了對稱性。對于ReLU,有些人喜歡使用0.01初始化偏置值。因為這保證了所有的ReLU單元在最開始就能激活,并能夠獲得并傳播梯度。但是,它是否能夠提供一個穩定的提高,并不明確(實際上,一些結果表明,它使得最終的效果變得更差)。所以人們經常用0來初始化偏置值。
在實踐中, 對于ReLU單元的神經元,我們經常使用的初始化公式為 w=np.random.randn(n)*sqrt(2.0/n)。
批量標準化 Szegedy 最近提出了一種新的技術叫批量標準化(Batch Normaization) ,它通過顯式地使得通過網絡的激活值在訓練的開始就服從正態分布。重要的是這是可能的,因為標準化是一個簡單的微分操作。在代碼實現中,我們經常通過插入一個批量標準化層(BatchNorm layer)來實現。批量標準化層通常插入在全連接層(或者卷積層)后面,在非線性層前面。我們不展開講這種技術,因為它在這篇論文中講的很清楚了。在神經網絡中使用這個技術,已經是一個非常常見的做法了。在實踐中,使用批量標準化的神經網絡對不好的初始化值具有更好的魯棒性。除此之外,批量標準化還可以解釋為對每一層的網絡中數據做預處理,然后以可微分的形式連接成一個網絡。
正則化
下面講解幾種常見的防止過擬合的方法:
L2 正則化 是最常見的正則化方法。它可以直接懲罰目標函數中所有參數的平方量。也就是說,對于神經網絡的每個權重w,我們增加一項 1/2λ(w^2),其中λ是正則強度。在λ前面我們通常增加1/2,是為了使得這一項的導數為λW,而不是2λW。L2正則項可以懲罰高的權重值,并且更喜歡分散的權值向量。就像我們在線性分類那一節講的那樣,由于權重和輸入有點積乘法,使得整個網絡能夠使用所有的輸入而不是部分輸入。最后,注意到在梯度下降參數更新過程中,使用L2正則化會最終意味著每個權重都線性地衰減: W += -λW,并不斷地趨近于零。
L1正則化 是另一種常見的正則化方法。對于每個權重w,我們增加λ|w|到目標函數中。我們將L1和L2結合起來也是可能的:
(這稱之為彈性網絡正則化 Elastic net regularization)。L1正則化的特性是它使得在優化過程中權重向量變得更加稀疏(非常接近于零)。換句話說,L1正則化的神經元只使用其中最重要的輸入子集,所以可以抵抗雜質輸入。而L2正則化的最終權重向量通常是分散的,小的數字。在實踐中,如果你不考慮顯式特征的選擇,那么L2正則項會比L1正則項的效果更好。
最大正則限制
另外一種正則方法是對每個神經元的權重向量規定一個絕對上限,使用映射后的梯度下降來強制限制。在實踐中,首先正常更新參數,然后通過設定一個c值,使得||w||< c。有些實驗表明這能夠提高效果。這種方法的一個好的特性當學習速率設置太高,但是網絡不會“爆炸”,因為更新后的值永遠有上限。
Dropout 是由Srivastava在論文Dropout: A simple Way to Prevent Neural Networks from Overfitting 中介紹的一種非常有效,簡單的正則方法。在訓練過程中,Dropout只以p(超參數)的概率激活一個神經元。
"""Vanilla Dropout: 一種不推薦的實現方法"""
p = 0.5
def train_step(X):
""" X contains the data"""
#三層神經網絡的前向傳播
H1 = np.maximum(0, np.dot(W1, X) + b1)
U1 = np.random.rand(*H1.shape) < p #第一個dropout遮蔽圖
H1 *= U1 #drop!
H2 = np.maximum(0, np.dot(W2, H1) + b2)
U2 = np.random.rand(*H2.shape) < p #第二個Dropout遮蔽圖
H2 *= U2 #drop
out = np.dot(W3, H2) + b3
#反向傳播,計算梯度...
#進行參數更新...
def predict(X):
H1 = np.maximum(0, np.dot(W1,X) + b1) * p
H2 = np.maximum(0, np.dot(W2,H1) + b2) * p
out = np.dot(W3, H2) + b3
在上面的代碼中,在train_step函數中,我們進行了兩次Dropout操作:在第一個隱藏層和第二個隱藏層。我們也可以直接在輸入層進行Dropout操作,反向傳播操作保持不變,但是我們必須要考慮到生成的遮蔽圖U1和U2。
注意: 我們在predict函數中不再丟掉神經元,但是我們將兩個隱藏層的參數的大小都縮小了p。這非常重要,因為在測試階段,神經元能看到所有的輸入,而我們希望在測試階段,神經元的輸入與訓練階段神經元的輸出一樣。比如,如果p=0.5, 為了使得測試神經元的輸出與訓練神經元的輸出一樣,那么在測試階段,所有的神經元都必須要減半。假設一個神經元的輸出為x, 那么加入Dropout之后,輸出為px+(1-p)0 = px。所以,在測試階段,我們必須將輸出結果乘以參數p,才能使得最后的輸出結果為px。
這種方法的一個不好的地方在于我們必須在測試階段乘以參數p。因為測試階段的時間花費很寶貴。所以我們經常使用inverted dropout 。它在訓練階段就將參數變化為1/p,而在測試階段不需要做任何其他的處理。代碼如下:
"""Inverted Dropout: 推薦的實踐方法.
在訓練階段進行drop和scale操作,而在測試階段不做任何事情
"""
p = 0.5
def train_step(X):
#三層神經網絡的前向傳導
H1 = np.maximun(0, np.dot(W1, X) + b1)
U1 = (np.random.rand(*H1.shape)<p)/p
H1 *= U1
H2 = np.maximum(0, np.dot(W2, H1) + b2)
U2 = (np.random.rand(*H2.shape)<p)/p
#反向傳播:計算梯度...
#參數更新
def predict(X):
#前向傳導
H1 = np.maximum(0, np.dot(W1, X) + b1)
H2 = np.maximum(0, np.dot(W2, H1) + b2)
out = np.dot(W3,H2) + b3
在Dropout概念介紹之后,有大量的研究試圖理解它的效果來源及它與其他正則技術的關系。有興趣的讀者可以閱讀以下論文:
- Dropout paper by Srivastava et al. 2014
- Dropout Training as Adaptive Regularization
前向傳導中的雜質
Dropout引入了神經網絡前向傳播的隨機行為。在測試階段,雜質分析地(在Dropout例子中,是乘以p)或數值地(先通過取樣,使用不同的隨機決定來進行幾次前向傳導,最后取它們的平均)邊緣化。一個在這方面的研究包括DropConnect, 其中在前向傳播過程中,隨機取一些權重,并把它們設為零。順便提一下,在卷積神經網絡中,也利用了這種方法,比如:隨機池化(stochastic pooling), 分數池化(factional pooling), 數據擴大(data augmentation)。在后面我們會詳細講解這些內容。
偏置正則化
在線性分類這一節中我們提過,通常我們不正則化偏置參數,因為它們并不和數據進行乘法操作。所以也無法在最后的目標函數中對數據維度有影響。然而,在實踐中,正則化偏置量很少會導致更差的效果。這很可能是因為相對于權重參數的個數來說,偏置量的參數個數非常少,所以分類器能夠通過改變自己而達到更好的結果。
每層的正則化
針對每一層,使用不同的正則項的做法并不常見。關于這方面的主題的研究相對來說比較少。
在實踐中, 我們通常使用一個全局的L2正則項(這個值由交叉驗證得出)。我們通過將它與Dropout(在所有層的最后使用)結合起來使用也非常常見。p=0.5是常用的默認值,但是也可以根據驗證集來調整。
損失函數
我們已經討論了目標函數中正則損失部分,它可以看成是懲罰模型的復雜性。目標函數的第二部分是數據損失,它在監督學習問題中測量預測和真實標簽的契合性。數據損失計算每個單獨例子的平均數據損失。計算公式如下:
其中N是訓練數據的個數。在實踐中,我們可能需要解決幾種類型的問題:
分類 問題我們在之前已經討論過。我們假設我們有一個數據集,而且每個數據集中的圖片都有一個標簽。兩種最常用的花費函數之一是SVM
有些人報告稱平方的鉸鏈損失能取得更好的效果。第二個常見的選擇是Softmax分類器,它使用交叉熵損失
大數量的類別
當標簽的數據集非常大(比如英語詞典的單詞,或者ImageNet包含22,000個類別),我們需要使用層級Softmax(Hierarchical Softmax),層級Softmax將類別組織成一棵樹。那么每個標簽可以表示為沿著一棵樹的路徑。Softmax分類器就訓練樹的每個節點來分辨左枝和右枝。樹的結構非常影響最終的效果,并且樹的結構一般都取決于問題的類別。
特征分類器
上面兩種損失都假設只有一個正確答案yi,如果yi是一個向量,其中每個值表示包含或者不包含某個特征,而且每個特征都不是互斥的。比如,在Instagram上的圖片都有很多標簽。解決這個問題的一個可行的方法是對應每張圖片,都建立一個特性向量。比如,一個二元分類器的公式如下:
其中 Li是所有類別j的總和。yij要么是+1,要么是-1,取決于第i個圖片是否具有第j種特性。當被預測成這個類別是,那么分數向量fj就為正,否則就為負。從上面的例子我們也可以看出,如果正樣例的分數大于+1, 或者當負樣例大于-1.
另外一種計算這種損失的方法是對每個特征單獨訓練一個邏輯回歸分類器。一個二元邏輯回歸分類器只有兩個種類(0,1),計算分到種類1的概率為:
因為種類1和種類0之和為1。種類0的概率為:
所以,一個樣例
或者 Wx+b > 0, 那么它就被歸類于正樣本(y=1)。損失函數然后最大化這個概率的log形式。你可以說服自己這種操作可以簡化為如下公式:
其中yij的取值是1或0。σ(.)是sigmoid函數。這個表達式咋看起來很嚇人,但是梯度f卻非常簡單。?Li/?fj=yij?σ(fj)?Li/?fj=yij?σ(fj)。
回歸 可以預測實數數量的任務。比如房子的價格,或者在圖片中某種紋理的長度。對于這種類型的任務,我們通常計算預測的數量與真實的答案之間的差別,然后計算L2平方差距或者L1差別。L2平方差距的計算公式如下:
L2平方的理由在于計算簡單,而且還能保持單調性不變。L1的計算公式如下:
如果維度的個數大于1,則求所有維度的和。我們現在只看第i個樣例的第j個維度,將預測與真實之間的差異表示為δij, 那么這個維度的梯度要么是δij(L2 norm),要么是sign(δij)。也就是說,分數梯度要么正比于差異,或者為差異的符號。
注意: L2損失比更穩定的損失比如Softmax更難優化。直覺上理解,網絡需要一個特別的特性來對每一個輸入產生一個正確結果。但是對Softmax,卻不是這樣。其中,每個分數的準確值不是最重要的。只要它們的大小合適就行。而且,L2損失具有更少的魯棒性因為雜質能夠導致更大的梯度。當遇到回歸問題的時候, 我們首先考慮是否將輸出量化為等間距的值是否是不夠的。比如,我們對意見產品進行評價,我們可以使用1~5來進行評分,而不是使用回歸損失。分類還有一個附加的好處,就是你可以看到回歸輸出的分布,而不是一個單獨的數字。如果你確定分類是不合適的,那么也可以使用L2,但是必須要小心:比如: L2更加脆弱,并且應用Dropout(特別是在L2損失之前)并不是一個很好的想法。
當遇到回歸問題,首先考慮這是否是必須的。我們應該首先考慮將你的輸出等間距化,并對它們使用分類。
結構化預測 結構化損失指的是標簽可以變成隨意的結構,比如圖,樹或者其他復雜的結構。通常我們也假設結構的空間非常大,而且不可數。結構化SVM背后的想法是要求正確結構yi和最高分數的錯誤答案的間距。解決這個問題的通常不使用梯度下降來解一個簡單的非限制的優化問題。我們通常會有一個簡單的假設,從而設計一個特別地求解方法。具體的細節已經超過了本課程。
總結
- 推薦的預處理數據方法是使得數據的平均值為0,而且將其范圍標準化為[-1,1]。
- 在初始化權重時,我們從標準差為sqrt(2/n)的高斯分布中選取數值,其中n為神經元輸入的個數。用numpy實現的代碼為 w = np.random.randn(n)*sqrt(2.0/n)
- 使用L2和Dropout(反向版本)
- 使用批量標準化
- 我們討論了在實踐中我們會遇到的不同的任務,以及每個任務的損失函數。
我們現在已經預處理了數據,初始化了模型,下一節我們會討論學習過程及它的動態性。