http://www.cnblogs.com/bambipai/p/7922981.html------誤差逆傳播算法講解
人工神經網絡包含多種不同的神經網絡,此處的代碼建立的是多層感知器網絡,代碼以《集體智慧編程》第四章 “nn.py" 為原型和框架,可以指定隱藏網絡的層數和每層的節點數,利用反向傳播法修正權值,並連接數據庫,保存每層每個節點的權值等信息。代碼在算法方面並沒有做出改進,結構上可能不是特別嚴謹和簡潔,在算法、結構方面並不一定可取,只是為建立多層隱藏網絡提供一個思路,可以對神經網絡有更好的理解。
新建一個文件(hiddens.py),並在其中新建一個類,取名為searchnet:
1 from math import tanh 2 import sqlite3 as sqlite 3 import random 4 class searchnet: 5 def __init__(self,dbname,n,num): 6 self.con=sqlite.connect(dbname) 7 self.h=n#隱藏層的數量
8 self.hiddennodes=num#每個隱藏層的節點數
11 def __del__(self): 12 self.con.close()
14 def maketables(self): 15 for i in range(self.h-1): 16 self.con.execute('create table hiddennode_%d(create_key,fromid,toid,strength)' % (i)) 17 self.con.execute('create table wordhidden(fromid,toid,strength)') 18 self.con.execute('create table hiddenurl(fromid,toid,strength)') 19 self.con.commit()
其中,n和num分別是隱藏層的數量以及對應層數的節點數,然后我們建立了n-1張表存放隱藏層節點之間的權值,creat_key起到標示節點的作用,用以區別不同輸入形成的隱藏層節點,input和out分別是輸入內容和分類類別。
接下來,我們來建立隱藏層以及節點之間的連接。
1 def generatehiddennode(self,wordids,urls): 2 #用以標示不同輸入產生的不同網絡
3 sorted_words=[id for id in wordids] 4 sorted_words.sort() 5 self.createkey='_'.join(sorted_words) 6
7 #生成所有隱藏層節點並建立連接,creatkey標示了輸入的數據,每層每個節點的fromid和toid均不相同,代表了其層次和第幾個
8 for i in range(self.h-1): 9 for j in range(self.hiddennodes[i]): 10 for k in range(self.hiddennodes[i+1]): 11 table='hiddennode_%d' % i 12 fromid=str(i)+'_'+str(j) 13 toid=str(i+1)+'_'+str(k) 14 strn=random.random() 15 self.con.execute("insert into %s (create_key,fromid,toid,strength) values ('%s','%s','%s',%.2f)" % (table,self.createkey,fromid,toid,strn)) 16
17 #建立輸入和隱藏層的連接
18 table='wordhidden'
19 strength=0.1
20 for j in range(self.hiddennodes[0]): 21 hiddenid='0_'+str(j) 22 for wordid in wordids: 23 self.con.execute("insert into %s (fromid,toid,strength) values ('%s','%s',%f)" % (table,wordid,hiddenid,strength)) 24
25 #建立輸出和隱藏層的連接
26 table='hiddenurl'
27 strength=0.2
28 for j in range(self.hiddennodes[self.h-1]): 29 hiddenid=str(self.h-1)+'_'+str(j) 30 for urlid in urls: 31 self.con.execute("insert into %s (fromid,toid,strength) values ('%s','%s',%f)" % (table,hiddenid,urlid,strength)) 32 self.con.commit()
首先連接輸入的內容作為標示不同輸入產生的不同隱藏層節點的標志,然后循環建立隱藏層節點之間的連接(因為是隱藏層之間的連接,所以只需要n-1個表),除了create_key還有一個id來區分節點,即代碼中的fromid,x_y代表的是第x層隱藏層,第y個節點(x>=0,y>=0),隱藏層K層的toid就是K+1層的fromid,節點連接之間的權重在0-1之間隨機產生。然后建立第0層隱藏層和輸入層之間的連接,權值默認為0.1,第n-1層(最后一層)隱藏層和輸出層之間的連接,權值默認為0.2,並將這些信息存入表。
我們可以運行看一下效果。


