上篇主要闡述 BP算法的過程, 以及 推導的 4 大公式的結論, 現在呢要來逐步推導出這寫公式的原理. 當理解到這一步, 就算真正理解 BP算法了. 也是先做一個簡單的回顧一下, 不是很細, 重點在推導, 不清楚就結合圖像呀, 其實很直觀的. 全篇其實就是在求偏導, 引入中間變量, 應用鏈式法則 而已.
BP算法-變量聲明
重點是理解 反向 即從 從右到左 的方向哦;

- \(w^l_{jk}\) 第 l 層, 第 j 個節點, 到 第 \((l-1)\) 層的 第 k 個節點的 權值 (weight) ( 反向, 反向, 方向, 反向, 說了4遍)
- \(w^l\) 第 l 層, 的 權值矩陣, 第 j 行, 第 k 列 為 \(w^l_{jk}\)
- \(b_j^l\) 第 l 層, 第 j 個節點 的 bias (偏置)
- \(b^l\) 第 l 層, 的 bias 向量
- \(a^l_j\) 第 l 層, 第 j 個節點的 激勵 (activation)
- \(a^l\) 第 l 層, 的 激勵 向量
假設激活函數是 \(\sigma\) , 根據網絡 層次間的 映射(加權求和) 的關系, (每個神經元的模型):
單個神經元: \(a^l_j = \sigma(\sum\limits _k w^l_{jk} \ a^{l-1}_k + b^l_j)\)
該層的神經元: \(a^l = \sigma (w^l a^{l-1} + b^l)\)
如不能理解每個變量代表的意義, 就看圖, 非常直觀的呀
\(z^l = w^l a^{l-1} + b^l\)
\(z^l\) The weighted input to the neurons in layer l.
再定義一個中間變量 \(z^l\) 即 第 l 層神經元的 加權求輸入向量, 其分量, \(z^l_j\) 為第 l 層, 第 j 個神經元的 加權求和輸入.
\(z^l_j =\sum \limits_k w_{jk} a_k ^{l-1} + b^l_j\)
於是呢, 對於每個節點的輸出, 就可以簡單表示為 (向量形式哈) :
\(a^l = \sigma(z^l)\)
然后來看定義 損失函數, 采用咱最熟悉的 平方損失 的形式:
-
\(y = y(x)\) 樣本 x 的標簽向量 (期望輸出)
-
\(a^L = a^L(x)\) 樣本 x 的網絡輸出激勵向量
-
n : 表示樣本數量; L 表示網絡層數
樣本 x 表示向量, 每個分量也是一個向量(多特征), 對應於數據的每一行. 因此, x 寫出來就是 nxp的矩陣
\(C = \frac {1}{2n} \sum \limits_x ||y(x) - a^L(x)||^2\)
這個 0.5 都懂哈, , 沒啥特定意義. 就是求導的時候, 式子的2范數, 要把2拿下來 再 乘0.5, 就為1 , 形式上美觀而已.
公式1: C 對於 - 輸出層的梯度
梯度, 在BP中, 就是誤差
\(\delta_j^L = \frac {\partial C}{\partial a^L_j} \ \sigma'(z^L_j) = \nabla _a C \odot \ \sigma'(z^L_j)\)
過程: (理解圖 和 求導鏈式法則哦)
因為, \(\delta_j^L = \frac {\partial C}{\partial a^L_j} \ \frac {\partial a^L_j}{\partial z^L_j}\) , 而 \(a^L_j = \sigma (z^L_j)\), 因此, \(\delta_j^L = \frac {\partial C}{\partial a^L_j} \ \sigma'(z^L_j)\)
- \(\frac {\partial C}{\partial a^L_j}\) 表示代價函數 C 對於 輸出層 第 j 個節點 的激勵變化程度.
- \(\sigma'(z^L_j)\) 表示 這第 j 個節點, 對於上層 加權輸入 的變化程度.
如果使用上面給定的 哈達瑪積公式, 寫成矩陣形式的話, 就變成了:
\(\nabla _a C \odot \ \sigma'(z^L_j)\)
- 向量 \(\nabla_a C\) 的第 j 個元素誤差為: \(\frac {\partial C}{\partial a^L_j} = \nabla_a C = (a^L - y)\)
- $ \delta ^L = (a^L - y) \odot \ \sigma'(z^L)$
過程:
\(\nabla_a C\) 即是對 \(C_x = \frac {1}{2} ||y - a^L||^2\) 的求導而得 ( \(y-a^L\) )
公式2: C 對於- 中間層 的梯度
\(\delta ^l = ((w^{l+1})^T \delta ^{l+1}) \odot \sigma '(z^l)\)
即假設已經知道 \(l+1\) 層的誤差, 通過 \(l+1\) 層 和 l 層之間的 權值矩陣 w, 將誤差進行回傳, 得到 l 層的誤差
推導:
根據上面 z 和 每層的節點的 誤差定義 (偏導數作為誤差) ,則第 l+1 層, 的第 k 個節點 的誤差表示為:
\(\delta ^{l+1}_k = \frac {\partial C} {\partial z_k^{l+1}}\)
對於 l 層, 第第 j 個節點, 的誤差表示為:
\(\delta ^l = \frac {\partial C}{\partial z^l_j}\) 這個是定義來的, 然后根據 層 之間的 加權輸入用 鏈式法則 展開為有關於 (l +1) 層的變量
\(=\sum \limits_k \frac {\partial C}{\partial z^{l+1}_k} \frac {\partial z^{l+1}_k}{\partial z^l_j} = \sum \limits_k \delta^{l+1}_k \frac {\partial z^{l+1}_k}{\partial z^l_j}\)
第一項已經知道了, 繼續探討下 第二項 (注意變量的下標, 結合圖形來理解)
\(z^{l+1}_k = \sum\limits_j w_{kj}^{l+1} a^l_j + b^{l+1}_k = \sum\limits_j w_{kj}^{l+1} \sigma (z^l_j) + b^{l+1}_k\)
\(\frac {\partial z^{l+1}_k}{\partial z^l_j} = w^{l+1}_{kj} \sigma '(z^l_j)\) 這里 \(\sum\) 是沒有了, 因為跟 其他的 j 項 是沒有關系的.
因此,
\(\delta ^l = \sum_k w^{l+1}_{kj} \sigma '(z^l_j) \delta_k ^{l+1}\)
\(\delta ^l = \sigma '(z^l_j) \sum_k w^{l+1}_{kj}\delta_k ^{l+1}\) 拎出 \(\sigma\) 是因為 \(\sum\) 對其不起作用, 可看作一個常數放在外面.
這個寫成 哈達瑪積的形式, 也就是上面的 \(\delta ^l = ((w^{l+1})^T \delta ^{l+1}) \odot \sigma '(z^l)\)
我個人感覺, 就直接寫成推導的式子挺好的, 寫為了 哈達瑪積 反而有些讓人看不懂, 而且吧, 我還容易寫錯. 都是矩陣嘛, 真的很容易就寫錯了, 這樣反而造成更大的誤解...不過呢, 多學學 debug 也是蠻重要的.
ps: 我現在就特別喜歡 debug 或者找 幫小伙伴找 bug 還有代碼重構, 我感覺這是一個最為高效的互相學習交流的方式, 既學習別人的開發思路和代碼風格 , 同時也跟別人分享自己的思路, 蠻有趣的體驗哦.
公式3: C 對於 - Bias 的梯度
\(\frac {\partial C}{\partial b^l_j} = \delta^l_j\) 寫成矩陣就是: \(\frac {\partial C}{\partial b} = \delta\)
推導:
\(z^l_j = \sum\limits_k w^l_{jk} a^{l-1}_k + b_j ^l\) 這個上面的 l 層 和 l+1 層是一樣的, 重在理解層間的, 加權求和輸入再激活的 關系
易知: \(\frac {\partial z^l_j}{\partial b^l_j} = 1\)
則: \(\frac {\partial C}{\partial b^l_j} = \frac {\partial C}{\partial z^l_j} \frac {\partial z^l_j}{\partial b^l_j} = \frac {\partial C}{\partial z^l_j} = \delta^l_j\)
公式4: C 對於 - 權值 的梯度
\(\frac {\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j\) 也可這樣表達為: \(\frac {\partial C}{\partial w} = a_{in} \delta_{out}\)
推導: (跟公式3一樣, 還是厲害 層間的關系, 注意理解每個下標的含義哦)
\(z^l_j = \sum\limits_k w^l_{jk} a^{l-1}_k + b^l_j\)
易知: \(\frac {\partial z^l_j}{\partial w^l_{jk}} = a^{l-1}_k\)
則: \(\frac {\partial C}{\partial w^l_{jk}} = \frac {\partial C}{\partial z^l_j} \frac {\partial z^l_j}{\partial w^l_{jk}} = a^{l-1}_k \ \frac {\partial C}{\partial z^l_{j}} = a^{l-1}_k \delta^l_j\)
-
\(a_{in}\) 表示 輸入權值 w 上層 神經元的 激勵實值
-
\(\delta_{out}\) 表示 本層 權值 w 輸入到 下層 神經元的 誤差實值
如果 \(a_{in}\) 接近於0, 則表示該 權值的梯度也接近0, 此時稱該神經元的權值學習比較慢, 即梯度變化時, 對代價函數的影響較小. (激勵值過低的神經元學習很慢), 這種神經元呢, 也被稱為 飽和神經元 這就是我們期望的效果呀.
飽和神經元: (先看咱上邊推導出的結論)
\(\sigma(x) = \frac {1}{1+e^{-x}}\)
\(\sigma'(x) = \sigma(x) (1-\sigma(x))\)
\(\delta_j^L = \frac {\partial C}{\partial a^L_j} \ \sigma'(z^L_j) = \nabla _a C \odot \ \sigma'(z^L_j)\)
\(\delta ^l = ((w^{l+1})^T \delta ^{l+1}) \odot \sigma '(z^l)\)
\(\frac {\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j\)
從激勵函數來看,
\(\sigma(x) = \frac {1}{1+e^{-x}}\) 是一個 " s " 型的函數, 當 x = 0的時候, 激勵值為 0.5
當某層神經元的 加權輸入, **過大 或 過小 ** 時, 則 輸出的激勵值 要么接近1, 要么接近於 0, 這樣呢, 對於 C 來時, 會導致 激勵的導數值為 0, 從而 誤差減小, 即權值學習很慢, 逐漸接近 飽和 (saturated) , 逐漸停止學習.
理解上面式子, 因為 \(\sigma'(x)\) 變化所帶來的一連串影響哦
一個權值學習慢, 可能是因為 輸入的神經元的激勵很小, 或者其輸出的神經元接近飽和( 激勵過大接近1, 或過小, 接近0) .
小結 - 四大公式 及 矩陣表達
-
公式1: C 對於 - 輸出層的梯度: \(\delta_j^L = \frac {\partial C}{\partial a^L_j} \ \sigma'(z^L_j) = \nabla _a C \odot \ \sigma'(z^L_j)\)
-
公式2: C 對於 - 中間層的梯度: \(\delta ^l = ((w^{l+1})^T \delta ^{l+1}) \odot \sigma '(z^l)\)
-
公式3: C 對於 - Bias 的梯度: \(\frac {\partial C}{\partial b^l_j} = \delta^l_j\)
-
公式4: C 對於 - 權值 的梯度: \(\frac {\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j\)
然后來寫一波矩陣表達. (假設 隱含層是 m個節點, 輸出層是 n 個節點)
為啥要矩陣表達呢, 首先是比較簡潔呀, 公式上, 雖然有點不好理解. 但, 寫成矩陣, 容易編程實現呀
公式1 可寫為: \(\delta^L = \Sigma(z^L) \nabla_aC\)
\(\Sigma\) 這不是求和, 我寫的 latex 是這樣的: \Sigma 讀作 " C 格碼", 是個對角陣 \(\Sigma(z^L)\)主對角線元素為: \(\sigma (z^L_j)\)
維數: n x n, nx1 ==> nx1 的向量; L 表示輸出層
公式2 可寫為: \(\delta^l = \Sigma (z^L) (w^{l+1})^T \delta ^{l+1})\)
跟前面轉法一樣的, 維數分別是: mxm, (nxm) ^T, nx1 ==> m x 1 的向量, \(l\) 表示 中間的任意一層
公式3 可寫為: \(\frac {\partial C}{\partial b^l} = \delta^l\)
公式4 可寫為: \(\frac {\partial C}{\partial w} = a^{l-1}\ (\delta^l)^T\)
w 是 mxn 的矩陣; \(\delta ^l\) 是 l 層的誤差向量, nx1維; $a^{l-1} 是 (l-1)層 $ 的激勵向量, mx1維.
BP算法步驟 (SGD)

總體來看, BP 算法, 就2步
- 前向計算出誤差
- 誤差后傳, 更新權值
搞定, 還差 擼一把numpy 代碼, 這個不是很難哦, 網上也有很多, 不在這些了, 私下自己后面再編寫吧, 畢竟思路的很清楚了.
