撥開深度學習的迷霧:訓練一個性能優秀的深度模型

深度學習技術已經在很多領域取得了非常大的成功,然而其調參的複雜性也導致很多機器學習從業者迷失在叢林里。本文旨在總結一些訓練出一個性能優秀的深度模型的相關經驗,幫助自己以及這個領域的初學者少走彎路。

激活函數

sigmoid/tanh. 存在飽和的問題,現在不建議使用。

ReLU. 最近幾年常用的激活函數,形式為 f(x) = max(0, x) ,目前建議首先嘗試用這個激活函數。

  • 相比於sigmoid/tanh函數而言,ReLU能極大地加快SGD演算法的收斂速度,因為其分段線性的形式不會導致飽和;
  • 相比於sigmoid/tanh函數而言,ReLU實現簡單不需要昂貴的指數運算
  • 然而,訓練過程中ReLU單元可能會失效。例如,當一個非常大的梯度流經ReLU單元時,導致該單元對應的權重更新後(變得非常小)再也無法在任何數據點上激活。因此,需要小心設置學習率,較小的學習率可以緩解該問題。(太大的學習率可能會導致40%以上的ReLU單元變成dead的不可逆狀態)

    Leaky ReLU. 函數形式為: f(x) = mathbb{1}(x < 0) (alpha x) + mathbb{1}(x>=0) (x) ,其中 alpha 是一個小的常數。Leaky ReLU是為了修復ReLU的dying問題,然後實際使用的結果並不一致。

Maxout. 可以看作是ReLU的泛化。形式如: max(w_1^Tx+b_1, w_2^Tx + b_2)

如何決定網路結構(層數和每層的節點數)

增加神經網路的層數或者節點數,模型的容量(能夠表示的函數空間)會增大。下圖是在一個二分類問題上不同隱層節點數的3個單隱層神經網路模型的訓練結果。

可以看出,節點數越多越能夠表示複雜的函數,然而也越容易過擬合,因為高容量的模型容易捕獲到訓練數據噪音。如上圖所示,只有隱層只有3個節點的模型的分類決策面是比較光滑的,它把那些錯誤分類的點認為是數據中的異常點/噪音(outlier)。實際中,這樣的模型泛化性能可能更好。

那麼是否意味著我們應該偏好小的模型呢?答案是否定的,因為我們有其他更好的方法來防止模型過擬合,比如說正則化、dropout、噪音注入等。實際中,更常用這些方法來對抗過擬合問題,而不是簡單粗暴地減少節點數量。

這其中微妙的原因在於,小的神經網路用梯度下降演算法更難訓練。小的神經網路有更少的局部極小值,然而其中許多極小值點對應的泛化性能較差且易於被訓練演算法到達。相反,大的神經網路包含更多的局部極小值點,但這些點的實際損失是比較小的。更多內容請參考這篇論文《The Loss Surfaces of Multilayer Networks》。實際中,小的網路模型的預測結果方差比較大;大的網路模型方差較小。

重申一下,正則化是更加好的防止過擬合的方法,下圖是有20個節點的單隱層網路在不同正則化強度下的結果。

可見,合適的正則化強度可以使得一個較大的模型的決策分類面比較光滑。然而,千萬要注意的是不能讓正則項的值淹沒了原始數據損失,那樣的話梯度就主要有正則項來決定了。

因此,在計算能力預算充足的情況下,應該偏好使用大的網路模型,同時使用一些防止過擬合的技術。

數據預處理

