《深度学习--基于python的理论与实现》学习笔9:第五章误差反向传播(1)

第四章介绍了一个两层神经网络的实现,其中求取梯度的方法是数值微分,这种方法实现比较简单,但是速度慢。这就非常影响神经网络的性能,这文介绍了一种更快求取梯度的方法,为误差反向传播法,运用计算图进行说明。

5. 误差反向传播法

为了理解误差反向传播法的原理,可以基于数学式,也可以基于计算图,本书采取的是计算图,理解起来比较容易

5.1 计算图

5.1.1用计算图求解

首先举两个例子:
问题一: 小明在超市买了两个100日元一个的苹果,消费税为10%,计算小明支付的金额。

图1:基于计算图求解问题一:苹果的个数和消费税作为变量标在外部

问题二: 小明在超市买了两个100日元一个的苹果,三个150日元一个的橘子,消费税为10%,计算小明支付的金额。

图2:基于计算图求解问题二:苹果,橘子的个数和消费税作为变量标在外部

综合上面的解法,能够得到计算图计算的流程:

  1. 构建计算图
  2. 在计算图上,从左到右进行计算

正向传播:从左到右边进行计算称为正向传播
反向传播:那么从右到左计算也是可以的,称为反向传播,在计算导数中发 挥重要作用

5.1.2 局部计算

举例:在超市买了2个苹果和其他的东西,可以画出如下的计算图:

图3:买了2个苹果和其他的东西

从上图可以看出: 计算图可以集中精力于局部计算,不管全局计算有多么复杂,各个步骤所要做的就是对象节点的局部计算。

5.1.3 为何用计算图解题

通过计算图,能够使用反向传播进行高效计算梯度。
例子:求解问题一中,支付的总金额价格的导数。

图4:买了2个苹果,其中消费金额对于苹果价格的导数

根据上图可知: 消费金额对苹果价格的导数为2.2,即苹果的价格每变动一个单位,消费金额就会增长2.2个单位。在计算苹果价格导数的途中,期间其他的导数也能够求解出来,并且导数能够被共享,如最终消费的总金额对不包含消费税的总金额的梯度。

5.2 链式法则

链式法则:反向传播的核心。

5.2.1计算图的反向传播

图5:计算图的反向传播

5.2.2 什么是链式法则

链式法则是关于复合函数的导数的性质:

  • 如果某个函数由复合函数表示,则该复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示。
图6:复合函数求导的步骤

5.2.3 链式法则与计算图

图7:计算图求解复合函数的梯度
图8:计算图求解复合函数的梯度 (带入具体数据)

通过上面的两个图,能够了解计算图通过链式法则求解梯度信息的过程。

5.3 反向传播(基于计算图)

前面介绍了反向传播是基于链式法则成立的,本节将以+和x等运算为例,介绍反向传播的结构

5.3.1 加法节点的反向传播

z = x + y z = x + y z=x+y为例。

∂ z ∂ x = 1 ; ∂ z ∂ y = 1 {\frac{{\partial z}}{{\partial x}} = 1};{\frac{{\partial z}}{{\partial y}} = 1} xz=1yz=1

图9:加法节点的反向传播 如上图:坐标的图是正向传播,右边是反向传播,对于加法节点的反向传播,下游的梯度值等于上游的梯度值。

局部计算加法节点的反向传播:

图10:局部计算的加法节点反向传播 对于一个大型的网络,某一处的加法节点的反向传播结果还是成立的。

实际的一个例子: 10 + 5 = 15 10+5=15 10+5=15 ,反向传播时,上游传来的梯度值为1.3

图11:局部计算的加法节点反向传播的实例

5.3.2 乘法节点的反向传播

z = x ∗ y z = x * y z=xy为例。
∂ z ∂ x = y ; ∂ z ∂ y = x {\frac{{\partial z}}{{\partial x}} = y};{\frac{{\partial z}}{{\partial y}} = x} xz=yyz=x

图12:乘法节点的反向传播 如上图:左边的图是正向传播,右边是反向传播

实际的例子:

图13:从上游传下来的梯度值为1.3
  • 乘法的反向传播会乘以输入信号的翻转值,即对10求导要1.35=6.5;对5求导要1.310=13;
  • 实现乘法节点的反向传播时,要保存正向传播的输入信号

练手例子:
理解了加法节点和乘法节点的反向传播,可以试一试下面的问题,填写结果:

图14:检验的问题

5.4 反向传播的代码实现(基于计算图)

由上文可知,通过计算图求解梯度有两种情况,乘法节点和加法节点,这里我们定义两个层:乘法层和加法层,用来实现对这两种情况进行问题的求导。很多问题是加法层和乘法层的复合。

5.4.1 乘法层的实现

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):#dout是上一层传下来的导数
        #翻转x和y
        dx=dout * self.y
        dy=dout * self.x
        
        return dx,dy

5.4.2 加法层的实现

class Addlayer:
    def __init__(self):
       pass  #不用传参
    
    #forward 前向
    def forward(self,x,y):
        out=x+y
        return out
    
    #backward  反向
    def backward(self,out):
        dx=dout
        dy=dout
        return dx,dy

5.4.3 对于问题一的求解

复述问题一:* 小明在超市买了两个100日元一个的苹果,消费税为10%,计算小明支付的金额,计算支付金额对苹果的单价,苹果的数量,税率的梯度?


#购买两个苹果的误差反向传播:
#深度学习简单层的实现(乘法层和加法层)
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):#dout是上一层传下来的导数
        #翻转x和y
        dx=dout * self.y
        dy=dout * self.x
        
        return dx,dy
