2016年11月5日 星期六

人工神經網路(1)--使用Python實作perceptron(感知器)



        這篇文章介紹人工神經網路最基本的單元,perceptron(感知器)。使用最簡化的數學公式
說明,並利用Python語言實作單一感知器and 及or 的邏輯功能.

        1943年,Warren McCulloch 和Walter Pitts 提出了一簡單計算元素的神經元,這個思想現在仍然是大多數人工神經網路的基礎.
        如下<圖一>神經元接收了來自輸入連結的一些輸入信號(X1......Xn),這些輸入信號可以是原始資料或是其他神經元的輸出信號。輸出信號則可以是最終答案,也可以是其他神經元的輸入信號。
        神經元計算帶權重輸入信號和並將結果和臨界值θ比較,如果網路淨輸入比臨界值低,則神經元輸出0,反之,如果網路淨輸入比臨界值低,則神經元輸出1。

<圖一>典型的神經元




        換言之,神經元使用下面的轉移或激勵函數.
其中X是神經元的淨權重輸入,Xi 是"輸入i"的值,Wi是"輸入i"的權重,
n是神經元輸入數量,Y是神經元的輸出。






這種激勵函數稱作符號函數,因此實際輸出可以表示為


激勵函數也可以使用底下的步階函數,那麼Y輸出就為1或0,底下我們就利用步階函數來實作一感知器,可以訓練出具有AND 及OR的邏輯功能.


<圖二>步階函數


感知器(Perceptron):

        1958年Frank Rosenblatt提出一種訓練演算法,提供第一個ANN的訓練步驟,感知器(Perceptron),他是神經網路最簡單的形式,如下<圖三>,硬限幅器在這裡即為步階函數
也就是限制為輸出非1即0.

<圖三>單層雙輸入感知器

感知器的作用是將輸入X1.......Xn,作為分為兩類,如同下<圖四>A1區域及A2區域,臨界值θ即用來改變決策邊界的偏移值。在下<圖四>中,P1點與P2點分別落在藍色線決策邊界<x1w1+x2w2-θ=0>的A1區域及A2區域,因此藉由感知器訓練找出權重W1及W2即可以找出決策邊界,並對輸入完成分類.

<圖四>兩輸入感測器的決策邊界



而感知器即透過訓練調節權重值Wi來減少感知器"期望輸出""實際輸出"之間的"誤差",來達到這任務,也就是說當得到適當的權重值Wi時,我們即可得到決策邊界。而對於感知器,權重調整的過程非常簡單,假設反覆疊代訓練p次,期望輸出為Yd(p),實際輸出為Y(p),那麼誤差e(p)為:

                  e(p)=Yd(p)-Y(p)       ,其中p =1,2,3......
如果e(p)為正,就需要增加感知器輸出Y(p);如果e(p)為負,就需要減少感知器輸出Y(p)。
考慮X(p)為每個感知器的總輸入=xi(p)×wi(p)。於是:
如果輸入值xi(p)為正,那麼增加其權重wi(p)可以增加感知器輸出Y(p),
如果輸入值xi(p)為負,那麼減少其權重wi(p)可以減少感知器輸出Y(p)。
於是可以建立感知器的學習規則如下:
                  wi(p+1)=wi(p)+α×xi(p)×e(p)
其中α定義為學習率,為一小於1的正常數。
其中定義Δwi(p)=α×xi(p)×e(p),意義為疊代p上的權重校正。
於是修正後新的權重  wi(p+1) 也可寫成:
                 wi(p+1)=wi(p)+Δwi(p)
i 為第i個輸入數,在<圖三>單層雙輸入感知器範例,xi即為X1,X2,wi即為W1,W2。
而當e(p) 收斂到小於一定數值後,可以認定訓練結束,在這個範例e(p) 會收斂至0。

訓練演算法步驟:

      底下我們就利用<圖三>單層雙輸入感知器及上述訓練法則來實作一感知器,可以順練出具有AND 及OR的邏輯功能。考慮以下基本邏輯真值表,可知輸入X1及X2其實只有4種輸入的樣本變化組合,而且輸入樣本非1即0,這樣的範例可以簡化我們計算及訓練的步驟,而表中AND的運算結果即為我們預期的輸出Yd(p),如果改成訓練OR的邏輯功能,那麼表中OR的運算結果即為我們訓練OR的邏輯功能預期的輸出Yd(p)。


於是我們可以建立以下訓練步驟:

STEP 1:初始化
       設定權重w1,w2和臨界值θ,在這程式範例中預設初始
             w1=0.3,w2=-0.1,θ=0.2,α=0.1。
       輸入X1為一[0,0,1,1,0,0,1,1......],4個一組反覆的陣列,陣列裡的數值可視為輸入X1的樣本
       輸入X2為一[0,1,0,1,0,1,0,1......],4個一組反覆的陣列,陣列裡的數值可視為輸入X2的樣本
       同理預期的AND運算結果輸出Yd(p):
             Yd(p)為一[0,0,0,1,0,0,0,1......],4個一組反覆的陣列
       如果作為OR的運算,預期輸出Yd(p):
             Yd(p)應為一[0,1,1,1,0,1,1,1......],4個一組反覆的陣列
       而樣本數必須大於或等於最大疊代次數Pmax。

STEP 2:激勵
       套用上述公式與激勵函數,在此範例使用步階函數即可
       可以計算實際輸出為:

       其中n為感知器輸入的數量,在此為例n=2,Step為步階函數

STEP 3:修改權重
        修改感知器的權重,其中Δwi(p)為疊代p上的權重校正。

STEP 4:疊代
       疊代p加1,回到步驟2,重複以上過程直至收斂。在這範例誤差e(p) 會收斂至0。



訓練結果:

       下列<圖五><圖六>分別為感知器訓練邏輯AND及OR的功能,左圖為每次疊代的訓練結果,包括w1,w2權重修改的變化及實際輸出與預期輸出的誤差e(p)。
而右圖即為訓練結束後所找到的w1,w2權重形成的決策邊界,在AND邏輯訓練的結果可以看出只有P3(1,1)在決策邊界(含)之上,其餘P0(0,0)P1(0,1)P2(1,0)皆在邊界下,而實際的AND運算也為0。而在OR邏輯訓練的結果,只有P(0,0)是在邊界之下的區域實際OR運算為0,其餘三點OR運算皆為1。



<圖五>感知器訓練邏輯AND功能


<圖六>感知器訓練邏輯OR功能



        不過單一層的感知器無法訓練出XOR的邏輯功能,也因此雖然此一神經網路結構很早就被提出,卻因此還有其他因素而停擺,其他因素包括只能做線性切割以及須大量疊代訓練效率不佳。直到多層人工神經網路模型被提出,再次點燃了人工神經網路的希望,下一篇
會來介紹使用多層神經網路及反向傳遞演算法(BP)可以解決不能實作XOR邏輯功能的問題。


<Python 完整範例程式>

import cv2
import numpy as np
from matplotlib import pyplot as plt
from decimal import *

#getcontext().prec = 4 #設定湖點數精度4位
#getcontext().rounding = ROUND_HALF_UP #4捨5入
#print(getcontext())
def Fx2(x0,s,w1,w2):
 r2 = (s-x0*w1)/w2
 #print(r1)
 return r2
#最大疊代次數Pmax =預設1000次 意為最大訓練次數
Pmax=22  
 # 產生輸入一維矩陣 x1 ,序列為[0.,0.,1.,1.,0.,0.,1.,1.,0.,0.,1.,1.,........]
