-
lms算法的verilog实现_神经网络设计(第十二章-反向传播算法的变形)
2020-12-14 16:07:00第十二章 反向传播算法的变形反向传播算法的缺点LMS算法中,只要学习率的值不是太大,都能保证收敛到一个能最小化均方误差的解。因为对于一个单层的线性网络来说,均方误差是一个二次函数,二次函数仅有一个驻点。第...本章先研究了固定学习率最速下降反向传播算法运算时的一些局限性,然后从改进SDBP和其他衍生算法的角度来解决这些局限性。
第十二章 反向传播算法的变形
反向传播算法的缺点
LMS算法中,只要学习率的值不是太大,都能保证收敛到一个能最小化均方误差的解。因为对于一个单层的线性网络来说,均方误差是一个二次函数,二次函数仅有一个驻点。
第11章中的最速下降反向传播算法(SDBP)是LMS算法的推广,也是一种最小化均方误差的近似最速下降法。事实上,应用于单层线性神经网络时,SDBP算法等价于LMS算法。但是应用于多层网络时,SDBP算法的特性非常不同,因为多层网络的性能曲面可能会存在多个局部极小值点。
1. 性能曲面的实例
这里举一个函数逼近的例子来研究多层网络均方误差的性能曲面。上图是一个1-2-1结构的网络,其中每一层的传输函数都使用对数-S型函数。我们使用这样一个网络(记为网络1)逼近与它结构完全相同,且权值和偏置值设置如下的另一个网络(记为网络2)。
网络2的输入p在[-2 2]之间取值时,其输出如下图。
假设函数在多个p值处采样:
,且每个样本出现的概率是一样的。性能指标将是41个点的平方误差之和(省去了均方误差计算)。此时性能指标是权值和偏置值函数。为了能绘出性能指标的图像,一次只改变两个变量进行研究。
当仅调整
,其他变量被设置为网络2的参数(即最优值)时,性能指标的图像如下。仅当
时最小误差为0,在等高线图中用圆圈表示。在右侧的函数图像中可以看出,性能指标不是一个二次函数,且曲率在空间上急剧变化,因此为最速下降算法选择一个合适的学习率是很困难的。这个曲面在某些区域非常平缓,允许使用大一点的学习率,而在某些曲率比较大的地方,则需要较小的学习率(之前证明过学习率与曲率的大小成反比)。误差曲面的另一个特点是存在多个局部极小值点,由等高线可以看出,全局极小值点存在于与 轴平行的凹槽中,即
处。然而与
轴平行的凹槽中还有一个局部极小值点。
当仅调整
,其他变量被设置为网络2的参数(即最优值)时,性能指标的图像如下。仅当
时最小误差为0,在等高线图中用圆圈表示。这个函数图像十分扭曲,有些地方十分陡峭,有些地方非常平缓,在这样的曲面上使用标准的最速下降法会遇到一些麻烦,因为在某些点处的梯度会十分接近于0,容易使最速下降法提前结束。
当仅调整
,其他变量被设置为网络2的参数(即最优值)时,性能指标的图像如下。仅当
时最小误差为0,在等高线图中用圆圈表示。这个曲面展示了多层神经网络的对称性。这里可以看到有两个局部极小点,这两个点处取得相同的平方误差。第二个解对应于被倒置的同一个网络(即第一层顶部的神经元与底部的神经元进行互换)。正是由于这个特性,我们一般不把权值以及偏置值的初始值设置为零。对称性将会导致零点变成性能曲面上的鞍点。
关于以上几个曲面的简要研究提供了一些关于如何设置SDBP初始参数的提示。
首先,不要把初始参数设置为0,这是因为参数空间的原点倾向于成为性能曲面的鞍点。
其次,不要把参数的初始值设置的太大,因为远离最优点时,性能曲面倾向于具有非常平缓的区域。
2. 收敛性的实例
在这一部分中,将使用批处理的方法代替在线学习的方法,也就是说参数的更新在整个训练数据集都传过神经网络后在进行。将对每个训练样本计算的梯度求平均值,以得到对梯度更加准确地估计。
当仅调整
,其他变量被设置为网络2的参数(即最优值)时,性能指标的图像如下。
当初始化条件为a点时,算法最终会收敛到最优结果,但是由于该轨迹的路径所经过的性能曲面的变化的原因,在平缓的区域使用小的学习率导致收敛速度比较慢。如果增大学习率收敛速度会加快,但落在凹槽中时会变得不稳定。当初始化条件为b时,最终收敛到局部极小值点,在有多个局部极小值点的情况普遍存在于多层网络的性能曲面,因此在取初始值时尽可能尝试多种情况,以保证可以获得全局极小值。
反向传播算法的启发式改进
1. 冲量
由上一部分中的观察可以发现,如果可以在平滑的轨迹上振荡,将会提升算法的收敛性。我们可以使用一个低通滤波器来平滑这种振荡。
首先,举一个例子研究平滑的影响,考虑下面的一阶滤波器:
如果将以下正弦波作为输入,冲量系数分别设置成
和
滤波器的输入与输出如下图。
可以看到滤波器的输出振荡小于滤波器的输入振荡。当γ增加的时候,滤波器的输出振荡在减小。滤波器的平均输出和平均输入是相同的。随着γ的增加,滤波器对输入的响应越来越慢。总得来说,滤波器会逐渐减小振荡的总量,同时追踪平均值。
现在让我们看一看在神经网络问题上滤波器如何工作。回顾一下SDBP算法中的更新过程:
当冲量滤波器被应用于参数更新时,我们可以得到下面反向传播算法的冲量改进公式:
如果将这些改进的等式用于前面介绍的例子中,得到的结果将如下图所示。
这里使用了批处理的方法,即参数的更新仅在整个训练集传过网络后进行,每个训练样本上计算出的梯度被平均以得到更准确的梯度估计。
在同样的学习率与初始条件下,如果不使用滤波器,得到的结果是不稳定的,这说明了学习率过大以至于在性能曲面较陡峭的地方不稳定。
对比上面两图,可以得出结论,通过使用冲量,我们可以使用较大的学习率同时保持算法的稳定性。冲量的另外一个特征是:当轨迹保持在一致的移动方向上时,会加速收敛。
2. 可变学习率
单层线性神经网络的均方误差性能曲面总是一个二次函数,因而它的Hessian矩阵也是常数矩阵。最速下降算法的最大稳定学习率是2除以Hessian矩阵的最大特征值。
多层神经网络的误差曲面不是一个二次函数,曲面形状在参数空间的不同区域可以是非常不同的。在训练过程中,我们也许可以通过调整学习率来加速收敛。这个技巧的关键在于改变学习率的时机,以及改变的程度。
有很多不同的变化学习率的方法,这里介绍一种非常直接的批处理过程,其中学习率根据算法性能的变化而变化,这种可变学习率反向传播算法(VLBP)的规则为:
- 当一次权值更新之后,如果平方误差(在整个训练集上)增长率超过某一个设置好的百分比ζ(一般设置为1%~5%),那么就丢弃这个对权值的更新,同时学习率乘以一个小于1的因子(0<ρ<1),并将冲量系数γ(如果在此算法中使用冲量)设置为0。
- 如果平方误差在某一次权值更新后下降,就接受对这个权值的更新,同时学习率乘以一个大于1的因子(η>1)。如果γ已经在之前被设置为0,则将其设置为最初的初始值。
- 如果平方误差增长率小于ζ,就接受这个对权值的更新,但学习率保持不变。如果γ已经被设置为0,将它重新设置成最初的值。
为了阐述VLBP,我们把它应用到前面部分的函数逼近问题。算法中的初始值、初始学习率以及冲量系数的设置与之前相同。其他参数设置为
。
图中可以看出,当轨迹达到一个狭窄的凹槽时,学习率被迅速减小。否则,轨迹会变得振荡,并且误差会急剧增加。对于每一个可能会使误差增长超过4%的步长,学习率将被减小,同时冲量会被消除,这样可以允许轨迹快速转到指向极小值点的凹槽方向。然后学习率将会再次增加,从而加速收敛。当轨迹跨过极小值点时,此学习率将会再一次被减小,此时这个算法已经基本收敛。
SDBP算法的启发式改进在某些问题上将会大大提高收敛速度,然而这些方法有两个主要的缺点:第一个缺点是这些改进方法需要设置多个参数,而SDBP算法中只需要设置学习率一个参数,一些更复杂的启发式改进算法需要选择5~6个参数,并且算法对这些参数的变化十分敏感,参数的选择同样依赖于问题本身。第二个缺点是在解决某些问题时可能无法收敛,而SDBP则总能找到一个解。
数值优化技术
1. 共轭梯度
之前介绍过用于二次函数的共轭梯度法,本节中将描述如何使用共轭梯度算法来训练多层网络,该算法称为共轭梯度反向传播算法。
首先回顾一下共轭梯度算法:
- 选择第一个搜索方向p0,使其与梯度方向相反,即
,其中
- 选择一个学习率αk沿着此搜索方向最小化该函数:
- 选择下一个搜索方向
, 其中
- 如果算法还未收敛,继续跳至步骤2执行。
由于神经网络的性能指标不是二次的,共轭梯度算法不能直接应用于神经网络的训练任务。具体地说,我们不能像第二步那样沿着某一条直线来最小化函数。其次,准确的最小值通常不能在有限步内获得,所以算法需要在若步迭代之后重置。
我们可以通过线性搜索来来确定每一步的学习率αk以解决第一个问题。线性搜索即一维搜索,其中分为两步。首先是寻找极小值存在的区间,然后是缩小区间找到极小值点。
AirHead:工程优化设计与Matlab实现——一维搜索方法(一)zhuanlan.zhihu.comAirHead:工程优化设计与Matlab实现——一维搜索方法(二)zhuanlan.zhihu.comAirHead:工程优化设计与Matlab实现——一维搜索方法(三)zhuanlan.zhihu.comAirHead:工程优化设计与Matlab实现——一维搜索方法(总结)zhuanlan.zhihu.com对于二次函数,算法将会在最多n步内收敛到最小值,其中n是被优化的参数的个数。多层网络的均方误差性能指标不是二次的,因此这个算法将不会在有限的n步内收敛。共轭梯度算法并没有指出当n步的迭代周期完成之后采用什么搜索方向。目前已经提出了很多方法,但最简单的方法是在n次迭代之后重新把最速下降的方向设置为搜索方向(梯度的反方向)。
共轭梯度算法同样是一种批处理算法,虽然共轭梯度算法迭代次数比其他算法少很多,但其每一次迭代的计算量比其他算法需要更多的计算量。但尽管如此,对于多层网络来说CGBP已经被证明是最快的批处理训练算法之一。
2. Levenberg-Marquardt算法
Levenberg-Marquardt算法是牛顿法的一种变形,用于最小化非线性函数的平方和。
首先回顾一下牛顿法:
如果假设F(x)是平方函数的和:
那么梯度的第j个元素按照下面的方式进行计算:
这些梯度可以写成矩阵形式:
Hessian矩阵的第(k,j)项元素是:
Hessian矩阵可以写成矩阵形式:
如果假设S(x)是一个较小的值,可以得到Hessian矩阵的近似:
所以可以得到高斯牛顿法:
高斯牛顿法的相比于牛顿法具有不需要计算二次导数的优点。高斯牛顿法的一个问题是
的逆可能不存在。对近似的Hessian矩阵做一下改进,以解决这个问题。
假设Hessian矩阵的特征值和特征向量分别是
,那么:
因此G的特征向量和H的特征向量是一样的,G的特征值是
。通过加大μ的值直到
对所有取值i都满足,G可以被转换成正定矩阵,然后这个矩阵将是可逆的。
这就得到了Levenberg-Marquardt算法:
这个算法的特点在于,当μk增加时,它的效果接近采用最小学习率的最速下降法
当μk减小到0时,这个算法就变成了高斯-牛顿法。
算法开始的时候设置μk乘上某一个较小的值(比如μk=0.01)。如果一步之后并没有得到F(x)的一个更小的值,那么将μk乘上某一个大于1的因子
(例如
)并重复这个步骤。由于在最速下降的方向上前进了一小步,F(x)将会逐渐减小。如果产生了一个较小的值F(x),那么在下一步中,μk除以因子
,这样这个算法将接近于高斯牛顿算法,将会提高收敛速度。此算法综合了牛顿法的训练速度以及最速下降法算法的确定性的收敛性。
将LMBP算法应用到之前的函数逼近问题中,取
,LMBP的收敛轨迹的路径如下图。此算法收敛所需的迭代数比前面讨论的所有算法都要少。因为需要求解矩阵的逆,该算法比其他算法在每一次迭代中需要更多的计算。
LMBP的主要缺点是存储的需求。这个算法需要存储近似Hessian矩阵,这是一个n×n的矩阵,其中n是神经网络中参数(权值与偏置值的个数)。
-
深度学习-误差反向传播算法
2020-10-17 09:31:381.误差反向传播算法是干什么的? 通过数值微分计算了神经网络的权重参数的梯度,数值微分虽然简单,也容易实现,但缺点是计算上比较费时间。本章我们将学习一个能够高效计算权重参数的梯度的方法——误差反向传播法...
一.误差反向传播算法是干什么的?
通过数值微分计算了神经网络的权重参数的梯度,数值微分虽然简单,也容易实现,但缺点是计算上比较费时间。本章我们将学习一个能够高效计算权重参数的梯度的方法——误差反向传播法。
用梯度的方法计算神经网络的权重参数,虽然理解起来很容易理解,但是费时间。
二.计算图
(一) 了解计算图的写法
计算图是从左到右的进行计算, 圆圈里面写 计算符号 ,将计算的中间结果写在箭头的上方,表示各个节点的计算结果从左向右传递。
正向传播是从计算图出发点到结束点的传播,当然也可以考虑反向的传播,这种传播称为反向传播。
(二)局部计算
计算图的特征是可以通过传递“局部计算”获得最终结果。“局部”这个词的意思是“与自己相关的某个小范围”。局部计算是指,无论全局发生了什么,都能只根据与自己相关的信息输出接下来的结果。
计算图可以集中精力于局部计算。无论全局的计算有多么复杂,各个步骤所要做的就是对象节点的局部计算。虽然局部计算非常简单,但是通过传递它的计算结果,可以获得全局的复杂计算的结果。
(三) 计算图的优点
- 简化计算过程,让我们可以集中尽力于局部的计算
- 可以保留中间的计算过程,比如最后的总钱数,(这个特点,在误差反向传播算法的,可以实现结果的共享,大大的加快了计算的过程)
- 其实更重要的,是可以通过反向传播高效的计算导数
三. 误差反向传播算法的具体实现
下面来看,具体是如何进行计算的:
如图所示,反向传播使用与正方向相反的箭头(粗线)表示。反向传播传递“局部导数”,将导数的值写在箭头的下方。在这个例子中,反向传播从右向左传递导数的值(1→1.1→2.2)。从这个结果中可,“支付金额关于苹果的价格的导数”的值是2.2。这意味着,如果苹果的价格上涨1日元,最终的支付金额会增加2.2日元。
付金额关于苹果的个数的导数”等也都可以用同样的方式算出来。并且,计算中途求得的导数的结果(中间传递的导数)可以被共享,从而可以高效地计算多个导数。综上,计算图的优点是,可以通过正向传播和反向传播高效地计算各个变量的导数值。
-
误差与反向传播算法
2020-06-18 02:49:33我们将学习一个能够高效计算权重参数的梯度的方法——误差反向传播法。 5.1 计算图 计算图将计算过程用图形表示出来。这里说的图形是数据结构图,通过多个节点和边表示(连接节点的直线称为“边”)。为了让大家...我们介绍了神经网络的学习,并通过数值微分计算了神经网络的权重参数的梯度(严格来说,是损失函数关于权重参数的梯度)。数值微分虽然简单,也容易实现,但缺点是计算上比较费时间。我们将学习一个能够高效计算权重参数的梯度的方法——误差反向传播法。
5.1 计算图
计算图将计算过程用图形表示出来。这里说的图形是数据结构图,通过多个节点和边表示(连接节点的直线称为“边”)。为了让大家熟悉计算图,本节先用计算图解一些简单的问题。从这些简单的问题开始,逐步深入,最终抵达误差反向传播法。
5.1.1 用计算图求解
现在,我们尝试用计算图解简单的问题。下面我们要看的几个问题都是用心算就能解开的简单问题,这里的目的只是通过它们让大家熟悉计算图。掌握了计算图的使用方法之后,在后面即将看到的复杂计算中它将发挥巨大威力,所以本节请一定学会计算图的使用方法。
问题 1:太郎在超市买了 2 个 100 日元一个的苹果,消费税是 10%,请计算支付金额。
计算图通过节点和箭头表示计算过程。节点用○表示,○中是计算的内容。将计算的中间结果写在箭头的上方,表示各个节点的计算结果从左向右传递。用计算图解问题 1,求解过程如图 5-1 所示。
图 5-1 基于计算图求解的问题 1 的答案
如图 5-1 所示,开始时,苹果的 100 日元流到“× 2”节点,变成 200 日元,然后被传递给下一个节点。接着,这个 200 日元流向“× 1.1”节点,变成 220 日元。因此,从这个计算图的结果可知,答案为 220 日元。
虽然图 5-1 中把“× 2”“× 1.1”等作为一个运算整体用○括起来了,不过只用○表示乘法运算“×”也是可行的。此时,如图 5-2 所示,可以将“2”和“1.1”分别作为变量“苹果的个数”和“消费税”标在○外面。
图 5-2 基于计算图求解的问题 1 的答案:“苹果的个数”和“消费税”作为变量标在○外面
再看下一题。
问题 2:太郎在超市买了 2 个苹果、3 个橘子。其中,苹果每个 100 日元,橘子每个 150 日元。消费税是 10%,请计算支付金额。
同问题 1,我们用计算图来解问题 2,求解过程如图 5-3 所示。
图 5-3 基于计算图求解的问题 2 的答案
这个问题中新增了加法节点“+”,用来合计苹果和橘子的金额。构建了计算图后,从左向右进行计算。就像电路中的电流流动一样,计算结果从左向右传递。到达最右边的计算结果后,计算过程就结束了。从图 5-3 中可知,问题 2 的答案为 715 日元。
综上,用计算图解题的情况下,需要按如下流程进行。
- 构建计算图。
- 在计算图上,从左向右进行计算。
这里的第 2 歩“从左向右进行计算”是一种正方向上的传播,简称为正向传播(forward propagation)。正向传播是从计算图出发点到结束点的传播。既然有正向传播这个名称,当然也可以考虑反向(从图上看的话,就是从右向左)的传播。实际上,这种传播称为反向传播(backward propagation)。反向传播将在接下来的导数计算中发挥重要作用。
5.1.2 局部计算
计算图的特征是可以通过传递“局部计算”获得最终结果。“局部”这个词的意思是“与自己相关的某个小范围”。局部计算是指,无论全局发生了什么,都能只根据与自己相关的信息输出接下来的结果。
我们用一个具体的例子来说明局部计算。比如,在超市买了 2 个苹果和其他很多东西。此时,可以画出如图 5-4 所示的计算图。
图 5-4 买了 2 个苹果和其他很多东西的例子
如图 5-4 所示,假设(经过复杂的计算)购买的其他很多东西总共花费 4000 日元。这里的重点是,各个节点处的计算都是局部计算。这意味着,例如苹果和其他很多东西的求和运算(4000 + 200 → 4200)并不关心 4000 这个数字是如何计算而来的,只要把两个数字相加就可以了。换言之,各个节点处只需进行与自己有关的计算(在这个例子中是对输入的两个数字进行加法运算),不用考虑全局。
综上,计算图可以集中精力于局部计算。无论全局的计算有多么复杂,各个步骤所要做的就是对象节点的局部计算。虽然局部计算非常简单,但是通过传递它的计算结果,可以获得全局的复杂计算的结果。
5.1.3 为何用计算图解题
前面我们用计算图解答了两个问题,那么计算图到底有什么优点呢?一个优点就在于前面所说的局部计算。无论全局是多么复杂的计算,都可以通过局部计算使各个节点致力于简单的计算,从而简化问题。另一个优点是,利用计算图可以将中间的计算结果全部保存起来(比如,计算进行到 2 个苹果时的金额是 200 日元、加上消费税之前的金额 650 日元等)。但是只有这些理由可能还无法令人信服。实际上,使用计算图最大的原因是,可以通过反向传播高效计算导数。
在介绍计算图的反向传播时,我们再来思考一下问题 1。问题 1 中,我们计算了购买 2 个苹果时加上消费税最终需要支付的金额。这里,假设我们想知道苹果价格的上涨会在多大程度上影响最终的支付金额,即求“支付金额关于苹果的价格的导数”。设苹果的价格为 x,支付金额为 L,则相当于求
。这个导数的值表示当苹果的价格稍微上涨时,支付金额会增加多少。如前所述,“支付金额关于苹果的价格的导数”的值可以通过计算图的反向传播求出来。先来看一下结果,如图 5-5 所示,可以通过计算图的反向传播求导数(关于如何进行反向传播,接下来马上会介绍)。
图 5-5 基于反向传播的导数的传递
如图 5-5 所示,反向传播使用与正方向相反的箭头(粗线)表示。反向传播传递“局部导数”,将导数的值写在箭头的下方。在这个例子中,反向传播从右向左传递导数的值(1 → 1.1 → 2.2)。从这个结果中可知,“支付金额关于苹果的价格的导数”的值是 2.2。这意味着,如果苹果的价格上涨 1 日元,最终的支付金额会增加 2.2 日元(严格地讲,如果苹果的价格增加某个微小值,则最终的支付金额将增加那个微小值的 2.2 倍)。
这里只求了关于苹果的价格的导数,不过“支付金额关于消费税的导数”“支付金额关于苹果的个数的导数”等也都可以用同样的方式算出来。并且,计算中途求得的导数的结果(中间传递的导数)可以被共享,从而可以高效地计算多个导数。综上,计算图的优点是,可以通过正向传播和反向传播高效地计算各个变量的导数值。
5.2 链式法则
前面介绍的计算图的正向传播将计算结果正向(从左到右)传递,其计算过程是我们日常接触的计算过程,所以感觉上可能比较自然。而反向传播将局部导数向正方向的反方向(从右到左)传递,一开始可能会让人感到困惑。传递这个局部导数的原理,是基于链式法则(chain rule)的。本节将介绍链式法则,并阐明它是如何对应计算图上的反向传播的。
5.2.1 计算图的反向传播
话不多说,让我们先来看一个使用计算图的反向传播的例子。假设存在 y = f (x) 的计算,这个计算的反向传播如图 5-6 所示。
图 5-6 计算图的反向传播:沿着与正方向相反的方向,乘上局部导数
这就是反向传播的计算顺序。通过这样的计算,可以高效地求出导数的值,这是反向传播的要点。那么这是如何实现的呢?我们可以从链式法则的原理进行解释。下面我们就来介绍链式法则。
5.2.2 什么是链式法则
介绍链式法则时,我们需要先从复合函数说起。复合函数是由多个函数构成的函数。比如,z=(x+y)2 是由式(5.1)所示的两个式子构成的。
链式法则是关于复合函数的导数的性质,定义如下。
如果某个函数由复合函数表示,则该复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示。
5.2.3 链式法则和计算图
现在我们尝试将式(5.4)的链式法则的计算用计算图表示出来。如果用“
**2
”节点表示平方运算的话,则计算图如图 5-7 所示。图 5-7 式(5.4)的计算图:沿着与正方向相反的方向,乘上局部导数后传递
**图 5-8 根据计算图的反向传播的结果
5.3 反向传播
上一节介绍了计算图的反向传播是基于链式法则成立的。本节将以“+”和“×”等运算为例,介绍反向传播的结构。
5.3.1 加法节点的反向传播
首先来考虑加法节点的反向传播。这里以 z = x + y 为对象,观察它的反向传播。z = x + y 的导数可由下式(解析性地)计算出来。
图 5-10 加法节点存在于某个最后输出的计算的一部分中。反向传播时,从最右边的输出出发,局部导数从节点向节点反方向传播
现在来看一个加法的反向传播的具体例子。假设有“10 + 5=15”这一计算,反向传播时,从上游会传来值 1.3。用计算图表示的话,如图 5-11 所示。
图 5-11 加法节点的反向传播的具体例子
因为加法节点的反向传播只是将输入信号输出到下一个节点,所以如图 5-11 所示,反向传播将 1.3 向下一个节点传递。
5.3.2 乘法节点的反向传播
接下来,我们看一下乘法节点的反向传播。这里我们考虑 z = xy。这个式子的导数用式(5.6)表示。
根据式(5.6),可以像图 5-12 那样画计算图。
图 5-12 乘法的反向传播:左图是正向传播,右图是反向传播
乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值”后传递给下游。翻转值表示一种翻转关系,如图 5-12 所示,正向传播时信号是 x 的话,反向传播时则是 y;正向传播时信号是 y 的话,反向传播时则是 x。
现在我们来看一个具体的例子。比如,假设有“10 × 5 = 50”这一计算,反向传播时,从上游会传来值 1.3。用计算图表示的话,如图 5-13 所示。
图 5-13 乘法节点的反向传播的具体例子
因为乘法的反向传播会乘以输入信号的翻转值,所以各自可按 1.3 × 5 = 6.5、1.3 × 10 = 13 计算。另外,加法的反向传播只是将上游的值传给下游,并不需要正向传播的输入信号。但是,乘法的反向传播需要正向传播时的输入信号值。因此,实现乘法节点的反向传播时,要保存正向传播的输入信号。
5.3.3 苹果的例子
再来思考一下本章最开始举的购买苹果的例子(2 个苹果和消费税)。这里要解的问题是苹果的价格、苹果的个数、消费税这 3 个变量各自如何影响最终支付的金额。这个问题相当于求“支付金额关于苹果的价格的导数”“支付金额关于苹果的个数的导数”“支付金额关于消费税的导数”。用计算图的反向传播来解的话,求解过程如图 5-14 所示。
图 5-14 购买苹果的反向传播的例子
如前所述,乘法节点的反向传播会将输入信号翻转后传给下游。从图 5-14 的结果可知,苹果的价格的导数是 2.2,苹果的个数的导数是 110,消费税的导数是 200。这可以解释为,如果消费税和苹果的价格增加相同的值,则消费税将对最终价格产生 200 倍大小的影响,苹果的价格将产生 2.2 倍大小的影响。不过,因为这个例子中消费税和苹果的价格的量纲不同,所以才形成了这样的结果(消费税的 1 是 100%,苹果的价格的 1 是 1 日元)。
最后作为练习,请大家来试着解一下“购买苹果和橘子”的反向传播。在图 5-15 中的方块中填入数字,求各个变量的导数(答案在若干页后)。
图 5-15 购买苹果和橘子的反向传播的例子:在方块中填入数字,完成反向传播
5.4 简单层的实现
本节将用 Python 实现前面的购买苹果的例子。这里,我们把要实现的计算图的乘法节点称为“乘法层”(
MulLayer
),加法节点称为“加法层”(AddLayer
)。5.4.1 乘法层的实现
层的实现中有两个共通的方法(接口)
forward()
和backward()
。forward()
对应正向传播,backward()
对应反向传播。现在来实现乘法层。乘法层作为
MulLayer
类,其实现过程如下所示。class MulLayer: def __init__(self): self.x = None self.y = None def forward(self, x, y): self.x = x self.y = y out = x * y return out def backward(self, dout): dx = dout * self.y # 翻转x和y dy = dout * self.x return dx, dy
__init__()
中会初始化实例变量x
和y
,它们用于保存正向传播时的输入值。forward()
接收x
和y
两个参数,将它们相乘后输出。backward()
将从上游传来的导数(dout
)乘以正向传播的翻转值,然后传给下游。上面就是
MulLayer
的实现。现在我们使用MulLayer
实现前面的购买苹果的例子(2 个苹果和消费税)。上一节中我们使用计算图的正向传播和反向传播,像图 5-16 这样进行了计算。图 5-16 购买 2 个苹果
使用这个乘法层的话,图 5-16 的正向传播可以像下面这样实现。
apple = 100 apple_num = 2 tax = 1.1 # layer mul_apple_layer = MulLayer() mul_tax_layer = MulLayer() # forward apple_price = mul_apple_layer.forward(apple, apple_num) price = mul_tax_layer.forward(apple_price, tax) print(price) # 220
此外,关于各个变量的导数可由
backward()
求出。# backward dprice = 1 dapple_price, dtax = mul_tax_layer.backward(dprice) dapple, dapple_num = mul_apple_layer.backward(dapple_price) print(dapple, dapple_num, dtax) # 2.2 110 200
这里,调用
backward()
的顺序与调用forward()
的顺序相反。此外,要注意backward()
的参数中需要输入“关于正向传播时的输出变量的导数”。比如,mul_apple_layer
乘法层在正向传播时会输出apple_price
,在反向传播时,则会将apple_price
的导数dapple_price
设为参数。另外,这个程序的运行结果和图 5-16 是一致的。5.4.2 加法层的实现
接下来,我们实现加法节点的加法层,如下所示。
class AddLayer: def __init__(self): pass def forward(self, x, y): out = x + y return out def backward(self, dout): dx = dout * 1 dy = dout * 1 return dx, dy
加法层不需要特意进行初始化,所以
__init__()
中什么也不运行(pass
语句表示“什么也不运行”)。加法层的forward()
接收x
和y
两个参数,将它们相加后输出。backward()
将上游传来的导数(dout
)原封不动地传递给下游。现在,我们使用加法层和乘法层,实现图 5-17 所示的购买 2 个苹果和 3 个橘子的例子。
图 5-17 购买 2 个苹果和 3 个橘子
用 Python 实现图 5-17 的计算图的过程如下所示。
apple = 100 apple_num = 2 orange = 150 orange_num = 3 tax = 1.1 # layer mul_apple_layer = MulLayer() mul_orange_layer = MulLayer() add_apple_orange_layer = AddLayer() mul_tax_layer = MulLayer() # forward apple_price = mul_apple_layer.forward(apple, apple_num) #(1) orange_price = mul_orange_layer.forward(orange, orange_num) #(2) all_price = add_apple_orange_layer.forward(apple_price, orange_price) #(3) price = mul_tax_layer.forward(all_price, tax) #(4) # backward dprice = 1 dall_price, dtax = mul_tax_layer.backward(dprice) #(4) dapple_price, dorange_price = add_apple_orange_layer.backward(dall_price) #(3) dorange, dorange_num = mul_orange_layer.backward(dorange_price) #(2) dapple, dapple_num = mul_apple_layer.backward(dapple_price) #(1) print(price) # 715 print(dapple_num, dapple, dorange, dorange_num, dtax) # 110 2.2 3.3 165 650
这个实现稍微有一点长,但是每一条命令都很简单。首先,生成必要的层,以合适的顺序调用正向传播的
forward()
方法。然后,用与正向传播相反的顺序调用反向传播的backward()
方法,就可以求出想要的导数。综上,计算图中层的实现(这里是加法层和乘法层)非常简单,使用这些层可以进行复杂的导数计算。下面,我们来实现神经网络中使用的层。
5.5 激活函数层的实现
现在,我们将计算图的思路应用到神经网络中。这里,我们把构成神经网络的层实现为一个类。先来实现激活函数的
ReLU
层和Sigmoid
层。5.5.1 ReLU 层
激活函数 ReLU(Rectified Linear Unit)由下式(5.7)表示。
通过式(5.7),可以求出 y 关于 x 的导数,如式(5.8)所示。
在式(5.8)中,如果正向传播时的输入 x 大于 0,则反向传播会将上游的值原封不动地传给下游。反过来,如果正向传播时的 x 小于等于 0,则反向传播中传给下游的信号将停在此处。用计算图表示的话,如图 5-18 所示。
现在我们来实现
ReLU
层。在神经网络的层的实现中,一般假定forward()
和backward()
的参数是NumPy
数组。另外,实现ReLU
层的源代码在common/layers.py
中。图 5-18 ReLU 层的计算图
class Relu: def __init__(self): self.mask = None def forward(self, x): self.mask = (x <= 0) out = x.copy() out[self.mask] = 0 return out def backward(self, dout): dout[self.mask] = 0 dx = dout return dx
Relu
类有实例变量mask
。这个变量mask
是由True/False
构成的 NumPy 数组,它会把正向传播时的输入x
的元素中小于等于 0 的地方保存为True
,其他地方(大于 0 的元素)保存为False
。如下例所示,mask
变量保存了由True/False
构成的 NumPy 数组。>>> x = np.array( [[1.0, -0.5], [-2.0, 3.0]] ) >>> print(x) [[ 1. -0.5] [-2. 3. ]] >>> mask = (x <= 0) >>> print(mask) [[False True] [ True False]]
如图 5-18 所示,如果正向传播时的输入值小于等于 0,则反向传播的值为 0。因此,反向传播中会使用正向传播时保存的
mask
,将从上游传来的dout
的mask
中的元素为True
的地方设为 0。5.5.2 Sigmoid 层
现在,我们用 Python 实现 Sigmoid 层。参考图 5-22,可以像下面这样实现。
class Sigmoid: def __init__(self): self.out = None def forward(self, x): out = 1 / (1 + np.exp(-x)) self.out = out return out def backward(self, dout): dx = dout * (1.0 - self.out) * self.out return dx
这个实现中,正向传播时将输出保存在了实例变量
out
中。然后,反向传播时,使用该变量out
进行计算。5.6 Affine/Softmax 层的实现
5.6.1 Affine 层
神经网络的正向传播中,为了计算加权信号的总和,使用了矩阵的乘积运算(NumPy 中是
np.dot()
,具体请参照 3.3 节)。比如,还记得我们用 Python 进行了下面的实现吗?>>> X = np.random.rand(2) # 输入 >>> W = np.random.rand(2,3) # 权重 >>> B = np.random.rand(3) # 偏置 >>> >>> X.shape # (2,) >>> W.shape # (2, 3) >>> B.shape # (3,) >>> >>> Y = np.dot(X, W) + B
这里,X、W、B**** 分别是形状为 (2,)、(2, 3)、(3,) 的多维数组。这样一来,神经元的加权和可以用
Y = np.dot(X, W) + B
计算出来。然后,Y 经过激活函数转换后,传递给下一层。这就是神经网络正向传播的流程。此外,我们来复习一下,矩阵的乘积运算的要点是使对应维度的元素个数一致。比如,如下面的图 5-23 所示,X 和 W 的乘积必须使对应维度的元素个数一致。另外,这里矩阵的形状用 (2, 3) 这样的括号表示(为了和 NumPy 的shape
属性的输出一致)。图 5-23 矩阵的乘积运算中对应维度的元素个数要保持一致
现在将这里进行的求矩阵的乘积与偏置的和的运算用计算图表示出来。将乘积运算用“dot”节点表示的话,则
np.dot(X, W) + B
的运算可用图 5-24 所示的计算图表示出来。另外,在各个变量的上方标记了它们的形状(比如,计算图上显示了 X 的形状为 (2,),X · W 的形状为 (3,) 等)。图 5-24 Affine 层的计算图(注意变量是矩阵,各个变量的上方标记了该变量的形状)
图 5-24 是比较简单的计算图,不过要注意 X、W、B 是矩阵(多维数组)。之前我们见到的计算图中各个节点间流动的是标量,而这个例子中各个节点间传播的是矩阵。
现在我们来考虑图 5-24 的计算图的反向传播。以矩阵为对象的反向传播,按矩阵的各个元素进行计算时,步骤和以标量为对象的计算图相同。实际写一下的话,可以得到下式(这里省略了式(5.13)的推导过程)。
式(5.13)中 **W**T 的 T 表示转置。转置操作会把 W 的元素 (i, j) 换成元素 (j, i)。用数学式表示的话,可以写成下面这样。
如式(5.14)所示,如果 W 的形状是 (2, 3),**W**T 的形状就是 (3, 2)。
现在,我们根据式(5.13),尝试写出计算图的反向传播,如图 5-25 所示。
图 5-25 Affine 层的反向传播:注意变量是多维数组。反向传播时各个变量的下方标记了该变量的形状
图 5-26 矩阵的乘积(“dot”节点)的反向传播可以通过组建使矩阵对应维度的元素个数一致的乘积运算而推导出来
5.6.2 批版本的 Affine 层
前面介绍的 Affine层的输入 X 是以单个数据为对象的。现在我们考虑 N 个数据一起进行正向传播的情况,也就是批版本的 Affine层。
先给出批版本的 Affine层的计算图,如图 5-27 所示。
图 5-27 批版本的 Affine 层的计算图
加上偏置时,需要特别注意。正向传播时,偏置被加到 X · W 的各个数据上。比如,N = 2(数据为 2 个)时,偏置会被分别加到这 2 个数据(各自的计算结果)上,具体的例子如下所示。
>>> X_dot_W = np.array([[0, 0, 0], [10, 10, 10]]) >>> B = np.array([1, 2, 3]) >>> >>> X_dot_W array([[ 0, 0, 0], [ 10, 10, 10]]) >>> X_dot_W + B array([[ 1, 2, 3], [11, 12, 13]])
正向传播时,偏置会被加到每一个数据(第 1 个、第 2 个……)上。因此,反向传播时,各个数据的反向传播的值需要汇总为偏置的元素。用代码表示的话,如下所示。
>>> dY = np.array([[1, 2, 3,], [4, 5, 6]]) >>> dY array([[1, 2, 3], [4, 5, 6]]) >>> >>> dB = np.sum(dY, axis=0) >>> dB array([5, 7, 9])
这个例子中,假定数据有 2 个(N = 2)。偏置的反向传播会对这 2 个数据的导数按元素进行求和。因此,这里使用了
np.sum()
对第 0 轴(以数据为单位的轴,axis=0
)方向上的元素进行求和。综上所述,Affine 的实现如下所示。另外,
common/layers.py
中的 Affine 的实现考虑了输入数据为张量(四维数据)的情况,与这里介绍的稍有差别。class Affine: def __init__(self, W, b): self.W = W self.b = b self.x = None self.dW = None self.db = None def forward(self, x): self.x = x out = np.dot(x, self.W) + self.b return out def backward(self, dout): dx = np.dot(dout, self.W.T) self.dW = np.dot(self.x.T, dout) self.db = np.sum(dout, axis=0) return dx
5.6.3 Softmax-with-Loss 层
最后介绍一下输出层的 softmax 函数。前面我们提到过,softmax 函数会将输入值正规化之后再输出。比如手写数字识别时,Softmax 层的输出如图 5-28 所示。
图 5-28 输入图像通过 Affine 层和 ReLU 层进行转换,10 个输入通过 Softmax 层进行正规化。在这个例子中,“0”的得分是 5.3,这个值经过 Softmax 层转换为 0.008(0.8%);“2”的得分是 10.1,被转换为 0.991(99.1%)
在图 5-28 中,Softmax 层将输入值正规化(将输出值的和调整为 1)之后再输出。另外,因为手写数字识别要进行 10 类分类,所以向Softmax 层的输入也有 10 个。
下面来实现 Softmax 层。考虑到这里也包含作为损失函数的交叉熵误差(cross entropy error),所以称为“Softmax-with-Loss 层”。Softmax-with-Loss 层(Softmax 函数和交叉熵误差)的计算图如图 5-29 所示。
图 5-29 Softmax-with-Loss 层的计算图
可以看到,Softmax-with-Loss 层有些复杂。这里只给出了最终结果,对 Softmax-with-Loss 层的导出过程感兴趣的读者,请参照附录 A。
图 5-29 的计算图可以简化成图 5-30。
图 5-30 的计算图中,softmax 函数记为 Softmax 层,交叉熵误差记为 Cross Entropy Error 层。这里假设要进行 3 类分类,从前面的层接收 3 个输入(得分)。如图 5-30 所示,Softmax 层将输入(_a_1, _a_2, _a_3) 正规化,输出(_y_1, _y_2, _y_3)。Cross Entropy Error 层接收 Softmax 的输出(_y_1, _y_2, _y_3) 和教师标签(_t_1, _t_2, _t_3),从这些数据中输出损失 L。
图 5-30 “简易版”的 Softmax-with-Loss 层的计算图
图 5-30 中要注意的是反向传播的结果。Softmax 层的反向传播得到了(y1-t1, y2-t2, y3-t3)这样“漂亮”的结果。由于(y1, y2, y3)是 Softmax 层的输出,(t1, t2, t3)是监督数据,所以(y1-t1, y2-t2, y3-t3)是 Softmax 层的输出和教师标签的差分。神经网络的反向传播会把这个差分表示的误差传递给前面的层,这是神经网络学习中的重要性质。
神经网络学习的目的就是通过调整权重参数,使神经网络的输出(Softmax 的输出)接近教师标签。因此,必须将神经网络的输出与教师标签的误差高效地传递给前面的层。刚刚的(y1-t1, y2-t2, y3-t3)正是 Softmax 层的输出与教师标签的差,直截了当地表示了当前神经网络的输出与教师标签的误差。
这里考虑一个具体的例子,比如思考教师标签是(0, 1, 0),Softmax 层的输出是 (0.3, 0.2, 0.5) 的情形。因为正确解标签处的概率是 0.2(20%),这个时候的神经网络未能进行正确的识别。此时,Softmax 层的反向传播传递的是 (0.3, -0.8, 0.5) 这样一个大的误差。因为这个大的误差会向前面的层传播,所以 Softmax 层前面的层会从这个大的误差中学习到“大”的内容。
再举一个例子,比如思考教师标签是 (0, 1, 0),Softmax 层的输出是 (0.01, 0.99, 0) 的情形(这个神经网络识别得相当准确)。此时 Softmax 层的反向传播传递的是 (0.01, -0.01, 0) 这样一个小的误差。这个小的误差也会向前面的层传播,因为误差很小,所以 Softmax 层前面的层学到的内容也很“小”。
现在来进行 Softmax-with-Loss 层的实现,实现过程如下所示。
class SoftmaxWithLoss: def __init__(self): self.loss = None # 损失 self.y = None # softmax的输出 self.t = None # 监督数据(one-hot vector) def forward(self, x, t): self.t = t self.y = softmax(x) self.loss = cross_entropy_error(self.y, self.t) return self.loss def backward(self, dout=1): batch_size = self.t.shape[0] dx = (self.y - self.t) / batch_size return dx
这个实现利用了 3.5.2 节和 4.2.4 节中实现的
softmax()
和cross_entropy_error()
函数。因此,这里的实现非常简单。请注意反向传播时,将要传播的值除以批的大小(batch_size
)后,传递给前面的层的是单个数据的误差。5.7 误差反向传播法的实现
通过像组装乐高积木一样组装上一节中实现的层,可以构建神经网络。本节我们将通过组装已经实现的层来构建神经网络。
5.7.1 神经网络学习的全貌图
在进行具体的实现之前,我们再来确认一下神经网络学习的全貌图。神经网络学习的步骤如下所示。
前提
神经网络中有合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为学习。神经网络的学习分为下面 4 个步骤。
步骤 1(mini-batch)
从训练数据中随机选择一部分数据。
步骤 2(计算梯度)
计算损失函数关于各个权重参数的梯度。
步骤 3(更新参数)
将权重参数沿梯度方向进行微小的更新。
步骤 4(重复)
重复步骤 1、步骤 2、步骤 3。
之前介绍的误差反向传播法会在步骤 2 中出现。上一章中,我们利用数值微分求得了这个梯度。数值微分虽然实现简单,但是计算要耗费较多的时间。和需要花费较多时间的数值微分不同,误差反向传播法可以快速高效地计算梯度。
5.7.2 对应误差反向传播法的神经网络的实现
现在来进行神经网络的实现。这里我们要把2层神经网络实现为
TwoLayerNet
。首先,将这个类的实例变量和方法整理成表 5-1 和表 5-2。表 5-1 TwoLayerNet 类的实例变量
实例变量 说明 params
保存神经网络的参数的字典型变量。 params['W1']
是第 1 层的权重,params['b1']
是第 1 层的偏置。params['W2']
是第 2 层的权重,params['b2']
是第 2 层的偏置layers
保存神经网络的层的有序字典型变量。 以 layers['Affine1']
、layers['ReLu1']
、layers['Affine2']
的形式,通过有序字典保存各个层lastLayer
神经网络的最后一层。本例中为 SoftmaxWithLoss
层表 5-2 TwoLayerNet 类的方法
方法 说明 __init__(self, input_size, hidden_size, output_size, weight_init_std)
进行初始化。 参数从头开始依次是输入层的神经元数、隐藏层的神经元数、输出层的神经元数、初始化权重时的高斯分布的规模 predict(self, x)
进行识别(推理)。 参数 x
是图像数据loss(self, x, t)
计算损失函数的值。 参数 X
是图像数据、t
是正确解标签accuracy(self, x, t)
计算识别精度 numerical_gradient(self, x, t)
通过数值微分计算关于权重参数的梯度(同上一章) gradient(self, x, t)
通过误差反向传播法计算关于权重参数的梯度 这个类的实现稍微有一点长,但是内容和 4.5 节的学习算法的实现有很多共通的部分,不同点主要在于这里使用了层。通过使用层,获得识别结果的处理(
predict()
)和计算梯度的处理(gradient()
)只需通过层之间的传递就能完成。下面是TwoLayerNet
的代码实现。import sys, os sys.path.append(os.pardir) import numpy as np from common.layers import * from common.gradient import numerical_gradient from collections import OrderedDict class TwoLayerNet: def \_\_init\_\_(self, input\_size, hidden\_size, output_size, weight\_init\_std=0.01): # 初始化权重 self.params = {} self.params\['W1'\] = weight\_init\_std * \ np.random.randn(input\_size, hidden\_size) self.params\['b1'\] = np.zeros(hidden_size) self.params\['W2'\] = weight\_init\_std * \ np.random.randn(hidden\_size, output\_size) self.params\['b2'\] = np.zeros(output_size) # 生成层 **self.layers = OrderedDict()** **self.layers\['Affine1'\] = \** **Affine(self.params\['W1'\], self.params\['b1'\])** **self.layers\['Relu1'\] = Relu()** **self.layers\['Affine2'\] = \** **Affine(self.params\['W2'\], self.params\['b2'\])** **self.lastLayer = SoftmaxWithLoss()** def predict(self, x): **for layer in self.layers.values():** **x = layer.forward(x)** return x # x:输入数据, t:监督数据 def loss(self, x, t): y = self.predict(x) return self.lastLayer.forward(y, t) def accuracy(self, x, t): y = self.predict(x) y = np.argmax(y, axis=1) if t.ndim != 1 : t = np.argmax(t, axis=1) accuracy = np.sum(y == t) / float(x.shape\[0\]) return accuracy # x:输入数据, t:监督数据 def numerical_gradient(self, x, t): loss_W = lambda W: self.loss(x, t) grads = {} grads\['W1'\] = numerical\_gradient(loss\_W, self.params\['W1'\]) grads\['b1'\] = numerical\_gradient(loss\_W, self.params\['b1'\]) grads\['W2'\] = numerical\_gradient(loss\_W, self.params\['W2'\]) grads\['b2'\] = numerical\_gradient(loss\_W, self.params\['b2'\]) return grads def gradient(self, x, t): **\# forward** **self.loss(x, t)** **\# backward** **dout = 1** **dout = self.lastLayer.backward(dout)** **layers = list(self.layers.values())** **layers.reverse()** **for layer in layers:** **dout = layer.backward(dout)** # 设定 grads = {} grads\['W1'\] = self.layers\['Affine1'\].dW grads\['b1'\] = self.layers\['Affine1'\].db grads\['W2'\] = self.layers\['Affine2'\].dW grads\['b2'\] = self.layers\['Affine2'\].db return grads
请注意这个实现中的粗体字代码部分,尤其是将神经网络的层保存为
OrderedDict
这一点非常重要。OrderedDict
是有序字典,“有序”是指它可以记住向字典里添加元素的顺序。因此,神经网络的正向传播只需按照添加元素的顺序调用各层的forward()
方法就可以完成处理,而反向传播只需要按照相反的顺序调用各层即可。因为 Affine 层和 ReLU 层的内部会正确处理正向传播和反向传播,所以这里要做的事情仅仅是以正确的顺序连接各层,再按顺序(或者逆序)调用各层。像这样通过将神经网络的组成元素以层的方式实现,可以轻松地构建神经网络。这个用层进行模块化的实现具有很大优点。因为想另外构建一个神经网络(比如 5 层、10 层、20 层……的大的神经网络)时,只需像组装乐高积木那样添加必要的层就可以了。之后,通过各个层内部实现的正向传播和反向传播,就可以正确计算进行识别处理或学习所需的梯度。
5.7.3 误差反向传播法的梯度确认
到目前为止,我们介绍了两种求梯度的方法。一种是基于数值微分的方法,另一种是解析性地求解数学式的方法。后一种方法通过使用误差反向传播法,即使存在大量的参数,也可以高效地计算梯度。因此,后文将不再使用耗费时间的数值微分,而是使用误差反向传播法求梯度。
数值微分的计算很耗费时间,而且如果有误差反向传播法的(正确的)实现的话,就没有必要使用数值微分的实现了。那么数值微分有什么用呢?实际上,在确认误差反向传播法的实现是否正确时,是需要用到数值微分的。
数值微分的优点是实现简单,因此,一般情况下不太容易出错。而误差反向传播法的实现很复杂,容易出错。所以,经常会比较数值微分的结果和误差反向传播法的结果,以确认误差反向传播法的实现是否正确。确认数值微分求出的梯度结果和误差反向传播法求出的结果是否一致(严格地讲,是非常相近)的操作称为梯度确认(gradient check)。梯度确认的代码实现如下所示。
import sys, os sys.path.append(os.pardir) import numpy as np from dataset.mnist import load_mnist from two_layer_net import TwoLayerNet # 读入数据 (x_train, t_train), (x_test, t_test) = \ load_mnist(normalize=True, one_ hot_label = True) network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10) x_batch = x_train[:3] t_batch = t_train[:3] grad_numerical = network.numerical_gradient(x_batch, t_batch) grad_backprop = network.gradient(x_batch, t_batch) # 求各个权重的绝对误差的平均值 for key in grad_numerical.keys(): diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) ) print(key + ":" + str(diff))
和以前一样,读入 MNIST 数据集。然后,使用训练数据的一部分,确认数值微分求出的梯度和误差反向传播法求出的梯度的误差。这里误差的计算方法是求各个权重参数中对应元素的差的绝对值,并计算其平均值。运行上面的代码后,会输出如下结果。
b1:9.70418809871e-13 W2:8.41139039497e-13 b2:1.1945999745e-10 W1:2.2232446644e-13
从这个结果可以看出,通过数值微分和误差反向传播法求出的梯度的差非常小。比如,第 1 层的偏置的误差是
9.7e-13
(0.00000000000097
)。这样一来,我们就知道了通过误差反向传播法求出的梯度是正确的,误差反向传播法的实现没有错误。5.7.4 使用误差反向传播法的学习
最后,我们来看一下使用了误差反向传播法的神经网络的学习的实现。和之前的实现相比,不同之处仅在于通过误差反向传播法求梯度这一点。这里只列出了代码,省略了说明。
import sys, os sys.path.append(os.pardir) import numpy as np from dataset.mnist import load_mnist from two\_layer\_net import TwoLayerNet \# 读入数据 (x\_train, t\_train), (x\_test, t\_test) = \ load\_mnist(normalize=True, one\_hot_label=True) network = TwoLayerNet(input\_size=784, hidden\_size=50, output_size=10) iters_num = 10000 train\_size = x\_train.shape\[0\] batch_size = 100 learning_rate = 0.1 train\_loss\_list = \[\] train\_acc\_list = \[\] test\_acc\_list = \[\] iter\_per\_epoch = max(train\_size / batch\_size, 1) for i in range(iters_num): batch\_mask = np.random.choice(train\_size, batch_size) x\_batch = x\_train\[batch_mask\] t\_batch = t\_train\[batch_mask\] **\# 通过误差反向传播法求梯度** **grad = network.gradient(x\_batch, t\_batch)** # 更新 for key in ('W1', 'b1', 'W2', 'b2'): network.params\[key\] -= learning_rate * grad\[key\] loss = network.loss(x\_batch, t\_batch) train\_loss\_list.append(loss) if i % iter\_per\_epoch == 0: train\_acc = network.accuracy(x\_train, t_train) test\_acc = network.accuracy(x\_test, t_test) train\_acc\_list.append(train_acc) test\_acc\_list.append(test_acc) print(train\_acc, test\_acc)
-
基于Python的深度学习理论与实现(P8——误差反向传播算法)
2019-10-22 21:53:23误差反向传播算法 之前,我们使用数值微分计算了神经... 书中以计算图的方法引入关于反向传播算法的概念。 计算图将计算过程用图形的方式表现出来,这里的图指的是数据结构图,即节点和边。现在我们用计算图来...误差反向传播算法
之前,我们使用数值微分计算了神经网络的权重参数的梯度(严格来说,是损失函数关于权重参数的梯度)。数值微分容易实现,但是缺点在于计算上比较耗费时间。所以,在训练神经网络时,一般使用误差反向传播 算法来高效的计算权重参数的梯度。
计算图
书中以计算图的方法引入关于反向传播算法的概念。
计算图将计算过程用图形的方式表现出来,这里的图指的是数据结构图,即节点和边。现在我们用计算图来表示计算过程:
图示中为一个简单的计算图示例,其中输入为100,输出为220,输入到输出称为计算图的正向传播,它先后经过了两个乘节点,第一个乘节点参数为2,第二个乘节点参数为1.1;当然我们也可以将参数放到节点中,或者用不同的形状来表示计算节点。
通过上例可以看出计算图就是一个用图来描述计算过程的方法,我们再看一个更为复杂的计算图,这次我们用方形节点表示输入输出,圆形节点表示计算:
这个示例中加入了新增的节点类型——加法。计算图的独特之处在于可以通过传递局部计算获得最终结果,局部计算指的是无论全局发生了什么,都只需要根据与自己相关联的信息计算结果即可,比如下图:
图中,我们将原本的左上部分的输入以及节点用云来表示,对于加法节点而言,完全无需知道云中的内容即可执行计算。计算图可以集中精力于局部计算,无论全局的计算有多么复杂,各个步骤所要做的就是对象节点的局部计算。虽然局部计算非常简单,但是通过传递它的计算结果可以获得全局的复杂计算结果。
计算图除了局部计算的优势以外,另外一大优势在于可以通过反向传播高校的计算导数。利用计算图的反向传播
计算图的局部计算的优势在反向传播中也有显现。计算图的正向传播是将信号经过函数映射得到输出,而计算图的反向传播也是将信号经过函数映射得到输出,二者的本质是相同的,只是反向相反,更为重要的是,反向传播的函数映射是特殊的:将输入信号乘以当前节点的局部导数。
图中,输入信号x正向传播通过f节点得到Y,用数学表达为:
在反向传播过程中,输入信号E通过反向传播,得到E’,E’是输入信号乘以局部导数,用数学表达为:
假设我们的节点为平方计算,即正向传播为:
同时假设反向传播的信号值为a,则:
下面我们来看反向传播更复杂一些的例子,如下图:
该图中,计算节点f有了x和y两个输入,即:
则反向信号为E时,应该计算局部偏导数乘以反向输入信号作为反向计算的输出,即:
同理,无论正向传播的结构是怎样的,都只需要计算局部偏导数即可。
以加法节点为例:
图中的计算节点为加法,即:
则,计算局部偏导数有:
所以反向传播的结果如图中所示。再看看乘法节点的示例:
图中的计算节点为乘法,即:
则,计算局部偏导数有:
所以计算结果如图中所示。简单层的实现
既然知道了反向传播算法的基本原理,现在我们将反向传播算法带入到神经网络中,实现神经网络中最基本的单位——层。
我们将构建神经网络中的层实现为一个类。层指的是神经网络中的功能单位,比如sigmoid层指的是负责进行sigmoid计算等。层的实现中,除了层的构造方法外,还应当有一个forward()方法和一个backward()方法,分别用于该层的正向传播和反向传播。class AddLayer: def __init__(self): pass def forward(self,x,y): return x+y def backward(self,dout): dx = dout *1 dy = dout *1 return dx,dy class MulLayer: def __init__(self): self.x = None self.y = None def forward(self,x,y): self.x = x self.y = y return self.x *self.y def backward(self,dout): dx = dout * self.y dy = dout * self.x return dx,dy
如上代码中我们实现了一个加法层和一个乘法层,我们用实现的加法层和乘法层对下图进行正向传播和反向传播:
if __name__ == '__main__': x = 100 a1 = 2 a2 = 1.1 y = 150 b1 =3 mx_a1 = MulLayer() mxa1_a2 = MulLayer() my_b1 = MulLayer() axy = AddLayer() #正向传播 x_a1 = mx_a1.forward(x,a1) y_b1 = my_b1.forward(y,b1) xa1_a2 =mxa1_a2.forward(x_a1,a2) xy = axy.forward(xa1_a2,y_b1) print(xy) #输出结果670. #反向传播 z = 1 dxa1a2,dyb1 = axy.backward(z) print(dxa1a2,dyb1) #输出结果是:1 1 dxa1,da2 = mxa1_a2.backward(dxa1a2) print(dxa1,da2) #输出结果是:1.1 200 dx,da1 = mx_a1.backward(dxa1) print(dx,da1) #输出结果是:2.2 110.00000000000001 dy,db1 = my_b1.backward(dyb1) print(dy,db1) #输出结果是:3 150
以上示例中,我们使用实现的简单层完成了一个计算图的正向传播和反向传播,在神经网络中,误差的反向传播算法表示的是输出层的产生的误差(损失函数的结果)反向传入网络中,通过反向传播算法计算出每个参数对误差的梯度,由此来调整参数值来达到学习的效果。
激活函数层的实现
现在我们来实现神经网络中存在的层,首先是两个激活函数层——ReLU层和Sigmoid层。
ReLu层是一个单变量层,其向前传播的数学表达为:
则求出y关于x的导数为:
由以上的数学表达,我们可以如下实现ReLU层。class ReLU: def __init__(self): self.mask = None def forward(self,x): self.mask = (x<=0) out = x.copy() out[self.mask] = 0 return out def backward(self,dout): dout[self.mask] = 0 dx = dout return dx
Sigmoid层同样是一个单变量层,但是它的数学形式比ReLU函数较复杂:
对该函数的求导需要用到一些链式求导技巧:
由以上的数学表达,可以将Sigmoid层实现为:class Sigmoid: def __init__(self): self.out = None def forward(self,x): out = 1/(1+np.exp(-x)) self.out = out return out def backward(self,dout): dx = dout * (1.0-self.out)* self.out
仿射层与输出层的实现
在神经网络的正向传播中,为了计算加权信号的总和,使用了矩阵乘积运算,这在几何邻域中称为仿射变换,因此,将进行仿射变换的处理实现称为仿射层或Affine层也常常被称为全连接层。
仿射层的正向传播的数学表达为:
则其反向传播的数学表达为:
由以上,可以将Affine层实现为:class Affine: def __init__(self,W,b): self.W = W self.b = b self.x = None self.dW = None self.db = None def forward(self,x): self.x = x out = np.dot(x,self.W) + self.b return out def backward(self,dout): dx = np.dot(dout,self.W.T) self.dW = np.dot(self.x.T,dout) self.db = np.dot(dout,axis=0)
对于输出层,我们会使用softmax函数或恒等函数,同时,往往会在输出层后添加一个损失函数的计算,我们将Softmax函数和交叉熵误差函数合并在同一层中实现,称为Softmax-with-Loss层。
我们这里不加证明的给出Softmax-with-Loss层的反向传播结果(整个证明过程有点麻烦,但实际上也就是较为复杂的复合函数求导):Softmax层的输入假设为(a1,a2,…,an),Softmax层的输出为(y1,y2,…,yn),监督数据t为(t1,t2,…,tn),则如果反向输入为1,则整个Softmax-with-Loss层的输出为(y1 - t1,y2 - t2,…yn - tn),需要注意的是,之所以整个Softmax-with-Loss层的反向传播结果形式这么简单是由于交叉熵函数作为损失函数,是专门设计出来为了使得反向传播的结果形式变得简单的。Softmax-with-Loss层的实现为:
class SoftWithLoss: def __init__(self): self.loss = None self.y = None self.t = None def forward(self,x,t): self.t = t self.y = softmax(x) self.loss = cross_entropy_error(self.y,self.t) return self.loss def backward(self,dout=1): batch_size = self.t.shape[0] dx = (self.y - self.t)/batch_size return dx
至此,我们已经实现了很多神经网络中的层,已经有了足够充足的砖头,只要将其堆砌起来就可以完成一个既可以向前传播进行预测,又可以向后传播进行学习的神经网络,书中实现了一个简单的二层神经网络:
class TwoLayerNet: def __init__(self,input_size,hidden_size,output_size,weight_init_std=0.01): self.params = {} self.params['W1'] = weight_init_std * np.random.randn(input_size,hidden_size) self.params['b1'] = np.zeros(hidden_size) self.params['W2'] = weight_init_std * np.random.randn(hidden_size,output_size) self.params['b2'] = np.zeros(output_size) self.layers = OrderedDict() self.layers['Affine1'] = Affine(self.params['W1'],self.params['b1']) self.layers['Relu1'] = ReLU() self.layers['Affine2'] = Affine(self.params['W2'],self.params['b2']) self.lastLayer = SoftmaxWithLoss() def predict(self,x): for layer in self.layers.values(): x = layer.forward(x) return x def loss(self,x,t): y = self.predict(x) return self.lastLayer.forward(y,t) def gradient(self,x,t): #要使用向后传播算法必须有向前传播的结果,即必须产生误差。 self.loss(x,t) dout = 1 dout = self.lastLayer.backward(dout) layers = list(self.layers.values()) layers.reverse() for layer in layers: dout = layer.backward(dout) grads= {} grads['W1'] = self.layers['Affine1'].dW grads['b1'] = self.layers['Affine1'].db grads['W2'] = self.layers['Affine2'].dW grads['b2'] = self.layers['Affine2'].db return grads
代码中没有包含精度计算和数值微分验证的实现。
-
吴恩达机器学习-神经网络-反向传播算法
2020-07-29 00:42:04神经网络-反向传播算法神经网络的学习神经网络模型特征和直观理解多类分类神经网络参数的反向传播代价函数反向传播算法反向传播算法的直观理解展开参数梯度检验综合起来(使用神经网络步骤)自主驾驶 神经网络的学习... -
深度学习 --- BP算法详解(误差反向传播算法)
2018-11-11 13:37:42本节开始深度学习的第一个算法BP算法,本打算第一个算法为单层感知器,但是感觉太简单了,不懂得找本书看看就会了,这里简要的介绍一下单层感知器: 图中可以看到,单层感知器很简单,其实本质上他就是线性分类器... -
反向传播算法(过程及公式推导)_循环神经网络及反向过程推导
2020-12-10 04:54:13本文着重讨论循环神经网络(Recurrent Neural Network,RNN)的...当然,不清楚也没关系,下面的文章了解一下:Numpy实现神经网络框架(1)Numpy实现神经网络框架(2)——梯度下降、反向传播Numpy实现神经网络框架(3)... -
弹性反向传播
2017-05-23 20:05:31弹性反向传播算法学习笔记 ...其二为反向传播算法的梯度弥散作用,即距离输出层越远的神经元学习的速度越慢。 Martin Riedmiller也因此提出了弹性反向传播算法(Rprop) 1、学习率 反向传播算法中的学习率为 -
深度学习01-(深度学习概述、感知机、神经网络、损失函数与梯度下降、反向传播算法、卷积神经网络理论)
2020-07-13 17:20:50深度学习-01深度学习-01深度学习概述引入人工智能划时代事件深度学习巨大影响深度学习什么是深度学习深度神经网络深度学习与机器学习的关系深度学习的特点深度学习的优点深度学习的缺点为什么要学习深度学习深度学习... -
论文研究-引入反向传播机制的概率神经网络模型.pdf
2019-09-20 18:51:02改进模型继承了概率神经网络模型的分类原理和结构特征,同时应用了多层感知器神经网络模型的反向传播算法进行函数和参数学习,由此解决了函数估计和空间复杂度高的问题. 通过三组数值实验的验证,结果表明该模型还有着... -
深度学习系列(2):前向传播和后向传播算法
2017-12-01 23:14:33今天重温后向传播算法的推导,但重要的是比较前向传播和后向传播的优缺点,以及它们在神经网络中起到了什么不一般的作用,才让我们如此着迷。反向传播的由来反向传播由Hinton在1986年发明,该论文发表在 -
CV学习第四课——神经网络,反向传播和正则化
2020-03-10 14:35:21无论是线性回归还是逻辑回归都有一个缺点:当特征太多的时,计算负荷会非常大。例如在图片识别的模型中,由于每个像素都是255个特征,而一个50×50像素的小图片,都拥有巨大量的特征,普通的逻辑回归模型,不能有效... -
基于改进PSO-BP混合算法的电力变压器故障诊断
2021-01-14 15:32:56将改进的粒子群优化(PSO)算法与误差反向传播(BP)算法相结合构成混合算法训练人工神经网络。改进的PSO算法中,惯性权重从最大到最小线性减小,以平衡局部和全局搜索能力,并将类似“选择”的概念引入PSO算法,使该算法更... -
基于GA-BP算法的气化配煤灰熔点预测
2020-07-07 12:44:35为了提高气化配煤煤灰流动温度预测的精度和稳定性,提出将遗传算法(GA)与误差反向传播神经网络(BP)相结合的预测方法,采用GA优化BP神经网路的权值和阈值,再用BP算法训练网络,结合仿真实验分析比较了GA-BP网络算法与... -
14.预测算法的先天缺陷,及解决
2018-04-28 21:47:23每种预测算法都有自己的适用场景,以及先天的优缺点。我们一般会根据研究的问题选取合适的算法,但是因此就要解决算法带来的先天缺陷。于是出现了大量的结合模型,取长补短。或者进行一些处理来避免可能产生的问题。... -
python实现梯度下降优化算法
2020-05-16 21:06:50友情链接 结合numpy及mnist库的...在前面的博客中,我们已经能够基本完成一个简单神经网络的编写,通过使用反向传播的方式来有效地提高了计算速率。而前面构造的简单神经网络除了使用最普通的SGD外,像mini-batch一. -
变频调速SVPWM技术的原理、算法与应用
2018-12-17 13:38:212.4.1 基本原理 2.4.2 实施算法 2.4.3 对称调制模式与SPWM的比较 2.4.4 对称调制模式的特点和优点 2.4.5 对称调制模式的推广 2.5 两电平SVPWM的新算法 2.5.1 随机控制算法 2.5.2 免疫算法 2.5.3 反向传播神经网络... -
变频调速SVPWM技术的原理、算法与应用(带自制书签,完整方便)
2018-03-08 15:26:432.5.3 反向传播神经网络算法 2.6 两电平三维空间电压矢量SVPWM控制 2.6.1 三相四桥臂逆变器 2.6.2 三相四桥臂逆变器的电压空间矢量 2.6.3 三相四桥臂逆变器的电压空间矢量控制 参考文献 第3章 两电平SVPWM技术的应用... -
机器学习——单层前馈神经网络与BP算法
2018-12-18 21:13:57反向传播(英语:Backpropagation,缩写为BP)是“误差反向传播”的简称,是一种与最优化方法(如梯度下降法)结合使用的,用来训练人工神经网络的常见方法。该方法对网络中所有权重计算损失函数的梯度。这个... -
为什么logistic回归的要用sigmoid函数?优缺点?
2019-03-21 14:23:262.非0中心化,在神经网络算法等情况下,造成反向传播时权重的全正全负的情况。 为什么要用? 答案1:logistic是基于Bernoulli分布的假设,也就是y|X~Bernoulli分布,而Bernoulli分布的指数族的形式就是1/(1+exp(-z)) ... -
矿用红外甲烷传感器温湿度补偿算法研究
2020-05-25 14:39:31针对环境温湿度对矿用红外甲烷传感器测量精度的影响问题,以人工神经网络为基础,通过遗传优化对传统误差反向传播(back propagation)神经网络算法进行改进,有效改善了BP神经网络收敛速度慢,易陷入局部极小值等缺点。... -
深度学习 发展 优缺点
2018-07-26 22:46:11神经网络的发展经历了3次大的转折。由于早期的计算资源的...在07年,hitton提出了利用自编码器来stack by stack的学习图像的表征,然后堆叠起来作为神经网络参数的初始化值,然后在统一采用反向传播算法(BP算法)... -
神经网络历史与缺点
2017-08-07 17:48:00历史 人工神经网络诞生于20世纪50年代,那时她叫感知机。 1969年,Marvin Minsky出版的《Perceptrons》将她打入...1989年,燕大侠加入贝尔实验室,他开始将1974年提出的标准反向传播算法应用于深度神经网络,CNN诞... -
深度学习基础04---实现一个简单的神经网络NN算法
2020-11-02 14:12:181.关于非线性转化方程(non-linear transformation function) sigmoid函数(S 曲线)用来作为 activation function: 形状大致为S型,y的取值在【0,1】之间,当x...缺点:激活函数计算量大,反向传播求误差梯度时, -
DNN,CNN和RNN优缺点/区别
2019-02-28 19:48:39DNN,CNN和RNN优缺点/...使用sigmoid或tanh等连续函数模拟神经元对激励的响应,在训练算法上则使用Werbos发明的反向传播BP算法。这就是现在所说的神经网络NN。 问题: 其一,随着神经网络层数的加深,优化函数越来... -
深度学习基础模型
2019-12-11 22:32:26文章目录DNN神经网络前向传播算法反向传播算法(BP)CNN卷积神经网络哪些部分构成?各部分作用分别是什么?矩阵的大小为什么CNN能够用来做文本分类?CNN用于自然语言处理的缺点池化层的优点和缺点CNN的前向传播CNN的... -
MDPs —— 马尔可夫决策定义与算法
2020-11-17 17:30:03MDPs 的基本原理、表示光环原理效用的求解是反向传播的原则不变条件MDPs 的表示MDPs 求解效用迭代法缺点原则迭代法 MDPs 定义——由实例开始 时序决策问题 假设有一个 agent,从下图的 start 开始,移动到图中的 +1... -
各种梯度下降算法(SGB,Momentum,Adagrad,Adam)简介及特点
2019-11-29 09:31:45上一篇文章介绍了什么是误差反向传播和梯度下降:https://blog.csdn.net/qq_38232171/article/details/103288345 本文将对现有的一些梯度下降的优化算法进行总结和对比。 BGD:上文中的公式其实算是BGD,就是利用了... -
深度学习不得不知的英文名称
2018-08-16 16:05:08BP:反向传播算法(back propagation),采用迭代的算法来训练整个网络,随机设定初值,计算当前网络的输出,然后根据当前输出和label之间的差去改变前面各层的参数,直到收敛(整体是一个梯度下降法),缺点:对于...