郑捷《机器学习算法原理与编程实践》学习笔记(第六章 神经网络初步)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()

 

 

 

资料来源:郑捷《机器学习算法原理与编程实践》 仅供学习研究

 

posted on 2017-02-09 16:09  金秀  阅读(1142)  评论(0编辑  收藏  举报

导航