Deep Learning

ニューラルネットワーク


ニューラルネットワークの例

入力層、中間層(= 隠れ層)、出力層から構成される。 以下、入力層、中間層、出力層をそれぞれ第0層、第1層、第2層と呼ぶ。

パーセプトロンを表す数式は以下の通り。

$\displaystyle \begin{equation} y = \left\{ \begin{matrix} 0 & (b + w_1 x_1 + w_2 x_2 \leqq 0) \\ 1 & (b + w_1 x_1 + w_2 x_2 > 0) \end{matrix} \right. \quad\quad\quad\quad(3.1) \end{equation}$

(3.1) を簡単にするために、場合分けの動作(0より大きい入力で1を出力、それ以外は0を出力) を一つの関数 $h(x)$ で表現する。 $h(x)$ は活性化関数 (activation function) と呼ばれる。

$y =$ $h(b + w_1 x_1 + w_2 x_2 ) \quad\quad\quad\quad(3.2)$
$\displaystyle \begin{equation} h(x) = \left\{ \begin{matrix} 0 & (x \leqq 0) \\ 1 & (x > 0) \end{matrix} \right. \quad\quad\quad\quad(3.3) \end{equation}$

(3.3) の$h(x)$ は 閾値を境にして出力が切り替わる関数なので、 「ステップ関数」とか「階段関数」と呼ばれる。 すなわち、「パーセプトロンは活性化関数としてステップ関数を用いている」といえる。 ステップ関数以外の活性化関数としては、(3.6)のシグモイド関数が有名である。

$\displaystyle h(x) = \frac{1}{1+ e^{-x}} \quad\quad\quad\quad(3.6)$

ステップ関数の実装

step_function.py
def step_function(x):
  if x > 0:
    return 1
  else:
    return 0

Numpy配列に対応したstep_function.py
import numpy as np
def step_function(x):       # np.array([1.0, -1.0])
  y = x > 0                 # [ True, False]
  return y.astype(np.int)   # [ 1, 0 ]

Numpy配列に対応したstep_function.pyの実行
import numpy as np
import matplotlib.pylab  as plt

def step_function(x):
  return np.array(x > 0, dtype=np.int)

x = np.arrange(-5.0, 5.0, 0.1)
y = step_function(x)
plt.plot(x, y)
plt.ylim(0-0.1, 1.1)
plt.show()

シグモイド関数の実装

sigmoid.pyと実行
import numpy as np
import matplotlib.pylab  as plt

def sigmoid(x):
  return 1 / (1 + np.exp(-x))

x = np.arrange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(0-0.1, 1.1)
plt.show()

ステップ関数もシグモイド関数もどちらも非線形関数である。 ニューラルネットワークでは、活性化関数に非線形関数を使うこと。 なぜならば、もし線形関数を使ってしまうと、隠れ層を持たないネットワークで 表現できてしまい、わざわざニューラルネットワークを使う意味がなくなってしまうので。

ReLU関数

シグモイド関数は古くから利用されてきたが、最近使われているのが ReLU関数である。 ReLU関数では、0を越えている入力はそのまま出力し、それ以外は0を出力する。

$\displaystyle \begin{equation} h(x) = \left\{ \begin{matrix} 0 & (x \leqq 0) \\ x & (x \gt 0) \end{matrix} \right. \quad\quad\quad\quad(3.7) \end{equation}$
relu.py
def relu(x):
  return np.maximum(0, x)


ニューラルネットーワクの内積

例として、次のニューラルネットワークを考える。

$ x_1 \xrightarrow[]{1} y_1 \\ x_1 \xrightarrow[]{3} y_2 \\ x_1 \xrightarrow[]{5} y_3 \\ x_2 \xrightarrow[]{2} y_1 \\ x_2 \xrightarrow[]{4} y_2 \\ x_2 \xrightarrow[]{6} y_3 \\ $

これを行列で表すと次のようになる。

$\displaystyle \begin{equation} \left( \begin{matrix} 1 & 2 \end{matrix} \right) \left( \begin{matrix} 1 & 3 & 5 \\ 2 & 4 & 6 \end{matrix} \right) = \left( \begin{matrix} 5 & 11 & 17 \end{matrix} \right) \end{equation}$

この計算をpythonで実行する。

dot_neuro.py
import numpy as np

x = np.array([1, 2])
w = np.array([[1, 3, 5], [2, 4, 6]])
y = np.dot(x, w)
print(x.shape)    # (2L,)
print(w.shape)    # (2L, 3L)
print(y.shape)    # (3L,)
print(y)          # [ 5 11 17 ]


3層ニューラルネットワークの実装

$$ w^{(1)}_{12} \cdots エッジの「重み」を表す。{}^{(1)} の部分は、第1層のネットワークのエッジであることを表している。{}_{12}は、前層の2番目のノードから次の層の最初のノードへ向かうエッジを表す。 $$

図3-{17,18,19,20} を表す。

$\displaystyle \begin{equation} A^{(1)} = X W^{(1)} + B^{(1)}\\ A^{(1)} = \left( \begin{matrix} a^{(1)}_1 & a^{(1)}_2 & a^{(1)}_3 \end{matrix} \right)\\ X^{(1)} = \left( \begin{matrix} x_1 & x_2 \end{matrix} \right)\\ B^{(1)} = \left( \begin{matrix} b^{(1)}_1 & b^{(1)}_2 & b^{(1)}_3 \end{matrix} \right)\\ W^{(1)} = \left( \begin{matrix} w^{(1)}_{11} & w^{(1)}_{21} & w^{(1)}_{31} \\ w^{(1)}_{12} & w^{(1)}_{22} & w^{(1)}_{32} \end{matrix} \right)\\ \end{equation}$
transit.py
import numpy as np

X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])
A1 = np.dot(X,W1) + B1

print(A1)    # [ 0.3  0.7  1.1]

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

Z1 = sigmoid(A1)  # [ 0.57444252  0.66818777  0.75026011]

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

A2 = np.dot(Z1, W2) + B2  # [ 0.51615984  1.21402696]

Z2 = sigmoid(A2)     # [ 0.62624937  0.7710107 ]

def identity_function(x):
    return x

W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])

