NLP教程(3) - 神經網路與反向傳播

ShowMeAI發表於2022-05-05

神經網路與反向傳播
本系列為史丹佛CS224n《自然語言處理與深度學習(Natural Language Processing with Deep Learning)》的全套學習筆記,對應的課程視訊可以在 這裡 檢視。

神經網路知識回顧

神經網路反向傳播與計算圖

ShowMeAI為CS224n課程的全部課件,做了中文翻譯和註釋,並製作成了 GIF動圖!點選 第3講-詞向量進階第4講-神經網路反向傳播與計算圖 檢視的課件註釋與帶學解讀。更多資料獲取方式見文末。


引言

CS224n是頂級院校史丹佛出品的深度學習與自然語言處理方向專業課程,核心內容覆蓋RNN、LSTM、CNN、transformer、bert、問答、摘要、文字生成、語言模型、閱讀理解等前沿內容。

這組筆記介紹了單層和多層神經網路,以及如何將它們用於分類目的。然後我們討論如何使用一種稱為反向傳播的分散式梯度下降技術來訓練它們。我們將看到如何使用鏈式法則按順序進行引數更新。在對神經網路進行嚴格的數學討論之後,我們將討論一些訓練神經網路的實用技巧和技巧,包括:神經元單元(非線性)、梯度檢查、Xavier引數初始化、學習率、Adagrad等。最後,我們將鼓勵使用遞迴神經網路作為語言模型。

內容要點

  • 神經網路
  • 反向傳播
  • 梯度計算
  • 神經元
  • 合頁損失
  • 梯度檢查
  • Xavier引數初始化
  • 學習率
  • Adagrad優化演算法

1.神經網路基礎