另外我想說明的一點是,我們所建立的節點以及下面要建立的網絡都是抽象的,而數據庫中的表是具象的,但這並不是說表就是網絡的具象化,它僅是存儲了網絡中節點之間的連接,對於一個表中的fromid和toid來說,僅僅是一個名稱,並不代表真正抽象的節點,所以我們在建立隱藏層K層與K+1層節點之間的連接時,即便還並沒有生成及存儲K+1層的formid,我們仍然可以完成K層數據的生成與存儲,只要我們知道我們即將要生成的K+1層節點的名稱(fromid)即可。
產生了隱藏層所有節點之后,可以開始建立網絡了,利用數據庫中保存的信息,建立起包括所有當前權重值在內的相應網絡。setupnetwork函數為searchnet類定義了多個實例變量,包括:輸入內容列表、隱藏層節點及分類分別,每個節點的數值輸出,節點之間的權重值(從數據庫中獲得)。
1 def getallhiddenids(self,wordids,urlids): 2 ll={} 3 ll.setdefault(0,{}) 4 cur=self.con.execute("select toid from wordhidden where fromid='%s'" % wordids[0]) 5 for row in cur: ll[0].setdefault(row[0],1) 6 res=row[0] 7 for i in range(self.h-1): 8 ll.setdefault(i+1,{}) 9 cur=self.con.execute("select toid from hiddennode_%d where create_key='%s' and fromid='%s' " % (i,res,self.createkey)) 10 for row in cur: ll[i+1].setdefault(row[0],1) 11 res=row[0] 12 hn={} 13 for i in range(self.h): 14 node=sorted(ll[i].keys()) 15 hn.setdefault(i,node) 16 return hn
1 def getstrength(self,fromid,toid,layer): 2 if layer==-1: table='wordhidden'#-1層是輸入層
3 elif 0<=layer<self.h-1: table='hiddennode_%d' % layer 4 else: table='hiddenurl'
5 res=self.con.execute("select strength from %s fromid='%s' and toid='%s'" % (table,fromid,toid)).fetchone() 6 if res==None: 7 if layer==-1: return -0.2
8 if 0<=layer<self.h: return 0 9 return res[0]
在獲得隱藏層權值和節點時,要注意create_key這一項,如果沒有create_key這一項,儲存在同一張表中不同輸入所獲得的隱藏層節點id是相同的,那么在獲得權值時就會產生錯誤,這是create_key這一項存在的重要意義。
接下來建立網絡,並計算輸出。對於網絡中的每一層的節點,有來自上一層的輸入值,Σw*x,即權值和來自上一層輸入乘積的和,即為程序中的sum;輸入通過“激活函數”后即為該層節點的輸出,即為程序中self.ah,程序中使用反雙曲正切函數tanh(x)作為激活函數。(程序沒有對輸入值進行保存,只保存了輸出值)。
#初始化當前實例的ai,ah,ao #獲取數據中輸入權重wi,輸出權重wo def setupnetwork(self,wordids,urlids): # value lists self.wordids=wordids self.hns=self.getallhiddenids(wordids,urlids)#hns是個嵌套列表的字典 self.urlids=urlids self.ah={}#ah是隱藏層的節點值,key是哪一層,value是節點值的列表 self.w={}#w是權重值,key是該權重指向的哪一層,value是嵌套的列表,v[i][j]代表 i-j的weight # node outputs self.ai = [1.0]*len(self.wordids) for i in range(self.h): self.ah.setdefault(i,[1.0]*len(self.hns[i])) self.ao = [1.0]*len(self.urlids) # create weights matrix self.w.setdefault(-1,[[self.getstrength(wordid,hiddenid,-1)for hiddenid in self.hns[0]] for wordid in self.wordids]) for i in range(self.h-1): self.w.setdefault(i,[[self.getstrength(fromid,toid,i)for toid in self.hns[i+1]] for fromid in self.hns[i]]) self.w.setdefault('o',[[self.getstrength(hiddenid,urlid,1) for urlid in self.urlids] for hiddenid in self.hns[self.h-1]])
def feedforward(self): # the only inputs are the query words for i in range(len(self.wordids)): self.ai[i] = 1.0 # 首先利用輸入值得到第一層隱藏層的節點值 for j in range(len(self.hns[0])): sum = 0.0 for i in range(len(self.wordids)): sum = sum + self.ai[i] * self.w[-1][i][j] self.ah[0][j] = tanh(sum) #然后循環得到其他隱藏層的節點值 for i in range(1,self.h): for j in range(len(self.hns[i])): sum=0.0 for k in range(len(self.hns[i-1])): sum = sum + self.ah[i-1][k] * self.w[i-1][k][j]#i層的輸入 self.ah[i][j] = tanh(sum)#i層的輸出 # 最后得到輸出層的 for k in range(len(self.urlids)): sum = 0.0 for j in range(len(self.hns[self.h-1])): sum = sum + self.ah[self.h-1][j] * self.w['o'][j][k] self.ao[k] = tanh(sum) return self.ao[:]

