【神經網絡算法入門】詳細推導全連接神經網絡算法及反向傳播算法+Python實現代碼

一、單個神經元

神經網絡算法,是使用計算機模擬生物神經系統,來模擬人類思維方式的算法。它的基本單位就是人工神經元。通過相互連接形成一張神經網絡。

生物神經網絡中,每個神經元與其他神經元連接,當它“激活”時,會傳遞化學物質到相連的神經元,改變其他神經元的電位,當電位達到一定“閾值”,那麼這個神經元也會被激活。

單個人工神經元的計算公式:

其中:

 爲輸入參數向量,表示其他神經元輸入的信號。

 爲每個輸入參數的權重值,表示對應神經元信號的權重。

θ爲閾值或者偏差值,是指該激活神經元的難易程度。

y爲神經元的輸出值,表示該神經元是否被激活。

Act()爲激活函數,理想的激活函數是下圖(a)中的躍階函數,“1”爲神經元興奮,“0”爲神經元抑制,但由於躍階函數具有不是連續可導等不好的性質,因此一般採用下圖(b)的Sigmoid函數作爲激活函數。

 

二、全連接神經網絡結構

我們來定義一個全連接神經網絡:

全連接神經網絡,就是指每一層的每個神經元都和下一層的每個神經元相連接。

Layer:0爲輸入層,

Layer:L爲輸出層

其他L-1個Layer爲隱層

輸入x

 

我們稱一個輸入值x爲一個樣本

輸出 y

變量的上標(0)(L),表示該變量處於神經網絡的哪一層。

表示第L層編號爲i的神經元。 表示第L層的神經元數量。

更好的理解神經網絡,可觀看此視頻:https://www.bilibili.com/video/av15532370

 

三、反向傳播算法(BP算法)

下面來說明如何調整一個神經網絡的參數,也就是誤差反向傳播算法(BP算法)。以得到一個能夠根據輸入,預測正確輸出的模型。

1、首先我們要了解優化的目標

根據人工神經元的定義,有以下三個公式:

其中,Act()是激活函數,之前已經說過。

根據公式(2)和公式(3),可以得出各個神經元之間的通用計算公式,如下所示:

公式(4)是人工神經網絡正向傳播的核心公式。

 

那麼,我們根據什麼來調整神經網絡的參數,以得到一個能夠正確預測結果的模型呢?請看下面的公式:

公式(5)用來計算我們期望的輸出和實際輸出的“差別”,其中cost()叫做損失函數。我們的期望是損失值達到最小。

但是,只根據一次輸出的損失值,對參數進行調整,無法使模型適應所有輸入樣本。我們需要的是,調整參數,使得所有輸入樣本,得到輸出的總損失值最小,而不是隻讓其中一個樣本的損失值最小,導致其他樣本損失值增大。因此有如下公式:

公式(6)表示一個batch的所有樣本輸出的總損失值的平均值。其中,bn表示一個batch中樣本的數量。

爲什麼不用所有的樣本計算損失值,而將所有樣本分成一個個的batch呢?因爲所有的訓練樣本數量太大了,可能有數以百萬計,將所有的樣本損失值都一起進行運算,計算量過於龐大,大大降低了模型計算的速度。

公式(6)中計算總的損失值C,其實是一個以所有的連接權值ω和所有的閾值θ未爲變量的多元函數。我們想要的模型就是求得C最小時,所有ω和θ的值。直接計算顯然是不可能的,因爲對於一個大的深度神經網絡,所有的參數變量,可能數以萬計。

在這裏我們使用梯度下降算法來逐步逼近C的最小值,也就是先隨機得到一組參數變量的值,然後計算參數變量當前的梯度,向梯度的反方向,也就是C變小最快的方向,逐步調整參數值,最終得到C的最小值,或者近似最小值。

而將所有樣本,隨機分成一個個固定長度的batch,以得到近似的梯度方向,叫做隨機梯度下降算法

