【算法】BP神经网络(BP,Back Propagation)

参考资料:来自于老哥数学建模课程。

BP神经网络的背景

1986年,Rumelhart等提出了误差反向传播神经网络,简称BP网络(Back Propagation),该网络是一种单向传播的多层前向网络。误差反向传播的学习算法简称BP算法,其基本思想是梯度下降法,BP神经网络是到目前为止使用最多、最成熟、采用最速下降的学习方式,在训练过程中通过误差的反向传播不断调整网络的权值和阈值,使得网络的误差平方和最小。BP神经网络可以全局逼近任意非线性的映射,具有良好的泛化性能。除此只挖掘,BP神经网络还具有强大的容错能力、鲁棒性好,具有自学习、自组织和自适应性等优点。因此,该神经网络自提出之后就得到了众多研究人员关注,并已经应用于语言综合、语言识别、自适应控制等领域。

输入层、隐含层、输出层

输入层:指的就是我们输入数据的种类数量,输入几类,输入层神经元就有几层

隐含层:夹在输入层和输出层之间的神经元,数量可以自行设置,不过一般通过经验公式来确定:

h = m + n + a h=\sqrt{m+n+a} h=m+n+a

其中,m,n分别为输入、输出层节点个数,a为1-10之间的调节常数。

输出层:指的就是我们相应得到的指标,一般就一个

权值

权值(Weight):输入层与隐含层之间

权值是神经元之间连接的强度,标示量一个神经元的输出与其输入直接的关联程度,在神经网络的每个连接中,都会有一个对应的权值。当一个神经元与另一个神经元连接时,输入信号会乘以对应的权值,然后传递给下一个神经元。权值可以看作是输入信号的重要性或权重,它决定了输入信号对神经元激活状态的影响程度。权值是神经网络训练的主要目标,通过反向传播算法等优化方法,调整权值使得神经网络能够更好地拟合训练数据,准确预测。

阈值

阈值(Biases):隐含层和输出层之间

阈值是神经元的激活门限值,用于控制神经元是否被激活。在神经元接收到加权输入后,会将输入值与对应的权值相加,然后与阈值进行比较。如果加权输入超过了阈值,神经元将被激活,产生输出。否则将保持非激活状态。阈值可以看作是神经元的敏感度,调整阈值可以改变神经元的激活情况。在神经网络的训练过程中,也会优化阈值,以使神经元的激活情况更符合预期输出。

需要注意的是,权值和阈值都是可以学习更新的,神经网络会自动调整,以优化神经网络的性能、准确性。

激活函数

常用的激活函数有两个:

  1. tanh(双曲正切)激活函数:

tanh函数的定义是:

f ( x ) = e x − e − x e x + e − x f(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}} f(x)=ex+exexex

这个函数的输出范围在-1到1之间,即在输入较大或较小时,都能产生较大的输出值,因此相对于Sigmoid函数,它的输出更接近于零中心化,有时能帮助神经网络更快地收敛。

  1. Sigmoid激活函数:

Sigmoid函数的定义是:

f ( x ) = 1 1 + e − x f(x)=\frac{1}{1+e^{-x}} f(x)=1+ex1

这个函数的输出范围是0-1之间,当输入值较大/较小时,输出值接近于0/1,容易导致梯度消失问题,即在反向传播过程中梯度接近于0,使得网络难以更新权重。

这两种激活函数主要用于在神经元之间传递信号并引入非线性特性,它们的主要作用在于增加网络的表达能力,使得神经网络可以学习非线性函数。

Tanh函数的求导过程:

d tanh ⁡ ( x ) d x = 1 − tanh ⁡ 2 ( x ) \frac{d\tanh(x)}{dx} = 1 - \tanh^{2}(x) dxdtanh(x)=1tanh2(x)

Sigmoid函数的求导过程:

d σ ( x ) d x = σ ( x ) ⋅ ( 1 − σ ( x ) ) \frac{d\sigma(x)}{dx} = \sigma(x) \cdot (1 - \sigma(x)) dxdσ(x)=σ(x)(1σ(x))

较小规模选Sigmoid,更复杂的选tanh,因为他在输入较大或较小时有更大的导数,可以有效避免梯度消失问题。

评价指标

BP神经网络优化前后的预测误差评价指标有4种,分别为:

(1)平均绝对误差mae:

