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

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

  • 相关阅读:
    Linux脚本文件注释
    Linux三剑客之grep命令
    Linux获取本机IP
    Linux的cut命令详解
    Linux的wc命令详解
    Linux的uniq命令详解
    Linux的sort命令详解
    shell之a+b求和脚本的三种写法
    shell的文件比较运算符和字符串比较运算符
    shell中变量$系列的含义
  • 原文地址:https://www.cnblogs.com/wuchuanying/p/6370671.html
Copyright © 2011-2022 走看看