2.3 Sigmoid/ReLU

2.3.1 ReLU レイヤ

ReLUレイヤの計算式は以下の通りです。

y = \begin{cases} x & x > 0 \\ 0 & x \leq 0 \end{cases}
\frac{\partial y}{\partial x} = \begin{cases} 1 & x > 0 \\ 0 & x \leq 0 \end{cases}

順伝搬から見ていきます。

Code 2.4 Relu.forwardメソッド

    def forward(self):
        self.mask = self.x.d <= 0
        y = self.x.d.copy()
        y[self.mask] = 0
        self.y.d = y

「ゼロから作るDeep Learning」と同じですが、引数と戻り値がないことに着目します。self.x.dはこのレイヤの入力のデータでした。この値自体は前段での順伝搬ですでに計算されています。その計算結果をここでそのまま使っています。またゼロ以下の値をゼロでマスクしたデータを新たにself.y.dすなわちこのレイヤの出力のデータにセットしています。この値は後段のレイヤにおいて、self.x.dとして再利用されます。

逆伝搬も見てみます。

Code 2.5 Relu.backwardメソッド

    def backward(self):
        dx = self.y.g.copy()
        dx[self.mask] = 0
        self.x.g = dx

今度はデータではなく、勾配を伝搬させています。ここで設定されたself.x.gは前段のレイヤにおいて、self.y.gとして再利用されます。

2.3.2 Sigmoid レイヤ

Sigmoidレイヤの計算式は以下の通りです。

y = \frac{1}{1 + \exp(-x)}
\frac{\partial y}{\partial x} = y(1 - y)

少し寄り道をして、\partial y/\partial x が本当に上式のようになるのか、SymPyを使って計算してみます。

import sympy as sp

x, y, z = sp.symbols("x y z")
y_x = 1 / (1 + sp.exp(-x))
dy_x = sp.diff(y_x, x)
dy_x

[4] 2019-06-12 20:00:57 (17.0ms) python3 (272ms)

$$\frac{e^{- x}}{\left(1 + e^{- x}\right)^{2}}$$

z=e^{- x}とすると、

dy_z = dy_x.subs(sp.exp(-x), z)
dy_z

[7] 2019-06-12 20:00:57 (32.3ms) python3 (318ms)

$$\frac{z}{\left(z + 1\right)^{2}}$$

ここで、 y = 1/(z+1) だから、z で解いて、元の式に代入すると、

sol = sp.solve(y - 1 / (z + 1), z)[0]
sp.simplify(dy_z.subs(z, sol))

[9] 2019-06-12 20:00:57 (41.1ms) python3 (365ms)

$$y \left(1 - y\right)$$

確かにあっています。実装コードは以下の通りです。

Code 2.6 Sigmoid.forward および Sigmoid.backwardメソッド

    def forward(self):
        self.y.d = 1 / (1 + np.exp(-self.x.d))

    def backward(self):
        self.x.g = (1 - self.y.d) * self.y.d * self.y.g