m a e = 1 n ∑ i = 1 n ∣ y i − h ( x i ) ∣ mae=\frac{1}{n}\sum^n_{i=1}\lvert{y_i-h(x_i)}\rvert mae=n1i=1nyih(xi)

y y y代表真实值, h ( x i ) h(x_i) h(xi)代表预测值。

(2)均方误差mse:

m s e = ∑ i = 1 n ( X i − x i ) 2 N mse=\frac{\sum^n_{i=1}(X_i-x_i)^2}{N} mse=Ni=1n(Xixi)2

其中, X i X_i Xi x i x_i xi分别为真实值和预测值, N N N为样本个数。

(3)均方误差根rmse:

r m s e = ∑ i = 1 n ( X i − x i ) 2 N rmse=\sqrt{\frac{\sum^n_{i=1}(X_i-x_i)^2}{N}} rmse=Ni=1n(Xixi)2

(4)平均绝对百分比误差mape:

m a p e = 100 % n 1 n ∑ i = 1 n ∣ y i − h ( x i ) y i ∣ mape=\frac{100\%}{n}\frac{1}{n}\sum^n_{i=1}\lvert\frac{{y_i-h(x_i)}}{y_i}\rvert mape=n100%n1i=1nyiyih(xi)

其中, y i y_i yi代表真实值, h ( x i ) h(x_i) h(xi)代表预测值,mape值越小,代表模型拟合的越好越准确,mape=0即完美,mape>100%则表示模型劣质、不合适。

如何建立一个BP神经网络

步骤:

  1. 定义输入和输出:明确你输入的特征/数据是什么,输出是你希望神经网络预测/分类的目标。
  2. 设计网络结构:确定输入层的节点数量(与输入特征数相同)、输出层的节点数量(与输出维度相同),以及中间的隐藏层的数量和节点数量。层数的增加只可能是隐含层的增加。
  3. 初始化权值和阈值:这两个值一般会被初始化为随机值
  4. 前向传播(Forward Propagation):数据从输入层传到输出层,每个神经元中,输入值乘以权重加上阈值,通过激活函数计算神经元的输出。直到累计产生最终预测结果。
  5. 计算损失(Loss):损失函数用于衡量神经网络的预测输出与实际输出之间的误差。常见的损失函数包括包括均方误差和交叉熵损失等。目标是通过训练来最小化损失,使得神经网络的输出尽可能接近真实值。
  6. 反向传播(Back Propagation):反向传播是训练神经网络的核心步骤,通过比较预测输出和实际输出,计算损失函数对权值和阈值的梯度,然后,使用优化算法(如梯度下降法)来根据这些梯度逐渐调整权值和阈值,使损失函数减小。
  7. 重复训练
  8. 验证测试
  9. 模型应用

BP神经网络实际代码

import numpy as np
import math
import random
import string
import matplotlib as mpl
import matplotlib.pyplot as plt

#random.seed(0)  #当我们设置相同的seed,每次生成的随机数相同。如果不设置seed,则每次会生成不同的随机数
               #参考https://blog.csdn.net/jiangjiang_jian/article/details/79031788

#生成区间[a,b]内的随机数
def random_number(a,b):
   return (b-a)*random.random()+a

#生成一个矩阵,大小为m*n,并且设置默认零矩阵
def makematrix(m, n, fill=0.0):
   a = []
   for i in range(m):
       a.append([fill]*n)
   return a

#函数sigmoid(),这里采用tanh,因为看起来要比标准的sigmoid函数好看
def sigmoid(x):
   return math.tanh(x)

#函数sigmoid的派生函数
def derived_sigmoid(x):
   return 1.0 - x**2

