机器学习入门(11)— 反向传播的加法节点、乘法节点、加法层代码实现、乘法层代码实现
1. 加法节点
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的理论与实现》
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)