代碼為MNIST數據集上運行簡單BP神經網絡的python實現。
以下公式和文字來自Wanna_Go的博文 http://www.cnblogs.com/wxshi/p/6077734.html,包含詳盡的描述和推導。
BP神經網絡
單個神經元
神經網絡是由多個“神經元”組成,單個神經元如下圖所示:
這其實就是一個單層感知機,輸入是由ξ1 ,ξ2 ,ξ3和Θ組成的向量。其中Θ為偏置(bias),σ為激活函數(transfer function),本文采用的是sigmoid函數,功能與階梯函數(step function)相似控制設神經元的輸出,它的優點是連續可導。
是神經元的輸出,結果為
可以看得出這個“神經元”的輸入-輸出映射其實就是一個邏輯回歸,常用的激活函數還有雙曲正切函數 。
激活函數
sigmoid:函數
取值范圍為[0,1],它的圖像如下:
求導結果為:
tanh函數:
取值范圍為[-1,1],圖像如下:
求導數結果為。本文采用的是sigmoid函數作為激活函數。
神經網絡模型
神經網絡將許多“神經元”聯結在一起,一個神經元的輸出可以是另一個“神經元”的輸入,神經元之間的傳遞需要乘法上兩個神經元對應的權重,下圖就是一個簡單的神經網絡:
這是一個三層的神經網絡,使用圓圈來表示神經元的輸入,“+1”被稱為偏置節點,從左到右依次為輸入層、隱藏層和輸出層,從圖中可以看出,有3個輸入節點、3個隱藏節點和一個輸出單元(偏置不接受輸入)。
本例神經網絡的參數有,其中
是第l層第 j 單元與 l+1層第
單元之間的聯接參數,即:節點連線的權重,本圖中
是第l+1 層第i單元的偏置項。
向前傳播
機器學習(有監督)的任務無非是損失函數最小化,BP神經網絡的原理是前向傳播得到目標值(分類),再通過后向傳播對data loss進行優化求出參數。可見最優化部分
表示
層第
單元激活值(輸出值)。當
時,
,也就是第
個輸入值。對於給定參數集
,
來表示神經網絡最后計算輸出的結果。上圖神經網絡計算步驟如下:
可以看出,神經網絡的核心思想是這一層的輸出乘上相應的權重加上偏置,帶入激活函數后的輸出又是下一層的輸入。用 表示第
層第
單元輸入加權和
,則
。使用向量化表示方法表示,上面的公式可以簡寫為:
這些計算步驟就是前向傳播,將計算過程進行推廣,給定第 層的激活值
,第
層的激活值
的計算過程為:
反向傳播
在前向傳播中,我們得到了神經網絡的預測值,這時候可以通過反向傳播的方法計算出參數
符號定義
:第l層第j個節點的輸入。
:從第l-1層第i個節點到第l層第j個節點的權值。
:Sigmoid激活函數。
::第l層第j個節點的偏置。
::第l層第j個節點的輸出。
::輸出層第j個節點的目標值(label)。
使用梯度下降的方法求解參數,在求解的過程中需要對輸出層和隱藏層分開計算
輸出層權重計算
給定樣本標簽和模型輸出結果
,輸出層的損失函數為:
這其實就是均方差項,訓練的目標是最小化該誤差,使用梯度下降方法進行優化,對上式子對權重W進行求導:
,整理,
其中=
帶入
,對sigmoid求導得:
輸出層第k個節點的輸入等於上一層第j個節點的輸出
乘上
,即
=
,而上一層的輸出
與輸出層的權重變量
無關,可以看做一個常數,所以直接求導可以得到:
所以將=
帶入式子中就得到:
為了方便表示將上式子記作:
其中:
隱藏層權重計算
采用同樣方法對隱藏層的權重進行計算,與前面不同的是關於隱藏層和前一層權重的調整
整理
替換sigmoid函數
對sigmoid求導
把帶入進去,使用求導的鏈式法則:
輸出層的輸入等於上一層的輸入乘以相應的權重,即:於是得到
對進行求導(
=
,同樣適用於j),
同輸出層計算的方法一樣,再次利用,j換成i,k換成j同樣成立,帶入進去:
整理,得到:
其中:
我們還可以仿照的定義來定義一個
,得到:
其中:
偏置調整
從上面的計算步驟中可以看出:例如,偏置節點是不存在對應的權值參數,也就是不存在關於權值變量的偏導數。
對偏置直接求導:
又有
得到:
,其中:
BP算法步驟
1. 隨機初始化W和b,需要注意的是,隨機初始化並是不是全部置為0,如果所有參數都是用相同的值初始化,那么所有隱藏單元最終會得到與輸入值相關、相同的函數(也就是說,對於所有 ,
都會取相同的值,那么對於任何輸入
都會有:
),隨機初始化的目的是使對稱失效。
2.對每個輸出節點按照這個公式計算delta:
3.對每個隱藏節點按照這個公式計算delta:
4.更新W和b的公式為:
並更新參數,這里的η是學習率。
1 # coding:utf8 2 import cPickle 3 import numpy as np 4 5 6 class Network(object): 7 def __init__(self, sizes): 8 self.num_layers = len(sizes) 9 self.sizes = sizes 10 self.biases = [np.random.randn(y, 1) for y in sizes[1:]] # L(n-1)->L(n) 11 self.weights = [np.random.randn(y, x) 12 for x, y in zip(sizes[:-1], sizes[1:])] 13 14 def feedforward(self, a): 15 for b_, w_ in zip(self.biases, self.weights): 16 a = self.sigmoid(np.dot(w_, a)+b_) 17 return a 18 19 def SGD(self, training_data, test_data,epochs, mini_batch_size, eta): 20 n_test = len(test_data) 21 n = len(training_data) 22 for j in xrange(epochs): 23 np.random.shuffle(training_data) # shuffle 24 for k in xrange(0, n, mini_batch_size): 25 mini_batch = training_data[k:k+mini_batch_size] 26 self.update_mini_batch(mini_batch, eta) 27 print "Epoch {0}: {1} / {2}".format( 28 j, self.evaluate(test_data), n_test) 29 30 def update_mini_batch(self, mini_batch, eta): 31 for x, y in mini_batch: 32 delta_b, delta_w = self.backprop(x, y) 33 self.weights -= eta/len(mini_batch)*delta_w 34 self.biases -= eta/len(mini_batch)*delta_b 35 36 def backprop(self, x, y): 37 b=np.zeros_like(self.biases) 38 w=np.zeros_like(self.weights) 39 a_ = x 40 a = [x] 41 for b_, w_ in zip(self.biases, self.weights): 42 a_ = self.sigmoid(np.dot(w_, a_)+b_) 43 a.append(a_) 44 for l in xrange(1, self.num_layers): 45 if l==1: 46 delta= self.sigmoid_prime(a[-1])*(a[-1]-y) # O(k)=a[-1], t(k)=y 47 else: 48 sp = self.sigmoid_prime(a[-l]) # O(j)=a[-l] 49 delta = np.dot(self.weights[-l+1].T, delta) * sp 50 b[-l] = delta 51 w[-l] = np.dot(delta, a[-l-1].T) 52 return (b, w) 53 54 def evaluate(self, test_data): 55 test_results = [(np.argmax(self.feedforward(x)), y) 56 for (x, y) in test_data] 57 return sum(int(x == y) for (x, y) in test_results) 58 59 def sigmoid(self,z): 60 return 1.0/(1.0+np.exp(-z)) 61 62 def sigmoid_prime(self,z): 63 return z*(1-z) 64 65 if __name__ == '__main__': 66 67 def get_label(i): 68 c=np.zeros((10,1)) 69 c[i]=1 70 return c 71 72 def get_data(data): 73 return [np.reshape(x, (784,1)) for x in data[0]] 74 75 f = open('mnist.pkl', 'rb') 76 training_data, validation_data, test_data = cPickle.load(f) 77 training_inputs = get_data(training_data) 78 training_label=[get_label(y_) for y_ in training_data[1]] 79 data = zip(training_inputs,training_label) 80 test_inputs = training_inputs = get_data(test_data) 81 test = zip(test_inputs,test_data[1]) 82 net = Network([784, 30, 10]) 83 net.SGD(data,test,20,10, 3.0,) # 9496/10000