からっぽのしょこ

読んだら書く!書いたら読む!読書読読書読書♪同じ事は二度調べ(たく)ない

5.5:活性化関数レイヤの実装【ゼロつく1のノート(実装)】

はじめに

 「プログラミング」学習初手『ゼロから作るDeep Learning』民のための実装攻略ノートです。『ゼロつく1』学習の補助となるように適宜解説を加えています。本と一緒に読んでください。

 関数やクラスとして実装される処理の塊を細かく分解して、1つずつ処理を確認しながらゆっくりと組んでいきます。

 この記事は、5.5節「活性化関数レイヤの実装」の内容になります。ReLUレイヤとSigmoidレイヤ順伝播と逆伝播をPythonで実装します。

【前節の内容】

https://www.anarchive-beta.com/entry/2020/07/30/180000www.anarchive-beta.com

【他の節の内容】

www.anarchive-beta.com

【この節の内容】

5.5 活性化関数レイヤの実装

 ニューラルネットワークでも計算グラフの考え方を導入するために、各活性化関数の順伝播と逆伝播の機能を持ったクラスとして実装します。この節では、ReLU関数とシグモイド関数を扱います。

# 5.5節で利用するライブラリを読み込む
import numpy as np


5.5.1 ReLUレイヤ

 ReLU関数とは次のように出力する関数でした。

$$ y = \begin{cases} x \quad (x > 0) \\ 0 \quad (x \leq 0) \end{cases} \tag{5.7} $$

 ReLU関数の入力$x$が、0より大きいとき$x$の値をそのまま返し、0以下だと0を返します。これがReLU関数の順伝播の計算になります。

 ReLU関数の微分は、次のようになります。

$$ \frac{\partial y}{\partial x} = \begin{cases} 1 \quad (x > 0) \\ 0 \quad (x \leq 0) \end{cases} \tag{5.8} $$

 入力$x$が0より大きいとき$x$を$x$で微分するので1になり、入力$x$が0以下だと0を$x$で微分するので0になります。

 逆伝播では、逆伝播の入力(これまでの微分の積)とそのレイヤの微分(順伝播の出力の入力での微分)の積を計算します。
 つまりReLU関数の逆伝播では、逆伝播の入力を$\frac{\partial L}{\partial y}$とすると、$x > 0$のとき$\frac{\partial L}{\partial y}$を(に1を掛ける、つまり何もせず)そのまま次のレイヤに伝播し、$x \leq 0$のとき($\frac{\partial L}{\partial y}$に0を掛ける、つまり)0を次のレイヤに伝播します。

 ReLU関数の処理を実現するには、数値計算を行うのではなくNumPy配列の機能を用います。まずはその機能を確認します。

# 仮の値を指定
x = np.array([[1.0, -0.5], [-2.0, 3.0]])
print(x)

# 0以下の要素を調べる
mask = (x <= 0)
print(mask)
[[ 1.  -0.5]
 [-2.   3. ]]
[[False  True]
 [ True False]]

 比較演算子の条件に合う要素がTrue、合わない要素はFalseとなります。

 これを添字として使うと、Trueの要素のみを取り出せます。

 この機能を利用して、True(0以下)の要素にのみ0を代入します。

# 0以下の要素を抽出
print(x[mask])

# 0以下の要素を0置換
x[mask] = 0
print(x)
[-0.5 -2. ]
[[1. 0.]
 [0. 3.]]

 0以上の要素はそのままの値を持ち、0以下の要素は0となりました。

 これが順伝播の処理になります。

 逆伝播の処理でも、このmaskの情報を利用します。

# ReLUレイヤを実装
class Relu:
    
    # インスタンス変数を定義
    def __init__(self):
        # 値を初期化
        self.mask = None
    
    # 順伝播メソッドを定義
    def forward(self, x):
        # 式(5.7)の計算
        self.mask = (x <= 0) # 0以下の要素のインデックスを検索
        out = x.copy() # 引数の値をコピー
        out[self.mask] = 0 # 0以下の要素を0に置換
        
        return out
    
    # 逆伝播メソッドを定義
    def backward(self, dout):
        # 入力と式(5.8)の積を計算
        dout[self.mask] = 0 # 0以下の要素を0に置換
        dx = dout
        
        return dx

 順伝播のメソッド引数に指定された変数(順伝播の入力)について、値が0以下の要素のインデックスをインスタンス変数maskとして保存します。

 簡単に動作確認をしておきます。

