欢迎光临散文网 会员登陆 & 注册

鱼书(深度学习入门):第五章 误差反向传播法

2023-07-23 00:27 作者:棒棒x  | 我要投稿

    上一章介绍了神经网络的学习,并通过数值微分计算了神经网络的权重参数的梯度。数值微分虽然简单,也容易实现,但缺点是计算上比较费时间。本章将介绍一个能够高效计算权重参数的梯度的方法——误差反向传播法。本章将主要使用计算图理解误差反向传播法。

一、计算图

    计算图将计算过程用图形表示出来。这里说的图形是数据结构图,通 过多个节点和边表示(连接节点的直线称为“边”)。

    1.用计算图求解

    下面介绍通过计算图求解问题的简单例子。计算图通过节点和箭头表示计算过程。

问题1:太郎在超市买了2个100日元一个的苹果,消费税是10%,请计算支付金额。

基于计算图求解的问题1的答案:“苹果的个数”和“消费税”作为变量标在○外面

问题2:太郎在超市买了2个苹果、3个橘子。其中,苹果每个100日元,橘子每个150日元。消费税是10%,请计算支付金额。

基于计算图求解的问题2的答案

    综上,用计算图解题的情况下,需要按如下流程进行。

    1.构建计算图。

    2.在计算图上,从左向右进行计算。

    这里的第2歩“从左向右进行计算”是一种正方向上的传播,简称为正向传播(forward propagation)。正向传播是从计算图出发点到结束点的传播。既然有正向传播这个名称,当然也可以考虑反向(从图上看的话,就是从右向左)的传播。实际上,这种传播称为反向传播(backward propagation)。反向传播将在接下来的导数计算中发挥重要作用。

    2.局部计算

    计算图的特征是可以通过传递“局部计算”获得最终结果。“局部”这个词的意思是“与自己相关的某个小范围”。局部计算是指,无论全局发生了什么,都能只根据与自己相关的信息输出接下来的结果。如下例:

买了2个苹果和其他很多东西的例子

    计算图可以集中精力于局部计算。无论全局的计算有多么复杂, 各个步骤所要做的就是对象节点的局部计算。虽然局部计算非常简单,但是通过传递它的计算结果,可以获得全局的复杂计算的结果。

    3.为何用计算图解题

    计算图有哪些优点?一个是前面所提到的局部计算。无论全局是多么复杂的计算,都可以通过局部计算使各个节点致力于简单的计算,从而简化问题。另一个优点是,利用计算图可以将中间的计算结果全部保存起来。实际上,使用计算图最大的原因是,可以通过反向传播高效计算导数。

    在介绍计算图的反向传播之前,我们先介绍一个例子。对于问题1,假设我们想知道苹果价格的上涨会在多大程度上影响最终的支付金额,即求“支付金额关于苹果的价格的导数” ,这个导数的值表示当苹果的价格稍微上涨时,支付金额会增加多少。这个导数的值可以通过计算图的反向传播求出来。先来看一下结果,如下图所示:

基于反向传播的导数的传递

    如图所示,反向传播使用与正方向相反的箭头(粗线)表示。反向传播传递“局部导数”,将导数的值写在箭头的下方。在这个例子中,反向传播从右向左传递导数的值(1 → 1.1 → 2.2)。从这个结果中可知,“支付金额关于苹果的价格的导数”的值是2.2。

    这里只求了关于苹果的价格的导数,不过“支付金额关于消费税的导数”“支付金额关于苹果的个数的导数”等也都可以用同样的方式算出来。并且,计算中途求得的导数的结果(中间传递的导数)可以被共享,从而可以 高效地计算多个导数。综上,计算图的优点是,可以通过正向传播和反向传播高效地计算各个变量的导数值。

二、链式法则

    反向传播将局部导数向正方向的反方向(从右到左)传递,一开始可能会让人感到困惑。传递这个局部导数的原理,是基于链式法则(chain rule)的。

    1.计算图的反向传播

    我们先来看一个使用计算图的反向传播的例子。假设存在 y = f(x)的计算,这个计算的反向传播如图所示。

