前ステップまでに実装したコード
[1]:
import numpy as np
class Variable:
def __init__(self, data):
self.data = data
class Function:
def __call__(self, input):
x = input.data
y = self.forward(x)
output = Variable(y)
return output
def forward(self, x):
raise NotImplementedError()
class Square(Function):
def forward(self, x):
return x ** 2
ここまで私たちは、DeZeroの「変数」と「関数」を作ってきました。そして、前ステップでは、Square
という2乗の計算を行う関数クラスを実装しました。本ステップでは、別の新しい関数を実装し、複数の関数を組み合わせて計算を行います。
まずは、DeZeroの新しい関数を1つ実装します。ここでは、\(y = e^x\)という計算を実装します(\(e\)はネイピア数で、具体的には\(e=2.718...\)という値です)。早速そのコードを実装しましょう。
[2]:
class Exp(Function):
def forward(self, x):
return np.exp(x)
Square
クラスのときと同様に、Function
クラスを継承してforward
メソッドに目的の計算を実装します。Square
クラスと異なるのは、forward
メソッドの中身がx ** 2
からnp.exp(x)
に変わった点だけです。
Function
クラスの__call__
メソッドの入力と出力は、ともにVariable
インスタンスです。そのため、DeZeroの関数を連続して使用することは自然にできます。たとえば、\(y = (e^{x^2})^2\)という計算を考えてみましょう。その場合、次のようなコードを書くことができます。
[3]:
A = Square()
B = Exp()
C = Square()
x = Variable(np.array(0.5))
a = A(x)
b = B(a)
y = C(b)
print(y.data)
1.648721270700128
ここでは、3つの関数――A
、B
、C
――を連続して適用するコードが示されています。重要な点は、途中に登場する4つの変数――x
、a
、b
、y
――はすべてVariable
インスタンスだということです。Function
クラスの__call__
メソッドの入出力がVariable
インスタンスで統一されているため、上記のように複数の関数を連続して適用できます。ちなみに、ここで行った計算は、図3-1のように、関数と変数が交互に並ぶ計算グラフで表すことができます。
図3-1 複数の関数による計算グラフ(○は変数、□は関数)
NOTE
図3-1のように、複数の関数を順に適用して作られる変換は、1つの大きな関数と見ることができます。この複数の関数によって構成される関数は、合成関数と呼ばれます。ここで重要な点は、合成関数を構成する各関数が単純な計算であったとしても、それらを連続して適用すれば、より複雑な計算が行えるということです。
ところで、なぜ私たちは一連の計算を「計算グラフ」として表すのでしょうか? その答えは、各変数の微分を効率良く求めることができる(正確には、その準備が整う)からです。そのアルゴリズムがバックプロパゲーション(誤差逆伝播法)です。次のステップからは、バックプロパゲーションが実現できるように、DeZeroを拡張していきます。