(本部分內容也可以參考ShowMeAI的對吳恩達老師課程的總結文章深度學習教程 | 神經網路基礎深度學習教程 | 淺層神經網路深度學習教程 | 深層神經網路

在前面的討論中認為,因為大部分資料是線性不可分的所以需要非線性分類器,不然的話線性分類器在這些資料上的表現是有限的。神經網路就是如下圖所示的一類具有非線性決策分界的分類器。我們可以在圖上清晰地看到其非線性決策邊界,下面我們來看看模型是如何學習得到它的。

神經網路是受生物學啟發的分類器,這就是為什麼它們經常被稱為“人工神經網路”,以區別於有機類。然而,在現實中,人類神經網路比人工神經網路更有能力、更復雜,因此通常最好不要在兩者之間畫太多的相似點。

神經網路基礎

1.1 單個神經元

神經元是一個通用的計算單元,它接受 \(n\) 個輸入併產生一個輸出。不同的神經元根據它們不同的引數(一般認為是神經元的權值)會有不同的輸出。

對神經元來說一個常見的選擇是 \(sigmoid\) ,或者稱為“二元邏輯迴歸”單元。這種神經元以 \(n\) 維的向量作為輸入,然後計算出一個啟用標量(輸出) \(a\) 。該神經元還與一個 \(n\) 維的權重向量 \(w\) 和一個偏置標量 \(b\) 相關聯。

這個神經元的輸出是:

$$ a=\frac{1}{1+exp(-(w^{T}x+b))} $$

我們也可以把上面公式中的權值和偏置項結合在一起:

$$ a=\frac{1}{1+exp(-[w^{T}\;\;x]\cdot [x\;\;1])} $$

上述公式視覺化如下圖所示:

單個神經元

❐ 神經元是神經網路的基本組成部分。我們將看到神經元可以是許多允許非線性在網路中積累的函式之一。

1.2 單層神經網路

我們將上述思想擴充套件到多個神經元,考慮輸入 \(x\) 作為多個這樣的神經元的輸入,如下圖所示。

單層神經網路

如果我們定義不同的神經元的權值為 \({w^{(1)}, \cdots ,w^{(m)}}\) 、偏置為 \({b_1, \cdots ,b_m}\) 和相對應的啟用輸出為 \({a_1, \cdots ,a_m}\) :

$$ a_{1} =\frac{1}{1+exp(-(w^{(1)T}x+b_1))} $$

$$ \vdots $$

$$ a_{m} =\frac{1}{1+exp(-(w^{(m)T}x+b_m))} $$

讓我們定義簡化公式以便於更好地表達複雜的網路:

$$ \sigma(z) = \begin{bmatrix} \frac{1}{1+exp(z_1)} \\ \vdots \\ \frac{1}{1+exp(z_m)} \end{bmatrix} $$

$$ b = \begin{bmatrix} b_{1} \\ \vdots \\ b_{m} \end{bmatrix} \in \mathbb{R}^{m} $$

$$ W = \begin{bmatrix} -\;\;w^{(1)T}\;\;- \\ \cdots \\ -\;\;w^{(m)T}\;\;- \end{bmatrix} \in \mathbb{R}^{m\times n} $$

我們現在可以將縮放和偏差的輸出寫成:

$$ z=Wx+b $$

啟用函式sigmoid可以變為如下形式:

$$ \begin{bmatrix} a_{1} \\ \vdots \\ a_{m} \end{bmatrix} = \sigma(z) = \sigma(Wx+b) $$

那麼這些啟用的作用是什麼呢?我們可以把這些啟用看作是一些加權特徵組合存在的指標。然後,我們可以使用這些啟用的組合來執行分類任務。

1.3 前向與反向計算

到目前為止我們知道一個輸入向量 \(x\in \mathbb{R}^{n}\) 可以經過一層 \(sigmoid\) 單元的變換得到啟用輸出 \(a\in \mathbb{R}^{m}\) 。但是這麼做的直覺是什麼呢?讓我們考慮一個NLP中的命名實體識別問題作為例子:

Museums in Paris are amazing

這裡我們想判斷中心詞 Paris是不是以命名實體。在這種情況下,我們很可能不僅想要捕捉視窗中單詞的單詞向量,還想要捕捉單詞之間的一些其他互動,以便進行分類。例如,可能只有 Museums 是第一個單詞和 in 是第二個單詞的時候, Paris 才是命名實體。這樣的非線性決策通常不能被直接提供給Softmax函式的輸入捕獲,而是需要新增神經網路中間層再進行評分。因此,我們可以使用另一個矩陣 \(\mathbf{U} \in \mathbb{R}^{m \times 1}\) 與啟用輸出計算得到未歸一化的得分用於分類任務:

$$ s=\mathbf{U}^{T}a=\mathbf{U}^{T}f(Wx+b) $$

其中, \(f\) 是啟用函式(例如sigmoid函式)。

前向與反向計算

維度分析:如果我們使用 \(4\) 維的詞向量來表示每個單詞,並使用 \(5\) 個詞的視窗,則輸入是 \(x\in \mathbb{R}^{20}\) 。如果我們在隱藏層使用 \(8\) 個sigmoid單元和從啟用函式中生成一個分數輸出,其中 \(W\in \mathbb{R}^{8\times 20}\) , \(b\in \mathbb{R}^{8}\) , \(U\in \mathbb{R}^{8\times 1}\) , \(s\in \mathbb{R}\) 。

1.4 合頁損失

類似很多的機器學習模型,神經網路需要一個優化目標函式,一個我們想要最小化或最大化的誤差。這裡我們討論一個常用的誤差度量方法:maximum margin objective 最大間隔目標函式。使用這個目標函式的背後的思想是保證對“真”標籤資料的計算得分要比“假”標籤資料的計算得分要高。

回到前面的例子,如果我們令“真”標籤視窗 Museums in Paris are amazing 的計算得分為 \(s\) ,令“假”標籤視窗 Not all museums in Paris 的計算得分為 \(s_c\) (下標 \(c\) 表示這個這個視窗corrupt)

然後,我們對目標函式最大化 \((s-s_c)\) 或者最小化 \((s_c-s)\) 。然而,我們修改目標函式來保證誤差僅在 \(s_c > s \Rightarrow (s_c-s) > 0\) 才進行計算。這樣做的直覺是,我們只關心“正確”資料點的得分高於“錯誤”資料點,其餘的都不重要。因此,當 \(s_c > s\) 則誤差為 \((s_c-s)\) ,否則為0。因此,我們的優化的目標函式現在為:

$$ minimize\;J=max\,(s_c-s,0) $$

然而,上面的優化目標函式是有風險的,因為它不能創造一個安全的間隔。我們希望“真”資料要比“假”資料的得分大於某個正的間隔 \(\Delta\) 。換而言之,我們想要誤差在 \((s-s_c < \Delta)\) 就開始計算,而不是當 \((s-s_c < 0)\) 時就計算。因此,我們修改優化目標函式為:

$$ minimize\;J=max\,(\Delta+s_c-s,0) $$

我們可以把這個間隔縮放使得 \(\Delta=1\) ,讓其他引數在優化過程中自動進行調整,並且不會影響模型的表現。(合頁損失與最小間隔問題,大家可以閱讀ShowMeAI機器學習演算法教程中對SVM演算法的講解)。最後,我們定義在所有訓練視窗上的優化目標函式為:

$$ minimize\;J=max\,(1+s_c-s,0) $$

按照上面的公式有:

$$ s_c=\mathbf{U}^{T}f(Wx_c+b) $$

$$ s=\mathbf{U}^{T}f(Wx+b) $$

❐ 最大邊際目標函式通常與支援向量機一起使用

1.5 反向傳播(單樣本形態)

上一節我們提到了合頁損失,下面我們講解一下當損失函式 \(J\) 為正時,模型中不同引數時是如何訓練的。如果損失為 \(0\) 時,那麼不需要再更新引數。我們一般使用梯度下降(或者像SGD這樣的變體)來更新引數,所以要知道在更新公式中需要的任意引數的梯度資訊:

$$ \theta^{(t+1)}=\theta^{(t)}-\alpha\nabla_{\theta^{(t)}}J $$

反向傳播是一種利用微分鏈式法則來計算模型上任意引數的損失梯度的方法。為了更進一步理解反向傳播,我們先看下圖中的一個簡單的網路:

反向傳播(單樣本形態)

這裡我們使用只有單個隱藏層和單個輸出單元的神經網路。現在讓我們先建立一些符號定義:

  • \(x_i\) 是神經網路的輸入
  • \(s\) 是神經網路的輸出
  • 每層(包括輸入和輸出層)的神經元都接收一個輸入和生成一個輸出。第 \(k\) 層的第 \(j\) 個神經元接收標量輸入 \(z_j^{(k)}\) 和生成一個標量啟用輸出 \(a_j^{(k)}\)
  • 我們把 \(z_j^{(k)}\) 計算出的反向傳播誤差定義為 \(\delta_j^{(k)}\)
  • 第 \(1\) 層是輸入層,而不是第 \(1\) 個隱藏層。對輸入層而言, \(x_j=z_j^{(1)}=a_j^{(1)}\)
  • \(W^{(k)}\) 是將第 \(k\) 層的輸出對映到第 \(k+1\) 層的輸入的轉移矩陣,因此將這個新的符號用在上面1.3節中的例子 \(W^{(1)}=W\) 和 \(W^{(2)}=U\)

現在開始反向傳播

假設損失函式 \(J=(1+s_c-s)\) 為正值,我們想更新引數 \(W_{14}^{(1)}\) ,我們看到 \(W_{14}^{(1)}\) 只參與了 \(z_1^{(2)}\) 和 \(a_1^{(2)}\) 的計算。這點對於理解反向傳播是非常重要的——反向傳播的梯度只受它們所貢獻的值的影響。 \(a_1^{(2)}\) 在隨後的前向計算中和 \(W_1^{(2)}\) 相乘計算得分。我們可以從最大間隔損失看到:

$$ \frac{\partial J}{\partial s}=-\frac{\partial J}{\partial s_c}=-1 $$

為了簡化我們只分析 \(\frac{\partial s}{\partial W_{ij}^{(1)}}\) 。所以,

$$ \begin{aligned} \frac{\partial s}{\partial W_{ij}^{(1)}} &= \frac{\partial W^{(2)}a^{(2)}}{\partial W_{ij}^{(1)}}=\frac{\partial W_i^{(2)}a_i^{(2)}}{\partial W_{ij}^{(1)}}=W_i^{(2)}\frac{\partial a_i^{(2)}}{\partial W_{ij}^{(1)}} \\ \Rightarrow W_i^{(2)}\frac{\partial a_i^{(2)}}{\partial W_{ij}^{(1)}} &= W_i^{(2)}\frac{\partial a_i^{(2)}}{\partial z_i^{(2)}}\frac{\partial z_i^{(2)}}{\partial W_{ij}^{(1)}} \\ &= W_i^{(2)}\frac{f(z_i^{(2)})}{\partial z_i^{(2)}}\frac{\partial z_i^{(2)}}{\partial W_{ij}^{(1)}} \\ &= W_i^{(2)}f^{\prime}(z_i^{(2)})\frac{\partial z_i^{(2)}}{\partial W_{ij}^{(1)}} \\ &= W_i^{(2)}f^{\prime}(z_i^{(2)})\frac{\partial}{\partial W_{ij}^{(1)}}(b_i^{(1)}+a_1^{(1)}W_{i1}^{(1)}+a_2^{(1)}W_{i2}^{(1)}+a_3^{(1)}W_{i3}^{(1)}+a_4^{(1)}W_{i4}^{(1)}) \\ &= W_i^{(2)}f^{\prime}(z_i^{(2)})\frac{\partial}{\partial W_{ij}^{(1)}}(b_i^{(1)}+\sum_{k}a_{k}^{(1)}W_{ik}^{(1)}) \\ &= W_i^{(2)}f^{\prime}(z_i^{(2)})a_j^{(1)} \\ &= \delta_i^{(2)}\cdot a_j^{(1)} \end{aligned} $$

其中, \(a^{(1)}\) 指輸入層的輸入。我們可以看到梯度計算最後可以簡化為 \(\delta_i^{(2)}\cdot a_j^{(1)}\) ,其中 \(\delta_i^{(2)}\) 本質上是第 \(2\) 層中第 \(i\) 個神經元反向傳播的誤差。 \(a_j^{(1)}\) 與 \(W_{ij}\) 相乘的結果,輸入第 \(2\) 層中第 \(i\) 個神經元中。

我們以下圖為例,讓我們從“誤差共享/分配”的來闡釋一下反向傳播,現在我們要更新 \(W_{14}^{(1)}\) :

反向傳播(單樣本形態)

  • ① 我們從 \(a_1^{(3)}\) 的1的誤差訊號開始反向傳播
  • ② 然後我們把誤差與將 \(z_1^{(3)}\) 對映到 \(a_1^{(3)}\) 的神經元的區域性梯度相乘。在這個例子中梯度正好等於1,則誤差仍然為1。所以有 \(\delta_1^{(3)}=1\)
  • ③ 這裡誤差訊號1已經到達 \(z_1^{(3)}\) 。我們現在需要分配誤差訊號使得誤差的“公平共享”到達 \(a_1^{(2)}\)
  • ④ 現在在 \(a_1^{(2)}\) 的誤差為 \(\delta_1^{(3)}\times W_1^{(2)}=W_1^{(2)}\) (在 \(z_1^{(3)}\) 的誤差訊號為 \(\delta_1^{(3)}\) )。因此在 \(a_1^{(2)}\) 的誤差為 \(W_1^{(2)}\)
  • ⑤ 與第2步的做法相同,我們在將 \(z_1^{(2)}\) 對映到 \(a_1^{(2)}\) 的神經元上移動誤差,將 \(a_1^{(2)}\) 與區域性梯度相乘,這裡的區域性梯度為 \(f'(z_1^{(2)})\)
  • ⑥ 因此在 \(z_1^{(2)}\) 的誤差是 \(f'(z_1^{(2)})W_1^{(2)}\) ,我們將其定義為 \(\delta_1^{(2)}\)
  • ⑦ 最後,我們通過將上面的誤差與參與前向計算的 \(a_4^{(1)}\) 相乘,把誤差的“誤差共享”分配到 \(W_{14}^{(1)}\) 。
  • ⑧ 所以,對於 \(W_{14}^{(1)}\) 的梯度損失可以計算為 \(a_4^{(1)}f'(z_1^{(2)})W_1^{(2)}\)

注意我們使用這個方法得到的結果是和之前微分的方法的結果是完全一樣的。因此,計算網路中的相應引數的梯度誤差既可以使用鏈式法則也可以使用誤差共享和分配的方法——這兩個方法能得到相同結果,但是多種方式考慮它們可能是有幫助的。

偏置更新:偏置項(例如 \(b_1^{(1)}\) )和其他權值在數學形式是等價的,只是在計算下一層神經 \(z_1^{(2)}\) 元輸入時相乘的值是常量1。因此在第k層的第 \(i\) 個神經元的偏置的梯度時 \(\delta_i^{(k)}\) 。例如在上面的例子中,我們更新的是 \(b_1^{(1)}\) 而不是 \(W_{14}^{(1)}\) ,那麼這個梯度為 \(f'(z_1^{(2)})W_1^{(2)}\) 。

從 \(\delta^{(k)}\) 到 \(\delta^{(k-1)}\) 反向傳播的一般步驟:

  • ① 我們有從 \(z_i^{(k)}\) 向後傳播的誤差 \(\delta_i^{(k)}\) ,如下圖所示

反向傳播(單樣本形態)

  • ② 我們通過把 \(\delta_i^{(k)}\) 與路徑上的權值 \(W_{ij}^{(k-1)}\) 相乘,將這個誤差反向傳播到 \(a_j^{(k-1)}\)
  • ③ 因此在 \(a_j^{(k-1)}\) 接收的誤差是 \(\delta_i^{(k)}W_{ij}^{(k-1)}\)
  • ④ 然而, \(a_j^{(k-1)}\) 在前向計算可能出下圖的情況,會參與下一層中的多個神經元的計算。那麼第 \(k\) 層的第 \(m\) 個神經元的誤差也要使用上一步方法將誤差反向傳播到 \(a_j^{(k-1)}\) 上

反向傳播(單樣本形態)

  • ⑤ 因此現在在 \(a_j^{(k-1)}\) 接收的誤差是 \(\delta_i^{(k)}W_{ij}^{(k-1)}+\delta_m^{(k)}W_{mj}^{(k-1)}\)
  • ⑥ 實際上,我們可以把上面誤差和簡化為 \(\sum_i\delta_i^{(k)}W_{ij}^{(k-1)}\)
  • ⑦ 現在我們有在 \(a_j^{(k-1)}\) 正確的誤差,然後將其與區域性梯度 \(f^{\prime}(z_j^{(k-1)})\) 相乘,把誤差資訊反向傳到第 \(k-1\) 層的第 \(j\) 個神經元上
  • ⑧ 因此到達 \(z_j^{(k-1)}\) 的誤差為 \(f ^{\prime} (z_j^{(k-1)})\sum_i\delta_i^{(k)}W_{ij}^{(k-1)}\)

1.6 反向傳播(向量化形態)

在真實的神經網路訓練過程中,我們通常會基於一批樣本來更新網路權重,這裡更高效的方式是向量化方式,藉助於向量化的形態,我們可以直接一次更新權值矩陣和偏置向量。注意這只是對上面模型的簡單地擴充套件,這將有助於更好理解在矩陣-向量級別上進行誤差反向傳播的方法。

對更定的引數 \(W_{ij}^{(k)}\) ,我們知道它的誤差梯度是 \(\delta_j^{(k+1)}\cdot a_j^{(k)}\) 。其中 \(W^{(k)}\) 是將 \(a^{(k)}\) 對映到 \(z^{(k+1)}\) 的矩陣。因此我們可以確定整個矩陣 \(W^{(k)}\) 的梯度誤差為:

$$ \nabla_{W^{(k)}} = \begin{bmatrix} \delta_1^{(k+1)}a_1^{(k)} & \delta_1^{(k+1)}a_2^{(k)} & \cdots \\ \delta_2^{(k+1)}a_1^{(k)} & \delta_2^{(k+1)}a_2^{(k)} & \cdots \\ \vdots & \vdots & \ddots \\ \end{bmatrix} = \delta^{(k+1)}a^{(k)T} $$

因此我們可以將整個矩陣形式的梯度寫為在矩陣中的反向傳播的誤差向量和前向啟用輸出的外積。

現在我們來看看如何能夠計算誤差向量 \(\delta^{(k+1)}\) 。

我們從上面的例子中有

$$ \delta_i^{(k)}=f^{\prime}(z_j^{(k)})\sum_i\delta_i^{(k+1)}W_{ij}^{(k)} $$

這可以簡單地改寫為矩陣的形式:

$$ \delta_i^{(k)}=f^{\prime} (z^{(k)})\circ (W^{(k)T}\delta^{(k+1)}) $$

在上面的公式中 \(\circ\) 運算子是表示向量之間對應元素的相乘( \(\mathbb{R}^{N}\times \mathbb{R}^{N}\rightarrow \mathbb{R}^{N}\) )。

計算效率:在探索了element-wise的更新和vector-wise的更新之後,必須認識到在科學計算環境中,如MATLAB或Python(使用Numpy / Scipy 庫),向量化運算的計算效率是非常高的。因此在實際中應該使用向量化運算。此外,我們也要減少反向傳播中的多餘的計算——例如,注意到 \(\delta^{(k)}\) 是直接依賴在 \(\delta^{(k+1)}\) 上。所以我們要保證使用 \(\delta^{(k+1)}\) 更新 \(W^{(k)}\) 時,要儲存 \(\delta^{(k+1)}\) 用於後面 \(\delta^{(k)}\) 的計算-然後計算 \((k-1) \cdots (1)\) 層的時候重複上述的步驟。這樣的遞迴過程是使得反向傳播成為計算上可負擔的過程。

2.神經網路:技巧與建議

(本部分內容也可以參考ShowMeAI的對吳恩達老師課程的總結文章深度學習教程 | 深度學習的實用層面

2.1 梯度檢查

上一部分我們介紹瞭如何用基於微積分的方法計算神經網路中的引數的誤差梯度/更新。

這裡我們介紹一種用數值近似這些梯度的方法——雖然在計算上的低效不能直接用於訓練神經網路,這種方法可以非常準確地估計任何引數的導數;因此,它可以作為對導數的正確性的有用的檢查。

給定一個模型的引數向量 \(\theta\) 和損失函式 \(J\) ,圍繞 \(\theta_i\) 的數值梯度由 central difference formula 得出:

$$ f^{\prime}(\theta)\approx \frac{J(\theta^{(i+)})-J(\theta^{(i-)})}{2\varepsilon } $$

其中 \(\varepsilon\) 是一個很小的值(一般約為 \(1e^{-5}\) )。當我們使用 \(+\varepsilon\) 擾動引數 \(\theta\) 的第 \(i\) 個元素時,就可以在前向傳播上計算誤差 \(J(\theta^{(i+)})\) 。相似地,當我們使用 \(-\varepsilon\) 擾動引數 \(\theta\) 的第 \(i\) 個元素時,就可以在前向傳播上計算誤差 \(J(\theta^{(i-)})\) 。

因此,計算兩次前向傳播,我們可以估計在模型中任意給定引數的梯度。我們注意到數值梯度的定義和導數的定義很相似,其中,在標量的情況下:

$$ f^{\prime}(\theta)\approx \frac{f(x+\varepsilon)-f(x)}{\varepsilon} $$

當然,還是有一點不同——上面的定義僅僅在正向擾動 \(x\) 計算梯度。雖然是可以用這種方式定義數值梯度,但在實際中使用 central difference formula 常常可以更準確和更穩定,因為我們在兩個方向都對引數擾動。為了更好地逼近一個點附近的導數/斜率,我們需要在該點的左邊和右邊檢查函式 \(f^{\prime}\) 的行為。也可以使用泰勒定理來表示 central difference formula 有 \(\varepsilon^{2}\) 比例誤差,這相當小,而導數定義更容易出錯。

現在你可能會產生疑問,如果這個方法這麼準確,為什麼我們不用它而不是用反向傳播來計算神經網路的梯度?

  • ① 我們需要考慮效率——每當我們想計算一個元素的梯度,需要在網路中做兩次前向傳播,這樣是很耗費計算資源的。
  • ② 很多大規模的神經網路含有幾百萬的引數,對每個引數都計算兩次明顯不是一個好的選擇。
  • ③ 在例如 SGD 這樣的優化技術中,我們需要通過數千次的迭代來計算梯度,使用這樣的方法很快會變得難以應付。

我們只使用梯度檢驗來驗證我們的分析梯度的正確性。梯度檢驗的實現如下所示:

def eval_numerical_gradient(f, x):
    """
    a naive implementation of numerical gradient of f at x
    - f should be a function that takes a single argument
    - x is the point (numpy array) to evaluate the gradient  
    at
    """

    f(x) = f(x) # evaluate function value at original point
    grad = np.zeros(x.shape)
    h = 0.00001

    # iterate over all indexes in x
    it = np.nditer(x, flags=['multi_index',
                     op_flags=['readwrite'])

    while not it.finished:

        # evaluate function at x+h
        ix = it.multi_index
        old_value = x[ix]
        x[ix] = old_value + h # increment by h
        fxh_left = f(x) # evaluate f(x + h)
        x[ix] = old_value - h # decrement by h
        fxh_right = f(x) # evaluate f(x - h)
        # restore to previous value (very important!)
        x[ix] = old_value 

        # compute the partial derivative
        # the slope
        grad[ix] = (fxh_left - fxh_right) / (2 * h)
        it.iternext() # step to next dimension
    return grad

2.2 正則化

和很多機器學習的模型一樣,神經網路很容易過擬合,這令到模型在訓練集上能獲得近乎完美的表現,但是卻不能泛化到測試集上。一個常見的用於解決過擬合(“高方差問題”)的方法是使用 \(L2\) 正則化。我們只需要在損失函式 \(J\) 上增加一個正則項,現在的損失函式如下:

$$ J_{R}=J+\lambda\sum_{i=1}^{L}\left \| W^{(i)} \right \| _F $$

在上面的公式中, \(\left | W^{(i)} \right | _F\) 是矩陣 \(W^{(i)}\) (在神經網路中的第 \(i\) 個權值矩陣)的 Frobenius 範數, \(\lambda\) 是超引數控制損失函式中的權值的大小。

❐ 矩陣 \(U\) 的 Frobenius 範數的定義: \(\left | U \right | _F=\sqrt{\sum_i \sum_{l} U_{i l}^{2}}\)

當我們嘗試去最小化 \(J_R\) ,正則化本質上就是當優化損失函式的時候,懲罰數值太大的權值(讓權值的數值分配更加均衡,防止出現部分權值特別大的情況)。

由於 Frobenius 範數的二次的性質(計算矩陣的元素的平方和), \(L2\) 正則項有效地降低了模型的靈活性和因此減少出現過擬合的可能性。

增加這樣一個約束可以使用貝葉斯派的思想解釋,這個正則項是對模型的引數加上一個先驗分佈,優化權值使其接近於 0——有多接近是取決於 \(\lambda\) 的值。選擇一個合適的 \(\lambda\) 值是很重要的,並且需要通過超引數調整來選擇。

  • \(\lambda\) 的值太大會令很多權值都接近於 \(0\) ,則模型就不能在訓練集上學習到有意義的東西,經常在訓練、驗證和測試集上的表現都非常差。
  • \(\lambda\) 的值太小,會讓模型仍舊出現過擬合的現象。

需要注意的是,偏置項不會被正則化,不會計算入損失項中——嘗試去思考一下為什麼

為何在損失項中不計算偏置項

偏置項在模型中僅僅是偏移的關係,使用少量的資料就能擬合到這項,而且從經驗上來說,偏置值的大小對模型表現沒有很顯著的影響,因此不需要正則化偏置項

有時候我們會用到其他型別的正則項,例如 \(L1\) 正則項,它將引數元素的絕對值全部加起來-然而,在實際中很少會用 \(L1\) 正則項,因為會令權值引數變得稀疏。在下一部分,我們討論 Dropout ,這是另外一種有效的正則化方法,通過在前向傳播過程隨機將神經元設為 \(0\)

❐ Dropout 實際上是通過在每次迭代中忽略它們的權值來實現“凍結”部分 unit 。這些“凍結”的 unit 不是把它們設為 \(0\) ,而是對於該迭代,網路假定它們為 \(0\) 。“凍結”的 unit 不會為此次迭代更新

2.3 隨機失活Dropout

Dropout 是一個非常強大的正則化技術,是 Srivastava 在論文 《Dropout: A Simple Way to Prevent Neural Networks from Overfitting》中首次提出,下圖展示了 Dropout 如何應用在神經網路上。

隨機失活Dropout

這個想法是簡單而有效的——訓練過程中,在每次的前向/反向傳播中我們按照一定概率 \((1-p)\) 隨機地“ drop ”一些神經元子集(或者等價的,我們保持一定概率 \(p\) 的神經元是啟用的)。然後,在測試階段,我們將使用全部的神經元來進行預測。

使用 Dropout 神經網路一般能從資料中學到更多有意義的資訊,更少出現過擬合和通常在現今的任務上獲得更高的整體表現。這種技術應該如此有效的一個直觀原因是, Dropout 本質上作的是一次以指數形式訓練許多較小的網路,並對其預測進行平均。

實際上,我們使用 Dropout 的方式是我們取每個神經元層的輸出 \(h\) ,並保持概率 \(p\) 的神經元是啟用的,否則將神經元設定為 \(0\) 。然後,在反向傳播中我們僅對在前向傳播中啟用的神經元回傳梯度。最後,在測試過程,我們使用神經網路中全部的神經元進行前向傳播計算。然而,有一個關鍵的微妙之處,為了使 Dropout 有效地工作,測試階段的神經元的預期輸出應與訓練階段大致相同——否則輸出的大小可能會有很大的不同,網路的表現已經不再明確了。因此,我們通常必須在測試階段將每個神經元的輸出除以某個值——這留給讀者作為練習來確定這個值應該是多少,以便在訓練和測試期間的預期輸出相等(該值為 \(p\) ) 。

1) Dropout內容補充

以下源於 《神經網路與深度學習》
  • 目的:緩解過擬合問題,一定程度上達到正則化的效果
  • 效果:減少下層節點對其的依賴,迫使網路去學習更加魯棒的特徵

2) 整合學習的解釋

每做一次丟棄,相當於從原始的網路中取樣得到一個子網路。 如果一個神經網路有 \(n\) 個神經元,那麼總共可以取樣出 \(2^n\) 個子網路。

每次迭代都相當於訓練一個不同的子網路,這些子網路都共享原始網路的引數。那麼,最終的網路可以近似看作是整合了指數級個不同網路的組合模型。

3) 貝葉斯學習的解釋

丟棄法也可以解釋為一種貝葉斯學習的近似。用 \(y=f(\mathbf{x}, \theta)\) 來表示要學習的神經網路,貝葉斯學習是假設引數 \(\theta\) 為隨機向量,並且先驗分佈為 \(q(\theta)\) ,貝葉斯方法的預測為:

$$ \begin{aligned} \mathbb{E}_{q(\theta)}[y] &=\int_{q} f(\mathbf{x}, \theta) q(\theta) d \theta \\ & \approx \frac{1}{M} \sum_{m=1}^{M} f\left(\mathbf{x}, \theta_m\right) \end{aligned} $$

其中 \(f(\mathbf{x}, \theta_m)\) 為第m次應用丟棄方法後的網路,其引數 \(\theta_m\) 為對全部引數 \(\theta\) 的一次取樣。

4) RNN中的變分Dropout (Variational Dropout)

Dropout一般是針對神經元進行隨機丟棄,但是也可以擴充套件到對神經元之間的連線進行隨機丟棄,或每一層進行隨機丟棄。

在RNN中,不能直接對每個時刻的隱狀態進行隨機丟棄,這樣會損害迴圈網路在時間維度上記憶能力。一種簡單的方法是對非時間維度的連線(即非迴圈連線)進行隨機丟失。如圖所示,虛線邊表示進行隨機丟棄,不同的顏色表示不同的丟棄掩碼。

針對非迴圈連線的丟棄法

然而根據貝葉斯學習的解釋,丟棄法是一種對引數 \(θ\) 的取樣。每次取樣的引數需要在每個時刻保持不變。因此,在對迴圈神經網路上使用丟棄法時,需要對引數矩陣的每個元素進行隨機丟棄,並在所有時刻都使用相同的丟棄掩碼。這種方法稱為變分丟棄法(Variational Dropout)。

下圖給出了變分丟棄法的示例,相同顏色表示使用相同的丟棄掩碼。

變分丟棄法

2.4 神經元啟用函式

我們在前面看到的神經網路都是基於sigmoid啟用函式做非線性分類的。但是在很多應用中,使用其他啟用函式可以設計更好的神經網路。下面列出一些常見的啟用函式和啟用函式的梯度定義,它們可以和前面討論過的sigmoidal函式互相替換。

1) Sigmoid

神經元啟用函式 - Sigmoid

這是我們討論過的常用選擇,啟用函式 \(\sigma\) 為:

$$ \sigma(z)=\frac{1}{1+exp(-z)} $$

其中 \(\sigma(z)\in (0,1)\)

\(\sigma(z)\) 的梯度為:

$$ \sigma^{\prime}(z)=\frac{-exp(-z)}{1+exp(-z)}=\sigma(z)(1-\sigma(z)) $$

2) tanh

神經元啟用函式 - tanh

tanh函式是sigmoid函式之外的另一個選擇,在實際中它能更快地收斂。tanh和sigmoid的主要不同在於tanh的輸出範圍在-1到1,而sigmoid的輸出範圍在0到1。

$$ tanh(z)=\frac{exp(z)-exp(-z)}{exp(z)+exp(-z)}=2\sigma(2z)-1 $$

其中 \(tanh(z)\in (-1, 1)\)

\( anh(z) \)的梯度為:

$$ tanh^{\prime}(z)=1-\bigg(\frac{exp(z)-exp(-z)}{exp(z)+exp(-z)}\bigg)^{2}=1-tanh^{2}(z) $$

3) hard tanh

