【フレームワークを使用せず】ゼロから作る物体検出AIモデル
【No.7】ニューラルネットワークの実装(Leaky ReLU)

1. 本記事について

本記事は、これまでの内容(ニューラルネットワークの理解その1〜4)を踏まえ、ニューラルネットワークをpythonプログラムに落とし込んでいきます。
本記事は活性化関数(Leaky ReLU)の実装を解説していきます。

前回前々回は、Affine層について解説しました。)

ニューラルネットワークの理解その1~4を見てから、本記事を見ることをお勧めします(特にその4)。

注意事項として、本シリーズではpythonの基礎的な解説、numpyの基礎的な解説は割愛させていただきます。特に、numpy配列やshape等頻出なので、その辺の知識が不十分の場合は、そちらの習得を先に行うことをお勧めします。

本シリーズを進めていくにあたり、参考にさせていただく書籍があります。オライリー・ジャパンから出版されている「ゼロから作るDeep Learning ーPythonで学ぶディープラーニングの理論と実装」です。

本記事は、上記書籍を参考にさせていただいております。

2. Leaky ReLU層

活性化関数の処理をpythonで実装していきます。YOLOv1は活性化関数にLeaky ReLUを採用しているので、Leaky ReLUを実装していきます(Leaky ReLUについてはこちらをみてみてください)。

Affineと同様に順伝播の処理と逆伝播の処理に分けて実装します。

順伝播

Leaky ReLUはReLUの派生系で、以下の式で表せるのでした。

式1

\( f(x) = \left\{ \begin{eqnarray} αx \quad (x \leq 0) \\ x \quad (x \gt 0) \end{eqnarray} \right. \)

\(x \gt 0\)の時は\(f(x) = x\)(恒等関数)となり、\(x \leq 0\)の時は\(f(x) = αx\)(\(α\)は定数)となります。

式1をpythonで書くと以下となります。
入力をx、αをalphaとします。

              
                import numpy as np

                self.x = x# 順伝播の入力を保持
                out = np.where(x > 0, x, alpha*x)
              
            

メインの処理はout = の部分で、np.whereを用いて条件分岐しています。xが0超の時そのままxを返し、xがそれ以外の時alpha*xを返します。

self.x = xがありますが、これは順伝播の入力において、0以下の要素の情報を保存しておく為のものです。逆伝播の時に使用しますので、いったんここではスルーします。

具体的に、xとalphaをそれぞれ以下とし、順伝播の計算をしてみます。

              
                import numpy as np

                # 入力: ノード数3
                x = np.array([[-0.8157, 1.3761, -1.555 ]])
                alpha = 0.1

                out = np.where(x > 0, x, alpha*x)

                print(out)
              
            
              
                [[-0.08157  1.3761  -0.1555 ]]
              
            

順伝播はこれで完了です。続いて逆伝播を解説します。

逆伝播

Leaky ReLUの逆伝播は以下の式(式1を微分したもの)を使用します。

式2

\( f'(x) = \left\{ \begin{eqnarray} α \quad (x \leq 0) \\ 1 \quad (x \gt 0) \end{eqnarray} \right. \)

\(f(x)\)を\(x\)で微分する形です。

因みに、これは前回Affineの実装時に除外したところになります(詳しくはこちら)。
今回(=活性化関数の逆伝播処理)の実装で、逆伝播してきた\(δ\)に活性関数の微分結果を掛けてあげます。そしてその結果を次の逆伝播として渡してあげます。

式2をpythonで表すと以下の様になります。
逆伝播の入力をdout、αをalpha、順伝播の時の入力をxとします。

              
                import numpy as np

                dx = np.where(self.x > 0, dout*1, dout*alpha)
              
            

ここは結構誤解が発生するので、詳しくみていきます。

np.whereで条件分岐されているものを言葉でいうと以下になります。
\(x \leq 0\)の時 dx = dout * alpha
\(x \gt 0\)の時 dx = dout * 1

ここのポイント1つ目に「\(x \leq 0\)のxが、順伝播の際の入力値を指している」ということです。「逆伝播のdoutで条件分岐しているわけではない」ということになります。

ここのポイント2つ目に「dx = dout * 1 のように、doutを掛けることを忘れない」ということです。忘れるとdx = 1若しくはdx = alphaになってしまうので、注意しましょうということです。

以下がたまにみる実装です。

              
                import numpy as np

                dx = np.where(dout > 0, 1, alpha)
              
            

これだと、せっかく出力層から逆伝播してきた情報が、全て1かalphaに置換されてしまいます。

その後でdoutを掛けて逆伝播する処理があれば良いのですが、そうでなければ注意が必要です(Leaky ReLUではみかけませんが、ReLUの実装でたまに見る気がします)。

逆伝播に関して、具体的に数値を入れて流してみます。
doutとalphaとxをそれぞれ以下とし、逆伝播の計算をします。

              
                import numpy as np

                # δ: ノード数3
                dout = np.array([[-0.71, 0.57, -0.35]])
                alpha = 0.1

                # 順伝播の入力: ノード数3
                x = np.array([[-0.8157, 1.3761, -1.555 ]])

                dx = np.where(x > 0, dout*1, dout*alpha)

                print(dx)
              
            
              
                [[-0.071  0.57  -0.035]]
              
            

Leaky ReLUまとめ

順伝播・逆伝播をそれぞれ1つのクラスにまとめます。

              
                class LeakyReLU:
                    # 各値の初期化
                    def __init__(self, alpha):
                        self.x = None# 逆伝播の時に使う
                        self.alpha = alpha

                    # 順伝播
                    def forward(self, x):
                        self.x = x# 順伝播の入力を保持
                        out = np.where(x > 0, x, self.alpha*x)

                        return out

                    # 逆伝播
                    def backward(self, dout):
                        dx = np.where(self.x > 0, dout*1, dout*self.alpha)# δの計算

                        return dx
              
            

これでLeaky ReLU層をpythonで実装完了しました。最後に、実際に数値を入れて動作を確認します。

              
                class LeakyReLU:
                    # 各値の初期化
                    def __init__(self, alpha):
                        self.x = None# 逆伝播の時に使う
                        self.alpha = alpha

                    # 順伝播
                    def forward(self, x):
                        self.x = x# 順伝播の入力を保持
                        out = np.where(x > 0, x, self.alpha*x)

                        return out

                    # 逆伝播
                    def backward(self, dout):
                        dx = np.where(self.x > 0, dout*1, dout*self.alpha)# δの計算

                        return dx

                ### 実際に数値を入力してみる ###

                # 入力: ノード数3
                x = np.array([[-0.8157, 1.3761, -1.555 ]])

                # δ: ノード数3
                dout = np.array([[-0.71, 0.57, -0.35]])

                alpha = 0.1

                # インスタンス化
                f = LeakyReLU(alpha)

                # 順伝播
                Y = f.forward(x)
                print(Y)
                print()

                # 逆伝播
                dX = f.backward(dout)
                print(dX)
              
            
              
                [[-0.08157  1.3761  -0.1555 ]]

                [[-0.071  0.57  -0.035]]
              
            

大丈夫そうです!

3. まとめ

今回はニューラルネットワークの実装(Leaky ReLU層)について解説しました。

次回も是非みてみてください!

4. 参考文献

斎藤康毅 著 「ゼロから作るDeep Learning ーPythonで学ぶディープラーニングの理論と実装」

・物体検出AIの導入
・アノテーションサービス
・手書き計算サイト ZONE++ の運営
・技術ブログ LAB++の運営
   上記をメインにおこなっております

詳しくはこちら

Category

Search