20分钟搞懂神经网络BP算法

简介: 通过一个具体的例子来说明神经网络中的BP算法,使大家能够很直观地感受BP算法的过程,对BP算法加深了解和认识。

在学习深度学习过程中,无意间发现一篇介绍BP算法的文章,感觉非常直观,容易理解。这篇文章的最大亮点是:不像其他介绍BP算法的文章,用一堆数据符号和公式来推导。文中通过使用一条具体的样本数据,为我们展示了模型训练中的参数迭代计算过程,为我们理解BP算法提供了很直观的理解视角;其次,作者也给出了使用python来实现BP的算法。只要你了解过传统神经网络结构以及大学微积分的知识,都可以毫不费力的在20分钟内完全理解BP算法。这里整理出来,供大家学习参考。要看原文的同学,直接跳到文末点击原文链接。


在开始之前,提醒下大家,注意公式中的下标,结合网络结构帮忙我们理解算法推导计算过程和细节。

网络结构和样本数据

跟所有训练神经网络或深度学习模型的流程一样,首先要先确定网络结构。这里为了介绍上的方便,以2个输入节点,2个隐藏节点,2个输出节点的网络(包括bias项)为例,展开对BP算法的介绍。如下图所示:
nn_BP_1.png
下面作者开始引入网络中参数的初始权重,以及一个训练样本,如下图中节点和边上的数值:
nn_BP_2.png

BP算法的目标就是优化神经网络的权重使得学习到的模型能够将输入值正确地映射到实际的输出值(也就是,希望模型能够模型真实数据产生的机制。在统计学中就是,我们要学习一个统计模型(统计分布函数),使得真实数据分布与统计模型产生的样本分布尽可能一致)。

如上图所示,下面的参数求解迭代过程,就是为了使得输入样本是0.05和0.10时(一个2维的样本数据),神经网络的输出与0.01和0.99接近。


前向传播过程

前向传播很简单,就是在已经给定的数据和参数下,按照网络结构来逐层传递数据,最后在输出层计算网络的输出与样本真实的目标值误差,这个误差就是模型的目标函数。

具体到这个case中,在给定模型输出权重和bias的条件下,我们需要把样本数据(0.05,0.10)通过图二中的网络逐步向后传递,看网络的输出与实际的输出的差异。

下面推导计算过程中,网络中使用的激活函数是logistic函数(或sigmoid函数):

$$ \sigma(x) = \frac{1}{1+e^{-x}} $$

首先来计算隐藏节点 $h_1$ 的输入值:

$$ net_{h1} = 0.15 * 0.05 + 0.2 * 0.1 + 0.35 * 1 = 0.3775 $$

得到$h_1$的输入值后,我们使用激活函数(logistic函数)来将输入值转化为为$h_1$的输出值:

$$ out_{h1} = \frac{1}{1+e^{-net_{h1}}} = \frac{1}{1+e^{-0.3775}} = 0.593269992 $$

按同样的方式,我们可以计算$h_2$的输出值:

$$ out_{h2} = 0.596884378 $$

类似于计算$h_1$和$h_2$的过程,我们可以计算输出层节点$o_1$和$o_2$的值。下面是$o_1$的输出值计算过程:

$$ net_{o1} = w_5 * out_{h1} + w_6 * out_{h2} + b_2 * 1 $$

$$ net_{o1} = 0.4 * 0.593269992 + 0.45 * 0.596884378 + 0.6 * 1 = 1.105905967 $$

$$ out_{o1} = \frac{1}{1+e^{-net_{o1}}} = \frac{1}{1+e^{-1.105905967}} = 0.75136507 $$

同样的方式,$o_2$的输出值为:

$$ out_{o2} = 0.772928465 $$

计算模型总误差

得到了网络的输出值后,就可以计算输出值与真实值之间的误差。这里我们使用平方误差来计算模型总误差:

$$ E_{total} = \sum \frac{1}{2}(target - output)^{2} $$