#构造三层BP网络架构
class BPNN:
   def __init__(self, num_in, num_hidden, num_out):
       #输入层,隐藏层,输出层的节点数
       self.num_in = num_in + 1  #增加一个偏置结点
       self.num_hidden = num_hidden + 1   #增加一个偏置结点
       self.num_out = num_out
       
       #激活神经网络的所有节点(向量)
       self.active_in = [1.0]*self.num_in
       self.active_hidden = [1.0]*self.num_hidden
       self.active_out = [1.0]*self.num_out
       
       #创建权重矩阵
       self.wight_in = makematrix(self.num_in, self.num_hidden)
       self.wight_out = makematrix(self.num_hidden, self.num_out)
       
       #对权值矩阵赋初值
       for i in range(self.num_in):
           for j in range(self.num_hidden):
               self.wight_in[i][j] = random_number(-0.2, 0.2)
       for i in range(self.num_hidden):
           for j in range(self.num_out):
               self.wight_out[i][j] = random_number(-0.2, 0.2)
   
       #最后建立动量因子(矩阵)
       self.ci = makematrix(self.num_in, self.num_hidden)
       self.co = makematrix(self.num_hidden, self.num_out)        
       
   #信号正向传播
   def update(self, inputs):
       if len(inputs) != self.num_in-1:
           raise ValueError('与输入层节点数不符')
           
       #数据输入输入层
       for i in range(self.num_in - 1):
           #self.active_in[i] = sigmoid(inputs[i])  #或者先在输入层进行数据处理
           self.active_in[i] = inputs[i]  #active_in[]是输入数据的矩阵
           
       #数据在隐藏层的处理
       for i in range(self.num_hidden - 1):
           sum = 0.0
           for j in range(self.num_in):
               sum = sum + self.active_in[i] * self.wight_in[j][i]
           self.active_hidden[i] = sigmoid(sum)   #active_hidden[]是处理完输入数据之后存储,作为输出层的输入数据
           
       #数据在输出层的处理
       for i in range(self.num_out):
           sum = 0.0
           for j in range(self.num_hidden):
               sum = sum + self.active_hidden[j]*self.wight_out[j][i]
           self.active_out[i] = sigmoid(sum)   #与上同理
           
       return self.active_out[:]
   
   #误差反向传播
   def errorbackpropagate(self, targets, lr, m):   #lr是学习率, m是动量因子
       if len(targets) != self.num_out:
           raise ValueError('与输出层节点数不符!')
           
       #首先计算输出层的误差
       out_deltas = [0.0]*self.num_out
       for i in range(self.num_out):
           error = targets[i] - self.active_out[i]
           out_deltas[i] = derived_sigmoid(self.active_out[i])*error
       
       #然后计算隐藏层误差
       hidden_deltas = [0.0]*self.num_hidden
       for i in range(self.num_hidden):
           error = 0.0
           for j in range(self.num_out):
               error = error + out_deltas[j]* self.wight_out[i][j]
           hidden_deltas[i] = derived_sigmoid(self.active_hidden[i])*error
       
       #首先更新输出层权值
       for i in range(self.num_hidden):
           for j in range(self.num_out):
               change = out_deltas[j]*self.active_hidden[i]
               self.wight_out[i][j] = self.wight_out[i][j] + lr*change + m*self.co[i][j]
               self.co[i][j] = change
               
       #然后更新输入层权值
       for i in range(self.num_in):
           for i in range(self.num_hidden):
               change = hidden_deltas[j]*self.active_in[i]
               self.wight_in[i][j] = self.wight_in[i][j] + lr*change + m* self.ci[i][j]
               self.ci[i][j] = change
               
       #计算总误差
       error = 0.0
       for i in range(len(targets)):
           error = error + 0.5*(targets[i] - self.active_out[i])**2
       return error

   #测试
   def test(self, patterns):
       for i in patterns:
           print(i[0], '->', self.update(i[0]))
   #权重
   def weights(self):
       print("输入层权重")
       for i in range(self.num_in):
           print(self.wight_in[i])
       print("输出层权重")
       for i in range(self.num_hidden):
           print(self.wight_out[i])
           
   def train(self, pattern, itera=100000, lr = 0.1, m=0.1):
       for i in range(itera):
           error = 0.0
           for j in pattern:
               inputs = j[0]
               targets = j[1]
               self.update(inputs)
               error = error + self.errorbackpropagate(targets, lr, m)
           if i % 100 == 0:
               print('误差 %-.5f' % error)
   
#实例
def demo():
   patt = [
           [[1,2,5],[0]],
           [[1,3,4],[1]],
           [[1,6,2],[1]],
           [[1,5,1],[0]],
           [[1,8,4],[1]]
           ]
   #创建神经网络,3个输入节点,3个隐藏层节点,1个输出层节点
   n = BPNN(3, 3, 1)
   #训练神经网络
   n.train(patt)
   #测试神经网络
   n.test(patt)
   #查阅权重值
   n.weights()

    
if __name__ == '__main__':
   demo()

运行结果如图所示:

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

城主_全栈开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值