上一章通过数值微分计算神经网络权重参数的梯度(损失函数关于权重参数的梯度),数值微分虽然简单,但是计算上费时间,现在学习一个高效计算权重参数的梯度的方法----误差反向传播法。
下面通过计算图来理解误差反向传播法
5.1 计算图
计算图将计算过程用图形表示出来。
5.1.1 用计算图求解
举一个简单的例子:太郎在超市买了2个100日元一个的苹果,消费税是10%,请计算支付金额。
将计算过程用计算图表示如下
也可以如下这样表示
所以,用计算图解题需按照以下流程:1.构建计算图 2.在计算图上,从左到右进行计算。此处的从左到右进行计算是一种正方向上的传播,称为正向传播。即从计算图出发点到结束点的传播。反向的话为反向传播,反向传播在下面的导数计算中发挥重要作用。
5.1.2 局部计算
计算图的特征可以通过传递局部计算获得最终结果。
5.1.3 为何用计算图解题
计算图其中一个优点是可以进行局部计算从而简化问题。另一个优点是利用计算图可以将中间的计算结果全部保存起来。使用计算图最大的原因是可以通过反向传播高效地计算导数。
在上述苹果问题中,假设想要知道苹果价格的上涨会在多大程度上影响最终的支付金额,即求支付金额关于苹果价格的导数。设苹果价格x,支付金额L,则相当于求.该导数即表示苹果的价格稍微上涨时,支付金额会增加多少。
上述支付金额关于苹果价格的导数的值可以通过计算图的反向传播求出。如下图所示
从结果可知, 支付金额关于苹果价格的导数值是2.2。这意味着如果苹果的价格增加某个微小值,则最终的支付金额将增加该值的2.2倍。
5.2 链式法则
反向传播将局部导数向正方向的反方向传递,传递这个局部导数的原理是基于链式法则。
5.2.1 计算图的反向传播
有一个使用反向传播的例子,假设存在y=f(x)的计算,反向传播如下所示
反向传播的计算顺序是,将信号E乘以节点的局部导数,然后将结果传递给下一个节点。 此处的局部导数即正向传播中y=f(x)的导数。
5.2.2 什么是链式法则
链式法则需要从复合函数说起。链式法则是关于复合函数的导数的性质,
5.2.3 链式法则和计算图
现使用计算图将上式的链式法则表示出来
反向传播的计算顺序是先将节点的输入信号乘以节点的局部导数,再传递给下一个节点。比如,反向传播时,**2节点的输入是,将其乘以局部导数
(因为正向传播时,输入t,输出z),然后传递给下一个节点。
5.3 反向传播
本节以+和×运算为例,介绍反向传播的结构。
5.3.1 加法节点的反向传播
先考虑加法节点的反向传播,以z=x+y为对象,它的导数可由下式计算
用计算图表示如下,反向传播将从上游传过来的导数乘以1传递给下游。
将上游传过来的导数值设为
,原因如下图
现在看一个具体例子,假设有10 + 5 = 15,反向传播时会从上游传来值1.3,用计算图表示如下
5.3.2 乘法节点的反向传播
现考虑z=xy,计算图如下
乘法的反向传播会将上游的值乘以正向传播时的输入信号的翻转值,后传递给下游。比如正向传播时信号是x,反向传播时则为y。 因此,实现乘法的反向传播,要保存正向传播的输入信号。
5.3.3 苹果的例子
5.4 简单层的实现
层的实现有两个共通的方法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
dy = dout * self.x
return dx, dy
backward()将从上游传递过来的导数dout乘以正向传播的翻转值,然后传递给下游。
实现上面的苹果案例,可以进行如下操作
apple = 100
apple_num = 2
tax = 1.1
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)
# backward
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print("price:", int(price))
print("dApple:", dapple)
print("dApple_num:", int(dapple_num))
print("dTax:", dtax)
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
backward()将上游传来的导数原封不动传递给下游。
现在使用乘法层和加法层实现购买2个苹果和3个橘子的案例。
使用python实现如下
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)
5.5 激活函数层的实现
5.5.1 ReLU层
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
5.5.2 sigmoid层
上面的计算图可以进行sigmoid层的反向传播,可以简化为下图
另外,可以进一步整理如下
使用python实现如下
class Sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
out = sigmoid(x)
self.out = out
return out
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
5.6 Affine/Softmax层的实现
神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿射变换”A。因此,这里将进行仿射变换的处理实现为“Affine层”。
神经网络中进行的处理有推理和学习两个阶段。神经网络的推理通常不使用Softmax层。当神经网络的推理只需要给出一个答案的情况下,因为此时只对得分最大值感兴趣,所以不需要Softmax层。不过,神经网络的学习阶段则需要Softmax层。
5.7 误差反向传播法的实现
5.8 小结
本章介绍神经网络中的误差反向传播法,并以层为单位实现了神经网络的处理。通过使用层进行模块化,神经网络中可以自由地组装层,轻松构建出自己喜欢的网络。