分類
發燒車訊

從 0 開始機器學習 – 神經網絡反向 BP 算法!

最近一個月項目好忙,終於擠出時間把這篇 BP 算法基本思想寫完了,公式的推導放到下一篇講吧。

一、神經網絡的代價函數

神經網絡可以看做是複雜邏輯回歸的組合,因此與其類似,我們訓練神經網絡也要定義代價函數,之後再使用梯度下降法來最小化代價函數,以此來訓練最優的權重矩陣。

1.1 從邏輯回歸出發

我們從經典的邏輯回歸代價函數引出,先來複習下:

\[J(\theta) = \frac{1}{m}\sum\limits_{i = 1}^{m}{[-{y^{(i)}}\log ({h_\theta}({x^{(i)}}))-( 1-{y^{(i)}})\log ( 1 – h_\theta({x^{(i)}}))]} + \frac{\lambda}{2m} \sum\limits_{j=1}^{n}{\theta_j^2} \]

邏輯回歸代價函數計算每個樣本的輸入與輸出的誤差,然後累加起來除以樣本數,再加上正則化項,這個我之前的博客已經寫過了:

  • 從 0 開始機器學習 – 邏輯回歸原理與實戰!
  • 從 0 開始機器學習 – 正則化技術原理與編程!

這裏補充一點對單變量邏輯回歸代價函數的理解,雖然這一行代價公式很長:

\[cost(i) = -{y^{(i)}}\log ({h_\theta}({x^{(i)}}))-( 1-{y^{(i)}})\log ( 1 – h_\theta({x^{(i)}})) \]

但是其實可以把它簡單的理解為輸出與輸入的方差,雖然形式上差別很大,但是可以幫助我們理解上面這個公式到底在計算什麼,就是計算輸出與輸入的方差,這樣理解就可以:

\[cost(i) = h_{\theta}(x^{(i)} – y^{(i)})^2 \]

1.2 一步步寫出神經網絡代價函數

前面講的簡單邏輯回歸的只有一個輸出變量,但是在神經網絡中輸出層可以有多個神經元,所以可以有很多種的輸出,比如 K 分類問題,神經元的輸出是一個 K 維的向量:

因此我們需要對每個維度計算預測輸出與真實標籤值的誤差,即對 K 個維度的誤差做一次求和:

\[\sum\limits_{i = 1}^{k}{[-{y_k^{(i)}}\log ({h_\theta}({x^{(i)}}))_k-( 1-{y_k^{(i)}})\log ( 1 – h_\theta({x^{(i)}})_k)]} \]

然後累加訓練集的 m 個樣本:

\[-\frac{1}{m}[\sum\limits_{i = 1}^{m}\sum\limits_{k = 1}^{k}{[-{y_k^{(i)}}\log ({h_\theta}({x^{(i)}}))_k-( 1-{y_k^{(i)}})\log ( 1 – h_\theta({x^{(i)}})_k)]}] \]

再加上所有權重矩陣元素的正則化項,注意 \(i, j\) 都是從 1 開始的,因為每一層的 \(\theta_0\) 是偏置單元,不需要對其進行正則化:

\[\frac{\lambda}{2m}\sum\limits_{i = l}^{L – 1}\sum\limits_{i = 1}^{S_l}\sum\limits_{j = 1}^{S_l + 1}(\theta_{ji}^{(l)})^2 \]

  • 最內層求和:循環一個權重矩陣所有的行,行數是 \(S_l + 1\) 層激活單元數
  • 中間層求和:循環一個權重矩陣所有的列,列數是 \(S_l\) 層激活單元數
  • 最外層求和:循環所有的權重矩陣

這就得到了輸出層為 K 個單元神經網絡最終的代價函數:

\[J(\theta) = -\frac{1}{m}[\sum\limits_{i = 1}^{m}\sum\limits_{k = 1}^{k}{[-{y_k^{(i)}}\log ({h_\theta}({x^{(i)}}))_k-( 1-{y_k^{(i)}})\log ( 1 – h_\theta({x^{(i)}})_k)]}] + \frac{\lambda}{2m}\sum\limits_{i = l}^{L – 1}\sum\limits_{i = 1}^{S_l}\sum\limits_{j = 1}^{S_l + 1}(\theta_{ji}^{(l)})^2 \]

有了代價函數后,就可以通過反向傳播算法來訓練一個神經網絡啦!

二、神經網絡反向 BP(Back Propagation) 算法

2.1 BP 算法簡介

之前寫神經網絡基礎的時候,跟大家分享了如何用訓練好的神經網絡來預測手寫字符:從 0 開始機器學習 – 神經網絡識別手寫字符!,只不過當時我們沒有訓練網絡,而是使用已經訓練好的神經網絡的權重矩陣來進行前饋預測,那麼我們如何自己訓練神經網絡呢?