# 仮の順伝播の入力の値を指定
x = np.array([[1.0, -0.5], [-2.0, 3.0]])
print(x)

# インスタンスを作成
network = Relu()

# 順伝播
y = network.forward(x)
print(y)
[[ 1.  -0.5]
 [-2.   3. ]]
[[1. 0.]
 [0. 3.]]


# 仮の逆伝播の入力の値を指定
dy = np.array([[3.0, -2.5], [-1.0, 2.0]])
print(dy)

# 逆伝播
dx = network.backward(dy)
print(dx)
[[ 3.  -2.5]
 [-1.   2. ]]
[[3. 0.]
 [0. 2.]]


 以上でReLU関数の順伝播と逆伝播を実装できました。次はシグモイド関数を実装します。

5.5.2 Sigmoidレイヤ

 順伝播については3.2.4項で実装しました。ここでは逆伝播の確認を行います。シグモイド関数の微分と逆伝播の導出については「シグモイド関数の逆伝播」の方でやります。

 シグモイド関数とは、次の関数でした。

$$ y = \frac{1}{1 + \exp(-x)} \tag{5.9} $$

 ここで、$x$はシグモイド関数の入力で、$y$は出力です。

 逆伝播の入力を$\frac{\partial L}{\partial y}$とすると、逆伝播の出力は次の式で計算できます。

$$ \frac{\partial L}{\partial y} y (1 - y) \tag{5.12} $$

 この式は、このレイヤまでの計算結果$\frac{\partial L}{\partial y}$と、シグモイド関数の微分$y (1 - y)$の積です。

 それでは、シグモイド関数レイヤを実装します。もうこのくらいの処理内容なら、このまま実装してしまっても大丈夫ですよね。

# Sigmoidレイヤの実装
class Sigmoid:
    
    # インスタンス変数を定義
    def __init__(self):
        # 値を初期化
        self.out = None
    
    # 順伝播メソッドを定義
    def forward(self, x):
        # シグモイド関数の計算
        out = 1 / (1 + np.exp(-x)) # 式(5.9)
        
        # 計算結果をインスタンス変数に代入
        self.out = out
        
        return out
    
    # 逆伝播メソッドを定義
    def backward(self, dout):
        # 入力と微分の積を計算
        dx = dout * self.out * (1.0 - self.out) # 式(5.12)
        
        return dx

 逆伝播の計算では順伝播の出力$y$を用いるので、順伝播の計算時に(順伝播メソッド使用時に)計算結果をインスタンス変数outに保存しておきます。逆伝播メソッドでは、その値を用いて計算します。

 簡単に動作確認を行いましょう。

# 仮の順伝播の入力の値を指定
x = np.array([[1.0, -0.5], [-2.0, 3.0]])
print(x)

# インスタンスを作成
network = Sigmoid()

# 順伝播
y = network.forward(x)
print(y)
[[ 1.  -0.5]
 [-2.   3. ]]
[[0.73105858 0.37754067]
 [0.11920292 0.95257413]]


# 仮の逆伝播の入力の値を指定
dy = np.array([[3.0, -2.5], [-1.0, 2.0]])
print(dy)

# 逆伝播
dx = network.backward(dy)
print(dx)
[[ 3.  -2.5]
 [-1.   2. ]]
[[ 0.5898358  -0.58750928]
 [-0.10499359  0.09035332]]


 以上でシグモイド関数の順伝播と逆伝播の実装ができました。

 これで2種類の活性化関数レイヤを実装できました。次はニューラルネットワークを構成するメインの計算である重み付き和の計算に関する順伝播と逆伝播を実装します。

参考文献

  • 斎藤康毅『ゼロから作るDeep Learning』オライリー・ジャパン,2016年.

おわりに

 順調ですか?

【次節の内容】

https://www.anarchive-beta.com/entry/2020/08/02/180000www.anarchive-beta.com