上式中的target就是样本目标值,或真实值。$\frac{1}{2}$只是为了计算上的整洁,对实际参数的估计没有影响 。(The $\frac{1}{2}$ is included so that exponent is cancelled when we differentiate later on. The result is eventually multiplied by a learning rate anyway so it doesn’t matter that we introduce a constant here。)

对于输出节点$o_1$的误差为:

$$ E_{o1} = \frac{1}{2}(target_{o1} - out_{o1})^{2} = \frac{1}{2}(0.01 - 0.75136507)^{2} = 0.274811083 $$

类似的计算方法,$o_2$的误差为:

$$ E_{o2} = 0.023560026 $$

最后,通过这个前向传递后,这个神经网络的总误差为:

$$ E_{total} = E_{o1} + E_{o2} = 0.274811083 + 0.023560026 = 0.298371109 $$

后向传播过程

后向传播过程就是迭代网络参数的过程,通过误差的后向传播得到新的模型参数,基于这个新的模型参数,再经过下一次的前向传播,模型误差会减小,从而使得模型输出值与实际值越接近。

输出层(output layer)

我们先来看了离误差最近的输出层中涉及的参数。以$w_5$为例,我们想知道$w_5$的改变对整体误差的影响,那么我们自然会想到对模型总误差求关于$w_5$的偏导数$\frac{\partial E_{total}}{\partial w_{5}}$。这个值也称为误差在$w_5$方向上的梯度。
应用求导的链式法则,我们可以对偏导数$\frac{\partial E_{total}}{\partial w_{5}}$进行如下的改写:

$$ \frac{\partial E_{total}}{\partial w_{5}} = \frac{\partial E_{total}}{\partial out_{o1}} * \frac{\partial out_{o1}}{\partial net_{o1}} * \frac{\partial net_{o1}}{\partial w_{5}} $$

这个公式可以对应到具体的相应网络结构:
nn_BP_3.png
为了得到$\frac{\partial E_{total}}{\partial w_{5}}$的值,我们需要计算上式中的每个因子的值。首先我们来计算误差关于$o_1$输出值的偏导数,计算方式如下:

$$ E_{total} = \frac{1}{2}(target_{o1} - out_{o1})^{2} + \frac{1}{2}(target_{o2} - out_{o2})^{2} $$

$$ \frac{\partial E_{total}}{\partial out_{o1}} = 2 * \frac{1}{2}(target_{o1} - out_{o1})^{2 - 1} * -1 + 0 $$

$$ \frac{\partial E_{total}}{\partial out_{o1}} = -(target_{o1} - out_{o1}) = -(0.01 - 0.75136507) = 0.74136507 $$

下一步就是要计算$ \frac{\partial out_{o1}}{\partial net_{o1}}$,这个值的含义如上图中所示,就是激活函数对自变量的求导:

$$ out_{o1} = \frac{1}{1+e^{-net_{o1}}} $$

$$ \frac{\partial out_{o1}}{\partial net_{o1}} = out_{o1}(1 - out_{o1}) = 0.75136507(1 - 0.75136507) = 0.186815602 $$

logistic函数对自变量求导,可参考:https://en.wikipedia.org/wiki/Logistic_function#Derivative

现在还需要计算最后一个引子的值$ \frac{\partial net_{o1}}{\partial w_{5}}$,这里$net_{o1}$就是激活函数的输入值:

$$ net_{o1} = w_5 * out_{h1} + w_6 * out_{h2} + b_2 * 1 $$

那么对$w_5$求偏导就很直接了:

$$ \frac{\partial net_{o1}}{\partial w_{5}} = 1 * out_{h1} * w_5^{(1 - 1)} + 0 + 0 = out_{h1} = 0.593269992 $$

得到三个因子后,我们就得到了总误差关于$w_5$的偏导数:

$$ \frac{\partial E_{total}}{\partial w_{5}} = \frac{\partial E_{total}}{\partial out_{o1}} * \frac{\partial out_{o1}}{\partial net_{o1}} * \frac{\partial net_{o1}}{\partial w_{5}} $$