计算图的反向传播:沿着与正方向相反的方向,乘上局部导数

    如图,反向传播的计算顺序是,将信号E乘以节点的局部导数 ,然后将结果传递给下一个节点。这里所说的局部导数是指正向传播中y = f(x)的导数,也就是y关于x的导数。把这个局部导数乘以上游传过来的值(本例中为E),然后传递给前面的节点。这就是反向传播的计算顺序。通过这样的计算,可以高效地求出导数的值,这是反向传播的要点。这是通过链式法则的原理实现的,下面介绍链式法则.

    2.什么是链式法则

    我们需要先从复合函数说起。复合函数是由多个函数构成的函数。比如,z = (x + y) 2 是由下图所示的两个式子构成的。

    链式法则是关于复合函数的导数的性质,定义如下。如果某个函数由复合函数表示,则该复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示。

        其原理可以表示为下图.其导数正好可以像下面这样“互相抵消”,所以记起来很简单。

        所以上式的导数可以由下图所示:

    3.链式法则和计算图

    现在我们尝试将上式的链式法则的计算用计算图表示出来。如下图所示:

上式的计算图:沿着与正方向相反的方向,乘上局部导数后传递

    如图所示,计算图的反向传播从右到左传播信号。反向传播的计算顺序是,先将节点的输入信号乘以节点的局部导数(偏导数),然后再传递给下一个节点。

    代入具体式子如下图所示.

三、反向传播

    下面将以“+” 和“×”等运算为例,介绍反向传播的结构。

    1.加法节点的反向传播

    首先来考虑加法节点的反向传播。这里以z = x + y为对象,观察它的反向传播。z = x + y的导数可由下式(解析性地)计算出来。

用计算图表示的话,如下图所示。

加法节点的反向传播:左图是正向传播,右图是反向传播。如右图的反向传播所示,
加法节点的反向传播将上游的值原封不动地输出到下游

    从图中可知,加法节点的反向传播只乘以1,所以输入的值会原封不动地流向下一个节点。加法节点的反向传播只是将输入信号输出到下一个节点.

    2.乘法节点的反向传播

    接下来,我们看一下乘法节点的反向传播。这里我们考虑z = xy。这个式子的导数用下式表示。

    我们可以跟上面一样,画出相似的计算图.

乘法的反向传播:左图是正向传播,右图是反向传播

    由图可知,乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值” 后传递给下游。翻转值表示一种翻转关系,如图所示,正向传播时信号是x的话,反向传播时则是y;正向传播时信号是y的话,反向传播时则是x。

    另外,加法的反向传播只是将上游的值传给下游,并不需要正向传播的输入信号。但是,乘法的反向传播需要正向传播时的输入信号值。因此,实现乘法节点的反向传播时,要保存正向传播的输入信号。

    3.苹果的例子

    再来思考一下本章最开始举的购买苹果的例子(2个苹果和消费税)。这里要解的问题是苹果的价格、苹果的个数、消费税这3个变量各自如何影响最终支付的金额。这个问题相当于求“支付金额关于苹果的价格的导数”“支付金额关于苹果的个数的导数”“支付金额关于消费税的导数”。用计算图的反向传播来解的话,求解过程如下图所示。

购买苹果的反向传播的例子

四、简单层的实现

    本节将用Python实现前面的购买苹果的例子。这里,我们把要实现的计算图的乘法节点称为“乘法层”(MulLayer),加法节点称为“加法层” (AddLayer)。

    1.乘法层的实现

    层的实现中有两个共通的方法(接口)forward()和backward()。forward()对应正向传播,backward()对应反向传播。

    现在来实现乘法层。乘法层作为MulLayer类,其实现过程如下所示:

    __init__()中会初始化实例变量x和y,它们用于保存正向传播时的输入值。forward()接收x和y两个参数,将它们相乘后输出。backward()将从上游传来的导数(dout)乘以正向传播的翻转值,然后传给下游。这就是MulLayer的实现。

    对于上面购买苹果的例子,其中正向传播,反向传播可以像下面这样实现:

    这里,调用backward()的顺序与调用forward()的顺序相反。此外,要注意backward()的参数中需要输入“关于正向传播时的输出变量的导数”。比如,mul_apple_layer乘法层在正向传播时会输出apple_price,在反向传播时,则会将apple_price的导数dapple_price设为参数。

    2.加法层的实现

    接下来,我们实现加法节点的加法层,如下所示。

    加法层不需要特意进行初始化,所以__init__()中什么也不运行(pass语句表示“什么也不运行”)。加法层的forward()接收x和y两个参数,将它们相加后输出。backward()将上游传来的导数(dout)原封不动地传递给下游。