因為初始化輸入值是相同的,因此兩個節點的輸出值也均為相同。
下面利用反向傳播法調整權值,反向傳播,即讓誤差沿着網絡反向傳播,以ωij為例,以j層的輸出對於輸入的導數作為一個調節因子,然后沿着網絡反向傳播,在傳播的過程中權值會對調節后的誤差加權,並與i層節點的輸出和學習率相乘,就是ωij的調節量。反向傳播法是經典的權值修正算法,但此處對於算法不做具體說明,需要了解的童鞋可以參考http://www.cnblogs.com/bambipai/p/7922981.html。下面程序中,N是學習率,tanh(y)=1-y*y近似反正切函數的導數,作為誤差的調節因子,y為0時,tanh最大,y為1時,tanh最小,因為,我們在訓練時,會指定一個輸出節點的輸出目標為1(或接近1),這樣,如果輸出節點的輸出接近於1,tanh(output)很小,權值的修正量就小,反之,權值的修正量就大,所以它可以作為調節因子。
def backPropagate(self, targets, N=0.5): # calculate errors for output output_deltas = {}#每一層每個結點的“調節后的誤差”=error*dtanh(out),out是正向傳播時相對於這一層的輸出 change={}#權值的改變值 output_deltas.setdefault('o',[]) for k in range(len(self.urlids)): error = targets[k]-self.ao[k]#期望輸出與實際輸出的差值 output_deltas['o'].append(dtanh(self.ao[k]) * error)#誤差的調節因子:tanh(out) hid=self.h-1 output_deltas.setdefault(hid,[]) for j in range(len(self.hns[hid])): error = 0.0 for k in range(len(self.urlids)): error = error + output_deltas['o'][k]*self.w['o'][j][k]#沿網絡反向傳播的誤差,與權值相乘 output_deltas[hid].append(dtanh(self.ah[hid][j]) * error)#該層的“調節后的誤差” # calculate errors for hidden layer for i in range(hid-1,-1,-1): output_deltas.setdefault(i,[]) for j in range(len(self.hns[i])): error = 0.0 for k in range(len(self.hns[i+1])): error = error + output_deltas[i+1][k]*self.w[i][j][k] output_deltas[i].append(dtanh(self.ah[i][j]) * error) # update output weights for j in range(len(self.hns[hid])): for k in range(len(self.urlids)): change = output_deltas['o'][k]*self.ah[hid][j]#調節量=“誤差”x該層的輸入(正向傳播的輸入) self.w['o'][j][k] = self.w['o'][j][k] + N*change # update input weights for i in range(hid-1,-1,-1): for k in range(len(self.hns[i])): for j in range(len(self.hns[i+1])): change = output_deltas[i+1][j]*self.ah[i][k] self.w[i][k][j] = self.w[i][k][j] + N*change for j in range(len(self.wordids)): for k in range(len(self.hns[0])): change = output_deltas[0][k]*self.ai[j] self.w[-1][j][k] = self.w[-1][j][k] + N*change

我們看到,在經過5次的權值修正后,輸出結果相對於最初的輸出結果相對更接近於目標值。
我們可以把修正后的權值更新到數據庫,在這里同樣注意“creat_key".
1 def setstrength(self,fromid,toid,layer,strength): 2 if layer==-1: 3 table='wordhidden'
4 res=self.con.execute("select rowid from %s where fromid='%s' and toid='%s'" % (table,fromid,toid)).fetchone() 5 elif 0<=layer<self.h-1: 6 table='hiddennode_%d' % layer 7 res=self.con.execute("select rowid from %s where create_key='%s' and fromid='%s' and toid='%s'" % (table,self.createkey,fromid,toid)).fetchone() 8 else: 9 table='hiddenurl'
10 res=self.con.execute("select rowid from %s where fromid='%s' and toid='%s'" % (table,fromid,toid)).fetchone() 11 rowid=res[0] 12 self.con.execute('update %s set strength=%f where rowid=%d' % (table,strength,rowid))
1 def updatedatabase(self): 2 # set them to database values
3 for i in range(len(self.wordids)): 4 for j in range(len(self.hns[0])): 5 self.setstrength(self.wordids[i],self.hns[0][j],-1,self.w[-1][i][j]) 6 for k in range(self.h-1): 7 for i in range(len(self.hns[k])): 8 for j in range(len(self.hns[k+1])): 9 self.setstrength(self.hns[k][i],self.hns[k+1][j],k,self.w[k][i][j]) 10 for i in range(len(self.hns[self.h-1])): 11 for j in range(len(self.urlids)): 12 self.setstrength(self.hns[self.h-1][i],self.urlids[j],self.h,self.w['o'][i][j]) 13 self.con.commit()
到此,整個網絡的建立、訓練代碼就完成了,但是這個網絡只是對輸入過的內容分類效果較好,並不能進行預測。
以上代碼適用於Python3.4,python2.x需要稍作改變
1 1 def feedforward(self): 2 2 # the only inputs are the query words
3 3 for i in range(len(self.wordids)): 4 4 self.ai[i] = 1.0
5 6 # 首先利用輸入值得到第一層隱藏層的節點值
6 7 for j in range(len(self.hns[0])): 7 8 sum = 0.0
8 9 for i in range(len(self.wordids)): 9 10 sum = sum + self.ai[i] * self.w[-1][i][j] 10 11 self.ah[0][j] = tanh(sum) 11 13 #然后循環得到其他隱藏層的節點值
12 14 for i in range(1,self.h): 13 15 for j in range(len(self.hns[i])): 14 16 sum=0.0
15 17 for k in range(len(self.hns[i-1])): 16 18 sum = sum + self.ah[i-1][k] * self.w[i-1][k][j] 17 19 self.ah[i][j] = tanh(sum) 18 21 # 最后得到輸出層的
19 22 for k in range(len(self.urlids)): 20 23 sum = 0.0
21 24 for j in range(len(self.hns[self.h-1])): 22 25 sum = sum + self.ah[self.h-1][j] * self.w['o'][j][k] 23 26 self.ao[k] = tanh(sum) 24 28 retur