神經元啟用函式 – hard tanh

有時候hardtanh函式有時比tanh函式的選擇更為優先,因為它的計算量更小。然而當 \(z\) 的值大於 \(1\) 時,函式的數值會飽和(如下圖所示會恆等於1)。

hardtanh啟用函式為:

$$ \begin{aligned} hardtanh(z) = \begin{cases} -1& :z<1\\ z & :-1\le z \le 1 \\ 1 & :z>1 \end{cases} \end{aligned} $$

hardtanh這個函式的微分也可以用分段函式的形式表示:

$$ \begin{aligned} hardtanh ^{\prime}(z) &= \begin{cases} 1 & :-1\le z \le 1 \\ 0 & :otherwise \end{cases} \end{aligned} $$

4) soft sign

神經元啟用函式 – soft sign

soft sign函式是另外一種非線性啟用函式,它可以是tanh的另外一種選擇,因為它和hard clipped functions 一樣不會過早地飽和:

$$ softsign(z)=\frac{z}{1+ \left | z \right |} $$

soft sign函式的微分表示式為:

$$ softsign^{\prime}(z)=\frac{sgn(z)}{(1+z)^{2}} $$

其中, \(sgn\) 是符號函式,根據 \(z\) 的符號返回1或者-1。

5) ReLU