五、激活函数层的实现

    现在,我们将计算图的思路应用到神经网络中。这里把构成神经网络的层实现为一个类。先来实现激活函数的ReLU层和Sigmoid层。

    1.ReLU层

    激活函数ReLU(Rectified Linear Unit)由下式表示。

    可以求出y关于x的导数,如下式所示。

    如图所示,如果正向传播时的输入x大于0,则反向传播会将上游的值原封不动地传给下游。反过来,如果正向传播时的x小于等于0,则反向传播中传给下游的信号将停在此处。用计算图表示的话,如下图所示。

ReLU层的计算图

    现在我们来实现ReLU层。在神经网络的层的实现中,一般假定forward()和backward()的参数是NumPy数组。

    Relu类有实例变量mask。这个变量mask是由True/False构成的NumPy数组,它会把正向传播时的输入x的元素中小于等于0的地方保存为True,其他地方(大于0的元素)保存为False。

    如果正向传播时的输入值小于等于0,则反向传播的值为0。因此,反向传播中会使用正向传播时保存的mask,将从上游传来的dout的mask中的元素为True的地方设为0。

    2.Sigmoid层

    接下来,我们来实现sigmoid函数。sigmoid函数由下式表示。

    其计算图如下所示:

sigmoid层的计算图(仅正向传播)

    下面是sigmiod函数的反向传播计算图,其推导过程省略.

Sigmoid层的计算图

    将其进一步整理如下:

    则其计算图可以简化如下:

Sigmoid层的计算图:可以根据正向传播的输出y计算反向传播

    现在,我们用Python实现Sigmoid层。

    这个实现中,正向传播时将输出保存在了实例变量out中。然后,反向 传播时,使用该变量out进行计算。

六、Affine/Softmax层的实现

    1.Affine层

    神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿射变换”。因此,这里将进行仿射变换的处理实现为“Affine层”。其计算图如图所示:

Affine层的计算图(注意变量是矩阵,各个变量的上方标记了该变量的形状)

    神经元的加权和用Y = np.dot(X, W) + B计算出来。然后,Y 经过激活函数转换后,传递给下一层。这就是神经网络正向传播的流程。上图是比较简单的计算图,不过要注意X、W、B是矩阵(多维数组)。之前我们见到的计算图中各个节点间流动的是标量,而这个例子中各个节点间传播的是矩阵。

    以矩阵为对象的反向传播,按矩阵的各个元素进行计算时,步骤和以标量为对象的计算图相同。实际写一下的话,可以得到下式(这里省略了推导过程)。

    现在,我们根据上式,尝试写出计算图的反向传播,如下图所示。

Affi ne层的反向传播:注意变量是多维数组。反向传播时各个变量的下方标记了
该变量的形状

    这里要特别注意的是计算图中各个变量的形状。因为矩阵的乘积运算要求对应维度的元素 个数保持一致,通过确认一致性,就可以导出上式。

    2.批版本的Affine层

    前面介绍的Affi ne层的输入X是以单个数据为对象的。现在我们考虑N个数据一起进行正向传播的情况,也就是批版本的Affine层。先给出批版本的Affine层的计算图,如下图所示。

批版本的Affine层的计算图

        与刚刚不同的是,现在输入X的形状是(N, 2)。之后就和前面一样,在计算图上进行单纯的矩阵计算。加上偏置时,需要特别注意。正向传播时,偏置被加到X·W的各个数据上。正向传播时,偏置会被加到每一个数据(第1个、第2个……)上。因此,反向传播时,各个数据的反向传播的值需要汇总为偏置的元素。

    综上所述,Affine的实现如下所示。

    3.Softmax-with-Loss 层  

    最后介绍一下输出层的softmax函数。前面我们提到过,softmax函数会将输入值正规化之后再输出。  

    下面来实现Softmax层。考虑到这里也包含作为损失函数的交叉熵误差(cross entropy error),所以称为“Softmax-with-Loss层”。Softmax-with-Loss层(Softmax函数和交叉熵误差)的计算图如下图所示。

