順伝播ニューラルネットワークを手で解く


順伝播ニューラルネットワークは、入力層から出力層にかけて処理を行うニューラルネットワークモデルです。
ニューラルネットワークは、Python等のプログラム言語を使えば簡単に実装できます。
ですが、ここではプログラムの中身を理解するために、敢えてプログラムを使わずに(と言いつつ、検算でPythonを使いますが)順伝播ニューラルネットワークの出力値を計算してみます。

1.順伝播ニューラルネットワーク

まずは基本的な順伝播ニューラルネットワークモデル(入力層-中間層-出力層モデル)を考えます。

2.順伝播ニューラルネットワークモデルの出力値を計算で求めてみる

2.1.各層の計算

  • 入力層の計算
\boldsymbol{x}
  • 中間層の計算
\boldsymbol{u}^{(1)}=\boldsymbol{w}^{(1)}\cdot\boldsymbol{x}+ \boldsymbol{b}_1 \\
\boldsymbol{z}^{(1)} = f(\boldsymbol{u}^{(1)}) \\
  • 出力層の計算
\boldsymbol{u}^{(2)}=\boldsymbol{z}^{(1)}\cdot\boldsymbol{w}^{(2)} + \boldsymbol{b}_2 \\
\boldsymbol{z}^{(2)} = f(\boldsymbol{u}^{(2)})

2.2.初期条件の設定

インプット$\bf{x}$、重み$\bf{w}$、バイアス$\bf{b}$をそれぞれ下記の値とします。

\boldsymbol{x}=
\begin{pmatrix}
x_1\\
x_2
\end{pmatrix}
=
\begin{pmatrix}
1.0\\
5.0
\end{pmatrix}
\boldsymbol{w}^{(1)}=
\begin{pmatrix}
w_{11}^{(1)} & w_{12}^{(1)} \\
w_{21}^{(1)} & w_{22}^{(1)} \\
w_{31}^{(1)} & w_{32}^{(1)}
\end{pmatrix}
=
\begin{pmatrix}
0.1 & 0.2 \\
0.3 & 0.4 \\
0.5 & 0.6
\end{pmatrix}
\boldsymbol{b}^{(1)}=
\begin{pmatrix}
b_1^{(1)} \\
b_2^{(1)} \\
b_3^{(1)}
\end{pmatrix}
=
\begin{pmatrix}
0.1 \\
0.2 \\
0.3
\end{pmatrix}
\boldsymbol{w}^{(2)}=
\begin{pmatrix}
w_{11}^{(2)} & w_{12}^{(2)} & w_{13}^{(2)} \\
w_{21}^{(2)} & w_{22}^{(2)} & w_{23}^{(2)}
\end{pmatrix}
=
\begin{pmatrix}
0.1 & 0.2 & 0.3 \\
0.4 & 0.5 & 0.6
\end{pmatrix}
\boldsymbol{b}^{(2)}=
\begin{pmatrix}
b_1^{(2)} \\
b_2^{(2)}
\end{pmatrix}
=
\begin{pmatrix}
0.1 \\
0.2
\end{pmatrix}

2.3.順伝播型ニューラルネットワークの出力計算

中間層入力は、

\begin{equation}
\begin{split}
\boldsymbol{u}^{(1)} &= \boldsymbol{w}^{(1)}\cdot\boldsymbol{x}+ \boldsymbol{b}_1 \\
&=
\begin{pmatrix}
w_{11}^1 & w_{12}^1\\
w_{21}^1 & w_{22}^1\\
w_{31}^1 & w_{32}^1
\end{pmatrix}
\begin{pmatrix}
x_1\\
x_2
\end{pmatrix}
+
\begin{pmatrix}
b_{11} \\
b_{12} \\
b_{13}
\end{pmatrix} \\
&=
\begin{pmatrix}
0.1 & 0.2\\
0.3 & 0.4\\
0.5 & 0.6
\end{pmatrix}
\begin{pmatrix}
1.0\\
5.0
\end{pmatrix}
+
\begin{pmatrix}
0.1 \\
0.2 \\
0.3
\end{pmatrix} \\
&=
\begin{pmatrix}
1.2\\
2.5\\
3.8
\end{pmatrix}
\end{split}
\end{equation}

となります。
活性化関数はReLU関数とします。
ReLU関数は下記の式で与えられます。