更好理解梯度下降算法,逐步求得最優的參數值,可觀看此視頻:https://www.bilibili.com/video/av16144388

 

2、開始求梯度

那麼,根據梯度的定義,接下來的任務,就是求取各個參數變量相對於C的偏導數。我們將使用誤差反向傳播算法來求取各個參數變量的偏導數。

這裏先劇透一下,求取參數偏導數的方法,和神經網絡正向傳播(根據樣本計算輸出值)的方式類似,也是逐層求解,只是方向正好相反,從最後一層開始,逐層向前。

更好的理解誤差反向傳播算法,可觀看此視頻:https://www.bilibili.com/video/av16577449

首先,我們先求神經網絡最後一層,也就是輸出層的相關參數的偏導數。爲了降低推導的複雜性,我們只計算相對於一個樣本的損失值函數Cbi的偏導數,因爲相對於總損失值函數C的偏導數值,也不過是把某個參數的所有相對於Cbi偏導數值加起來而已。

根據公式(2)、公式(3)、公式(5),以及“複合函數求導法則”,可以得到輸出層(L層)某個神經元的權值參數ω的偏導數,計算公式如下:

根據公式(5)可以得到:

根據公式(2)可以得到:

根據公式(3)可以得到:

將公式(8)(9)(10),帶入公式(7),可以得到:

 

我們令:

根據公式(8)(9)則有:

將公式(13),帶入公式(11),可以得到:

這樣我們就得到了輸出層L相關的權值參數ω的偏導數計算公式!

接下來,同理可得輸出層L相關的閾值θ的偏導數計算公式爲:

而根據公式(3)可以得到:

將公式(16)帶入公式(15)可以得到:

這就是輸出層L相關的閾值θ的偏導數計算公式!

 

3、根據L層,求前一層參數的偏導函數

由公式(3)可知,一個權值參數ω隻影響一個L-1層的神經元,因此有:

根據公式(3)可以得到:

將公式(19)帶入公式(18)可以得到:

根據公式(12)可以得到:

將公式(21)帶入公式(20)可以得到:

同理,我們可以得到:

根據公式(3)可以得到:

將公式(24)帶入公式(23)可以得到:

這樣我們就得到了L-1層神經元相關參數的計算公式!

 

下面,我們還需要推導一下 之間的關係,根據公式(2)可以得到:

同樣根據公式(2)可以得到:

將公式(27)帶入公式(26)可以得到:

由公式(3)可知,一個權值參數ω隻影響一個L-1層的神經元,但這個L-1層神經元影響了所有L層的神經元。因此,根據“多元複合函數求導法則”有:

根據公式(12)可以得到:

將公式(27)帶入公式(26)可以得到:

根據公式(3)可以得到:

將公式(32)帶入到公式(31)可以得到:

將公式(33)帶入公式(28)可以得到:

這樣我們就得到了反向傳播,逐層推導的通用公式:

在這裏,ω和z都是正向傳播過程中,已經算好的常數,而  可以從L層開始逐層向前推導,直到第1層,第0層是輸入層,不需要調整參數。而第L層的   可參考公式(13)。

 

下面是全連接神經網絡的python實現代碼:

#coding=utf-8
import numpy as np
import matplotlib.pylab as plt
import random

