前言:上一章通过数值微分计算神经网络的权重参数的梯度,这种方法比较简单但比较耗时。所以现在介绍另外一种比较高效的方法 -- 误差反向传播法
目录
计算图
计算图通过节点和箭头表示计算过程。
举例
问题1:太郎在超市买了2个100日元一个的苹果,消费税是10%,请计算支付金额
计算图改进:将苹果和消费税标在圆圈外面
问题2:太郎在超市买了2个苹果、3个橘子。其中,苹果每个100日元, 橘子每个150日元。消费税是10%,请计算支付金额
综上,用计算图解题的情况下,需要按如下流程进行。
1.构建计算图。
2.在计算图上,从左向右进行计算。
第2步 从左往右计算的过程就是正向传播,反之从右往左是反向传播。
前面计算图中 各个节点处的计算都是局部计算,只需进行与自己有关的计算,不用考虑全局。
为什么用计算图求解
使用计算图最大的原因是,可以通过反向传播高效计算导数。
上述问题1补充 求“支付金额关于苹果的价格的导数”,这个导数的值表示当苹果的价格稍微上涨时,支付金额会增加多少
图5-5 反向传播使用与正方向相反的箭头(粗线)表示。反向传播传递“局部导数”,将导数的值写在箭头的下方。在这个例子中,反向传播从右向左传递导数的值(1 → 1.1 → 2.2)。从这个结果中可知,“支付金额关于苹果的价格的导数”的值是2.2。这意味着,如果苹果的价格上涨1日元, 最终的支付金额会增加2.2日元(严格地讲,如果苹果的价格增加某个微小值, 则最终的支付金额将增加那个微小值的2.2倍)。
计算图的优点
可以通过正向传播和反向传播高效地计算各个变量的导数值
链式法则
假设存在 y = f(x)的计算,这个计算的反向传播如图5-6所示
反向传播的计算顺序是,将信号E乘以节点的局部导数 ,然后将结果传递给下一个节点。
链式求导
计算图的反向传播是基于链式法则成立的。
复合函数求导一般用链式法则,这里是高数基础知识就不赘述了。比如 可以看作
和 两个式子构成。求z对x的导数
用计算图表示如下,**2表示平方运算。
反向传播
加法节点的反向传播
以 为例观察其反向传播, 用计算图表示。
加法节点的反向传播只是将输入信号输出到下一个节点。例如 15 = 10 + 5,反向传播会从上游传来1.3
乘法节点的反向传播
对x和y分别求导
乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值” 后传递给下游。翻转值表示一种翻转关系,如图5-12所示,正向传播时信号 是x的话,反向传播时则是y;正向传播时信号是y的话,反向传播时则是x。
其中 6.5 = 1.3 * 5, 13 = 1.3 * 10
小结:加法的反向传播只是将上游的值传给下游, 并不需要正向传播的输入信号。但是,乘法的反向传播需要正向传播时的输入信号值。因此,实现乘法节点的反向传播时,要保存正向传播的输入信号。
简单层的实现
python实现上述购买苹果的例子,计算图的乘法节点称为“乘法层”(MulLayer),加法节点称为“加法层” (AddLayer)。
乘法层实现
例如 z = xy
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)
执行结果和上图黑色加粗箭头表示的数字一致
price: 220
dApple: 2.2
dApple_num: 110
dTax: 200
加法层的实现
以 为例,backward()将上游传来的导数(dout)原封不动地传递给下游
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
python实现上面5-17的计算图,这里有3个乘法1个加法对应3个乘法层和1个加法层的实例。
虽然代码比较长,但是逻辑简单,按顺序传入参数。
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:", int(price))
print("dApple:", dapple)
print("dApple_num:", int(dapple_num))
print("dOrange:", dorange)
print("dOrange_num:", int(dorange_num))
print("dTax:", dtax)
计算的结果如下,和上图黑色加粗箭头表示的数字一致
price: 715
dApple: 2.2
dApple_num: 110
dOrange: 3.3000000000000003
dOrange_num: 165
dTax: 650
激活函数层的实现
ReLU层
y是分段函数,这里根据导数定义 求x = 0处的导数 ,因为x = 0处左右两边的导数值不同,所以x = 0处应该不可导。这里不知道原作者是如何考虑的,有同样疑问的小伙伴欢迎留言。
图5.8 如果正向传播时的输入x大于0,则反向传播会将上游的值原封不动地传给下游;如果正向传播时的x小于等于0,则反向传播中传给下游的信号将停在此处。计算图如下
python实现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
mask是由True/False构成的NumPy数组,它会把正向传播时的输入x的元素中小于等于0的地方保存为True,其他地方(大于0的元素)保存为False。
out = x.copy()
out[mask] = 0
print(out)
out数组将mask数组中值为True的(x <= 0)元素设置为0
[[1. 0.]
[0. 3.]]
Sigmoid层
图5-19的计算图的反向传播
Sigmoid层的计算图进一步简化为下图
Sigmoid层代码实现:
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
Affine/Softmax层的实现
前情回顾:使用Numpy的np.dot()实现矩阵的乘法
这里,X、W、B 分别是形状为(2,)、(2, 3)、(3,)的多维数组。这样一 来,神经元的加权和可以用Y = np.dot(X, W) + B计算出来。然后,Y 经过激活函数转换后,传递给下一层。这就是神经网络正向传播的流程。
注意:X和W 的乘积必须使对应维度的元素个数一致
Affine层
神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿射变换”。因此,这里将进行仿射变换的处理实现为“Affine层”
注意X、W、B是矩阵(多维数组),各个结点之间传播的是矩阵
推导后得到下面两个式子
标签:学习,入门,self,传播,鱼书,反向,计算,price,apple From: https://blog.csdn.net/chinn_1234/article/details/139528282