f(x) = ReLU(x) = \left\{
\begin{array}{l}
x & (x > 0) \\
0 & (otherwise)
\end{array}
\right.

よって中間層出力は、

\boldsymbol{z}^{(1)}
=
\begin{pmatrix}
z_1^{(1)} \\
z_2^{(1)} \\
z_3^{(1)}
\end{pmatrix}
=
\begin{pmatrix}
ReLU(u_1^{(1)}) \\
ReLU(u_2^{(1)}) \\
ReLU(u_3^{(1)})
\end{pmatrix}
=
\begin{pmatrix}
ReLU(1.2) \\
ReLU(2.5) \\
ReLU(3.8)
\end{pmatrix}
=
\begin{pmatrix}
1.2\\
2.5\\
3.8
\end{pmatrix}

となります。
同様に、出力層入力は、

\begin{equation}
\begin{split}
\boldsymbol{u}^{(2)} &= \boldsymbol{w}^{(2)}\cdot\boldsymbol{z}^{(1)}+ \boldsymbol{b}_2 \\
&=
\begin{pmatrix}
w_{11}^{(2)} & w_{12}^{(2)} & w_{13}^{(2)}\\
w_{21}^{(2)} & w_{22}^{(2)} & w_{23}^{(2)}
\end{pmatrix}
\begin{pmatrix}
z_1^{(1)}\\
z_2^{(1)}\\
z_3^{(1)}
\end{pmatrix}
+
\begin{pmatrix}
b_{21} \\
b_{22}
\end{pmatrix} \\
&=
\begin{pmatrix}
0.1 & 0.2 & 0.3 \\
0.4 & 0.5 & 0.6
\end{pmatrix}
\begin{pmatrix}
1.2\\
2.5\\
3.8
\end{pmatrix}
+
\begin{pmatrix}
0.1 \\
0.2
\end{pmatrix} \\
&=
\begin{pmatrix}
1.86\\
4.21
\end{pmatrix}
\end{split}
\end{equation}

となります。
今回は多クラス分類問題を想定しているため、出力層の活性化関数にはSoftmax関数を用います。
Softmax関数は下記で与えられます。

f(x) = Softmax(x) = \frac{exp(x_k)}{\sum_{i=1}^n exp(x_i)}

よって最終出力は、

\boldsymbol{z}^{(2)}
=
\begin{pmatrix}
z_1^{(1)} \\
z_2^{(2)}
\end{pmatrix}
=
\begin{pmatrix}
Softmax(u_1^{(2)}) \\
Softmax(u_2^{(2)})
\end{pmatrix}
=
\begin{pmatrix}
Softmax(1.86) \\
Softmax(4.21)
\end{pmatrix}
=
\begin{pmatrix}
\frac{exp(1.86)}{exp(1.86) + exp(4.21)} \\
\frac{exp(4.21)}{exp(1.86) + exp(4.21)}
\end{pmatrix}
=
\begin{pmatrix}
0.0870… \\
0.9129…
\end{pmatrix}

となります。

3.Pythonを用いて検算をする

実際にPythonに計算させて、手で解いた値が正しいかを検算します。

forward_network.py
import numpy as np
import sys

# 順伝播型ネットワークの実行
def main():

    args = sys.argv
    x = np.array([[float(args[1]), float(args[2])]])
    network =  init_network()
    y, z1 = forward(network, x)
    print(y)

# ネートワークを作成
def init_network():
    print("##### ネットワークの初期化 #####")

    network = {}
    network['W1'] = np.array([
        [0.1, 0.3, 0.5],
        [0.2, 0.4, 0.6]
    ])

    network['W2'] = np.array([
        [0.1, 0.4],
        [0.2, 0.5],
        [0.3, 0.6]
    ])

    network['b1'] = np.array([0.1, 0.2, 0.3])
    network['b2'] = np.array([0.1, 0.2])

    print_vec("重み1", network['W1'])
    print_vec("重み2", network['W2'])
    print_vec("バイアス1", network['b1'])
    print_vec("バイアス2", network['b2'])

    return network

# 順伝播
def forward(network, x):
    print("##### 順伝播開始 #####")

    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']

    u1 = np.dot(x, W1) + b1
    z1 = relu(u1)
    u2 = np.dot(z1, W2) + b2
    y = softmax(u2)

    print_vec("総入力1", u1)
    print_vec("中間層出力1", z1)
    print_vec("総入力2", u2)
    print_vec("出力", y)
    print("出力合計: " + str(np.sum(y)))

    return y, z1

# 表示
def print_vec(text, vec):
    print("*** " + text + " ***")
    print(vec)
    print("")

# 中間層の活性化関数
# ReLU関数
def relu(x):
    return np.maximum(0, x)

# 出力層の活性化関数
# ソフトマックス関数
def softmax(x):
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T

    x = x - np.max(x) # オーバーフロー対策
    return np.exp(x) / np.sum(np.exp(x))

if __name__ == '__main__':
    main()

このプログラムを実行してみます。
ターミナルを開いて下記コマンドで実行します。

$ python forward_network.py 1.0 5.0

出力結果は下記の通りとなり、結果が一致していることがわかります。

##### ネットワークの初期化 #####
*** 重み1 ***
[[0.1 0.3 0.5]
 [0.2 0.4 0.6]]

*** 重み2 ***
[[0.1 0.4]
 [0.2 0.5]
 [0.3 0.6]]

*** バイアス1 ***
[0.1 0.2 0.3]

*** バイアス2 ***
[0.1 0.2]

##### 順伝播開始 #####
*** 総入力1 ***
[[1.2 2.5 3.8]]

*** 中間層出力1 ***
[[1.2 2.5 3.8]]

*** 総入力2 ***
[[1.86 4.21]]

*** 出力 ***
[[0.08706577 0.91293423]]

出力合計: 1.0
[[0.08706577 0.91293423]]

4.終わりに

次は、逆伝播を手で解いてみたいと思います。