A3 = np.dot(Z2, W3) + B3   # [ 0.31682708  0.69627909]
Y = identity_function(A3)

$\displaystyle \begin{equation} A1 = \left( \begin{matrix} 1.0 & 0.5 \end{matrix} \right) \left( \begin{matrix} 0.1 & 0.3 & 0.5 \\ 0.2 & 0.4 & 0.6 \end{matrix} \right) + \left( \begin{matrix} 0.1 & 0.2 & 0.3 \end{matrix} \right) = \left( \begin{matrix} 0.2 & 0.5 & 0.8 \end{matrix} \right) + \left( \begin{matrix} 0.1 & 0.2 & 0.3 \end{matrix} \right) = \left( \begin{matrix} 0.3 & 0.7 & 1.1 \end{matrix} \right) \end{equation}$ $\displaystyle \begin{equation} A2 = \left( \begin{matrix} 0.57444252 & 0.66818777 & 0.75026011 \end{matrix} \right) \left( \begin{matrix} 0.1 & 0.4 \\ 0.2 & 0.5 \\ 0.3 & 0.6 \end{matrix} \right) + \left( \begin{matrix} 0.1 & 0.2 \end{matrix} \right) = \left( \begin{matrix} 0.51615984 & 1.21402696 \end{matrix} \right) \end{equation}$ $\displaystyle \begin{equation} Y = A3 = \left( \begin{matrix} 0.62624937 & 0.7710107 \end{matrix} \right) \left( \begin{matrix} 0.1 & 0.3 \\ 0.2 & 0.4 \end{matrix} \right) + \left( \begin{matrix} 0.1 & 0.2 \end{matrix} \right) = \left( \begin{matrix} 0.31682708 & 0.69627909 \end{matrix} \right) \end{equation}$
layer3.py
def init_network():
  network = {}
  network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
  network['b1'] = np.array([0.1, 0.2, 0.3])
  network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
  network['b2'] = np.array([0.1, 0.2])
  network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])
  network['b3'] = np.array([0.1, 0.2])
  return network

