Python語言編寫BP神經網絡
人工神經網絡是一種經典的機器學習模型,隨着深度學習的發展神經網絡模型日益完善.
聯想大家熟悉的回歸問題, 神經網絡模型實際上是根據訓練樣本創造出一個多維輸入多維輸出的函數, 並使用該函數進行預測, 網絡的訓練過程即為調節該函數參數提高預測精度的過程.神經網絡要解決的問題與最小二乘法回歸解決的問題並無根本性區別.
回歸和分類是常用神經網絡處理的兩類問題, 如果你已經了解了神經網絡的工作原理可以在http://playground.tensorflow.org/上體驗一個淺層神經網絡的工作過程.
感知機(Perceptron)是一個簡單的線性二分類器, 它保存着輸入權重, 根據輸入和內置的函數計算輸出.人工神經網絡中的單個神經元即是感知機.
在前饋神經網絡的預測過程中, 數據流從輸入到輸出單向流動, 不存在循環和返回的通道.
目前大多數神經網絡模型都屬於前饋神經網絡, 在下文中我們將詳細討論前饋過程.
多層感知機(Multi Layer Perceptron, MLP)是由多個感知機層全連接組成的前饋神經網絡, 這種模型在非線性問題中表現出色.
所謂全連接是指層A上任一神經元與臨近層B上的任意神經元之間都存在連接.
反向傳播(Back Propagation,BP)是誤差反向傳播的簡稱,這是一種用來訓練人工神經網絡的常見算法, 通常與最優化方法(如梯度下降法)結合使用.
本文介紹的神經網絡模型在結構上屬於MLP, 因為采用BP算法進行訓練, 人們也稱其為BP神經網絡.
BP神經網絡原理
經典的BP神經網絡通常由三層組成: 輸入層, 隱含層與輸出層.通常輸入層神經元的個數與特征數相關,輸出層的個數與類別數相同, 隱含層的層數與神經元數均可以自定義.
每個神經元代表對數據的一次處理:
每個隱含層和輸出層神經元輸出與輸入的函數關系為:
其中
表示神經元i與神經元j之間連接的權重,代表神經元j的輸出, sigmod是一個特殊的函數用於將任意實數映射到(0,1)區間.
上文中的sigmod函數稱為神經元的激勵函數(activation function), 除了sigmod函數
外, 常用還有tanh和ReLU函數.
我們用一個完成訓練的神經網絡處理回歸問題, 每個樣本擁有n個輸入.相應地,神經網絡擁有n個輸入神經元和1個輸出神經元.
實際應用中我們通常在輸入層額外增加一個偏置神經元, 提供一個可控的輸入修正;或者為每個隱含層神經元設置一個偏置參數.
我們將n個特征依次送入輸入神經元, 隱含層神經元獲得輸入層的輸出並計算自己輸出值, 輸出層的神經元根據隱含層輸出計算出回歸值.
上述過程一般稱為前饋(Feed-Forward)過程, 該過程中神經網絡的輸入輸出與多維函數無異.
現在我們的問題是如何訓練這個神經網絡.
作為監督學習算法,BP神經網絡的訓練過程即是根據前饋得到的預測值和參考值比較, 根據誤差調整連接權重
的過程.
訓練過程稱為反向傳播過程(BackPropagation), 數據流正好與前饋過程相反.
首先我們隨機初始化連接權重
, 對某一訓練樣本進行一次前饋過程得到各神經元的輸出.
首先計算輸出層的誤差:
其中
代表神經元j的誤差,表示神經元j的輸出, 表示當前訓練樣本的參考輸出,是上文sigmod函數的一階導數.
計算隱含層誤差:
隱含層輸出不存在參考值, 使用下一層誤差的加權和代替
.
計算完誤差后就可以更新
和:
其中
是一個稱為學習率的參數,一般在(0,0.1)區間上取值.
實際上為了加快學習的效率我們引入稱為矯正矩陣的機制, 矯正矩陣記錄上一次反向傳播過程中的
值, 這樣更新公式變為:
是一個稱為矯正率的參數.隨后更新矯正矩陣:
每一個訓練樣本都會更新一次整個網絡的參數.我們需要額外設置訓練終止的條件.
最簡單的訓練終止條件為設置最大迭代次數, 如將數據集迭代1000次后終止訓練.
單純的設置最大迭代次數不能保證訓練結果的精確度, 更好的辦法是使用損失函數(loss function)作為終止訓練的依據.
損失函數可以選用輸出層各節點的方差:
為了避免神經網絡進行無意義的迭代, 我們通常在訓練數據集中抽出一部分用作校驗.當預測誤差高於閾值時提前終止訓練.
Python實現BP神經網絡
首先實現幾個工具函數:
-
def rand(a, b):
-
return (b - a) * random.random() + a
-
-
-
def make_matrix(m, n, fill=0.0): # 創造一個指定大小的矩陣
-
mat = []
-
for i in range(m):
-
mat.append([fill] * n)
-
return mat
定義sigmod函數和它的導數:
-
def sigmoid(x):
-
return 1.0 / (1.0 + math.exp(-x))
-
-
-
def sigmod_derivate(x):
-
return x * (1 - x)
定義BPNeuralNetwork類, 使用三個列表維護輸入層,隱含層和輸出層神經元, 列表中的元素代表對應神經元當前的輸出值.使用兩個二維列表以鄰接矩陣的形式維護輸入層與隱含層, 隱含層與輸出層之間的連接權值, 通過同樣的形式保存矯正矩陣.
定義setup方法初始化神經網絡:
-
def setup(self, ni, nh, no):
-
self.input_n = ni + 1
-
self.hidden_n = nh
-
self.output_n = no
-
# init cells
-
self.input_cells = [1.0] * self.input_n
-
self.hidden_cells = [1.0] * self.hidden_n
-
self.output_cells = [1.0] * self.output_n
-
# init weights
-
self.input_weights = make_matrix(self.input_n, self.hidden_n)
-
self.output_weights = make_matrix(self.hidden_n, self.output_n)
-
# random activate
-
for i in range(self.input_n):
-
for h in range(self.hidden_n):
-
self.input_weights[i][h] = rand(-0.2, 0.2)
-
for h in range(self.hidden_n):
-
for o in range(self.output_n):
-
self.output_weights[h][o] = rand(-2.0, 2.0)
-
# init correction matrix
-
self.input_correction = make_matrix(self.input_n, self.hidden_n)
-
self.output_correction = make_matrix(self.hidden_n, self.output_n)
定義predict方法進行一次前饋, 並返回輸出:
-
def predict(self, inputs):
-
# activate input layer
-
for i in range(self.input_n - 1):
-
self.input_cells[i] = inputs[i]
-
# activate hidden layer
-
for j in range(self.hidden_n):
-
total = 0.0
-
for i in range(self.input_n):
-
total += self.input_cells[i] * self.input_weights[i][j]
-
self.hidden_cells[j] = sigmoid(total)
-
# activate output layer
-
for k in range(self.output_n):
-
total = 0.0
-
for j in range(self.hidden_n):
-
total += self.hidden_cells[j] * self.output_weights[j][k]
-
self.output_cells[k] = sigmoid(total)
-
return self.output_cells[:]
定義back_propagate方法定義一次反向傳播和更新權值的過程, 並返回最終預測誤差:
-
def back_propagate(self, case, label, learn, correct):
-
# feed forward
-
self.predict(case)
-
# get output layer error
-
output_deltas = [ 0.0] * self.output_n
-
for o in range(self.output_n):
-
error = label[o] - self.output_cells[o]
-
output_deltas[o] = sigmod_derivate( self.output_cells[o]) * error
-
# get hidden layer error
-
hidden_deltas = [ 0.0] * self.hidden_n
-
for h in range(self.hidden_n):
-
error = 0.0
-
for o in range(self.output_n):
-
error += output_deltas[o] * self.output_weights[h][o]
-
hidden_deltas[h] = sigmod_derivate( self.hidden_cells[h]) * error
-
# update output weights
-
for h in range(self.hidden_n):
-
for o in range(self.output_n):
-
change = output_deltas[o] * self.hidden_cells[h]
-
self.output_weights[h][o] += learn * change + correct * self.output_correction[h][o]
-
self.output_correction[h][o] = change
-
# update input weights
-
for i in range(self.input_n):
-
for h in range(self.hidden_n):
-
change = hidden_deltas[h] * self.input_cells[i]
-
self.input_weights[i][h] += learn * change + correct * self.input_correction[i][h]
-
self.input_correction[i][h] = change
-
# get global error
-
error = 0.0
-
for o in range(len(label)):
-
error += 0.5 * (label[o] - self.output_cells[o]) ** 2
-
return error
定義train方法控制迭代, 該方法可以修改最大迭代次數, 學習率
, 矯正率三個參數.
-
def train(self, cases, labels, limit=10000, learn=0.05, correct=0.1):
-
for i in range(limit):
-
error = 0.0
-
for i in range(len(cases)):
-
label = labels[i]
-
case = cases[i]
-
error += self.back_propagate(case, label, learn, correct)
編寫test方法,演示如何使用神經網絡學習異或邏輯:
-
def test(self):
-
cases = [
-
[ 0, 0],
-
[ 0, 1],
-
[ 1, 0],
-
[ 1, 1],
-
]
-
labels = [[ 0], [1], [1], [0]]
-
self.setup( 2, 5, 1)
-
self.train(cases, labels, 10000, 0.05, 0.1)
-
for case in cases:
-
print(self.predict(case))
完整源代碼參見bpnn.py