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