$$ \frac{\partial E_{total}}{\partial w_{5}} = 0.74136507 * 0.186815602 * 0.593269992 = 0.082167041 $$

为了减小误差,我们就可以类似于梯度下降的方式,来更新$w_5$的值:

$$ w_5^{+} = w_5 - \eta * \frac{\partial E_{total}}{\partial w_{5}} = 0.4 - 0.5 * 0.082167041 = 0.35891648 $$

上式中的$\eta$为学习率(learning rate),这里设为0.5. 在实际训练模型中,需要根据实际样本数据和网络结构来进行调整。

以类似的方式,我们同样可以得到 $w_6, w_7, w_8$的更新值:
$w_6^{+} = 0.408666186$
$w_7^{+} = 0.511301270$
$w_8^{+} = 0.561370121$
至此,我们得到了输出层节点中的参数更新值。下面我们以同样的方式来更新隐藏层节点中的参数值。

隐藏层 (hidden layer)

在隐藏层中,同样地,我们对总误差求关于$w_1, w_2, w_3, w_4$的偏导数,来获得更新值。首先还是应用求导的链式法则对总误差关于$w_1, w_2, w_3, w_4$的偏导数,以$w_1$为例,分解如下:

$$ \frac{\partial E_{total}}{\partial w_{1}} = \frac{\partial E_{total}}{\partial out_{h1}} * \frac{\partial out_{h1}}{\partial net_{h1}} * \frac{\partial net_{h1}}{\partial w_{1}} $$

用网络结构图来表示如下,从图中可以更直观地理解这种分解的物理意义:
nn_BP_4.png

与输出层中对权重求偏导数不同的一个地方是,由于每个隐藏层节点都会影响所有的输出层节点,在求总误差对隐藏层的输出变量求偏导数时,需要对组成总误差的每个输出层节点误差进行分别求偏导数。具体如下:

$$ \frac{\partial E_{total}}{\partial out_{h1}} = \frac{\partial E_{o1}}{\partial out_{h1}} + \frac{\partial E_{o2}}{\partial out_{h1}} $$

我们先来求第一项$\frac{\partial E_{o1}}{\partial out_{h1}}$的值,过程如下:

$$ \frac{\partial E_{o1}}{\partial out_{h1}} = \frac{\partial E_{o1}}{\partial net_{o1}} * \frac{\partial net_{o1}}{\partial out_{h1}} $$

$$ \frac{\partial E_{o1}}{\partial net_{o1}} = \frac{\partial E_{o1}}{\partial out_{o1}} * \frac{\partial out_{o1}}{\partial net_{o1}} = 0.74136507 * 0.186815602 = 0.138498562 $$

这一步可以利用输出层的计算结果。

$$ net_{o1} = w_5 * out_{h1} + w_6 * out_{h2} + b_2 * 1 $$

$$ \frac{\partial net_{o1}}{\partial out_{h1}} = w_5 = 0.40 $$

因此,

$$ \frac{\partial E_{o1}}{\partial out_{h1}} = \frac{\partial E_{o1}}{\partial net_{o1}} * \frac{\partial net_{o1}}{\partial out_{h1}} = 0.138498562 * 0.40 = 0.055399425 $$

类似地,我们可以求得$\frac{\partial E_{o2}}{\partial out_{h1}}$的值:

$$ \frac{\partial E_{o2}}{\partial out_{h1}} = -0.019049119 $$

那么我们就可以得到$\frac{\partial E_{total}}{\partial out_{h1}}$的值:

$$ \frac{\partial E_{total}}{\partial out_{h1}} = \frac{\partial E_{o1}}{\partial out_{h1}} + \frac{\partial E_{o2}}{\partial out_{h1}} = 0.055399425 + -0.019049119 = 0.036350306 $$

我们还需要计算$ \frac{\partial out_{h1}}{\partial net_{h1}}$和$\frac{\partial net_{h1}}{\partial w}$就可以得到$\frac{\partial E_{total}}{\partial w_{1}}$的值了。这两个值的计算方法跟输出层的完全类似,过程如下:

