第四章介绍了一个两层神经网络的实现,其中求取梯度的方法是数值微分,这种方法实现比较简单,但是速度慢。这就非常影响神经网络的性能,这文介绍了一种更快求取梯度的方法,为误差反向传播法,运用计算图进行说明。
5. 误差反向传播法
为了理解误差反向传播法的原理,可以基于数学式,也可以基于计算图,本书采取的是计算图,理解起来比较容易
5.1 计算图
5.1.1用计算图求解
首先举两个例子:
问题一: 小明在超市买了两个100日元一个的苹果,消费税为10%,计算小明支付的金额。
问题二: 小明在超市买了两个100日元一个的苹果,三个150日元一个的橘子,消费税为10%,计算小明支付的金额。
综合上面的解法,能够得到计算图计算的流程:
- 构建计算图
- 在计算图上,从左到右进行计算
正向传播:从左到右边进行计算称为正向传播
反向传播:那么从右到左计算也是可以的,称为反向传播,在计算导数中发 挥重要作用
5.1.2 局部计算
举例:在超市买了2个苹果和其他的东西,可以画出如下的计算图:
从上图可以看出: 计算图可以集中精力于局部计算,不管全局计算有多么复杂,各个步骤所要做的就是对象节点的局部计算。
5.1.3 为何用计算图解题
通过计算图,能够使用反向传播进行高效计算梯度。
例子:求解问题一中,支付的总金额价格的导数。
根据上图可知: 消费金额对苹果价格的导数为2.2,即苹果的价格每变动一个单位,消费金额就会增长2.2个单位。在计算苹果价格导数的途中,期间其他的导数也能够求解出来,并且导数能够被共享,如最终消费的总金额对不包含消费税的总金额的梯度。
5.2 链式法则
链式法则:反向传播的核心。
5.2.1计算图的反向传播
5.2.2 什么是链式法则
链式法则是关于复合函数的导数的性质:
- 如果某个函数由复合函数表示,则该复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示。
5.2.3 链式法则与计算图
通过上面的两个图,能够了解计算图通过链式法则求解梯度信息的过程。
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} ∂x∂z=1;∂y∂z=1
局部计算加法节点的反向传播:
实际的一个例子: 10 + 5 = 15 10+5=15 10+5=15 ,反向传播时,上游传来的梯度值为1.3
5.3.2 乘法节点的反向传播
以
z
=
x
∗
y
z = x * y
z=x∗y为例。
∂
z
∂
x
=
y
;
∂
z
∂
y
=
x
{\frac{{\partial z}}{{\partial x}} = y};{\frac{{\partial z}}{{\partial y}} = x}
∂x∂z=y;∂y∂z=x
实际的例子:
- 乘法的反向传播会乘以输入信号的翻转值,即对10求导要1.35=6.5;对5求导要1.310=13;
- 实现乘法节点的反向传播时,要保存正向传播的输入信号
练手例子:
理解了加法节点和乘法节点的反向传播,可以试一试下面的问题,填写结果:
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层
实现代码:
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
代码实现:
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=X∗W+B
代码实现:
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层,由于需要训练数据,所以在训练网络中,最后一层还需要有一个损失函数层,用来评估选取参数的优劣性。
在图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的理论与实现》