神經元啟用函式 - ReLU

ReLU ( Rectified Linear Unit )函式是啟用函式中的一個常見的選擇,當 \(z\) 的值特別大的時候它也不會飽和。在計算機視覺應用中取得了很大的成功:

$$ rect(z)=max(z,0) $$

ReLU函式的微分是一個分段函式:

$$ \begin{aligned} rect^{\prime}(z) &= \begin{cases} 1 & :z > 0 \\ 0 & :otherwise \end{cases} \end{aligned} $$

6) Leaky ReLU

神經元啟用函式 – Leaky ReLU

傳統的ReLU單元當 \(z\) 的值小於 \(0\) 時,是不會反向傳播誤差leaky ReLU改善了這一點,當 \(z\) 的值小於 \(0\) 時,仍然會有一個很小的誤差反向傳播回去。

$$ leaky(z)=max(z, k\cdot z) $$

其中 $$ 0。

leaky ReLU函式的微分是一個分段函式:

$$ \begin{aligned} leaky ^{\prime} (z) &= \begin{cases} 1 & :z > 0 \\ k & :otherwise \end{cases} \end{aligned} $$

2.5 資料預處理

與機器學習模型的一般情況一樣,確保模型在當前任務上獲得合理效能的一個關鍵步驟是對資料執行基本的預處理。下面概述了一些常見的技術。

