BP神经网络原理推导及python实现

  (一)BP神经网络简介

  BP神经网络是整个神经网络体系中的精华,与一般神经网络相比,它调整权值方式为从最后一层开始逐层调整,通过多次迭代,使得代价函数降低至可接受范围。因此,这就是BP神经网络的来源(Back Propagation,误差反向传播)。

  1、神经网络权值调整的一般形式为:

  

在这里插入图片描述


  (1)其中 η为学习率,可以理解为每次权值调整的步长

  (2)其中δ为学习信号,可以理解为每次权值调整的方向,实际上为一个梯度向量。

  由上式也可以发现不同神经网络的差异主要体现在学习信号上的差异。

  2、BP神经网络中关于学习信号的求取方法:

  假设一个神经网络共n层

  

在这里插入图片描述


  上式为求取最后一层,即输出层的学习信号,其中t为期望输出,y为模型输出,f(x)为激活函数(对激活函数不了解的可自行先百度或查看我的其他文章)。

  

在这里插入图片描述


  上式为求取除最后一层以外其他层的学习信号,i+1表示第i层后面的一层,可以发现求取第i层学习信号时,需要先求取出第i+1层的学习信号,这也是误差反向传播的来源。

  具体BP神经网络的学习信号推导过程见以下的原理推导,推导过程有点小复杂,即使不看,根据以上的结论也可以编写自己的BP神经网络程序了。

  (二)BP神经网络原理推导

  1、变量说明

  (1)以三层感知器构成的神经网络为例(实际为两层神经网络)。

  (2)定义各层的输入和输出:

  输入向量

  隐含层输出向量

  输出层输出向量

  期望输出向量

  (3)定义权值矩阵:

  输入层到隐含层的权值矩阵

  隐含层到输出层的权值矩阵

  (4)设定激活函数

  2、BP算法推导

  (1)构建计算输出层输出的函数

  (2)构建计算隐含层输出的函数

  (3)定义代价函数

  (4)式9中x与d分别为输入与对应的期望输出,皆为已知,所以代价函数则为两个权值矩阵的函数,为了使代价函数减小,权值的调整应往负梯度方向调整,如下:(η为学习率,可以理解为往负梯度方向改变的步长)

  (5)将式10和式11按链式求导法则进行展开:

  (6)对输出层和隐含层各定义一个误差信号(本质上为负梯度):

  (7)将式14和式15代入式12和式13,得:

  (8)求解输出层和隐含层的学习信号(需结合式1和式3):

  (9)计算式18和19中的偏导:

  (10)将式20和式21带回式18和19:

  (11)将式22)和式23带回式16和17,得:

  (12)将三层感知器推广到任意层数神经网络:

  假设神经网络共n层。

  (三)BP神经网络python实现

  如果读者想自己动手在python上实现BP神经网络的话,可以根据文章给出的关于学习信号的求取方法进行编程,本文给出了自己的实现代码,且将其封装成一个类,基本上的属性和方法都具有,通用性较强,读者既可调用其来训练自己的模型,也可以参考其架构自行编写代码。

  1、模型所需传参介绍

  layer 为神经网络各层神经元的个数,包括输出层神经元个数,传参形式以列表传入;

  activate:为各层的激活函数,传参形式为字符串或列表,

  若传入一个字符串,则各层激活函数相同,

  若传入一个列表,则列表元素代表各层激活函数

  可传参数有:

  (1)sigmoid:S型函数

  (2)tanh:双曲正弦函数

  (3)relu:max(0,x)函数

  (4)purline:线性函数

  (5)softsign:平滑函数

  lr:学习率,默认为0.01

  epoch:最大迭代次数 默认为1e4

  2、模型具有的主要方法和属性

  fit(X,Y):模型拟合方法

  predict(X):输出预测方法

  predict_label(X):分类标签输出预测方法

  activate:激活函数列表

  W:权值列表

  3、python代码

  import numpy as np

  import time

  clases Cyrus_BP(object):

  """

  layer 为神经网络各层神经元的个数,包括输出层神经元个数,传参形式以列表传入;

  activate:为各层的激活函数,传参形式为字符串或列表,

  若传入一个字符串,则各层激活函数相同,

  若传入一个列表,则列表元素代表各层激活函数

  可传参数有:(1)sigmoid:S型函数

  (2)tanh:双曲正弦函数

  (3)relu:max(0,x)函数

  (4)purline:线性函数

  (5)softsign:平滑函数

  lr:学习率,默认为0.01

  epoch:最大迭代次数 默认为1e4

  该模型具有的主要方法和属性如下:

  fit(X,Y):模型拟合方法

  predict(X):输出预测方法

  predict_label(X):分类标签输出预测方法

  activate:激活函数列表

  W:权值列表

  """

  def __init__(self,layer,**kargs):

  self.layer = np.array(layer).reshape(1,-1)

  if 'activate' in kargs.keys():

  if str(type(kargs["activate"])) == "":

  self.activate = [kargs["activate"]]*int(len(layer))

  else:

  self.activate = kargs["activate"]

  else:

  self.activate = ["sigmoid"]*int(len(layer))

  self.diff_activate = []

  if 'lr' in kargs.keys():

  self.lr = kargs["lr"]

  else:

  self.lr = 0.01

  if 'epoch' in kargs.keys():

  self.epoch = kargs["epoch"]

  else:

  self.epoch = int(1e4)

  self.X = None

  self.Y = None

  self.W = None

  self.output = []

  self.delta = []

  self.sum_input = []

  # 1、选择激活函数

  def activation_func(self):

  temp_func = []

  for i in range(len(self.activate)):

  if self.activate[i] == "sigmoid":

  temp_func.append(lambda x:1/(1+np.exp(-x)))

  self.diff_activate.append(lambda x:(1/(1+np.exp(-x)))*(1-(1/(1+np.exp(-x)))))

  if self.activate[i] == "tanh":

  temp_func.append(lambda x:(np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x)))

  self.diff_activate.append(lambda x:((-np.exp(x) + np.exp(-x))*(np.exp(x) - np.exp(-x))/(np.exp(x) + np.exp(-x))**2 + 1))

  if self.activate[i] == "softsign":

  temp_func.append(lambda x:x/(1+np.abs(x)))

  self.diff_activate.append(lambda x:1/((1+x/np.abs(x)*x)**2))

  if self.activate[i] == "relu":

  temp_func.append(lambda x:(x+np.abs(x))/(2*np.abs(x))*x)

  self.diff_activate.append(lambda x:(x+np.abs(x))/(2*np.abs(x)))

  if self.activate[i] == "purline":

  temp_func.append(lambda x:x)

  self.diff_activate.append(lambda x:1+x-x)

  self.activate = temp_func

  # 2、权值初始化函数

  def init_w(self):

  self.W = []

  for i in range(self.layer.shape[1]):

  if i == 0:

  w = np.random.random([self.X.shape[1]+1,self.layer[0,i]])*2-1

  else:

  w = np.random.random([self.layer[0,i-1]+1,self.layer[0,i]])*2-1

  self.W.append(w)

  # 3、权值调整函数

  def update_w(self):

  # 1 计算各层输出值

  self.output = []

  self.sum_input = []

  for i in range(self.layer.shape[1]):

  if i == 0:

  temp = np.dot(np.hstack((np.oneself.X.shape[0],1)),self.X)),self.W[i])

  self.sum_input.append(temp)

  self.output.append(self.activate[i](temp))

  else:

  temp = np.dot(np.hstack((np.oneself.output[i-1].shape[0],1)),self.output[i-1])),self.W[i])

  self.sum_input.append(temp)

  self.output.append(self.activate[i](temp))

  # 2 求每层的学习信号

  self.delta = [0 for i in range(len(self.output))]

  for i in range(len(self.output)):

  if i == 0:

  self.delta [-i-1] = ((self.Y-self.output[-i-1])*self.diff_activate[-i-1](self.sum_input[-i-1]))

  else:

  self.delta [-i-1] = ((self.delta[-i].dot(self.W[-i][1:,:].T))*self.diff_activate[-i-1](self.sum_input[-i-1]))

  # 3 更新权值

  for i in range(len(self.W)):

  if i == 0 :

  self.W[i] += self.lr * np.hstack((np.oneself.X.shape[0],1)),self.X)).T.dot(self.delta[i])

  else:郑州人流医院 http://m.zyfuke.com/

  self.W[i] += self.lr * np.hstack((np.oneself.output[i-1].shape[0],1)),self.output[i-1])).T.dot(self.delta[i])

  def fit(self,X,Y):

  self.X = np.array(X)

  self.Y = np.array(Y)

  # 1 权值初始化

  self.init_w()

  # 2 选择激活函数

  self.activation_func()

  # 3 更新权值

  start_time = time.time()

  for i in range(int(self.epoch)):

  self.update_w()

  end_time = time.time()

  if end_time - start_time >= 5:

  print("Epoch%d:"%(i+1),np.mean(np.square(self.Y-self.output[-1])))

  print("\n")

  start_time = time.time()

  def predict(self,x):

  x = np.array(x)

  result = []

  for i in range(self.layer.shape[1]):

  if i == 0:

  result.append(self.activate[i](np.dot(np.hstack((np.ones((x.shape[0],1)),x)),self.W[i])))

  else:

  result.append(self.activate[i](np.dot(np.hstack((np.ones((result[i-1].shape[0],1)),result[i-1])),self.W[i])))

  return result[-1]

  def predict_label(self,x):

  x = np.array(x)

  result = []

  for i in range(self.layer.shape[1]):

  if i == 0:

  result.append(self.activate[i](np.dot(np.hstack((np.ones((x.shape[0],1)),x)),self.W[i])))

  else:

  result.append(self.activate[i](np.dot(np.hstack((np.ones((result[i-1].shape[0],1)),result[i-1])),self.W[i])))

  result = result[-1]

  return np.array([result[i].argmax() for i in range(result.shape[0])]).reshape(-1,1)

  if __name__ == "__main__":

  bp = Cyrus_BP([50,10,3],lr=0.01,epoch = 2e5,activate = ["softsign","softsign","softsign"])

  from sklearn.datasets import load_iris

  from sklearn.metrics import accuracy_score

  data = load_iris()

  X = data["data"]

  Y = data["target"]

  import pandas as pd

  # 用神经网络进行分类时,需把输出先进行独热编码

  Y1 = pd.get_dummies(Y1) # 进行独热编码或将期望输出转换为哑变量

  bp.fit(X,Y1)

  Y_pre = bp.predict_label(X)

  print("准确率为:",accuracy_score(Y,Y_pre))

  4、代码运行结果

  Epoch9314: 0.02853577904298399

  Epoch19691: 0.02145897246261971

  Epoch31495: 0.01784770845276102

  Epoch42539: 0.01415043927077651

  Epoch53434: 0.015407038745481208

  Epoch64893: 0.016390764988851683

  Epoch76186: 0.015016316993973523

  Epoch86931: 0.013693150044879728

  Epoch97390: 0.013706384360315056

  Epoch108511: 0.012193768543380657

  Epoch118993: 0.010314480349340294

  Epoch128337: 0.009862103298377766

  Epoch138193: 0.01057658278951552

  Epoch147889: 0.009652582210903272

  Epoch157632: 0.009137051214565095

  Epoch165815: 0.009407398018203143

  Epoch175037: 0.009429640020604707

  Epoch185229: 0.00991562156191445

  Epoch194220: 0.009801710064963167

  准确率为: 0.9933333333333333

  可见模型训练结果还是不错的


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章