$$ out_{h1} = \frac{1}{1+e^{-net_{h1}}} $$

$$ \frac{\partial out_{h1}}{\partial net_{h1}} = out_{h1}(1 - out_{h1}) = 0.59326999(1 - 0.59326999 ) = 0.241300709 $$

$$ net_{h1} = w_1 * i_1 + w_3 * i_2 + b_1 * 1 $$

$$ \frac{\partial net_{h1}}{\partial w_1} = i_1 = 0.05 $$

最后把三个因子相乘就是我们需要计算的值:

$$ \frac{\partial E_{total}}{\partial w_{1}} = \frac{\partial E_{total}}{\partial out_{h1}} * \frac{\partial out_{h1}}{\partial net_{h1}} * \frac{\partial net_{h1}}{\partial w_{1}} $$

$$ \frac{\partial E_{total}}{\partial w_{1}} = 0.036350306 * 0.241300709 * 0.05 = 0.000438568 $$

$w_1$的更新值为:

$$ w_1^{+} = w_1 - \eta * \frac{\partial E_{total}}{\partial w_{1}} = 0.15 - 0.5 * 0.000438568 = 0.149780716 $$

同样的方式,$ w_2, w_3, w_4$的更新值为:

$$ w_2^{+} = 0.19956143 $$

$$ w_3^{+} = 0.24975114 $$

$$ w_4^{+} = 0.29950229 $$

从上面更新隐藏层节点参数的过程中,我们可以看到,这里的更新并没有用到输出层节点更新后的参数的值,还是基于老的参数来进行的。这个不能搞混。

上面的计算中,并没有对bias项的权重进行更新,更新方式其实也很简单。可以类似操作。

至此,我们已经完成了一轮BP的迭代。经过这轮迭代后,基于新的参数,再走一遍前向传播来计算新的模型误差,这时已经下降到0.291027924,相比第一次的误差 0.298371109貌似没减少太多。但是我们重复这个过程10000次后,误差已经下降到0.0000351085,下降了很多。这时模型的输出结果为0.015912196和0.984065734,跟实际的结果0.01和0.99已经很接近了。


这里只是一个样本数据,那么我们有很多样本呢?很多样本的情况下的计算跟这一个样本数据相比,有什么不同呢?自己比划比划吧~


原文链接地址:https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/
pyhont代码:https://github.com/mattm/simple-neural-network/blob/master/neural-network.py

附:神经网络入门材料:http://neuralnetworksanddeeplearning.com/index.html 可以整体上了解神经网络结构以及训练过程中存在的问题。虽然是英文,但使用的词汇都比较简单,看起来很顺畅

