反向傳播算法(BackPropagation)

你能夠在這裏閱讀 上一篇html

我是薛銀亮,感謝英文原版在線書籍,這是我學習機器學習過程當中感受很是適合新手入門的一本書。鑑於知識分享的精神,我但願能將其翻譯過來,並分享給全部想了解機器學習的人,本人翻譯水平有限,歡迎讀者提出問題和發現錯誤,更歡迎大牛的指導。由於篇幅較長,文章將會分爲多個部分進行,感興趣的能夠關注個人文集,文章會持續更新。python


在上一篇文章中,咱們看到了神經網絡如何經過梯度降低算法學習調整它的權重和b值,可是咱們省略瞭如何計算損失函數的梯度。在這一篇文章中,我將會介紹一種計算梯度的快速算法:反向傳播算法(BackPropagation)。算法

這篇文章會涉及不少數學,若是你以爲沒有興趣或者很迷惑,能夠跳過,那樣你能夠直接把反向傳播當成一個黑盒來使用。可是有什麼值得咱們花時間來學習這些細節呢?微信

理由固然是:理解。反向傳播的核心是經過改變對於網絡中的權重w和偏移b,計算損失函數C的偏導數∂C/∂w,從而求出損失變化的極值(若是這句話看不懂,我建議你去先看看微積分)。這個表達式告訴咱們當咱們改變權重w和偏移b,損失函數變化的速度。雖然它有時很複雜,可是它能夠對每一個元素有一個天然的,直觀的解釋,它實際給了咱們一個詳細的修改權重和b值來改變網絡總體行爲的辦法。這是很是值得研究的。網絡

熱身:一種基於矩陣的快速計算神經網絡輸出的方法


在討論反向傳播以前,讓咱們先用一種基於矩陣的快速傳播方法來預測一個神經網絡的輸出。這有助於咱們熟悉反向傳播中的符號。app

定義符號:
機器學習

這個符號表明第(l-1)層中第K個神經元和第l層中第j個神經元之間的權重。例以下圖:函數

定義符號:oop

表明第 l 層中第 j 個神經元的偏移值b。學習

表明第 l 層中第 j 個神經元的輸出激活a(activation)。以下圖所示:

由以上全部符號得出:第l層第j個神經元的激活等於:

上式括號中表示的是:第(l-1)層(前一層)中的全部k個神經元到第(l)層(後一層)第j個神經元的權重和第(l-1)層全部k個神經元的輸出乘積的和,加上第l層第j個神經元的偏移值(閥值)。相信看到這裏,這些符號的意義你都會很明確了。

咱們向量化公式(23),用σ(v)表示。例如,方程式f(x)=x^2,向量化就是:

向量化的f會對每個向量進行求平方操做。

因此公式(23)被向量化能夠表示爲下式:

這個表達式給咱們提供了一個更爲全局的思考方法:一層中的激活與前一層中的激活如何相關:咱們只是將權重矩陣應用於前一層激活,而後添加偏移向量(b),最後應用到σ函數。 比咱們如今採用的神經元 - 神經元更簡單,更簡潔(至少應用了更少的索引!)。 該表達式在實踐中也是有用的,由於大多數矩陣庫提供了實現矩陣乘法,矢量加法和矢量化的快速方法。 實際上,上一章中的代碼隱含地使用這個表達式來計算網絡的行爲。

咱們定義公式:

爲第l層神經元的加權輸入。若是將其寫成份量樣式:

即表明第j層神經元的激活函數對第l層的加權輸入。

關於損失函數的兩個假設


反向傳播的目標是經過計算損失函數C相對於網絡中的權重w或誤差b的偏導數∂C/∂w和∂C/∂b來實現的。 爲此,咱們須要對損失函數的形式作兩個假設。 在闡述這些假設以前,咱們參照一個損失函數示例。 咱們將使用上一節中的二次損失函數(c.f. Equation(6))。 在上一節的符號中,二次損失函數的形式是:

其中:n是訓練樣例的總數; x是每個訓練樣本;y(x)是指望輸出;L是網絡層數,a^L(x)是當x輸入是網絡的輸出。

第一個假設是:用損失函數的平均數表示在每個訓練樣例上的損失。

咱們作這個假設的緣由是,咱們求偏導數∂Cx/∂w 和 ∂Cx/∂b是針對每個樣例進行的。

第二個假設是將神經網絡的輸出表示成損失函數:

例如二次損失函數能夠知足這個要求,由於二次損失函數對於單一的輸入x能夠寫成:

Hadamard 積,s⊙t


