這篇文章介紹後向傳遞神經網路演算法(Backprogation artificial neature network),並使用Python語言實作實現一XOR邏輯功能的多層網路模型。
在底下前一篇文章單一神經元感知器的實作上知道,單一感知器無法實作出具XOR邏輯運算的功能,在這篇會改用多層網路模型並使用後向傳遞神經網路演算法(Backprogation artificial neature network)來實現XOR的邏輯功能。
人工神經網路(1)--使用Python實作perceptron(感知器)
多層神經網路:
多層感知器是有一個或是多個隱含層的神經網路,通常網路包含一個來源神經元的輸入層,至少包含一個計算元的隱含層,以及一個計算神經元的輸出層,輸入號信一層一層的向前傳遞,這動作也稱之為前饋式傳遞神經網路,其模型如下圖<一>所示
多層神經網路,各層多有其特定的功能,輸入層為接受外部的輸入信號,輸出層從隱含層接受輸出信號,為整個網路建立輸出形樣類別。
隱含層的神經元發現特徵,其權重表示了其輸入型樣中的特徵,輸出層再根據這些特徵確定輸出型樣。
利用一個隱含層,可以表示輸入信號的任何連續ˋ函數,利用兩個隱含層甚至可以表示不連續的函數,換言之,多個隱含層也可以解決單一感知器只能做單一線性分割的問題。
<圖一>有兩個隱含層的多層感知器神經網路
多層神經網路如何學習?
最常用的是後向傳遞神經網路演算法,在1969年被首次提出(Bryson和Ho),但是由於對計算要求過於嚴苛而被忽略,直到20世紀80年代這種演算法才又被重新重視。
多層網路的學習過程與感知器類似,提供輸入信號經過權重調節加總,計算出實際輸出,跟期望輸出比較算出誤差,再藉由調整權重來減小收斂誤差。
在感知器中,每個輸入Xi僅有一個相對應的權重Wi和一個輸出Y,但在多層神經網路中,每一個權重對每一個輸出都有貢獻,而每一個輸入信號Xi,連接到各隱含層的神經元都有相對應的權重Wij,每個隱含層輸出也會有相對應的Wjk連結到每個輸出Yk,如下<圖二>所示:
其中i為輸入層第i個輸入,j表示隱含層第j個神經元感知器,k為輸出層第k個輸出。
在後向傳遞網路中,學習演算法過程分為兩個階段,第一階段與感知器前饋式演算雷同,信號由輸入端向隱含層一層一層傳遞直到輸出層,如果實際輸出與預期的輸出不同,則計算其誤差,而第二階段則是將此誤差反向從輸出端經過隱含層再傳遞回輸入層,在這過程則同時調整其權重Wjk和Wij。反覆這樣的動作直到誤差收斂到一定的滿足條件值。
<圖二>三層後向傳遞網路
後向傳遞神經網路演算法:
接下來實作一個範例,利用<下圖三>的神經網路結構訓練可以具有邏輯XOR的功能:
<圖三>三層神經網路
首先在STEP 1:
先初始以下參數w13=0.5,w14=0.9,w23=0.4,w24=1.0,w35=-1.2,w45=1.1,θ3=0.8,θ4=-0.1,θ5=0.3,α=0.1。
在這裡隱含層或輸出層的某個神經元的臨界值的作用可以用權重θt來表示,這裡t=3,4,5,其固定與一個-1輸入相連接當初始值。
而設定X13=X14=X1,X23=X24=X2,意即神經元輸入編號1及2直接接受X1,X2的輸入不做任何前置處理即分配給隱含層神經元編號3,4。
故假設輸入樣本矩陣為
# x1 ,序列為[1.,0.,1.,0.,1.,0.,1.,0.,1.,0.,1.,0.,........]
# x2 ,序列為[1.,1.,0.,0.,1.,1.,0.,0.,1.,1.,0.,0.,........]
其對應的X1 XOR X2 的每次疊代預期輸出Yd應為
序列[0.,1.,1.,0., 0.,1.,1.,0.,0.,1.,1.,0.,....]
接著啟動程式反覆執行上述演算法的STEP2~4,直到誤差的平方含小於0.05認定為收斂。
底下<圖四><圖五>為範例程式跑出的結果,在<圖四>左上角即為激勵函數Sigmoid的函數圖,而右上角紅色線為誤差平方和收斂至0.05的過程,X軸為周期,在此範例四次疊代為一週期,Y軸即為每次疊代的誤差平方和。
底下<圖五>可以看出每次疊代的權重及誤差變化,並算出最後收斂的各權重值,最後則是利用訓練得到的權重結果代回隱含層的兩個神經元可以得到如下<圖四>左下角的結果與決策邊界,在兩決策邊界藍線及紅色所圍成的區域即為訓練XOR邏輯所得出的正確結果,可以看出在此範圍內的P1(0,1)及P4(1,0)做XOR運算可以得到1,另外兩點P0(0,0)及P3(1,1)做XOR運算可以得到0。可以看出此三層神經網路模型確實分類出結果,從左下圖的點分布也可以解釋為何單一感知器無法訓練出XOR的功能。
<圖四>三層神經網路訓練邏輯XOR的結果1
在底下<圖五>可以看出總共疊代了28832次才讓誤差平方和收斂至0.05以下,之後會在介紹如何加快訓練速度以提升訓練效率,其中也可以加大學習率α數值,如改成0.2 or 0.3...可以加快收斂速度,但是設定過大學習率α數值也可能使誤差上下大幅搖擺,而不易收斂。另外也可以使用別的激勵函數來加快收斂速度。
在這範例程式最後直接使用訓練完的網路重新預測輸入X1 XOR X2的結果,結果如<圖五>最下方的列表,當然此一模型也可以重新訓練上一篇感知器做過的AND 及Or邏輯功能,只需將對應的輸入X1,X2,預期輸出Yd重新給相對應的值即可以算出結果。
<圖五>三層神經網路訓練邏輯XOR的結果2
<Python 完整範例程式>
import cv2
import math
import numpy as np
from matplotlib import pyplot as plt
from decimal import *
getcontext().prec = 5 #設定湖點數精度4位
getcontext().rounding = ROUND_HALF_UP #4捨5入
#畫出激勵函數函數 Ysigmoid=1/(1+e^(-x)) ,
def sigmoid(x):
r = 1.00000/(1+math.pow(math.e,-x))
return r
#畫出激勵函數函數 Ysigmoid=1/(1+e^(-x)) ,
def EE(k):
r1 = Decimal(math.pow(E[k],2))+Decimal(math.pow(E[k+1],2))+Decimal(math.pow(E[k+2],2))+Decimal(math.pow(E[k+3],2))
#print(r1)
return r1
#畫出函數 x13w13+x23w23-s3=0 , x14w14+x24w24-s4=0 ,
def Fx2(x0,s,w1,w2):
r2 = (s-x0*w1)/w2
#print(r1)
return r2
#print(getcontext())
#最大疊代次數Pmax =預設為最大訓練次數
Pmax=50000
# 產生輸入x1=1,x2=1
# 產生輸入一維矩陣 x1 ,序列為[1.,0.,1.,0.,1.,0.,1.,0.,1.,0.,1.,0.,........]
xi1=[1.,0.,1.,0.]
x1=xi1*(Pmax//4)
#print(x1)
# 產生輸入一維矩陣 x2 ,序列為[1.,1.,0.,0.,1.,1.,0.,0.,1.,1.,0.,0.,........]
xi2=[1.,1.,0.,0.]
x2=xi2*(Pmax//4)
#print(x2)
#Y5d ,為x1 or x2 的預期輸出 ,故每次疊代後的預期輸出應為序列[0.,1.,1.,0., 0.,1.,1.,0.,0.,1.,1.,0.,....]
Yt=[0.,1.,1.,0. ]
Y5d=Yt*(Pmax//4)
x13=x1
x14=x1
x23=x2
x24=x2
#初始權重wij,及臨界值sx ,學習率a=0.1
w13=np.zeros(Pmax) #初始為0.0
w14=np.zeros(Pmax) #初始為0.0
w23=np.zeros(Pmax) #初始為0.0
w24=np.zeros(Pmax) #初始為0.0
w35=np.zeros(Pmax) #初始為0.0
w45=np.zeros(Pmax) #初始為0.0
s3=np.zeros(Pmax) #初始為0.0
s4=np.zeros(Pmax) #初始為0.0
s5=np.zeros(Pmax) #初始為0.0
w13[0]=0.5
w14[0]=0.9
w23[0]=0.4
w24[0]=1.0
w35[0]=-1.2
w45[0]=1.1
s3[0]=0.8
s4[0]=-0.1
s5[0]=0.3
a=0.2
#宣告初始権重差值矩陣為0,只是for程式設計使用預設值
#Dx 用來修正每次疊代後須修正的權值
DW13 =np.zeros(Pmax) #初始為0.0 0
DW14=np.zeros(Pmax) #初始為0.0
DW23 =np.zeros(Pmax) #初始為0.0 0
DW24=np.zeros(Pmax) #初始為0.0
DW35 =np.zeros(Pmax) #初始為0.0 0
DW45=np.zeros(Pmax) #初始為0.0 0
DS3=np.zeros(Pmax) #初始為0.0
DS4=np.zeros(Pmax) #初始為0.0
DS5=np.zeros(Pmax) #初始為0.0
#宣告初始誤差E為0,只是for程式設計使用預設值
#E 為每次疊代後,期望值與實際輸出值的誤差
E =np.zeros(Pmax) #初始為0.0
#為每次疊代後,誤差梯度
e3 =np.zeros(Pmax) #初始為0.0
e4 =np.zeros(Pmax) #初始為0.0
e5 =np.zeros(Pmax) #初始為0.0
#宣告初始實際輸出值Y矩陣為0,只是for程式設計使用預設值
#第p次疊代實際輸出Y3,Y4,Y5
Y3 =np.zeros(Pmax) #初始為0.0
Y4 =np.zeros(Pmax) #初始為0.0
Y5 =np.zeros(Pmax) #初始為0.0
#Epoch ,疊代次數p
print("疊代次數|輸入x1|輸入x2|期望輸出Yd|實際輸出Y| 權重w35 | 權重w45 | 誤差E | ")
for p in range(Pmax-1): #from 0,1... to Pmax
#print("疊代次數:",p)
#w13=w13[p]
#w23=w23[p]
#s3=s3[p]
#w14=w14[p]
#w24=w24[p]
#s4=s4[p]
Y3[p]=Decimal(sigmoid(x1[p]*w13[p]+x2[p]*w23[p]-s3[p]))
#print(Y3[p])
Y4[p]=Decimal(sigmoid(x1[p]*w14[p]+x2[p]*w24[p]-s4[p]))
#print(Y4[p])
Y5[p]=Decimal(sigmoid(Y3[p]*w35[p]+Y4[p]*w45[p]-s5[p]))
#print(Y5[p])
E[p]=Decimal(Y5d[p]-Y5[p])
#print(E[p])
e5[p]=Decimal(Y5[p])*Decimal(1-Y5[p])*Decimal(E[p])
#print(e5[p])
DW35[p]=Decimal(a)*Decimal(Y3[p])*Decimal(e5[p])
#print(DW35[p])
DW45[p]=Decimal(a)*Decimal(Y4[p])*Decimal(e5[p])
#print(DW45[p])
DS5[p]=Decimal(a)*Decimal(-1)*Decimal(e5[p])
#print("DS5[p]=",DS5[p])
e3[p]=Decimal(Y3[p])*Decimal(1-Y3[p])*Decimal(e5[p])*Decimal(w35[p])
#print(e3[p])
DW13[p]=Decimal(a)*Decimal(x1[p])*Decimal(e3[p])
#print(DW13[p])
DW23[p]=Decimal(a)*Decimal(x2[p])*Decimal(e3[p])
#print(DW23[p])
DS3[p]=Decimal(a)*Decimal('-1.00000')*Decimal(e3[p])
#print("DS3[p]=",DS3[p])
e4[p]=Decimal(Y4[p])*Decimal(1-Y4[p])*Decimal(e5[p])*Decimal(w45[p])
#print(e4[p])
DW14[p]=Decimal(a)*Decimal(x1[p])*Decimal(e4[p])
#print(DW14[p])
DW24[p]=Decimal(a)*Decimal(x2[p])*Decimal(e4[p])
#print(DW24[p])
DS4[p]=Decimal(a)*Decimal('-1.00000')*Decimal(e4[p])
#print("DS4[p]=",DS4[p])
w13[p+1]=Decimal(w13[p]+DW13[p])
#print(w13[p])
w14[p+1]=Decimal(w14[p]+DW14[p])
#print(w14[p])
w23[p+1]=Decimal(w23[p]+DW23[p])
#print(w23[p])
w24[p+1]=Decimal(w24[p]+DW24[p])
#print(w24[p])
w35[p+1]=Decimal(w35[p]+DW35[p])
#print(w35[p])
w45[p+1]=Decimal(w45[p]+DW45[p])
#print(w45[p])
s3[p+1]=Decimal(s3[p]+DS3[p])
#print(s3[p+1])
s4[p+1]=Decimal(s4[p]+DS4[p])
#print(s4[p+1])
s5[p+1]=Decimal(s5[p]+DS5[p])
#print(s5[p+1])
#print("疊代次數|輸入x1|輸入x2|期望輸出Yd|實際輸出Y| 權重w35 | 權重w45 | 誤差E | ")
print(' {0:1d} {1:1d} {2:1d} {3:1d} {4:1.5f} {5:1.5f} {6:1.5f} {7:1.5f} '\
.format(p, int(x1[p]),int(x2[p]),int(Y5d[p]),Y5[p],w35[p+1],w45[p+1],E[p]))
if p>0 & int(p%4)==0: #每4次唯一週期,計算依次誤差平方和
ee=EE(int(p-4))
if ee<0 -0.5="" .005:="" 0="" 10.0="" 50="" b-="" break="" d="" ee="" eer="" else:="" end-4="" end="" er="" f="" fontsize="10)" for="" format="" from="" if="" in="" int="" j="" k="" linewidth="2.0)" pend="Pmax" plt.axis="" plt.grid="" plt.plot="" plt.subplot="" plt.title="" plt.xlabel="" plt.ylabel="" pmax="" print="" r13="" r14="" r1="" r23-sr3="" r24-sr4="" r2="" r35="" r3="" r45-sr5="" r4="" r5="" r="" range="" rediction="=========================" rt="" rue="" s3=",s3[p+1])
print(" s4=",s4[p+1])
print(" s5=",s5[p+1])
print(" se.shape="" se="np.array([EE(k)" sg="" sigmoid="1/(1+e^(-x))" sp="np.arange(int(Pend/4))" sr3="s3[p+1]" sr4="s4[p+1]" sr5="s5[p+1]" sx="" t="" to="" w13=",w13[p+1])
print(" w14=",w14[p+1])
print(" w23=",w23[p+1])
print(" w24=",w24[p+1])
print(" w35=",w35[p+1])
print(" w45=",w45[p+1])
print(" wr13="w13[p+1]" wr14="w14[p+1]" wr23="w13[p+1]" wr24="w14[p+1]" wr35="w35[p+1]" wr45="w45[p+1]" x="" xr1="[1.,0.,1.,0.]" xr2="[1.,1.,0.,0.]" yr3="" yr4="" yr5="" yrt="[0.,1.,1.,0.]" ysigmoid="1/(1+e^(-x))">sp.shape:
sp=sp[0:se.shape[0]]
#print("sp.shape=",sp.shape)
#print("se.shape=",se.shape)
plt.subplot(2,2,2)
plt.plot(sp, se, 'r-',linewidth=2.0) #紅色線寬可以改成2.0或其他數值
plt.axis([0,Pend/4, 0.0,1.1]) #分別設定X,Y軸的最小最大值
plt.title('Sum(E)^2 ',fontsize=10)
plt.ylabel('Sum(E)^2')
plt.xlabel('Period')
plt.grid(True)
Px=np.linspace(-10.0, 10.0, 100)
p23=np.array([Fx2(k,s3[p+1],w13[p+1],w23[p+1]) for k in Px])
p24=np.array([Fx2(k,s4[p+1],w14[p+1],w24[p+1]) for k in Px])
plt.subplot(2,2,3)
plt.plot(Px, p23, 'b-',linewidth=2.0) #紅色線寬可以改成2.0或其他數值
plt.plot(Px, p24, 'r-',linewidth=2.0) #紅色線寬可以改成2.0或其他數值
plt.axis([-0.5,2.0,-0.5,2.0]) #分別設定X,Y軸的最小最大值
plt.title(' X1 xor X2 Result: ',fontsize=10)
plt.ylabel('X2')
plt.xlabel('X1')
plt.grid(True)
plt.plot(0,0,'ro')
plt.text(0, 0, r'P0(0,0)')
plt.plot(0,1,'ro')
plt.text(0, 1, r'P1(0,1)')
plt.plot(1,1,'ro')
plt.text(1, 1, r'P3(1,1)')
plt.plot(1,0,'ro')
plt.text(1, 0, r'P4(1,0)')
plt.fill_between(Px, p23, p24, color='green', alpha='0.5')
plt.show()
0>
加入阿布拉機的3D列印與機器人的FB專頁
https://www.facebook.com/arbu00/
<參考書籍及引用資料>
人工智慧:智慧型系統導論 ,作者:NEGNEVITSKY;謝正勳,廖珗洲,李聯旺編譯,全華圖書