5.4 简单层的实现

本节将用 Python 实现前面的购买苹果的例子。这里,我们把要实现的计算图的乘法节点称为“乘法层”(MulLayer),加法节点称为“加法层”(AddLayer)。

5.4 简单层的实现 - 图1 下一节,我们将把构建神经网络的“层”实现为一个类。这里所说的“层”是神经网络中功能的单位。比如,负责 sigmoid 函数的 Sigmoid、负责矩阵乘积的 Affine 等,都以层为单位进行实现。因此,这里也以层为单位来实现乘法节点和加法节点。

5.4.1 乘法层的实现

层的实现中有两个共通的方法(接口)forward()backward()forward() 对应正向传播,backward() 对应反向传播。

现在来实现乘法层。乘法层作为 MulLayer 类,其实现过程如下所示(源代码在 ch05/layer_naive.py 中)。

  1. class MulLayer:
  2. def __init__(self):
  3. self.x = None
  4. self.y = None
  5. def forward(self, x, y):
  6. self.x = x
  7. self.y = y
  8. out = x * y
  9. return out
  10. def backward(self, dout):
  11. dx = dout * self.y # 翻转x和y
  12. dy = dout * self.x
  13. return dx, dy

__init__() 中会初始化实例变量 xy,它们用于保存正向传播时的输入值。forward() 接收 xy 两个参数,将它们相乘后输出。backward() 将从上游传来的导数(dout)乘以正向传播的翻转值,然后传给下游。

上面就是 MulLayer 的实现。现在我们使用 MulLayer 实现前面的购买苹果的例子(2 个苹果和消费税)。上一节中我们使用计算图的正向传播和反向传播,像图 5-16 这样进行了计算。

5.4 简单层的实现 - 图2

图 5-16 购买 2 个苹果

使用这个乘法层的话,图 5-16 的正向传播可以像下面这样实现(源代码在 ch05/buy_apple.py 中)。

  1. apple = 100
  2. apple_num = 2
  3. tax = 1.1
  4. # layer
  5. mul_apple_layer = MulLayer()
  6. mul_tax_layer = MulLayer()
  7. # forward
  8. apple_price = mul_apple_layer.forward(apple, apple_num)
  9. price = mul_tax_layer.forward(apple_price, tax)
  10. print(price) # 220

此外,关于各个变量的导数可由 backward() 求出。

  1. # backward
  2. dprice = 1
  3. dapple_price, dtax = mul_tax_layer.backward(dprice)
  4. dapple, dapple_num = mul_apple_layer.backward(dapple_price)
  5. print(dapple, dapple_num, dtax) # 2.2 110 200

这里,调用 backward() 的顺序与调用 forward() 的顺序相反。此外,要注意 backward() 的参数中需要输入“关于正向传播时的输出变量的导数”。比如,mul_apple_layer 乘法层在正向传播时会输出 apple_price,在反向传播时,则会将 apple_price 的导数 dapple_price 设为参数。另外,这个程序的运行结果和图 5-16 是一致的。

5.4.2 加法层的实现

接下来,我们实现加法节点的加法层,如下所示。

  1. class AddLayer:
  2. def __init__(self):
  3. pass
  4. def forward(self, x, y):
  5. out = x + y
  6. return out
  7. def backward(self, dout):
  8. dx = dout * 1
  9. dy = dout * 1
  10. return dx, dy

加法层不需要特意进行初始化,所以 __init__() 中什么也不运行(pass 语句表示“什么也不运行”)。加法层的 forward() 接收 xy 两个参数,将它们相加后输出。backward() 将上游传来的导数(dout)原封不动地传递给下游。

现在,我们使用加法层和乘法层,实现图 5-17 所示的购买 2 个苹果和 3 个橘子的例子。

5.4 简单层的实现 - 图3

图 5-17 购买 2 个苹果和 3 个橘子

用 Python 实现图 5-17 的计算图的过程如下所示(源代码在 ch05/buy_apple_orange.py 中)。

  1. apple = 100
  2. apple_num = 2
  3. orange = 150
  4. orange_num = 3
  5. tax = 1.1
  6. # layer
  7. mul_apple_layer = MulLayer()
  8. mul_orange_layer = MulLayer()
  9. add_apple_orange_layer = AddLayer()
  10. mul_tax_layer = MulLayer()
  11. # forward
  12. apple_price = mul_apple_layer.forward(apple, apple_num) #(1)
  13. orange_price = mul_orange_layer.forward(orange, orange_num) #(2)
  14. all_price = add_apple_orange_layer.forward(apple_price, orange_price) #(3)
  15. price = mul_tax_layer.forward(all_price, tax) #(4)
  16. # backward
  17. dprice = 1
  18. dall_price, dtax = mul_tax_layer.backward(dprice) #(4)
  19. dapple_price, dorange_price = add_apple_orange_layer.backward(dall_price) #(3)
  20. dorange, dorange_num = mul_orange_layer.backward(dorange_price) #(2)
  21. dapple, dapple_num = mul_apple_layer.backward(dapple_price) #(1)
  22. print(price) # 715
  23. print(dapple_num, dapple, dorange, dorange_num, dtax) # 110 2.2 3.3 165 650

这个实现稍微有一点长,但是每一条命令都很简单。首先,生成必要的层,以合适的顺序调用正向传播的 forward() 方法。然后,用与正向传播相反的顺序调用反向传播的 backward() 方法,就可以求出想要的导数。

综上,计算图中层的实现(这里是加法层和乘法层)非常简单,使用这些层可以进行复杂的导数计算。下面,我们来实现神经网络中使用的层。