這就需要學習反向 BP 算法,這個算法可以幫助我們求出神經網絡權重矩陣中每個元素的偏導數,進而利用梯度下降法來最小化上面的代價函數,你可以聯想簡單的線性回歸算法:從 0 開始機器學習 – 一文入門多維特徵梯度下降法!,也是先求每個參數的偏導數,然後在梯度下降算法中使用求出的偏導數來迭代下降。

因此訓練神經網絡的關鍵就是:如何求出每個權重係數的偏導數?,反向 BP 就可以解決這個問題!這裏強烈建議你學習的時候完全搞懂 BP 算法的原理,最好自己獨立推導一遍公式,因為你以後學習深度學習那些複雜的網絡,不管是哪種,最終都要使用反向 BP 來訓練,這個 BP 算法是最核心的東西,面試也逃不過的,所以既然要學,就要學懂,不然就是在浪費時間。

2.2 BP 算法基本原理

我先用個例子簡單介紹下 BP 算法的基本原理和步驟,公式的推導放到下一節,反向 BP 算法顧名思義,與前饋預測方向相反:

  • 計算最後一層輸出與實際標籤值的誤差,反向傳播到倒數第二層
  • 計算倒數第二層的傳播誤差,反向傳播到倒數第三層
  • 以此類推,一層一層地求出各層的誤差
  • 直到第二層結束,因為第一層是輸入特徵,不是我們計算的,所以不需要求誤差

以下面這個 4 層的神經網絡為例:

