---
# System prepended metadata

title: 誤差反向傳播法

---

>本章節我們先回顧梯度下降法，以利後續說明!
>[name=澤]

## 梯度下降法(gradient descent)
>在想該問題前請思考如何找到一個輸入值使得函數值最小?
>沒錯!用微積分，找出局部最小值

這就是梯度下降法存在的意義，梯度的方向是走向局部最大的方向，梯度下降法則為往梯度的反方向行走，而這些負梯度只是個向量(方向)，而這些數字推動將找到令成本函數(cost function)快速下降，但問題來了，一個神經網路神經元數量動輒破億，訓練樣本如何修改權重與偏差值，不是說每個參數變大變小就好，而是這些變化的比例多大，才能最快降低數值(cost)，梯度下降得對好幾個樣本作這類操作，也因此需要batch降低計算量，那怎麼樣的算法可以讓**他快速計算呢**?
![image](https://hackmd.io/_uploads/H1iiUWI6T.png =50%x)
圖來源:[1]
## 誤差反向傳播法理論
多數的機器學習問題可以想成大量參數作為輸入，將**損失函數**作為 最終輸出，損失函數的輸出通常是一個純量，因此我們需要計算損失函數每個參數(神經元)的微分，若是從輸出往輸入方向傳播微分，僅需傳播一次，就能計算所有參數，因此該方法效率高。

那在我們推導前，先說明符號意義。
>若是不想看數學，或是沒學過偏微分可以先跳過下面部分，但請你想一下，為何反向會更有效率
>[name=澤]
### 數學推導
$\sigma$ : 激活函數
bias : 偏差值
$W_i$ : 權重
$X_i$ : 輸入
$\partial{x}$ :偏微分
$C_0$ :成本函數(cost function)

下圖為神經元之計算
![56a718af-2733-4b5c-9a23-31bbcbbfdbac](https://hackmd.io/_uploads/BkFY7fI66.jpg =50%x)

也就是說神經元之計算也可寫成 
$$z = \sum_{i = 0}^nw_ix_i + b$$

那請你現在觀察下圖兩個神經元(L代表最後一層)
![7243fb22-653e-4036-a246-6dd1a5412120](https://hackmd.io/_uploads/S1WrvzUT6.jpg =50%x)

根據上述公式，我可以將其改寫成 $$z^L = w^La^{L-1} + b^L$$
$$a^L = \sigma(z^L)$$

也就是說這些公式我又可以圖解成下圖
![4be39fe9-e897-49d7-9d71-8215d4a2d072](https://hackmd.io/_uploads/Bke4AGUpT.jpg =50%x)
先假設$${C_0} = ({a^L - y})^2$$
那問題來了，請問在正式計算微分時，該如何求出底下微分呢?$$\frac{\partial{C_0}}{\partial{W_0}}$$
>誤差反向傳播法的核心概念就是連鎖律!
[name=澤]

$$\frac{\partial{C_0}}{\partial{W_0}} = \frac{\partial{z^L}}{\partial{w^L}}\frac{\partial{a^L}}{\partial{z^L}}\frac{\partial{C_0}}{\partial{a^L}}$$
>值得一提的是右邊第一個式子被稱為forward pass，算神經元權重對z的偏微分，只要把input放入，然後計算每個nerual就結束了，詳細請看參考連結[4]10:59，如下列公式 

$$\frac{\partial{z^L}}{\partial{w^L}} = a^{(L-1)}$$
$$\frac{\partial{a^L}}{\partial{z^L}} = \sigma^{\prime}(z^{(L)})$$
$$\frac{\partial{C_0}}{\partial{a^L}} = 2(a^L - y)$$
>就僅是前一層給的輸入，很簡單吧，沒學過偏微分可以學一下
>[name=澤]

好，那如果今天我將函數稍微改變一下，改成
$$\frac{\partial{C_0}}{\partial{a^{(L-1)}}} = \frac{\partial{z^L}}{\partial{{a^{(L - 1)}}}}\frac{\partial{a^L}}{\partial{z^L}}\frac{\partial{C_0}}{\partial{a^L}}=w^{(L)}\sigma^{\prime}(z^{(L)})2(a^L - y) $$
$$\frac{\partial{z^L}}{\partial{a^{(L-1)}}} = w^{(L)}$$
那你會發現cost_function對前一層激活是多敏感並且更改後就可以往後傳播了，圖解在底下
![image](https://hackmd.io/_uploads/Hk-3eNPpT.png =30%x)
圖來源:[3]
>到這裡誤差反向傳播的推導差不多拉@@，有興趣讀更深可以看參考連結[2]
>[name=澤]

## 實作程式碼 
````python=
ss Network(object):
...
   def backprop(self, x, y):
        
        nabla_b = [np.zeros(b.shape) for b in self.biases]#取出bias
        nabla_w = [np.zeros(w.shape) for w in self.weights]#取出權重
        # 前向回饋
        activation = x
        activations = [x] # 儲存激活函數
        zs = [] # 儲存層與層之間向量
        for b, w in zip(self.biases, self.weights):
            z = np.dot(w, activation)+b #向量內積，即wi_Xi矩陣相乘
            zs.append(z)
            activation = sigmoid(z)
            activations.append(activation)
        # 反向傳播
        delta = self.cost_derivative(activations[-1], y) * 
        #delta :神經元的偏差
        sigmoid_prime(zs[-1])
        nabla_b[-1] = delta
        #輸出層偏差的梯度
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())
        
        for l in xrange(2, self.num_layers):#計算隱藏層梯度
            z = zs[-l]
            sp = sigmoid_prime(z)
            delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
            nabla_b[-l] = delta
            nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
        return (nabla_b, nabla_w)

...

    def cost_derivative(self, output_activations, y):
        """Return the vector of partial derivatives \partial C_x /
        \partial a for the output activations."""
        return (output_activations-y) 

def sigmoid(z):
    """The sigmoid function."""
    return 1.0/(1.0+np.exp(-z))

def sigmoid_prime(z):
    """Derivative of the sigmoid function."""
    return sigmoid(z)*(1-sigmoid(z))
````
程式碼來源:參考連結[2]
## 參考連結

[-[1]梯度下降，神經網絡如何學習](https://www.youtube.com/watch?v=IHZwWFHWa-w)

[-[2]How the backpropagation algorithm works(很詳細)](http://neuralnetworksanddeeplearning.com/chap2.html)

[-[3]Backpropagation calculus](https://www.youtube.com/watch?v=tIeHLnjs5U8)

[-[4]李弘毅Backpropagation](https://www.youtube.com/watch?v=ibJpTrp5mcE&t=322s)

[-[5]深度學習開發實作_core_simple](https://github.com/oreilly-japan/deep-learning-from-scratch-3/blob/master/dezero/core_simple.py)