class Addlayer:
    def __init__(self):
       pass  #不用传参
    
    #forward 前向
    def forward(self,x,y):
        out=x+y
        return out
    
    #backward  反向
    def backward(self,out):
        dx=dout
        dy=dout
        return dx,dy
    
#定义初始值
apple=100
appel_num=2
tax=1.1

#定义层
mul_apple_layer=MulLayer()
mul_tax_layer=MulLayer()

#forward
apple_price=mul_apple_layer.forward(apple,appel_num)
price=mul_tax_layer.forward(apple_price,tax)
print('price=','%.1f'%price)

# backward
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)

#输出结果
print('dapple=',dapple)
print('dapple_num=',int(dapple_num))
print('dtax=',dtax)

5.4.3 对于问题二的求解

复述问题二:* 小明在超市买了两个100日元一个的苹果,三个150日元一个的橘子,消费税为10%,计算小明支付的金额,支付金额对苹果的单价,苹果的数量,橘子的单价,橘子的数量税率的梯度?


#购买两个苹果和三个橘子的误差反向传播(包含加法和乘法的误差反向传播):
#深度学习简单层的实现(乘法层和加法层)
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):#dout是上一层传下来的导数
        #翻转x和y
        dx=dout * self.y
        dy=dout * self.x
        
        return dx,dy
class AddLayer:
    def __init__(self):
       pass  #不用传参
    
    #forward 前向
    def forward(self,x,y):
        out=x+y
        return out
    
    #backward  反向
    def backward(self,dout):
        dx=dout
        dy=dout
        return dx,dy
    
#定义初始值
apple=100
orange=150
appel_num=2
orange_num=3
tax=1.1

#定义层
mul_apple_layer=MulLayer()
mul_orange_layer=MulLayer()
mul_tax_layer=MulLayer()
add_orange_apple_layer=AddLayer()

#forward
apple_price=mul_apple_layer.forward(apple,appel_num)
orange_price=mul_orange_layer.forward(orange,orange_num)
sum_price=add_orange_apple_layer.forward(apple_price,orange_price)
price=mul_tax_layer.forward(sum_price,tax)
print('sumprice=','%.1f'%price)

# backward
dprice = 1
dsum_price, dtax = mul_tax_layer.backward(dprice)
dapple_price,dorange_price=add_orange_apple_layer.backward(dsum_price)
dorange,dorange_num=mul_orange_layer.backward(dorange_price)
dapple,dapple_num=mul_apple_layer.backward(dapple_price)

#输出结果
print('dapple=','%.1f'%dapple,'dapple_num=',int(dapple_num))
print('dorange=','%.1f'%dorange,'dorange_num=',int(dorange_num))

print('dtax=',dtax,'dapple_price=',dapple_price)

5.5 激活函数层的实现(基于计算图)

将计算图的思维应用到神经网络中,我们把神经网络的层的实现定义为一个类。通过加法层和乘法层来实现激活函数层的实现。激活函数有:ReLU层,Sigmoid层

5.5.1 ReLU层

图15:激活函数ReLU的函数表达式和梯度表达式
图16:ReLU层的计算图

实现代码:

class ReLU:
    def __init__(self):
        self.mask=None
     
    #前向传播
    def forward(self,x):
        self.mask=(x<=0)  #mask一个逻辑数组,其中x<=的值都是True
        out=x.copy()      #out与x相等
        out[self.mask]=0  #对mask中True中的值置0
                          #相当于完成了ReLU的功能,小于等于0时为0;大于0时,保持原值
        return out
    #反向传播
    def backward(self,dout):
        dout[mask]=0
        df=dout
        return df

5.5.2 Sigmoid层

根据定义,Sigmoid的函数表达式为:
y = 1 1 + exp ⁡ ( − x ) {\text{y}} = \frac{1}{{1 + \exp ( - x)}} y=1+exp(x)1

图17:激活函数Sigmoid的计算图
图18:激活函数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):
        return dout*self.out*(1.0-self.out)

5.5.3 Affine层

在进行神经网络的正向传播的时候,需要计算加权信号的总和,这里用Affine层来定义这一个操作。

Affine层的函数表达式(X, W和B均为矩阵): y = X ∗ W + B y = X*W + B y=XW+B

图19:批版本的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=self.x*self.W+b
        self.out=out 
        return out
    
    def backward(self,dout):
        dx=np.dot(dout,self.W.T)
        dW=np.dot(self.x.T,dout)
        db=np.sum(dout,axis=0)
        
        return dx

5.5.4 Softmax-with-Loss层

如果神经网络用于分类的话,最后一层是Softmax层,由于需要训练数据,所以在训练网络中,最后一层还需要有一个损失函数层,用来评估选取参数的优劣性。

图20:Softmax-with-Loss层计算图
图21:简易版-Softmax-with-Loss层计算图

在图21中,正向传播的时候把softmax层和cross entropy error层封装起来,能够更加看清楚。

代码实现:

class SoftmaxWithLoss():
    def __init__(slef):
        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)  #运用前面定义的softmax和cross_entropy_error求取误差值
        
        return self.loss
    
    def backward(self,dout=1):
        batch.size=self.t.shape[0]
        dx=(self.y-self.t)/batch_size   #梯度   除以批的原因:前面的误差求解的是一批
                                        #      中误差的总和,这里除以batch——size,是
                                        #      为了传递给前面的层的是单个数据的误差
                                        #      这里的梯度公式是推导出来的
        return dx

参考书籍:
1.《深度学习–基于python的理论与实现》

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值