相关文章
|
12天前
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
眼疾识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了4种常见的眼疾图像数据集(白内障、糖尿病性视网膜病变、青光眼和正常眼睛) 再使用通过搭建的算法模型对数据集进行训练得到一个识别精度较高的模型,然后保存为为本地h5格式文件。最后使用Django框架搭建了一个Web网页平台可视化操作界面,实现用户上传一张眼疾图片识别其名称。
57 4
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
|
25天前
|
机器学习/深度学习 算法
基于改进遗传优化的BP神经网络金融序列预测算法matlab仿真
本项目基于改进遗传优化的BP神经网络进行金融序列预测,使用MATLAB2022A实现。通过对比BP神经网络、遗传优化BP神经网络及改进遗传优化BP神经网络,展示了三者的误差和预测曲线差异。核心程序结合遗传算法(GA)与BP神经网络,利用GA优化BP网络的初始权重和阈值,提高预测精度。GA通过选择、交叉、变异操作迭代优化,防止局部收敛,增强模型对金融市场复杂性和不确定性的适应能力。
165 80
|
13天前
|
机器学习/深度学习 数据采集 算法
基于GA遗传优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
本项目基于MATLAB2022a实现时间序列预测,采用CNN-GRU-SAM网络结构。卷积层提取局部特征,GRU层处理长期依赖,自注意力机制捕捉全局特征。完整代码含中文注释和操作视频,运行效果无水印展示。算法通过数据归一化、种群初始化、适应度计算、个体更新等步骤优化网络参数,最终输出预测结果。适用于金融市场、气象预报等领域。
基于GA遗传优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
|
8天前
|
机器学习/深度学习 算法 计算机视觉
基于CNN卷积神经网络的金融数据预测matlab仿真,对比BP,RBF,LSTM
本项目基于MATLAB2022A,利用CNN卷积神经网络对金融数据进行预测,并与BP、RBF和LSTM网络对比。核心程序通过处理历史价格数据,训练并测试各模型,展示预测结果及误差分析。CNN通过卷积层捕捉局部特征,BP网络学习非线性映射,RBF网络进行局部逼近,LSTM解决长序列预测中的梯度问题。实验结果表明各模型在金融数据预测中的表现差异。
|
12天前
|
存储 监控 算法
局域网网络管控里 Node.js 红黑树算法的绝妙运用
在数字化办公中,局域网网络管控至关重要。红黑树作为一种自平衡二叉搜索树,凭借其高效的数据管理和平衡机制,在局域网设备状态管理中大放异彩。通过Node.js实现红黑树算法,可快速插入、查找和更新设备信息(如IP地址、带宽等),确保网络管理员实时监控和优化网络资源,提升局域网的稳定性和安全性。未来,随着技术融合,红黑树将在网络管控中持续进化,助力构建高效、安全的局域网络生态。
33 9
|
18天前
|
机器学习/深度学习 算法
基于遗传优化的双BP神经网络金融序列预测算法matlab仿真
本项目基于遗传优化的双BP神经网络实现金融序列预测,使用MATLAB2022A进行仿真。算法通过两个初始学习率不同的BP神经网络(e1, e2)协同工作,结合遗传算法优化,提高预测精度。实验展示了三个算法的误差对比结果,验证了该方法的有效性。
|
21天前
|
机器学习/深度学习 数据采集 算法
基于PSO粒子群优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
本项目展示了基于PSO优化的CNN-GRU-SAM网络在时间序列预测中的应用。算法通过卷积层、GRU层、自注意力机制层提取特征,结合粒子群优化提升预测准确性。完整程序运行效果无水印,提供Matlab2022a版本代码,含详细中文注释和操作视频。适用于金融市场、气象预报等领域,有效处理非线性数据,提高预测稳定性和效率。
|
19天前
|
算法 网络协议 Python
探秘Win11共享文件夹之Python网络通信算法实现
本文探讨了Win11共享文件夹背后的网络通信算法,重点介绍基于TCP的文件传输机制,并提供Python代码示例。Win11共享文件夹利用SMB协议实现局域网内的文件共享,通过TCP协议确保文件传输的完整性和可靠性。服务器端监听客户端连接请求,接收文件请求并分块发送文件内容;客户端则连接服务器、接收数据并保存为本地文件。文中通过Python代码详细展示了这一过程,帮助读者理解并优化文件共享系统。
|
15天前
|
传感器 算法
基于GA遗传优化的WSN网络最优节点部署算法matlab仿真
本项目基于遗传算法(GA)优化无线传感器网络(WSN)的节点部署,旨在通过最少的节点数量实现最大覆盖。使用MATLAB2022A进行仿真,展示了不同初始节点数量(15、25、40)下的优化结果。核心程序实现了最佳解获取、节点部署绘制及适应度变化曲线展示。遗传算法通过初始化、选择、交叉和变异步骤,逐步优化节点位置配置,最终达到最优覆盖率。
|
1月前
|
SQL 安全 网络安全
网络安全与信息安全:知识分享####
【10月更文挑战第21天】 随着数字化时代的快速发展,网络安全和信息安全已成为个人和企业不可忽视的关键问题。本文将探讨网络安全漏洞、加密技术以及安全意识的重要性,并提供一些实用的建议,帮助读者提高自身的网络安全防护能力。 ####
68 17

热门文章

最新文章