BP算法_机器学习

前言:

  

    BP(back propagation)神经网络是1986年由Rumelhart和McClelland为首的科学家提出的概念,是一种按照误差逆向传播算法训练的多层前馈神经网络。      主要应用在 函数逼近,模式识别,分类,数据压缩(降低数据维度)

  算法 优点:广泛的适应性和有效性

  缺点: 训练速度慢,算法不一定收敛

 

   推荐一个绘图的好网站:   https://www.echartsjs.com/examples/zh/index.html#chart-type-scatter

  这里主要结合乳腺癌例子,介绍一下BP算法。

  

   
 

目录

  1.        概述
  2.        算法原理
  3.        乳腺癌例子

 

一   概述

       1.1 BP 算法

       

      

   BP(back propagation)神经网络是1986年由Rumelhart和McClelland为首的科学家提出的概念,是一种按照误差逆向传播算法训练的多层前馈神经网络。    整个网络,主要分为输入层,隐藏层,输出层三部分。

   每个方框代表一个神经元, 神经元主要由 积分器和 激活函数组成

  

 


二  算法流程

      算法主要分为两个部分

      正向转播: 求损失函数,以及迭代终止判断条件

      反向传播: 更新隐藏层权重系数V,以及输出层权重系数W

 

     2.1 损失函数

          2.1.1  输出层损失函数

            L= \frac{1}{2}\sum_{k=1}^{K}(t_k-o_k)

             其中t_k 代表标签值    o_k代表计算值

              K: 代表输出层输出个数。

             比如2分类,K可以设计成1个,也可以设计成多个,但是每个输出的标签是一样的。

      

          

 2.1.2 展开至隐层

        L = \frac{1}{2}\sum_{k=1}^{K}(t_k-f(net_k))=\frac{1}{2}\sum_{k=1}^{K}(t_k-f(\sum_{j=1}^{m}W_{jk}y_j))^2

 

2.1.3 展开至输入层

       L=\frac{1}{2}\sum_{k=1}^{K}(t_k-f(\sum_{j=1}^{m}W_{jk}f(\sum_{i=1}^{n}V_{ij}x_i)))^2

 

   其中f代表激活函数,这里主要使用的是SigMoid函数:

   正在上传…重新上传取消

uploading.4e448015.gif

   f(x)=sigmoid(x)=\frac{1.0}{1.0+e^{-x}}

三  算法推导

      最终是更新输入层至隐藏层的权重系数V,以及隐藏层到输出层的权重系数W

 

       2.1 正向传播 计算误差(类似HMM中的前向算法)

             输入层至隐藏层公式2 

            HideInput_{ji}=\sum_{i}^{N}x_iv_j               

           隐层的积分器到激活函数输出公式3

          f(x)= sigMoid(x)= \frac{1}{1+e^{-x}}    (HideOuput)     

           

        因为此刻的隐层输出需要作为下一层的输入,所以需要加上参数b,公式4

      

Y = np.vstack((hide_output,self.HideB ))  公式4

  

      输出层输入,公式5

      OutInput_k=\sum_{j=1}^{m}W_{kj}Y_j     其中m是隐层神经元个数+1 (1是参数b)

 

    输出层输出, 公式6

      f(x)= sigMoid(x)= \frac{1}{1+e^{-x}}(Out_input)

     假设值为Z,其导数为Z(1-Z)

 

    最后得到当前的误差

     L = \frac{1}{2} \sum_{k=1}^{K} (t_k-o_k)^2

 

2.2 反向传播(类似HMM中的后向算法)

    隐藏层到输出层的更新过程:

   

\frac{\partial L}{\partial w_{kj}}=\frac{\partial L}{\partial O_k}* \frac{\partial O_k}{\partial OutInput}*\frac{\partial OutInput}{\partial W_{kj}}   公式7 

 

其中

 

 \frac{\partial L}{\partial O_k}=-(t_k-o_k)  公式1

\frac{\partial O_k}{\partial OutInput}=O_K*(1-O_k) 公式8 

\frac{\partial OutInput}{\partial W_{kj}}=Y_j

其中\delta_k =(t_k-O_k)*O_k*(1-O_k)

 

 W_{kj}=W_{kj}-\alpha* \frac{\partial L}{\partial w_{kj}}

      =W_{kj}+\delta_kY_j      公式13

 

 

  输入层到输出层的更新过程

    

  \frac{\partial L}{\partial V_{ji}}=\sum_{k=1}^{K} \frac{\partial L}{\partial O_{K}} *\frac{\partial O_k}{\partial OutInput_k}*\frac{\partial OutInput_k}{\partial Y_j}*\frac{\partial Y_j}{\partial HideInput_j}*\frac{\partial HideInput_j}{\partial V_{ji}}

  其中

      \frac{\partial L}{\partial O_k}=-(t_k-o_k)

     V_{ji}=V_{ji}-\frac{ \partial L}{\partial V_{ji}}\frac{\partial O_k}{\partial OutInput}=O_K*(1-O_k)

  \frac{\partial OutInput_k}{\partial Y_j}=W_{kj}