class NeuralNetwork(object):
    def __init__(self, sizes, act, act_derivative, cost_derivative):
        #sizes表示神經網絡各層的神經元個數,第一層爲輸入層,最後一層爲輸出層
        #act爲神經元的激活函數
        #act_derivative爲激活函數的導數
        #cost_derivative爲損失函數的導數
        self.num_layers = len(sizes)
        self.sizes = sizes
        self.biases = [np.random.randn(nueron_num, 1) for nueron_num in sizes[1:]]
        self.weights = [np.random.randn(next_layer_nueron_num, nueron_num)
            for nueron_num, next_layer_nueron_num in zip(sizes[:-1], sizes[1:])]
        self.act=act
        self.act_derivative=act_derivative
        self.cost_derivative=cost_derivative

    #前向反饋(正向傳播)
    def feedforward(self, a):
        #逐層計算神經元的激活值,公式(4)
        for b, w in zip(self.biases, self.weights):
            a = self.act(np.dot(w, a)+b)
        return a

    #隨機梯度下降算法
    def SGD(self, training_data, epochs, batch_size, learning_rate):
        #將訓練樣本training_data隨機分爲若干個長度爲batch_size的batch
        #使用各個batch的數據不斷調整參數,學習率爲learning_rate
        #迭代epochs次
        n = len(training_data)
        for j in range(epochs):
            random.shuffle(training_data)
            batches = [training_data[k:k+batch_size] for k in range(0, n, batch_size)]
            for batch in batches:
                self.update_batch(batch, learning_rate)
            print("Epoch {0} complete".format(j))

    def update_batch(self, batch, learning_rate):
        #根據一個batch中的訓練樣本,調整各個參數值
        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 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-(learning_rate/len(batch))*nw for w, nw in zip(self.weights, nabla_w)]
        self.biases = [b-(learning_rate/len(batch))*nb for b, nb in zip(self.biases, nabla_b)]

    #反向傳播
    def backprop(self, x, y):
        #保存b和w的偏導數值
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        #正向傳播
        activation = x
        #保存每一層神經元的激活值
        activations = [x]
        #保存每一層神經元的z值
        zs = []
        for b, w in zip(self.biases, self.weights):
            z = np.dot(w, activation)+b
            zs.append(z)
            activation = self.act(z)
            activations.append(activation)
        #反向傳播得到各個參數的偏導數值
        #公式(13)
        d = self.cost_derivative(activations[-1], y) * self.act_derivative(zs[-1])
        #公式(17)
        nabla_b[-1] = d
        #公式(14)
        nabla_w[-1] = np.dot(d, activations[-2].transpose())
        #反向逐層計算
        for l in range(2, self.num_layers):
            z = zs[-l]
            sp = self.act_derivative(z)
            #公式(36),反向逐層求參數偏導
            d = np.dot(self.weights[-l+1].transpose(), d) * sp
            #公式(38)
            nabla_b[-l] = d
            #公式(37)
            nabla_w[-l] = np.dot(d, activations[-l-1].transpose())
        return (nabla_b, nabla_w)

#距離函數的偏導數
def distance_derivative(output_activations, y):
    #損失函數的偏導數
    return 2*(output_activations-y)

# sigmoid函數
def sigmoid(z):
    return 1.0/(1.0+np.exp(-z))

# sigmoid函數的導數
def sigmoid_derivative(z):
    return sigmoid(z)*(1-sigmoid(z))

if __name__ == "__main__":
    #創建一個5層的全連接神經網絡,每層的神經元個數爲1,8,5,3,1
    #其中第一層爲輸入層,最後一層爲輸出層
    network=NeuralNetwork([1,8,5,3,1],sigmoid,sigmoid_derivative,distance_derivative)

    #訓練集樣本
    x = np.array([np.linspace(-7, 7, 200)]).T
    #訓練集結果,由於使用了sigmoid作爲激活函數,需保證其結果落在(0,1)區間內
    y = (np.cos(x)+1)/2

    #使用隨機梯度下降算法(SGD)對模型進行訓練
    #迭代5000次;每次隨機抽取40個樣本作爲一個batch;學習率設爲0.1
    training_data=[(np.array([x_value]),np.array([y_value])) for x_value,y_value in zip(x,y)]
    network.SGD(training_data,5000,40,0.1)

    #測試集樣本
    x_test = np.array([np.linspace(-9, 9, 120)])
    #測試集結果
    y_predict = network.feedforward(x_test)

    #圖示對比訓練集和測試集數據
    plt.plot(x,y,'r',x_test.T,y_predict.T,'*')
    plt.show()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章