一、單個神經元
神經網絡算法,是使用計算機模擬生物神經系統,來模擬人類思維方式的算法。它的基本單位就是人工神經元。通過相互連接形成一張神經網絡。
生物神經網絡中,每個神經元與其他神經元連接,當它“激活”時,會傳遞化學物質到相連的神經元,改變其他神經元的電位,當電位達到一定“閾值”,那麼這個神經元也會被激活。
單個人工神經元的計算公式:
其中:
爲輸入參數向量,表示其他神經元輸入的信號。
爲每個輸入參數的權重值,表示對應神經元信號的權重。
θ爲閾值或者偏差值,是指該激活神經元的難易程度。
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()