1) 去均值

給定一組輸入資料 \(X\) ,一般把 \(X\) 中的值減去 \(X\) 的平均特徵向量來使資料零中心化。在實踐中很重要的一點是,只計算訓練集的平均值,而且在訓練集,驗證集和測試集都是減去同一平均值。

2) 歸一化

另外一個常見的技術(雖然沒有 \(mean\;Subtraction\) 常用)是將每個輸入特徵維度縮小,讓每個輸入特徵維度具有相似的幅度範圍。這是很有用的,因此不同的輸入特徵是用不同“單位”度量,但是最初的時候我們經常認為所有的特徵同樣重要。實現方法是將特徵除以它們各自在訓練集中計算的標準差。

3) 白化

相比上述的兩個方法,whitening沒有那麼常用,它本質上是資料經過轉換後,特徵之間相關性較低,所有特徵具有相同的方差(協方差陣為 \(1\) )。首先對資料進行Mean Subtraction處理,得到 \(X ^{\prime}\) 。然後我們對 \(X ^{\prime}\) 進行奇異值分解得到矩陣 \(U\) , \(S\) , \(V\) ,計算 \(UX^{\prime}\) 將 \(X^{\prime}\) 投影到由 \(U\) 的列定義的基上。我們最後將結果的每個維度除以 \(S\) 中的相應奇異值,從而適當地縮放我們的資料(如果其中有奇異值為0,我們就除以一個很小的值代替)。