假如我們的訓練集只有 1 個樣本 \((x^{(1)}, y^{(1)})\),每層所有激活單元的輸出用 \(a^{(i)}\) 向量表示,每層所有激活單元的誤差用 \(\delta^{(i)}\) 向量表示,來看下反向傳播的計算步驟(公式的原理下一節講):

  1. 輸出層的誤差為預測值減去真實值:\(\delta^{(4)} = a^{(4)} – y^{(1)}\)
  2. 倒數第二層的誤差為:\(\delta^{(3)} = (W^{(3)})^T \delta^{(4)} * g'(z^{(3)})\)
  3. 倒數第三層的誤差為:\(\delta^{(2)} = (W^{(2)})^T \delta^{(3)} * g'(z^{(2)})\)
  4. 第一層是輸入變量,不需要計算誤差

有了每層所有激活單元的誤差后,就可以計算代價函數對每個權重參數的偏導數,即每個激活單元的輸出乘以對應的誤差,這裏不考慮正則化:

\[\frac {\partial}{\partial W_{ij}^{(l)}} J (W) = a_{j}^{(l)} \delta_{i}^{(l+1)} \]

解釋下這個偏導數的計算:

  • \(l\) 表示目前計算的是第幾層
  • \(j\) 表示當前層中正在計算的激活單元下標(\(j\) 作為列)
  • \(i\) 表示下一層誤差單元的下標(\(i\) 作為行)

這個計算過程是對一個樣本進行的,網絡的輸入是一個特徵向量,所以每層計算的誤差也是向量,但是我們的網絡輸入是特徵矩陣的話,就不能用一個個向量來表示誤差了,而是應該也將誤差向量組成誤差矩陣,因為特徵矩陣就是多個樣本,每個樣本都做一個反向傳播,就會計算誤差,所以我們每次都把一個樣本計算的誤差累加到誤差矩陣中:

\[\Delta_{ij}^{(l)} = \Delta_{ij}^{(l)} + a_{j}^{(l)} \delta_{i}^{(l+1)} \]

然後,我們需要除以樣本總數 \(m\),因為上面的誤差是累加了所有 \(m\) 個訓練樣本得到的,並且我們還需要考慮加上正則化防止過擬合,注意對偏置單元不需要正則化,這點已經提過好多次了:

  • 非偏置單元正則化后的偏導數 \(j \neq 0\)

\[D_{ij}^{(l)} = \frac {1}{m}\Delta_{ij}^{(l)}+\lambda W_{ij}^{(l)} \]

  • 偏置單元正則化后的偏導數 \(j = 0\)

\[D_{ij}^{(l)} = \frac{1}{m}\Delta_{ij}^{(l)} \]

最後計算的所有偏導數就放在誤差矩陣中:

\[\frac {\partial}{\partial W_{ij}^{(l)}} J (W) = D_{ij}^{(l)} \]

這樣我們就求出了每個權重參數的偏導數,再回想之前的梯度下降法,我們有了偏導數計算方法后,直接送到梯度下降法中進行迭代就可以最小化代價函數了,比如我在 Python 中把上面的邏輯寫成一個正則化梯度計算的函數 regularized_gradient,然後再用 scipy.optimize 等優化庫直接最小化文章開頭提出的神經網絡代價函數,以此來使用反向 BP 算法訓練一個神經網絡:

import scipy.optimize as opt

res = opt.minimize(fun = 神經網絡代價函數,
                       x0 = init_theta,
                       args = (X, y, 1),
                       method = 'TNC',
                       jac = regularized_gradient,
                       options = {'maxiter': 400})

所以神經網絡反向 BP 算法關鍵就是理解每個權重參數偏導數的計算步驟和方法!關於偏導數計算公式的詳細推導過程,我打算在下一篇文章中單獨分享,本次就不帶大家一步步推導了,否則內容太多,先把基本步驟搞清楚,後面推導公式就容易理解。

2.3 反向 BP 算法的直觀理解

之前學習前饋預測時,我們知道一個激活單元是輸入是上一層所有激活單元的輸出與權重的加權和(包含偏置),計算方向從左到右,計算的是每個激活單元的輸出,看圖:

其實反向 BP 算法也是做類似的計算,一個激活單元誤差的輸入是后一層所有誤差與權重的加權和(可能不包含偏置),只不過這裏計算的反向是從右向左,計算的是每個激活單元的誤差,對比看圖:

你只需要把單個神經元的前饋預測和反向 BP 的計算步驟搞清楚就可以基本理解反向 BP 的基本過程,因為所有的參數都是這樣做的。

三、神經網絡編程細節

3.1 隨機初始化

每種優化算法都需要初始化參數,之前的線性回歸初始化參數為 0 是沒問題的,但是如果把神經網絡的初始參數都設置為 0,就會有問題,因為第二層的輸入是要用到權重與激活單元輸出的乘積:

  • 如果權重都是 0,則每層網絡的輸出都是 0
  • 如果權重都是相同的常數 \(a\),則每層網絡的輸出也都相同,只是不為 0

所以為了在神經網絡中避免以上的問題,我們採用隨機初始化,把所有的參數初始化為 \([-\epsilon, \epsilon]\) 之間的隨機值,比如初始化一個 10 X 11 的權重參數矩陣:

\[initheta = rand(10, 11) * (2 * \epsilon) – \epsilon \]

3.2 矩陣 <-> 向量

注意上面優化庫的輸入 X0 = init_theta 是一個向量,而我們的神經網絡每 2 層之間就有一個權重矩陣,所以為了把權重矩陣作為優化庫的輸入,我們必須要把所有的權重參數都組合到一個向量中,也就是實現一個把矩陣組合到向量的功能,但是優化庫的輸出也是一個包含所有權重參數的向量,我們拿到向量后還需要把它轉換為每 2 層之間的權重矩陣,這樣才能進行前饋預測:

  • 訓練前:初始多個權重矩陣 -> 一個初始向量
  • 訓練后:一個最優向量 -> 多個最優權重矩陣

3.3 梯度校驗

梯度校驗是用來檢驗我們的 BP 算法計算的偏導數是否和真實的偏導數存在較大誤差,計算以下 2 個偏導數向量的誤差:

  • 反向 BP 算法計算的偏導數
  • 利用導數定義計算的偏導數

對於單個參數,在一點 \(\theta\) 處的導數可由 \([\theta – \epsilon, \theta + \epsilon]\) 表示,這也是導數定義的一種:

\[grad = \frac{J(\theta + \epsilon) – J(\theta – \epsilon)}{2 \epsilon} \]

如圖:

但是我們的神經網絡代價函數有很多參數,當我們把參數矩陣轉為向量后,可以對向量里的每個參數進行梯度檢驗,只需要分別用定義求偏導數即可,比如檢驗 \(\theta_1\)

\[\frac {\partial J}{\partial \theta_1} = \frac {J (\theta_1 + \varepsilon_1, \theta_2, \theta_3 … \theta_n ) – J(\theta_1 – \varepsilon_1, \theta_2, \theta_3 … \theta_n)}{2 \varepsilon} \]

以此類推,檢驗 \(\theta_n\)

\[\frac {\partial J}{\partial \theta_n} = \frac {J (\theta_1, \theta_2, \theta_3 … \theta_n + \varepsilon_n) – J(\theta_1, \theta_2, \theta_3 … \theta_n – \varepsilon_n)}{2 \varepsilon} \]

求出導數定義的偏導數后,與 BP 算法計算的偏導數計算誤差,在誤差範圍內認為 BP 算法計算的偏導數(D_vec)是正確的,梯度檢驗的偽代碼如下:

for i = 1 : n
  theta_plus = theta
  theta_plus[i] = theta_plus + epsilon
  
  theta_minu = theta
  theta_minu[i] = theta_minu - epsilon
  
  grad = (J(theta_plus) - J(theta_minu)) / (2 * epsilon)
end

check 誤差: grad 是否約等於 D_vec

注意一點:梯度檢驗通常速度很慢,在訓練神經網絡前先別進行檢驗!

今天就到這,溜了溜了,下篇文章見:)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準