郑捷《机器学习算法原理与编程实践》学习笔记(第六章 神经网络初步)6.2 BP神经网络
6.2.1 略
6.2.2 BP网络的构成
- 输入层:样本向量的维度
- 激活函数:Logistic
- 误差计算:实际分类-预测分类
- 输出层:类别标签向量
- 迭代公式
如下图所示BP网络的基本结构,该结构分为以下几个部分:
(1)输入层i
- 输入向量:x = (x1,x2,x3,...,xn)
- 输入层与隐含层链接权值:wih
输入层就是输入的数据集所构成上午向量集合。第一列偏置值为bi,第二列到最后一列的特征向量(分类标签除外)。输出层是输入层和权重的点积,然后该值与激活函数计算的结果。
(2)隐含层h
- 输入向量:hi=(hi1,hi2,hi3,...,hin)
- 输出向量:h0=(h01,h02,h03,...,h0n)
- 阀值:bh
- 隐含层与输出层链接权值:who
- 激活函数:Logistic函数
隐含层可以是一层,也可以是多层,但在BP网络中一般不超过两层。它的输入是上一层的输出层和权重的点积标量,输出是该标量与激活函数的计算结果。
(3)输出层o。
- 输入向量:yi=(yi1,yi2,yi3,...,yin)
- 输出向量:y0=(y01,y02,y03,...,y0n)
- 阀值:bo
输出层只有一层,它的输入是上一层输出层和权重的点积标量,输出是该标量与激活函数的计算结果。
(4)期望输出:do = (d1,d2,...,dn)就是分类的标签向量。
6.2.3 BP网络的训练过程
在流程,BP网络训练包括三个阶段。
1.正向传播过程
(1)输入函数:net = wTo+b
(2)传递(激活)函数:f(net)=1/(1+e-net)
In [1]: def logistic(self,net): ...: return 1.0/(1.0+exp(-net)) ...:
其中:
2.计算期望与实际分类的误差
误差向量:error=do-yo
全局误差函数:
全局误差函数的代码实现如下:
def errorfunc(self,inX): return sum(power(inX,2))*0.5
3.计算反向传播过程
若正向传播过程未能得到期望的输出值,则逐层计算输出与期望输出的误差值,根据误差值调解权重。
传递函数导函数的代码实现如下:
In [2]: def dlogit(self,net): ...: return multiply(net,(1.0-net)) ...:
(1)输出层误差:计算误差反向传播的输出层的梯度和微分,用于更新输出层权值。
输出层的微分可以写成如下的形式:
下面我们将等式的右边的两项分别进行推导:
其中左项推导得到:
右项推导得到:
将左右两项合在一起,得到:
(2)隐含层误差:计算误差反向传播的隐含层的梯度和微分,用于更新隐含层权值。
隐含层误差可以写成如下的形式:
其中左项推导得到:
右项推导得到:
将左右两项合并到一起,得到:
4.修正各层的权值
利用各层的神经元的梯度和微分修正链接权值。
6.3 BP网络的实现和评估
6.3.1 BP网络类与主要方法
BP网络类的基本结构如下:
class BPNet(object): def __init__(self): #构造函数 def logistic(self,net): #激活(传递)函数 def dlogit(self,net): #激活(传递)函数的导函数 def errorfunc(self,inX): #矩阵各元素平方之和 def normlize(self,dataMat): #数据标准化归一化 def loadDataSet(self,filename): #加载数据集 def addcol(self,matrixl,matrix2): #增加新列 def init_hiddenWB(self): #隐藏层初始化 def bpTrain(self): #BP网络主程序 def BPClassfier(self,start,end,steps = 30): #BP网络分类器 def classfyLine(self,plt,x,z): #绘制分类线 def TrenLine(self,plt,color = 'r'): #绘制趋势线,可调整颜色 def drawClassScatter(self,plt): #绘制分类点
1.设置网络初始化的基础参数
def __init__(self): #构造函数 #以下参数需要手工设置 self.eb = 0.01 #误差容限,当误差小于这个值时,算法收敛,程序停止 self.iterator = 0 #算法收敛时的迭代次数 self.eta = 0.1 #学习率,相当于步长 self.mc = 0.3 #动量因子:引入一个调优参数,是主要的调优参数 self.maxiter = 2000 #最大迭代次数 self.nHidden = 4 #隐含层神经元 self.nOut = 1 #输出层个数 #以下的属性由系统生成 self.errlist = [] #误差列表:保存了误差参数的变化用于评估收敛 self.dataMat = 0 #训练集 self.classLabels = 0 #分类标签 self.nSampNum = 0 #样本集行数 self.nSampDim = 0 #样本列数
上述代码中的部分参数含义如下。
(1)误差容限(eb)和迭代次数(iterator):当网络的实际分类与期望分类的误差的距离平方和小于这个数是,证明网络已经收敛,程序会自动跳出循环,此时的迭代次数就是网络实际的循环次数。
(2)学习率(eta):相当于步长,这个值越大,要求迭代的次数就越少,但是越容易跳过最优值;这个值越小,要求迭代的次数越多。
(3)动量因子(mc):用于网络调优,最早的BP算法在修正权值时,只按当前迭代的次数t的梯度调整,而不考虑t-1的梯度方向。这种方式经常会使网络发生震荡,收敛缓慢,难以调优。经过大量的实验,人们引入动量因子,该因子分配了t时刻和t-1时刻的梯度值,用以修正算法,以下是修正隐含层的权重代码:
self.hi_wb = self.hb_wb+(1.0-self.mc)*self.eta*dhi_wb+self.mc*dhi_wbOld
其中,self.hi_wb是权重值:self.mc是动量因子;dhi_wb是t时刻的权重微分,dhi_wbOld是t-1时刻的权重微分。
目前,动量项已经成为BP算法的标准参数,含有动量项的配置是BP算法的标准配置。
(4)最大的迭代次数:BP网络的迭代次数
(5)隐含层神经元:隐含层神经元个数的选择一般遵从以下法则
- 隐含层设计。最常见的BP网络结构是三层的结构:一个输入层,一个输出层和一个隐含层。有理论证明:单隐含层BP网络可以映射所有连续函数,只有训练离散函数是才需要一个以上的隐含层,因此BP网络的隐含层最多不超过两层。实际训练时,就必须依靠再增加隐含层达到最优。
- 隐含层节点上午设计。隐含层节点数的设计比隐含层的设计难度更大,经验公式
nNode = (m+n)1/2+a
nNode表示节点数,其中n为输入层节点数;m为输出层节点数,a为1~10之间的常数。
(6)输出层个数:该数目会根据结果自动调整。默认值1
def init_hiddenWB(self): #隐藏层初始化 self.hi_w = 2.0*(random.rand(self.nHidden,self.nSamDim)-0.5) self.hi_b = 2.0*(random.rand(self.nHidden,1)-0.5) self.hi_wb = mat(self.addcol(mat(self.hi_w),mat(self.hi_b))) def init_OutputWB(self): #输出层初始化 self.out_w = 2.0 * (random.rand(self.nOut,self.nHidden)-0.5) self.out_b = 2.0 * (random.rand(self.nOut,1)-0.5) self.out_wb = mat(self.addcol(mat(self.out_w),mat(self.out_b)))
6.3.3 辅助函数
(1)加载数据集
def loadDataSet(self,filename): #加载数据集 self.dataMat = [] self.classLabels = [] fr = open(filename) for line in fr.readlines(): lineArr = line.strip().split() self.dataMat.append([float(lineArr[0]),float(lineArr[1]),1.0]) self.classLabels.append(int(lineArr[2])) self.dataMat = mat(self.dataMat) m,n = shape(self.dataMat) self.nSampNum = m #样本数量 self.nSampNum = n-1 #样本维度
(2)数据集归一化
def normlize(self,dataMat): #数据标准化归一化 [m,n] = shape(dataMat) for i in xrange(n-1): dataMat[:,i] = (dataMat[:,i]-mean(dataMat[:,i]))/(std(dataMat[:,i])+1.0e-10) return dataMat
(3)矩阵增加新列
def addcol(self,matrixl,matrix2): #增加新列 [m1,n1] = shape(matrixl) [m2,n2] = shape(matrix2) if m1 != m2: print "different row,can not merge matix" return mergMat = zeros((m1,n1+n2)) mergMat[:,0:n] = matrixl[:,0:n1] mergMat[:,n1:(n1+n2)] = matrix2[:,0:n2] return mergMat
(4)绘制分类点
def drawClassScatter(self,plt): #绘制分类点 i = 0 for mydata in self.dataMat: if self.classLabels[i] == 0: plt.scatter(mydata[0,0],mydata[0,1],c='blue',marker = 's' ) else: plt.scatter(mydata[0,0],mydata[0,1],c='red',marker = 's' )
6.3.4 主函数
下面给出算法的主函数:
def bpTrain(self): #BP网络主程序 SampIn = self.dataMat.T #输入矩阵 expected = mat(self.classLabels) #预测输出 self.init_hiddenWB() self.init_OutputWB() dout_wbOld = 0.0;dhi_wbOld = 0.0 #默认t-1权值 #主循环 for i in xrange(self.maxiter): #1.工作信号正向传播 #1.1 信息从输入层到隐含层,这里使用了矢量计算, # 计算的是整个样本集的结果,结果是4行307列的矩阵 hi_input = self.hi_wb*SampIn hi_output = self.logistic(hi_input) hi2out = self.addcol(hi_output.T,ones((self.nSampNum,1))).T #1.2 从隐含层到输出层:结果是5行307列的矩阵 out_input = self.out_wb*hi2out out_ouput = self.logistic(out_input) #2.误差计算 err = expected-out_ouput sse = self.errorfunc(err) self.errlist.append(sse) if sse <= self.eb: #判断是否收敛至最优 self.iterator = i+1 break #3.误差信号反向传播 DELTA = multiply(err,self.dlogit(out_ouput)) #DELTA为输出层梯度 #delta为隐含层梯度 delta = multiply(self.out_wb[:,:-1].T*DELTA,self.dlogit(hi_output)) dout_wb = DELTA*hi2out.T #输出层权值微分 dhi_wb = delta.SamIn.T #隐含层权值微分 if i == 0: #更新输出层和隐含层权值 self.out_wb = self.out_wb + self.eta*dout_wb self.hi_wb = self.hi_wb +self.eta*dhi_wb else: self.out_wb = elf.out_wb + (1.0-self.mc)*self.eta*dout_wb+self.mc*dout_wbOld self.hi_wb = elf.hi_wb + (1.0-self.mc)*self.eta*dhi_wb+self.mc*dhi_wbOld dout_wbOld = dout_wb dhi_wbOld = dhi_wb
分类曲面由两个权重向量确定:self.hi_wb和self.out_wb
评估参数由一个向量确定:self.errlist
6.3.5 分类器
(1)分类器函数
def BPClassfier(self,start,end,steps = 30): #BP网络分类器 x = linspace(start,end,steps) xx = mat(ones(steps,steps)) xx[:,0:steps] = x yy = xx.T z = ones((len(xx),len(yy))) for i in range(len(xx)): for j in range(len(yy)): xi = [] tauex = [] tautemp = [] mat(xi.append([xx[i,j],yy[i.j],1])) hi_input = self.hi_wb*(mat(xi).T) hi_out = self.logistic(hi_input) taumrow,taucol = shape(hi_out) tauex = mat(ones((1,taumrow+1))) tauex[:,0:taumrow] = (hi_out.T)[:,0:taumrow] out_input = self.out_wb*(mat(tauex).T) out = self.logistic(out_input)
(2)绘制分类结果
def classfyLine(self,plt,x,z): #绘制分类线 plt.conour(x,x,z,1,colors = 'black')
(3)绘制误差曲线
def TrenLine(self,plt,color = 'r'): #绘制趋势线,可调整颜色 X = linspace(0,self.maxiter,self.maxiter) Y = log2(self.errlist) plt.plot(X,Y,color)
6.3.6 执行分类并输出结果
使用BP网络训练数据的主程序如下
if '__name__' == '__main__': #数据集 bpnet = BPNet() bpnet.loadDataSet("testSet2.txt") bpnet.dataMat = bpnet.normlize(bpnet.dataMat) #绘制数据集的散点图 bpnet.drawClassScatter(plt) #BP神经网络进行数据分类 bpnet.bpTrain() print bpnet.out_wb print bpnet.hi_wb #计算和绘制分类线 x,z = bpnet.BPClassfier(-3.0,3.0) bpnet.classfyLine(plt,x,z) plt.show()
总代码:
#coding:utf-8 from numpy import * import matplotlib.pyplot as plt import operator class BPNet(object): def __init__(self): #构造函数 #以下参数需要手工设置 self.eb = 0.01 #误差容限,当误差小于这个值时,算法收敛,程序停止 self.iterator = 0 #算法收敛时的迭代次数 self.eta = 0.1 #学习率,相当于步长 self.mc = 0.3 #动量因子:引入一个调优参数,是主要的调优参数 self.maxiter = 2000 #最大迭代次数 self.nHidden = 4 #隐含层神经元 self.nOut = 1 #输出层个数 #以下的属性由系统生成 self.errlist = [] #误差列表:保存了误差参数的变化用于评估收敛 self.dataMat = 0 #训练集 self.classLabels = 0 #分类标签 self.nSampNum = 0 #样本集行数 self.nSampDim = 0 #样本列数 def logistic(self,net): #激活(传递)函数 return 1.0/(1.0+exp(-net)) def dlogit(self,net): #激活(传递)函数的导函数 return multiply(net,(1.0-net)) def errorfunc(self,inX): #矩阵各元素平方之和 return sum(power(inX,2))*0.5 def normlize(self,dataMat): #数据标准化归一化 [m,n] = shape(dataMat) for i in xrange(n-1): dataMat[:,i] = (dataMat[:,i]-mean(dataMat[:,i]))/(std(dataMat[:,i])+1.0e-10) return dataMat def loadDataSet(self,filename): #加载数据集 self.dataMat = [] self.classLabels = [] fr = open(filename) for line in fr.readlines(): lineArr = line.strip().split() self.dataMat.append([float(lineArr[0]),float(lineArr[1]),1.0]) self.classLabels.append(int(float(lineArr[2]))) self.dataMat = mat(self.dataMat) m,n = shape(self.dataMat) self.nSampNum = m #样本数量 self.nSampDim = n-1 #样本维度 def addcol(self,matrixl,matrix2): #增加新列 [m1,n1] = shape(matrixl) [m2,n2] = shape(matrix2) if m1 != m2: print "different row,can not merge matix" return mergMat = zeros((m1,n1+n2)) mergMat[:,0:n1] = matrixl[:,0:n1] mergMat[:,n1:(n1+n2)] = matrix2[:,0:n2] return mergMat def init_hiddenWB(self): #隐藏层初始化 self.hi_w = 2.0*(random.rand(self.nHidden,self.nSampDim)-0.5) self.hi_b = 2.0*(random.rand(self.nHidden,1)-0.5) self.hi_wb = mat(self.addcol(mat(self.hi_w),mat(self.hi_b))) def init_OutputWB(self): #输出层初始化 self.out_w = 2.0 * (random.rand(self.nOut,self.nHidden)-0.5) self.out_b = 2.0 * (random.rand(self.nOut,1)-0.5) self.out_wb = mat(self.addcol(mat(self.out_w),mat(self.out_b))) def bpTrain(self): #BP网络主程序 SampIn = self.dataMat.T #输入矩阵 expected = mat(self.classLabels) #预测输出 self.init_hiddenWB() self.init_OutputWB() dout_wbOld = 0.0;dhi_wbOld = 0.0 #默认t-1权值 #主循环 for i in xrange(self.maxiter): #1.工作信号正向传播 #1.1 信息从输入层到隐含层,这里使用了矢量计算, # 计算的是整个样本集的结果,结果是4行307列的矩阵 hi_input = self.hi_wb*SampIn hi_output = self.logistic(hi_input) hi2out = self.addcol(hi_output.T,ones((self.nSampNum,1))).T #1.2 从隐含层到输出层:结果是5行307列的矩阵 out_input = self.out_wb*hi2out out_ouput = self.logistic(out_input) #2.误差计算 err = expected-out_ouput sse = self.errorfunc(err) self.errlist.append(sse) if sse <= self.eb: #判断是否收敛至最优 self.iterator = i+1 break #3.误差信号反向传播 DELTA = multiply(err,self.dlogit(out_ouput)) #DELTA为输出层梯度 #delta为隐含层梯度 delta = multiply(self.out_wb[:,:-1].T*DELTA,self.dlogit(hi_output)) dout_wb = DELTA*hi2out.T #输出层权值微分 dhi_wb = delta*SampIn.T #隐含层权值微分 if i == 0: #更新输出层和隐含层权值 self.out_wb = self.out_wb + self.eta*dout_wb self.hi_wb = self.hi_wb +self.eta*dhi_wb else: self.out_wb = self.out_wb + (1.0-self.mc)*self.eta*dout_wb+self.mc*dout_wbOld self.hi_wb = self.hi_wb + (1.0-self.mc)*self.eta*dhi_wb+self.mc*dhi_wbOld dout_wbOld = dout_wb dhi_wbOld = dhi_wb def BPClassfier(self,start,end,steps = 30): #BP网络分类器 x = linspace(start,end,steps) xx = mat(ones((steps,steps))) xx[:,0:steps] = x yy = xx.T z = ones((len(xx),len(yy))) for i in range(len(xx)): for j in range(len(yy)): xi = [] tauex = [] tautemp = [] mat(xi.append([xx[i,j],yy[i,j],1])) hi_input = self.hi_wb*(mat(xi).T) hi_out = self.logistic(hi_input) taumrow,taucol = shape(hi_out) tauex = mat(ones((1,taumrow+1))) tauex[:,0:taumrow] = (hi_out.T)[:,0:taumrow] out_input = self.out_wb*(mat(tauex).T) # out = self.logistic(out_input) z[i,j] = out_input return x,z def classfyLine(self,plt,x,z): #绘制分类线 plt.contour(x,x,z,1,colors = 'black') def TrenLine(self,plt,color = 'r'): #绘制趋势线,可调整颜色 X = linspace(0,self.maxiter,self.maxiter) Y = log2(self.errlist) plt.plot(X,Y,color) def drawClassScatter(self,plt): #绘制分类点 i = 0 for mydata in self.dataMat: if self.classLabels[i] == 0: plt.scatter(mydata[0,0],mydata[0,1],c='blue',marker = 'o' ) elif self.classLabels[i] == 1: plt.scatter(mydata[0,0],mydata[0,1],c='red',marker = 's' ) else: plt.scatter(mydata[0,0],mydata[0,1],c='green',marker = '^' ) i += 1
#coding:utf-8 from numpy import * import matplotlib.pyplot as plt import operator from BP import * #数据集 bpnet = BPNet() bpnet.loadDataSet("testSet2.txt") bpnet.dataMat = bpnet.normlize(bpnet.dataMat) #绘制数据集的散点图 bpnet.drawClassScatter(plt) #BP神经网络进行数据分类 bpnet.bpTrain() print bpnet.out_wb print bpnet.hi_wb #计算和绘制分类线 x,z = bpnet.BPClassfier(-3.0,3.0) bpnet.classfyLine(plt,x,z) plt.show() #绘制误差线 bpnet.TrenLine(plt) plt.show()
资料来源:郑捷《机器学习算法原理与编程实践》 仅供学习研究