2.6 引數初始化

讓神經網路實現最佳效能的關鍵一步是以合理的方式初始化引數。一個好的起始方法是將權值初始化為通常分佈在0附近的很小的隨機數-在實踐中效果還不錯。

在論文《Understanding the difficulty of training deep feedforward neural networks (2010)》, Xavier 研究不同權值和偏置初始化方案對訓練動力( training dynamics )的影響。實驗結果表明,對於sigmoid和tanh啟用單元,當一個權值矩陣 \(W\in \mathbb{R}^{n^{(l+1)}\times n^{(l)}}\) 以如下的均勻分佈的方式隨機初始化,能夠實現更快的收斂和得到更低的誤差:

$$ W\sim U\bigg[-\sqrt{\frac{6}{n^{(l)}+n^{(l+1)}}},\sqrt{\frac{6}{n^{(l)}+n^{(l+1)}}}\;\bigg] $$

其中 \(n^{(l)}\) 是W \((fan\text{-}in)\) 的輸入單元數, \(n^{(l+1)}\) 是W \((fan\text{-}out)\) 的輸出單元數。在這個引數初始化方案中,偏置單元是初始化為 \(0\) 。這種方法是嘗試保持跨層之間的啟用方差以及反向傳播梯度方差。如果沒有這樣的初始化,梯度方差(當中含有糾正資訊)通常隨著跨層的反向傳播而衰減。