xi1=[0.,0.,1.,1.]
x1=xi1*(Pmax//4)
#print(x1)
 # 產生輸入一維矩陣 x2 ,序列為[0.,1.,0.,1.,0.,1.,0.,1.,0.,1.,0.,1.,........]
xi2=[0.,1.,0.,1.]
x2=xi2*(Pmax//4)      
#print(x2)               
#Yd ,為x1 and x2 的預期輸出 ,故每次疊代後的預期輸出應為序列[0.,0.,0.,1., 0.,0.,0.,1.,0.,0.,0.,1.,....]
Yt=[0.,0.,0.,1. ]
#Yd ,為x1 or x2 的預期輸出 ,故每次疊代後的預期輸出應為序列[0.,0.,0.,1., 0.,0.,0.,1.,0.,0.,0.,1.,....]
#Yt=[0.,1.,1.,1. ]
Yd=Yt*(Pmax//4)   
#print(Yd)
#初始權重w1,w2,及臨界值s=0.2 ,學習率a=0.1
w1=np.zeros(Pmax) #初始為0.0
w1[0]=0.3    #第一次疊代初始值為0.3 ,預設buffer為可疊代Pmax次
#print(w1[0:4]) 
w2=np.zeros(Pmax) #初始為0.0
w2[0]=-0.1    #第一次疊代初始值為-0.1 ,預設buffer為可疊代Pmax次
#print(w2[0:4]) 
s=0.2
a=0.1
#宣告初始権重差值矩陣為0,只是for程式設計使用預設值
#Dw 用來修正每次疊代後須修正的權值
Dw1 =np.zeros(Pmax) #初始為0.0
Dw2 =np.zeros(Pmax) #初始為0.0
#宣告初始誤差E為0,只是for程式設計使用預設值
#E 為每次疊代後,期望值與實際輸出值的誤差
E =np.zeros(Pmax) #初始為0.0
#宣告初始實際輸出值Y矩陣為0,只是for程式設計使用預設值
#第p次疊代實際輸出Y
Y =np.zeros(Pmax) #初始為0.0
#Epoch ,疊代次數p
print("疊代次數|輸入x1|輸入x2|初始權重w1|初始權重w2|期望輸出Yd|實際輸出Y|  誤差E  |修正後權重w1|修正後權重w2|")
for p in range(Pmax-2):  #from  0,1...4 to Pmax
 #print("疊代次數:",p)
 #由於浮點數計算會有誤差,所以使用Decimal的quantize只取兩位並無條件捨去
 Y[p]=Decimal(x1[p]*w1[p]+x2[p]*w2[p]-s).quantize(Decimal('.01'), rounding=ROUND_DOWN)
 #print("實際輸出before F(step):",Y[p])
 #代入步階函數
 if  Y[p]>=0.0:
  Y[p]=1.0
 else:
  Y[p]=0.0
 #print("實際輸出2:",Y[p])
 #計算誤差並修改權重
 E[p]=Decimal(Yd[p]-Y[p]).quantize(Decimal('.01'), rounding=ROUND_HALF_UP)
 #print("誤差:",E[p])
 Dw1[p]=a*x1[p]*E[p]
 w1[p+1]=Decimal(w1[p]+Dw1[p]).quantize(Decimal('.01'), rounding=ROUND_HALF_UP)
 Dw2[p]=a*x2[p]*E[p]
 w2[p+1]=Decimal(w2[p]+Dw2[p]).quantize(Decimal('.01'), rounding=ROUND_HALF_UP)
 #if  (E[p,0]==0)&(E[p,1]==0)&(E[p,2]==0)&(E[p,3]==0)   :
  #break
 #print("修正後權重1:",w1[p+1])
 #print("修正後權重2:",w2[p+1])
 #print("疊代次數|輸入x1|輸入x2|初始權重w1|初始權重w2|期望輸出Yd|實際輸出Y|  誤差E  |修正後權重w1|修正後權重w2|")
 print('    {0:1d}      {1:1d}      {2:1d}       {3:.2f}        {4:.2f}       {5:1d}         {6:1d}        {7:.2f}        {8:.2f}         {9:.2f}              '.format(p, int(x1[p]),int(x2[p]),w1[p],w2[p],int(Yd[p]),int(Y[p]),E[p],w1[p+1],w2[p+1]))

#得到最後訓練好的w1,w2 
print("得到最後訓練好的權重w1,w2=",w1[p+1],w2[p+1])
print('代入原輸出方程式Y= {0:.2f}X1+ {1:.2f}X2- {2:.2f} '.format(w1[p+1],w2[p+1],s))
print("再代入Step 激勵函數Step(Y):If Y>=0 ,則Y=1 ,IF Y<0 -0="" 0="" 10.0="" 100="" 1="" and="" area="0" axis="" b-="" facecolor="green" fontsize="16" for="" function="" in="" k="" linewidth="3.0)" max="24" min="0" n="" p1="" p2="" p="" plt.axis="" plt.fill_between="" plt.grid="" plt.plot="" plt.show="" plt.subplot="" plt.text="" plt.title="" plt.xlabel="" plt.ylabel="" pre="" print="" r="" ro="" rue="" s="" sx1="" sx2-="" w1="" w1x1="" w2="" w2x2-0.2="0" w2x2-s="0為分界函數,在界線上方分類為A1區域=1,下方A2區域為0" x1="" x2="">


加入阿布拉機的3D列印與機器人的FB專頁
https://www.facebook.com/arbu00/


<參考書籍及引用資料>
人工智慧:智慧型系統導論 ,作者:NEGNEVITSKY;謝正勳,廖珗洲,李聯旺編譯,全華圖書