(一)BP神經網絡簡介
BP神經網絡是整個神經網絡體系中的精華,與一般神經網絡相比,它調整權值方式爲從最後一層開始逐層調整,通過多次迭代,使得代價函數降低至可接受範圍。因此,這就是BP神經網絡的來源(Back Propagation,誤差反向傳播)。
1、神經網絡權值調整的一般形式爲:
(1)其中 η爲學習率,可以理解爲每次權值調整的步長
(2)其中δ爲學習信號,可以理解爲每次權值調整的方向,實際上爲一個梯度向量。
由上式也可以發現不同神經網絡的差異主要體現在學習信號上的差異。
2、BP神經網絡中關於學習信號的求取方法:
假設一個神經網絡共n層
上式爲求取最後一層,即輸出層的學習信號,其中t爲期望輸出,y爲模型輸出,f(x)爲激活函數(對激活函數不瞭解的可自行先百度或查看我的其他文章)。
上式爲求取除最後一層以外其他層的學習信號,i+1表示第i層後面的一層,可以發現求取第i層學習信號時,需要先求取出第i+1層的學習信號,這也是誤差反向傳播的來源。
具體BP神經網絡的學習信號推導過程見以下的原理推導,推導過程有點小複雜,即使不看,根據以上的結論也可以編寫自己的BP神經網絡程序了。
(二)BP神經網絡原理推導
1、變量說明
(1)以三層感知器構成的神經網絡爲例(實際爲兩層神經網絡)。
(2)定義各層的輸入和輸出:
輸入向量
隱含層輸出向量
輸出層輸出向量
期望輸出向量
(3)定義權值矩陣:
輸入層到隱含層的權值矩陣
隱含層到輸出層的權值矩陣
(4)設定激活函數
2、BP算法推導
(1)構建計算輸出層輸出的函數
(2)構建計算隱含層輸出的函數
(3)定義代價函數
(4)式9中x與d分別爲輸入與對應的期望輸出,皆爲已知,所以代價函數則爲兩個權值矩陣的函數,爲了使代價函數減小,權值的調整應往負梯度方向調整,如下:(η爲學習率,可以理解爲往負梯度方向改變的步長)
(5)將式10和式11按鏈式求導法則進行展開:
(6)對輸出層和隱含層各定義一個誤差信號(本質上爲負梯度):
(7)將式14和式15代入式12和式13,得:
(8)求解輸出層和隱含層的學習信號(需結合式1和式3):
(9)計算式18和19中的偏導:
(10)將式20和式21帶回式18和19:
(11)將式22)和式23帶回式16和17,得:
(12)將三層感知器推廣到任意層數神經網絡:
假設神經網絡共n層。
(三)BP神經網絡python實現
如果讀者想自己動手在python上實現BP神經網絡的話,可以根據文章給出的關於學習信號的求取方法進行編程,本文給出了自己的實現代碼,且將其封裝成一個類,基本上的屬性和方法都具有,通用性較強,讀者既可調用其來訓練自己的模型,也可以參考其架構自行編寫代碼。
1、模型所需傳參介紹
layer 爲神經網絡各層神經元的個數,包括輸出層神經元個數,傳參形式以列表傳入;
activate:爲各層的激活函數,傳參形式爲字符串或列表,
若傳入一個字符串,則各層激活函數相同,
若傳入一個列表,則列表元素代表各層激活函數
可傳參數有:
(1)sigmoid:S型函數
(2)tanh:雙曲正弦函數
(3)relu:max(0,x)函數
(4)purline:線性函數
(5)softsign:平滑函數
lr:學習率,默認爲0.01
epoch:最大迭代次數 默認爲1e4
2、模型具有的主要方法和屬性
fit(X,Y):模型擬合方法
predict(X):輸出預測方法
predict_label(X):分類標籤輸出預測方法
activate:激活函數列表
W:權值列表
3、python代碼
import numpy as np
import time
clases Cyrus_BP(object):
"""
layer 爲神經網絡各層神經元的個數,包括輸出層神經元個數,傳參形式以列表傳入;
activate:爲各層的激活函數,傳參形式爲字符串或列表,
若傳入一個字符串,則各層激活函數相同,
若傳入一個列表,則列表元素代表各層激活函數
可傳參數有:(1)sigmoid:S型函數
(2)tanh:雙曲正弦函數
(3)relu:max(0,x)函數
(4)purline:線性函數
(5)softsign:平滑函數
lr:學習率,默認爲0.01
epoch:最大迭代次數 默認爲1e4
該模型具有的主要方法和屬性如下:
fit(X,Y):模型擬合方法
predict(X):輸出預測方法
predict_label(X):分類標籤輸出預測方法
activate:激活函數列表
W:權值列表
"""
def __init__(self,layer,**kargs):
self.layer = np.array(layer).reshape(1,-1)
if 'activate' in kargs.keys():
if str(type(kargs["activate"])) == "":
self.activate = [kargs["activate"]]*int(len(layer))
else:
self.activate = kargs["activate"]
else:
self.activate = ["sigmoid"]*int(len(layer))
self.diff_activate = []
if 'lr' in kargs.keys():
self.lr = kargs["lr"]
else:
self.lr = 0.01
if 'epoch' in kargs.keys():
self.epoch = kargs["epoch"]
else:
self.epoch = int(1e4)
self.X = None
self.Y = None
self.W = None
self.output = []
self.delta = []
self.sum_input = []
# 1、選擇激活函數
def activation_func(self):
temp_func = []
for i in range(len(self.activate)):
if self.activate[i] == "sigmoid":
temp_func.append(lambda x:1/(1+np.exp(-x)))
self.diff_activate.append(lambda x:(1/(1+np.exp(-x)))*(1-(1/(1+np.exp(-x)))))
if self.activate[i] == "tanh":
temp_func.append(lambda x:(np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x)))
self.diff_activate.append(lambda x:((-np.exp(x) + np.exp(-x))*(np.exp(x) - np.exp(-x))/(np.exp(x) + np.exp(-x))**2 + 1))
if self.activate[i] == "softsign":
temp_func.append(lambda x:x/(1+np.abs(x)))
self.diff_activate.append(lambda x:1/((1+x/np.abs(x)*x)**2))
if self.activate[i] == "relu":
temp_func.append(lambda x:(x+np.abs(x))/(2*np.abs(x))*x)
self.diff_activate.append(lambda x:(x+np.abs(x))/(2*np.abs(x)))
if self.activate[i] == "purline":
temp_func.append(lambda x:x)
self.diff_activate.append(lambda x:1+x-x)
self.activate = temp_func
# 2、權值初始化函數
def init_w(self):
self.W = []
for i in range(self.layer.shape[1]):
if i == 0:
w = np.random.random([self.X.shape[1]+1,self.layer[0,i]])*2-1
else:
w = np.random.random([self.layer[0,i-1]+1,self.layer[0,i]])*2-1
self.W.append(w)
# 3、權值調整函數
def update_w(self):
# 1 計算各層輸出值
self.output = []
self.sum_input = []
for i in range(self.layer.shape[1]):
if i == 0:
temp = np.dot(np.hstack((np.oneself.X.shape[0],1)),self.X)),self.W[i])
self.sum_input.append(temp)
self.output.append(self.activate[i](temp))
else:
temp = np.dot(np.hstack((np.oneself.output[i-1].shape[0],1)),self.output[i-1])),self.W[i])
self.sum_input.append(temp)
self.output.append(self.activate[i](temp))
# 2 求每層的學習信號
self.delta = [0 for i in range(len(self.output))]
for i in range(len(self.output)):
if i == 0:
self.delta [-i-1] = ((self.Y-self.output[-i-1])*self.diff_activate[-i-1](self.sum_input[-i-1]))
else:
self.delta [-i-1] = ((self.delta[-i].dot(self.W[-i][1:,:].T))*self.diff_activate[-i-1](self.sum_input[-i-1]))
# 3 更新權值
for i in range(len(self.W)):
if i == 0 :
self.W[i] += self.lr * np.hstack((np.oneself.X.shape[0],1)),self.X)).T.dot(self.delta[i])
else:鄭州人流醫院 http://m.zyfuke.com/
self.W[i] += self.lr * np.hstack((np.oneself.output[i-1].shape[0],1)),self.output[i-1])).T.dot(self.delta[i])
def fit(self,X,Y):
self.X = np.array(X)
self.Y = np.array(Y)
# 1 權值初始化
self.init_w()
# 2 選擇激活函數
self.activation_func()
# 3 更新權值
start_time = time.time()
for i in range(int(self.epoch)):
self.update_w()
end_time = time.time()
if end_time - start_time >= 5:
print("Epoch%d:"%(i+1),np.mean(np.square(self.Y-self.output[-1])))
print("\n")
start_time = time.time()
def predict(self,x):
x = np.array(x)
result = []
for i in range(self.layer.shape[1]):
if i == 0:
result.append(self.activate[i](np.dot(np.hstack((np.ones((x.shape[0],1)),x)),self.W[i])))
else:
result.append(self.activate[i](np.dot(np.hstack((np.ones((result[i-1].shape[0],1)),result[i-1])),self.W[i])))
return result[-1]
def predict_label(self,x):
x = np.array(x)
result = []
for i in range(self.layer.shape[1]):
if i == 0:
result.append(self.activate[i](np.dot(np.hstack((np.ones((x.shape[0],1)),x)),self.W[i])))
else:
result.append(self.activate[i](np.dot(np.hstack((np.ones((result[i-1].shape[0],1)),result[i-1])),self.W[i])))
result = result[-1]
return np.array([result[i].argmax() for i in range(result.shape[0])]).reshape(-1,1)
if __name__ == "__main__":
bp = Cyrus_BP([50,10,3],lr=0.01,epoch = 2e5,activate = ["softsign","softsign","softsign"])
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
data = load_iris()
X = data["data"]
Y = data["target"]
import pandas as pd
# 用神經網絡進行分類時,需把輸出先進行獨熱編碼
Y1 = pd.get_dummies(Y1) # 進行獨熱編碼或將期望輸出轉換爲啞變量
bp.fit(X,Y1)
Y_pre = bp.predict_label(X)
print("準確率爲:",accuracy_score(Y,Y_pre))
4、代碼運行結果
Epoch9314: 0.02853577904298399
Epoch19691: 0.02145897246261971
Epoch31495: 0.01784770845276102
Epoch42539: 0.01415043927077651
Epoch53434: 0.015407038745481208
Epoch64893: 0.016390764988851683
Epoch76186: 0.015016316993973523
Epoch86931: 0.013693150044879728
Epoch97390: 0.013706384360315056
Epoch108511: 0.012193768543380657
Epoch118993: 0.010314480349340294
Epoch128337: 0.009862103298377766
Epoch138193: 0.01057658278951552
Epoch147889: 0.009652582210903272
Epoch157632: 0.009137051214565095
Epoch165815: 0.009407398018203143
Epoch175037: 0.009429640020604707
Epoch185229: 0.00991562156191445
Epoch194220: 0.009801710064963167
準確率爲: 0.9933333333333333
可見模型訓練結果還是不錯的