1. 加法节点
以 z = x + y
为对象,观察它的反向传播。z = x + y
的导数可由下式(解析性地)计算出来。
计算图如图 5-9 中,反向传播将从上游传过来的导数(本例中是
∂
L
∂
z
\frac{\partial L}{\partial z}
∂z∂L)乘以1,然后传向下游。也就是说,因为加法节点的反向传播只乘以1,所以输入的值会原封不动地流向下一个节点。
另外,本例中把从上游传过来的导数的值设为
∂
L
∂
z
\frac{\partial L}{\partial z}
∂z∂L。这是因为,如图 5-10 所示,我们假定了一个最终输出值为 L
的大型计算图。z = x + y
的计算位于这个大型计算图的某个地方,从上游会传来
∂
L
∂
z
\frac{\partial L}{\partial z}
∂z∂L 的值,并向下游传递
∂
L
∂
x
\frac{\partial L}{\partial x}
∂x∂L和
∂
L
∂
y
\frac{\partial L}{\partial y}
∂y∂L 。
2. 乘法节点反向传播
以 z = xy
为对象。这个式子的导数用式(5.6)表示。
计算图如图5-12
乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值”后传递给下游。翻转值表示一种翻转关系,如图 5-12 所示,正向传播时信号是 x
的话,反向传播时则是 y
;正向传播时信号是 y
的话,反向传播时则是 x
。
加法的反向传播只是将上游的值传给下游,并不需要正向传播的输入信号。但是乘法的反向传播需要正向传播时的输入信号值。因此,实现乘法节点的反向传播时,要保存正向传播的输入信号。
3. 实例
问题1: 小明在超市买了2 个100 日元一个的苹果,消费税是10%,请计算支付金额。
对于问题 1 反向传播的例子:
如前所述,乘法节点的反向传播会将输入信号翻转后传给下游。
从图 5-14 的结果可知,苹果的价格的导数是 2.2,苹果的个数的导数是 110,消费税的导数是 200。这可以解释为,如果消费税和苹果的价格增加相同的值,则消费税将对最终价格产生200 倍大小的影响,苹果的价格将产生2.2 倍大小的影响。不过,因为这个例子中消费税和苹果的价格的量纲不同,所以才形成了这样的结果(消费税的1 是100%,苹果的价格的1 是1 日元)。
4. 乘法层代码实现
层的实现中有两个共通的方法(接口)forward()
和 backward()
。
forward()
对应正向传播;backward()
对应反向传播;
示例代码如下:
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
__init__()
中会初始化实例变量x
和y
,它们用于保存正向传播时的输入值。forward()
接收x
和y
两个参数,将它们相乘后输出。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()
的顺序与调用 forward()
的顺序相反。此外,要注意 backward()
的参数中需要输入“关于正向传播时的输出变量的导数”。
比如,mul_apple_layer
乘法层在正向传播时会输出 apple_price
,在反向传播时,则会将 apple_price
的导数 dapple_price
设为参数。
另外,这个程序的运行结果和图5-16 是一致的。
5. 加法层代码实现
加法层代码类实现如下:
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
加法层不需要特意进行初始化,所以 __init__()
中什么也不运行。加法层的 forward()
接收 x
和 y
两个参数,将它们相加后输出。backward()
将上游传来的导数(dout
)原封不动地传递给下游。
购买 2 个苹果和 3 个橘子的计算图:
示例代码:
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
首先,生成必要的层,以合适的顺序调用正向传播的 forward()
方法。然后,用与正向传播相反的顺序调用反向传播的 backward()
方法,就可以求出想要的导数。
参考:《深度学习入门:基于Python的理论与实现》