反向傳播要基於常見的線性代數運算,像矩陣的加、乘等等。可是有一種運算是不常見的。例如,s和t是兩個相同維度的向量,咱們使用s⊙t表示兩個向量按元素的積(elementwise product),如:

可用以下形式表示:

這種按元素相乘的積叫作Hadamard乘積。

反向傳播背後重要的四個方程


反向傳播算法的目的是經過改變(權重w和偏移b)來改善神經網絡損失函數。最終是經過求偏導數(∂C/∂w)和(∂C/∂b)來衡量的。爲此咱們先定義一箇中間量:δ(l,j),表明 l 層第 j 個神經元的偏差。咱們將會利用反向傳播來計算這個偏差。首先爲了理解這個中間量,咱們想象一下在神經網絡中有一個小惡魔:

這個惡魔站在第l層的第j個神經元,當有輸入過來的時候,它改變了一點加權輸入(上面定義的z):

形成了神經元輸出的改變:

最終,形成了損失函數的改變:

如今假設這個惡魔是個好惡魔,試圖幫助你提升輸出的準確率。它嘗試幫你調節∆z,使得輸出偏差最小。由上面的想象,咱們經過下式定義第l層第j個神經元的偏差:

做爲慣例,把上式中的j去掉,表示第l層的偏差向量(δ^l)。反向傳播爲咱們提供了計算它的方法,並經過:∂C/∂w 和∂C/∂b將偏差和真實數據結合起來。

反向傳播基於四個基本方程。 這些方程一塊兒給咱們提供了一種計算偏差δ和損失函數梯度的方法。 不過要注意的是:你不該該指望一會兒就理解這四個方程式。 這樣的指望可能會讓你失望。 由於反向傳播方程很是豐富,以致於深刻了解它們須要至關長的時間和耐心。

這裏先提供一個預覽的方式,咱們將會在後面章節給出更詳細的描述。這裏會給出一些簡單的證實,這有助於咱們對他們的理解。

輸出層偏差的等式:

表達式右邊的第一部分表示:第j個神經元輸出改變時損失C改變的速度(斜率)。若是當神經元輸出改變已經不能太顯著地改變C時,那麼偏差就會很小,這正是咱們指望的。右邊的第二個式子表示:測量激活函數σ在加權輸入變化時的變化速率。

將BP1以向量的形勢寫出來就是:

等號右邊第一部分表示一個向量,你能夠把它看做損失C對輸出偏導數的向量矩陣,很容易看出來(BP1a)和(BP1)是相等的。由於這個緣由,從如今起咱們將用BP1代替上式。咱們參照在二次均方偏差方程中的例子來理解,其損失函數爲:

可推導出:

因此:

代入方程(BP1a)得出:

正如你看到的,表達式中的每一部分都是向量格式,因此就能很容易的用Numpy來計算。

用(l + 1)層的偏差表示第(l)層的偏差

你能夠把它看作:偏差會隨着網絡進行傳遞。

與誤差b相關的偏差變化:

經過BP1和BP2,咱們能簡單表示BP3:

與權重相關的偏差變化:

簡潔格式:

a(in)可理解成權重w的輸入激活, δ(out)可理解成權重w輸入時神經元的偏差。圖示:

等式中a的值很小接近0時,公式值就會很小,此時咱們說這個權重下學習很慢,意味着它不能很快的改變梯度。

讓咱們從輸出層的角度觀察這4個公式,看一下公式BP1,再回想一下上一節中的sigmoid函數的圖,當函數接近0或1的時候就會變的很是水平。這時:

若是輸出神經元是低激活(≈0)或高激活(≈1),權重調節就是緩慢學習的。這種狀況下就說,輸出神經元飽和,權重中止學習,對於輸出神經元的誤差b,也有相似的說法。這種方法應用在BP2上也是相似的。

總結一下,咱們已經知道,若是輸入神經元是低激活的,或者若是輸出神經元飽和,即高或低激活,則權重學習效率將會很低。

注意:以上四個公式我但願讀者能夠本身證實出來,這能給你帶來更深刻的理解。這可能會花不少時間,可是這是挺有必要的。

簡單證實一下上看的式子:

四個式子都是能夠用多元微積分的鏈式法則求出來的。若是你熟悉鏈式法則,我但願你能夠本身證實。

  • BP1
    咱們知道偏差表達式爲:

根據微積分鏈式法則:

上式爲輸出層k個神經元的和,固然,輸出層第k個神經元的激活輸出a僅僅依賴於輸出層第j個神經元的加權輸入z(j)。因此除了這一項,其餘項都消失了,則:

而a=σ(z),因此:

  • BP2

這是表示相鄰兩層的偏差關係的式子,由式子(36)可得:

由鏈式法則可得:

而後已知的是:

對其求偏導數可得:

代入(42)可得:

這就是式子二的份量形勢。

關於式子三和四我但願讀者能夠本身證實,根據鏈式法則這也是相對容易的。

咱們從最後一層開始計算偏差向量,而後向後逐層計算偏差。

實際中,由於有不少訓練集,咱們常常須要結合隨機梯度降低和反向傳播算法來計算梯度。例如使用咱們第一章提到的mini-batch數據時:

  • 輸入訓練數據
  • 依照下面的步驟,爲每一個訓練數據設置激活a(x,1):

    • 爲每一層 l=2,3,…,L計算a和z:
      • 反向傳播計算每一層偏差:
  • 梯度降低,對每一層更新w和b:

此外,你須要一個外部循環mini-batches遍歷整個訓練數據集。

###反向傳播代碼

理解了反向傳播的抽象概念,咱們來理解一下上節的代碼。還記得在Network類中有update_mini_batch和backprop方法,這兩個方法是直接翻譯的上面的公式,update_mini_batch方法是經過計算當前訓練集(mini_batch)的梯度更新權重(w)和偏移值(b):

class Network(object):
...
    def update_mini_batch(self, mini_batch, eta):
        """Update the network's weights and biases by applying gradient descent using backpropagation to a single mini batch. The "mini_batch" is a list of tuples "(x, y)", and "eta" is the learning rate."""
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        for x, y in mini_batch:
            delta_nabla_b, delta_nabla_w = self.backprop(x, y)
            nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        self.weights = [w-(eta/len(mini_batch))*nw 
                        for w, nw in zip(self.weights, nabla_w)]
        self.biases = [b-(eta/len(mini_batch))*nb 
                       for b, nb in zip(self.biases, nabla_b)]複製代碼

大多數的工做都被delta_nabla_b, delta_nabla_w = self.backprop(x, y)這一行執行。這一行經過反向傳播方法計算b和w相對於C的偏導數。這個方法以下:

class Network(object):
...
   def backprop(self, x, y):
        """Return a tuple "(nabla_b, nabla_w)" representing the gradient for the cost function C_x. "nabla_b" and "nabla_w" are layer-by-layer lists of numpy arrays, similar to "self.biases" and "self.weights"."""
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        # feedforward
        activation = x
        activations = [x] # list to store all the activations, layer by layer
        zs = [] # list to store all the z vectors, layer by layer
        for b, w in zip(self.biases, self.weights):
            z = np.dot(w, activation)+b
            zs.append(z)
            activation = sigmoid(z)
            activations.append(activation)
        # backward pass
        delta = self.cost_derivative(activations[-1], y) * \
            sigmoid_prime(zs[-1])
        nabla_b[-1] = delta
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())
        # Note that the variable l in the loop below is used a little
        # differently to the notation in Chapter 2 of the book. Here,
        # l = 1 means the last layer of neurons, l = 2 is the
        # second-last layer, and so on. It's a renumbering of the
        # scheme in the book, used here to take advantage of the fact
        # that Python can use negative indices in lists.
        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))複製代碼

###反向傳播算法是什麼意思?
讓咱們想象一下網絡中權重有一點小小的改變:

這個小小的改變會形成相應神經元激活輸出的改變:

而後會形成下一層全部輸出激活的改變:

最終,將會影響到損失函數:

經過公式能夠表示成:

咱們知道一層中激活改變會形成下一層中輸出改變,這裏先把目光聚焦於下一層中的某一個的改變。

結合公式(48):

能夠想象,當有不少層時(每一層假設都有受影響的輸出):

這表明這某個權重改變通過網絡某一條路徑最終對偏差的影響,固然網絡中有不少的路徑,咱們須要計算全部的總和:

結合(47):

計算c相對於某個權重的變化速率,公式可看出每一層的激活相對於下一層激活的偏導數都是速率因子。體如今圖上:

這只是提供一種啓發式的思考,但願會對你理解反向傳播有幫助。

若是個人文章對你有幫助,我建議你能夠關注個人文集或者打賞,我建議打賞¥5。固然你也能夠隨意打賞。

微信
微信

支付寶
支付寶

若是有問題,歡迎跟我聯繫討論space-x@qq.com.我會盡可能及時回覆。

相關文章
相關標籤/搜索