假設有數據集為一個N * D的矩陣X,N是數據記錄數,D是每條數據的維數。

  • 減去均值。在每個特徵上都把原始數據減去該維特徵的均值是一種常用的預處理手段,處理之後的數據是以原點為中心的。X -= np.mean(X, axis = 0)
  • 歸一化。使得不同維度的數據有相同的scale。主要有兩種歸一化方法,一種是各個維度上首先減去均值後再除以標準差:X /= np.std(X, axis = 0;另一種是最小最大標準化,這種方法歸一化之後的範圍在[-1,1],只有在不同維度特徵數據有不同的單位或者scale時,採用這種方法才是有意義的。

  • 降維。如PCA方法、Whitening方法。這是一個可選的步驟。

注意數據預處理的陷阱。所有預處理階段需要用到的統計數據,比如均值、標準差等,只能在訓練集上計算,然後應用到包括測試集和驗證集的數據上。例如,在整個數據集上計算均值,然後每條數據減去均值做數據原點中心化,最後再把數據集分裂為訓練集、驗證集和測試集的流程是錯誤的。這種類型的錯誤有時候被叫做數據穿透,即訓練集中包含了本不該包含的數據或數據的統計特徵,是機器學習從業者常犯的一種數據。比如,在做商品點擊率預估時,假設我們用不包括昨天在內的前7天的日誌數據作為特徵提取的數據集,昨天的日誌數據作為數據樣本的label生成數據集,那麼需要格外小心計算特徵(比如,用戶的偏好類目)時,千萬不要把昨天的數據也統計進去。

權重初始化

神經網路權重初始化的基本原則是要打破網路結構的對稱性(symmetry breaking)。比如,權重全部初始化為0是錯誤的,這樣的話所有節點計算到的梯度值都是一樣的,權重更新也是一致的,最終的結果就是所有權重擁有相同的值。

  • 隨機初始化為小的數值。當然也不能太小,否則計算出的梯度就會很小。W = 0.01* np.random.randn(D,H)
  • 用n的平方根校正方差。w = np.random.randn(n) / sqrt(n),其中n是輸入的數量。這也意味著,權重需要逐層初始化,因為每層的節點對應的輸入通常是不同的。如果節點的激活函數為ReLU,那麼用sqrt(2.0/n)來校正方差更好:w = np.random.randn(n) * sqrt(2.0/n)
  • 稀疏初始化。首先把所有的權重初始化為0,然後為每個節點隨機選擇固定數量(比如10)的鏈接賦予小的高斯權重。該方法也可以打破網路結構的對稱性。
  • Batch Normalization。

偏置(biases)通常初始化為0。

損失函數

多分類問題的常見損失函數為:

  • SVM loss: L_i = sum_{j
eq y_i} max(0, f_j - f_{y_i} + 1)
  • Cross-entropy loss: L_i = -logleft(frac{e^{f_{y_i}}}{ sum_j e^{f_j} }
ight) 。對應非常多類別的分類任務,交叉熵損失的計算代價是非常大的,緩解這一問題的常用方法包括Hierarchical Softmax和negative sampling。

屬性分類問題(Attribute classification,即每個樣本的label不止一個)的常用損失函數為:

L_i = sum_j max(0, 1 - y_{ij} f_j)

上式中的加和是在所有類別 j 上進行的,當第 i 個樣本有第 j 個類別標籤時 y_{ij} 的值為1,否則為-1;當第j個類別被預測時, f_j 的值為正,否則為負。

另一種常見的方法,是為每一個類別訓練一個二分類模型,這時採用的損失為邏輯回歸損失函數:

L_i = sum_j y_{ij} log(sigma(f_j)) + (1 - y_{ij}) log(1 - sigma(f_j))

其中 y_{ij} 在模型預測為正例時值為1,預測為負例時值為0。

回歸問題的損失函數:

  • L2 loss: L_i = Vert f - y_i Vert_2^2
  • L1 loss: L_i = Vert f - y_i Vert_1 = sum_j mid f_j - (y_i)_j mid

L2損失通常比較難優化,相對於比較穩定的Softmax損失而言,因為它需要網路輸出儘可能精確逼近一個值;而對於Softmax而言,每個節點輸出的具體值並不是那麼重要,softmax只關心它們的(相對)大小是否恰當。並且,L2損失易受異常點的影響,魯棒性不夠。

因此,當遇到一個回歸問題時,首先考慮能否轉化為分類問題,即可否把要回歸的值劃分到固定大小的桶。比如,一個評分預測任務,與其訓練一個回歸模型,不如訓練5個獨立的分類模型,用來預測用戶是否給評分1~5。

When faced with a regression task, first consider if it is absolutely necessary. Instead, have a strong preference to discretizing your outputs to bins and perform classification over them whenever possible.

檢查梯度

如果自己實現模型,需要做梯度的解析解和數值解的對比驗證。數值解用下面的公式計算: frac{df(x)}{dx} = frac{f(x + h) - f(x - h)}{2h} hspace{0.1in}

其中, h 的推薦值為1e-4 ~ 1e-6。

在比較兩者的差異時,使用相對誤差,而不是絕對誤差: frac{mid f_a - f_n mid}{max(mid f_a mid, mid f_n mid)}

  • relative error > 1e-2 usually means the gradient is probably wrong
  • 1e-2 > relative error > 1e-4 should make you feel uncomfortable
  • 1e-4 > relative error is usually okay for objectives with kinks. But if there are no kinks (e.g. use of tanh nonlinearities and softmax), then 1e-4 is too high.
  • 1e-7 and less you should be happy.

網路越深,兩者的相對誤差越大。另外,為了防止數值問題,在計算過程中使用double類型而不是float類型。還需要額外注意不可導的點,比如ReLU在原點出不可導。需要在儘可能多的節點比較兩者,而不只是一小部分。可以只在一部分維度上做檢查。做梯度檢查時,需要關閉正則項、dropout等。

訓練前的檢查

  • 初始化權重之後,檢查損失是否符合預期。比如,10個類別的分類問題,採用交叉熵損失函數,那麼期望的初始數據損失(不包括正則項)為2.302左右,因為我們預計初始時每個類別被預測的概率都是0.1,因此交叉熵損失就是正確類別的負對數概率:-ln(0.1)=2.302。對於The Weston Watkins SVM損失,初始時假設有9個類別都違反了最小間隔是合理的,因此期望損失為9(因為每一個錯誤的列表的最小間隔為1)。
  • 增加正則項強度,應該要能對應地增加損失。
  • 用一小部分數據訓練模型,直到模型過擬合,最終的損失為0(正則項強度設為0)。如果這項檢查沒有通過,就不該開始訓練模型。

監控訓練過程

  • 跟蹤損失的變化情況(evaluated on the individual batches during the forward pass),驗證學習率是否設置合理。

  • 跟蹤正確率的變化(在訓練集和驗證集上分別跟蹤),判斷模型是否過擬合,以及模型該在什麼時候停止訓練。

如果發生過擬合,則應加強正則化的強度,比如增加L2正則項的係數 lambda ,或者增加dropout的概率等。當然,如果驗證集的正確率和訓練集的正確率一直吻合得很好也是有問題的,這意味著模型的容量可能不夠,應該嘗試增加更多的節點(參數)。

  • 跟蹤權重更新情況,計算並記錄每組參數更新的比率: frac{Delta w}{w} ,這個比率應該在1e-3左右,如果比這個值小意味著學習率可能過小,反之,則應懷疑學習率是否過大。

# assume parameter vector W and its gradient vector dWparam_scale = np.linalg.norm(W.ravel())update = -learning_rate*dW # simple SGD updateupdate_scale = np.linalg.norm(update.ravel())W += update # the actual updateprint update_scale / param_scale # want ~1e-3

  • 跟蹤每層的激活函數值分布或梯度分布,驗證初始化是否正確。比如使用tanh激活函數的層,如果看到激活函數的值大量集中在0、1或者-1,則表示不正常。
  • 如果是在處理圖像任務,則可以嘗試可視化第一層的權重,查看模擬的圖片是否光滑、乾淨。

參數更新

神經網路模型的參數更新有多種方式,具體可以查看這篇文章: 深度學習中的常用訓練演算法。

SGD+Nesterov Momentum 或者 Adam 是兩種推薦的參數更新方法。

超參數優化

神經網路模型的主要超參數包括:

  • 初始學習率
  • 學習率衰減調度方法
  • 正則化的強度

由於神經網路模型通常比較大,因此交叉驗證的代價很高,通常用一折驗證來代替交叉驗證。

在log scale上搜索超參數是推薦的做法,比如學習率的搜索範圍可以設為learning_rate = 10 ** uniform(-6, 1),正則化強度也可以採用類似的方法。這是因為學習率和正則化強度都是以乘積的形式影響代價函數的。如果學習率為0.001,那麼加上0.01就會產生巨大的影響;但如果學習率為10,那麼加上0.01則幾乎觀察不到任何影響,因此考慮學習率的範圍時乘以或除以一個數,要不加上或者減去一個數更好。在另外一些參數上,則還是保留原來的量級較好,比如dropout概率:dropout = uniform(0,1)

需要注意搜索範圍的邊界,如果效果在邊界處最好,則可能需要修改搜索範圍並重新搜索。

與Grid search相比,random search更好,據說random search效率更高。深度學習中經常發生的情況是,其中一些超參數要比另一些更加重要,與網格搜索相比隨機搜索能夠精確地在重要的超參數上發現更好的值。具體查看這偏論文:《Random Search for Hyper-Parameter Optimization》

超參搜索需要分階段,從粗粒度到細粒度分層進行。比如首先先搜索較粗的範圍(e.g. 10 ** [-6, 1]),然後根據搜索到的最好的值,使用更窄的搜索範圍。粗粒度搜索時,可以不用等待訓練完全收斂,只需要觀察前面幾個epoch即可。

貝葉斯超參數優化是一個研究如何高效地探索超參數空間的研究領域,其核心思想是在不同的超參數上驗證性能時做好探索和利用的平衡(exploration - exploitation trade-off)。 Spearmint,SMAC,Hyperopt是幾個有名的基於貝葉斯超參數優化方法的庫。

模型集成

訓練幾個獨立的神經網路模型,用他們預測結果的平均值(或者多數表決)來確定最終的結果,是一種常用的改進性能的方法。通常集成的模型數量越多,改進的空間也越大。當然,集成彼此之間有差異化的模型更好。幾種構建集成模型的常用方法如下:

  • 相同的模型,不同的初始化。用交叉驗證的方法確定最佳超參數,然後訓練多個使用最佳超參數但不同初始化的模型。這樣方法,集成的模型多樣性可能不夠。
  • 交叉驗證中發現的性能最好的多個模型。有足夠的多樣性,但也增加了集成進一些次優的模型的風險。
  • 保留同一個模型在訓練過程中的不同副本(checkpoint)。因為深度學習的訓練通常都是昂貴的,這種方法不需要訓練多個模型,是非常經濟的。但也有缺乏多樣性的風險。
  • 在訓練過程中平均模型的參數得到一個新的模型。在訓練過程中維護一個額外的網路,它的參數取值與正式模型權重的指數衰減和(an exponentially decaying sum of previous weights during training)。相對於是維護了最近幾次迭代生成的模型的移動平均,這種平滑的方法相對於是前一種方法的一種特殊實現,在實際中可以獲得一到兩個百分點的性能提升。對這種方法一個比較粗略的認識是,代價函數的誤差超平面是一個碗狀的結構,模型嘗試到達碗底的位置,因此平均之後的模型更容易到家接近碗底的位置。

參考資料

Dark Knowledge from Geoff Hinton

Practical Recommendations for Gradient-Based Training of Deep Architectures from Yoshua Bengio

CS231n: Convolutional Neural Networks for Visual Recognition from Andrew Ng

註:本文最新發表於:yangxudong.github.io/de


推薦閱讀:

TAG:深度學習DeepLearning | 機器學習 | 神經網路 |