\frac{\partial Y_j}{\partial HideInput_j}= Y_j*(1-Y_j)  

\frac{\partial HideInput_j}{\partial V_{ji}}= x_i

令A = A_{kj}= \delta_k*W_{kj}   公式12

 

   \frac{\partial L}{\partial V_{ji}}=A*Y_j(1-Y_j)*x_i

 

V_{ji}= V_{ji}- \frac{\partial L}{\partial V_{ji}}

=     V_{ji}+\sum_k \alpha*A_{kj}*Y_j(1-Y_j)*x_i   公式14 

 

三  算法例子

 

    数据集可以使用2种: 乳腺癌 和 LoadData 里面的

   

# -*- coding: utf-8 -*-
"""
Created on Wed Feb 26 11:20:16 2020

@author: chengxf2
"""

import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_breast_cancer 



class BP():
    
    
    """
    加载乳腺癌数据集,数据集总共有569个样本
    训练集用了300个样本
    
    Args
      None
    return
      None
    """
    def LoadCancerData(self):
        
        
        
        cancer = load_breast_cancer()
        X = cancer.data
        Y = cancer.target
        m,n = np.shape(X)
        
        scaler = StandardScaler().fit(X)
        X = scaler.transform(X)
        
        trainNum = 400 ##70% 作为样本,
        testNum = m- trainNum
        A = np.ones((trainNum,1))
        B = np.ones((testNum,1))
        self.trainData = np.hstack((X[0:trainNum,],A))
        self.trainLabel = Y[0:trainNum]
        

        
        self.testData = np.hstack((X[trainNum:,],B))
        self.testLabel = Y[trainNum:,]
        
        print("\n 训练样本: ", trainNum, "\t 测试样本: ",testNum)
        
        self.M, self.N = np.shape(self.trainData)
        
        self.nHide = self.N+5 ##隐藏层的个数
       
        
        self.V = np.random.rand(self.nHide, self.N) # 输入层到隐藏层的权重系数
        self.W = np.random.rand(self.nOut, self.nHide+1)  #隐藏层到输入层的权重系数
        
        
        
        
        #self.trainData =np.hstack((A,B))
        
        
        
        
        
        
    
    
    """
    加载数据集
    Args
      fileName
    return
      None
    """
    def LoadData(self, filename):
        
        
        


        dataList = []
        labelList = []
        BList = []
        fr = open(filename)
        

        
        for line in fr.readlines():
            lineArr = line.strip().split()
            dataList.append([float(lineArr[0]), float(lineArr[1])])
            labelList.append(int(lineArr[2]))
            BList.append([1.0])
            
            
        scaler = StandardScaler().fit(dataList)
        dataList = scaler.transform(dataList)
        
        A = np.array(dataList,np.float)
        B = np.array(BList)
        
   
        
        self.trainData =np.hstack((A,B))
        self.trainLabel = np.array(labelList,np.float)
        self.M, self.N = np.shape(self.trainData) ##样本个数, 样本维度
        
        
        


        self.V = np.random.rand(self.nHide, self.N) # 输入层到隐藏层的权重系数
        self.W = np.random.rand(self.nOut, self.nHide+1)  #隐藏层到输入层的权重系数
        
        for i in range(self.M):
            x = self.trainData[i,0]
            y = self.trainData[i,1]
            
            if self.trainLabel[i]==0:
                plt.scatter(x,y,c='r')
            else:
                plt.scatter(x,y,c='g')
        plt.show()
        
        

    
    """
    S函数,导数是连续的,y(1-y)
    
    args
       x: 实数
    return  [0,1]
    """
    def Sigmoid(self, x):
        
        y = 1.0/(1.0+np.exp(-x))
        
        return y
    
    """
    sigMoid 函数的导数
    Args
        y: 输出值
    return
       dy: 导数
    """
    def DSigmoid(self,y):
        #print("y: ",y)
        
        dy = np.multiply(y, (1.0 - y))
        return dy
    
    
    """
    前向传播
    Args
       None
    return 
       sse: 所有样本,在各个输出结点上的误差总和
    """
    def Forward(self):
        
       hide_input= np.dot(self.V, self.trainData.T) #隐藏层输入NetJ 公式2
       hide_output = self.Sigmoid(hide_input)  #隐藏层输出  公式 3

       
       self.Y = np.vstack((hide_output,self.HideB )) ##行顺序添加一个数组,增加一个参数B, 变成下一层的输入 公式4
       
 
       
       out_input = np.dot(self.W, self.Y)   #输出层输入, 公式5  
       self.out_output = self.Sigmoid(out_input) #输出层输出,公式6
       self.err = self.trainLabel - self.out_output #输出层误差,Loss, 公式1
       sse = np.sum(np.power(self.err, 2)) * 0.5
       
       return sse       
       
        
        
    
    
    def __init__(self):
        
        self.nHide = 50 #隐藏层神经元个数
        self.nOut = 1 ##输出层神经元个数
        self.V = None # 输入到隐藏层的矩阵
        self.Y = None #隐藏层的输出
        self.W = None  # 隐藏层到输出层的权重
        self.output = None  #输出层的输出
        self.maxIter = 50000 ##最大迭代次数
        self.errMax = 0.001  ##误差上限
        self.mc = 0.0  #动量因子
        self.errlist = []       # 误差列表
        self.delta = None #误差
        self.alpha = 0.1 ##步伐
        
    """
    训练
    """
    def Train(self):
    
       i = 0
       self.HideB = np.ones((1,self.M)) #相当于隐藏层增加一个B
       sse = 0.0
       oldsse = np.inf
       
       while i < self.maxIter:
           

           
           sse = self.Forward()  ##前向传导
           #print("\n i: ",i, "\t sse: ",sse)
           self.errlist.append(sse)
           # 判断是否收敛至最优
           
      
           
           if sse <= self.errMax:
                self.iterator = i + 1
                print("\n ************break *******",i)
                break
           
          
           Dout_put = self.DSigmoid(self.out_output)  ##公式8 输出层的误差
           delte_out = np.multiply(self.err,Dout_put) #公式9 delte参数
           delte_w = np.dot(delte_out,self.Y.T)  #公式7 输出层的w的偏导数
           
           
     
           A = np.dot(self.W[:,:-1].T, delte_out) #公式11 隐藏层的误差
           delte_hide = np.multiply( A, self.DSigmoid(self.Y[:-1,:])) #公式12 隐藏层的delte
           delte_V = np.dot(delte_hide , self.trainData) # 公式10 隐藏层V的偏导数
           

         
           if i == 0:
                 self.W = self.W+ self.alpha*delte_w #公式13 输出层的误差更新公式
                 self.V = self.V + self.alpha*delte_V #公式14 隐藏层的误差更新公式
           else:
                self.W = self.W + (1.0 - self.mc) * self.alpha * delte_w + self.mc * delte_wOld
                self.V = self.V + (1.0 - self.mc) * self.alpha * delte_V + self.mc * delte_vOld
           delte_wOld = delte_w
           delte_vOld = delte_V
           i = i+1
           
    def Perdict(self,dataArr, labelArr):
        
         m,n = np.shape(dataArr)
         
         HideB = np.ones((1,m)) #相当于隐藏层增加一个B
         hide_input= np.dot(self.V, dataArr.T) #隐藏层输入NetJ 公式2
         hide_output = self.Sigmoid(hide_input)  #隐藏层输出  公式 3
         self.Y = np.vstack((hide_output,HideB )) ##行顺序添加一个数组,增加一个参数B, 变成下一层的输入 公式4
         out_input = np.dot(self.W, self.Y)   #输出层输入, 公式5  
         perdictY = self.Sigmoid(out_input)#输出层输出,公式6
         
         #(2,307)
         #print("\n  perdictY: \n ",perdictY)
         Y= perdictY[0,:] 
         errNum = 0
         for i in range(m):
             label = int(labelArr[i]+0.5) ##标签值
             out = int(Y[i]+0.5) ##预测值
             
             if label !=out:
                 errNum  +=1
                 #print("\n  i",i ,"\t Y: ",label, "\t predict: ",out)
         err = float(errNum/m)*100
         print("\n 测试样本总数:",m,"\t 错误率 :%f "%err,"\t 错误样本 ",errNum)
         
         
           


bpNet = BP()
#bpNet.LoadData("data.txt")
bpNet.LoadCancerData()
bpNet.Train()
print("\n ====开始测试了==== \n ")

bpNet.Perdict(bpNet.testData, bpNet.testLabel)

        
        

 

参考文档

    https://blog.csdn.net/qq_32241189/article/details/80305566

    https://blog.csdn.net/weixin_42398658/article/details/83929474

    https://blog.csdn.net/wz2671/article/details/84177123

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值