Softmax-with-Loss层的计算图

    上面的计算图可以简化为以下形式.

“简易版”的Softmax-with-Loss层的计算图

    上图中softmax函数记为Softmax层,交叉熵误差记为 Cross Entropy Error层。这里假设要进行3类分类,从前面的层接收3个输入(得分)。如上图所示,Softmax层将输入(a1, a2, a3)正规化,输出(y1, y2, y3)。Cross Entropy Error层接收Softmax的输出(y1, y2, y3)和教师标签(t1, t2, t3),从这些数据中输出损失L。

    上图中要注意的是反向传播的结果。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层的输出与教师标签的差,直截了当地表示了当前神经网络的输出与教师标签的误差。

    现在来进行Softmax-with-Loss层的实现,实现过程如下所示。

    这个实现利用了之前实现的softmax()和cross_entropy_ error()函数。因此,这里的实现非常简单。请注意反向传播时,将要传播的值除以批的大小(batch_size)后,传递给前面的层的是单个数据的误差。

七、误差反向传播法的实现

    通过像组装乐高积木一样组装上一节中实现的层,可以构建神经网络。下面我们将通过组装已经实现的层来构建神经网络。

    1.神经网络学习的全貌

    在进行具体的实现之前,我们再来确认一下神经网络学习的全貌图。神经网络学习的步骤如下所示。

前提  神经网络中有合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为学习。神经网络的学习分为下面4个步骤。 

步骤1(mini-batch) 从训练数据中随机选择一部分数据。 

步骤2(计算梯度) 计算损失函数关于各个权重参数的梯度。 

步骤3(更新参数) 将权重参数沿梯度方向进行微小的更新。 

步骤4(重复) 重复步骤1、步骤2、步骤3。

    之前介绍的误差反向传播法会在步骤2中出现。数值微分虽然实现简单,但是计算要耗费较多的时间。和需要花费较多时间的数值微分不同,误差反向传播法可以快速高效地计算梯度。

    2.对应误差反向传播法的神经网络的实现

    现在来进行神经网络的实现。这里我们要把2层神经网络实现为TwoLayerNet。这个类的实例变量和方法如下图所示:


TwoLayerNet类的实例变量
TwoLayerNet类的方法

    通过使用层,获得识别结果的处理(predict())和计算梯度的处理(gradient())只需通过层之间的传递就能完成。下面是TwoLayerNet的代码实现。

   OrderedDict是有序字典,“有序”是指它可以记住向字典里添加元素的顺序。因此,神经网络的正向传播只需按照添加元素的顺序调用各层的forward()方法就可以完成处理,而反向传播只需要按照相反的顺序调用各层即可。因为Affine层和ReLU层的内部会正确处理正向传播和反向传播,所以这里要做的事情仅仅是以正确的顺序连接各层,再按顺序(或者逆序)调用各层。

    像这样通过将神经网络的组成元素以层的方式实现,可以轻松地构建神经网络。这个用层进行模块化的实现具有很大优点。

    3.误差反向传播法的梯度确认

    到这里,我们已经介绍了两种求梯度的方法.一种是基于数值微分的方法,另一种是解析性地求解数学式的方法。后一种方法通过使用误差反向传播法,即使存在大量的参数,也可以高效地计算梯度。所以,一般都使用误差反向传播法求梯度。但是,实际上,在确认误差反向传播法的实现是否正确时,是需要用到数值微分的。

    数值微分的优点是实现简单,因此,一般情况下不太容易出错。确认数值微分求出的梯度结果和误差反向传播法求出的结果是否一致(严格地讲,是非常相近)的操作称为梯度确认(gradient check)。

    梯度确认的代码实现如下所示:

    4.使用误差反向传播法的学习

    最后,我们来看一下使用了误差反向传播法的神经网络的学习的实现。 和之前的实现相比,不同之处仅在于通过误差反向传播法求梯度这一点。

    

鱼书(深度学习入门):第五章 误差反向传播法的评论 (共 条)

分享到微博请遵守国家法律