2.7 學習策略

訓練期間模型引數更新的速率/幅度可以使用學習率進行控制。在最簡單的梯度下降公式中, \(\alpha\) 是學習率:

$$ \theta^{new}=\theta^{old}-\alpha\nabla_{\theta}J_{t}(\theta) $$

你可能會認為如果要更快地收斂,我們應該對 \(\alpha\) 取一個較大的值——然而,在更快的收斂速度下並不能保證更快的收斂。實際上,如果學習率非常高,我們可能會遇到損失函式難以收斂的情況,因為引數更新幅度過大,會導致模型越過凸優化的極小值點,如下圖所示。在非凸模型中(我們很多時候遇到的模型都是非凸),高學習率的結果是難以預測的,但是損失函式難以收斂的可能性是非常高的。

學習策略

避免損失函式難以收斂的一個簡答的解決方法是使用一個很小的學習率,讓模型謹慎地在引數空間中迭代——當然,如果我們使用了一個太小的學習率,損失函式可能不會在合理的時間內收斂,或者會困在區域性最優點。因此,與任何其他超引數一樣,學習率必須有效地調整。

深度學習系統中最消耗計算資源的是訓練階段,一些研究已在嘗試提升設定學習率的新方法。例如,Ronan Collobert通過取 \(fan\text{-}in\) 的神經元 \((n^{(l)})\) 的平方根的倒數來縮放權值 \(W_{ij}\) ( \(W\in \mathbb{R}^{n^{(l+1)}\times n^{(l)}}\) )的學習率。