def forward(network, x):
  W1, W2, W3 = network['W1'], network['W2'], network['W3']
  b1, b2, b3 = network['b1'], network['b2'], network['b3']
  a1 = np.dot(x,W1) + b1
  z1 = sigmoid(a1)
  a2 = np.dot(z1,W2) + b2
  z2 = sigmoid(a2)
  a3 = np.dot(z2,W3) + b3
  y = identity_function(a3)
  return y

network = init_network()
x = np.array([1.0, 0.5])
y = forwad(network, x)   # [ 0.31682708  0.69627909]
print(y)


出力層の設計

$\displaystyle \begin{equation} 恒等関数 y_k = a_k \end{equation}$
$\displaystyle \begin{equation} ソフトマックス関数 y_k = \frac{e^{a_k}}{\Sigma^n_{i=1} e^{a_k}} \end{equation}$
softmax.py
# naive version
def softmax(a):
  exp_a = np.exp(a)
  sum_exp_a = np.sum(exp_a)
  y = exp_a / sum_exp_a
  return y

# overflow countermeasure
def softmax(a):
  c = np.max(a)
  exp_a = np.exp(a-c)  # overflow countermeasure
  sum_exp_a = np.sum(exp_a)
  y = exp_a / sum_exp_a
  return y

オーバーフロー対策として、最大の値を引いた値を扱っている。
$\displaystyle \begin{equation} ソフトマックス関数 y_k = \frac{e^{a_k}}{\Sigma^n_{i=1} e^{a_k}} \\ \quad = \frac{C e^{a_k}}{C \Sigma^n_{i=1} e^{a_k}} \\ \quad = \frac{e^{a_k + \log C}}{\Sigma^n_{i=1} e^{a_k + \log C}} \\ \quad = \frac{e^{a_k + C'}}{\Sigma^n_{i=1} e^{a_k + C'}} \\ \end{equation}$

ソフトマックス関数の出力は 0 から1.0の間の実数であり、全ての出力の和は1.0となる。 したがって、ソフトマックス関数の出力は「確率」として解釈できる。

ソフトマックス関数で用いている $e^x$ の計算は単調増加なので、 ある値にソフトマックス関数を適用してもその大小関係は変化しない。 ニューラルネットワークのクラス分類においては、 一般的に出力の一番大きいクラスを結果として出力するが、 ソフトマックス関数によって大小関係は変化しないので、 ソフトマックス関数の適用を省略しても結果は変らない。

出力層のニューロンの数

出力層のニューロンの数は、解くべき問題に対して適切に選ぶ必要がある。 クラス分類の問題では、分類したいクラスの数を出力層のニューロンの数とするのが一般的 である。


手書き数字認識

mnist_show.py
import sys, os
import numpy as np
sys.path.append(os.pardir)
from dataset.mnist import load_mnist
from PIL import Image

def img_show(img):
  pil_img = Image.fromarray(np.uint8(img))
  pil_img.show()

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)

img = x_train[0]
label = t_train[0]
print(label)   # 5
img = img.reshape(28,28)
img_show(img)


neuralnet_mnist.py
def get_data():
  (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
  return x_test, t_test

def init_network():
  with open("sample_weight.pkl", 'rb') as f:
    network = pickle.load(f)
  return network

def predict(network, x):
  W1, W2, W3 = network['W1'], network['W2'], network['W3']
  b1, b2, b3 = network['b1'], network['b2'], network['b3']
  a1 = np.dot(x, W1) + b1
  z1 = sigmoid(a1)
  a2 = np.dot(z1, W2) + b2
  z2 = sigmoid(a2)
  a3 = np.dot(z2, W3) + b3
  y = softmax(a3)
  return y


Yoshihisa Nitta

http://nw.tsuda.ac.jp/