還有其他已經被證明有效的技術-這個方法叫annealing退火,在多次迭代之後,學習率以以下方式降低:保證以一個高的的學習率開始訓練和快速逼近最小值;當越來越接近最小值時,開始降低學習率,讓我們可以在更細微的範圍內找到最優值。一個常見的實現annealing的方法是在每 \(n\) 次的迭代學習後,通過一個因子 \(x\) 來降低學習率 \(\alpha\) 。

指數衰減也是很常見的方法,在 \(t\) 次迭代後學習率變為 \(\alpha(t)=\alpha_0 e^{-kt}\) ,其中 \(\alpha_0\) 是初始的學習率和 \(k\) 是超引數。

還有另外一種方法是允許學習率隨著時間減少:

$$ \alpha(t)=\frac{\alpha_0\tau}{max(t,\tau)} $$

在上述的方案中, \(\alpha_0\) 是一個可調的引數,代表起始的學習率。 \(\tau\) 也是一個可調引數,表示學習率應該在該時間點開始減少。在實際中,這個方法是很有效的。在下一部分我們討論另外一種不需要手動設定學習率的自適應梯度下降的方法。

2.8 帶動量的優化更新(Momentum)

(神經網路優化演算法也可以參考ShowMeAI的對吳恩達老師課程的總結文章深度學習教程 | 神經網路優化演算法

動量方法,靈感來自於物理學中的對動力學的研究,是梯度下降方法的一種變體,嘗試使用更新的“速度”的一種更有效的更新方案。動量更新的虛擬碼如下所示:

# Computes a standard momentum update
# on parameters x
v = mu * v - alpha * grad_x
x += v

2.9 自適應優化演算法

(神經網路優化演算法也可以參考ShowMeAI的對吳恩達老師課程的總結文章深度學習教程 | 神經網路優化演算法

AdaGrad是標準的隨機梯度下降(SGD)的一種實現,但是有一點關鍵的不同:對每個引數學習率是不同的。每個引數的學習率取決於每個引數梯度更新的歷史,引數的歷史更新越小,就使用更大的學習率加快更新。換句話說,過去沒有更新太大的引數現在更有可能有更高的學習率。

$$ \theta_{t,i}=\theta_{t-1,i}-\frac{\alpha}{\sqrt{\sum_{\tau=1}^{t}g_{\tau,i}^{2}}} g_{t,i} \\ where \ g_{t,i}=\frac{\partial}{\partial\theta_i^{t}}J_{t}(\theta) $$

在這個技術中,我們看到如果梯度的歷史RMS很低,那麼學習率會非常高。這個技術的一個簡單的實現如下所示:

# Assume the gradient dx and parameter vector x
cache += dx ** 2
x += -learning_rate * dx / np.sqrt(cache + 1e-8)

其他常見的自適應方法有 RMSProp 和 Adam ,其更新規則如下所示:

# Update rule for RMS prop
cache = decay_rate * cache + (1 - decay_rate) * dx ** 2
x += -learning_rate * dx / (np.sqrt(cache) + eps)

# Update rule for Adam
m = beta * m + (1 - beta1) * dx
v = beta * v + (1 - beta2) * (dx ** 2)
x += -learning_rate * m / (np.sqrt(v) + eps)
  • RMSProp是利用平方梯度的移動平局值,是AdaGrad的一個變體——實際上,和AdaGrad不一樣,它的更新不會單調變小。
  • Adam更新規則又是RMSProp的一個變體,但是加上了動量更新。

3.參考資料

ShowMeAI 系列教程推薦

NLP系列教程文章

史丹佛 CS224n 課程帶學詳解

相關文章