人工ニューラルネットワーク

人工ニューラルネットワーク

ニューラルネットワークの構造

 生物のニューラルネットワークをコンピュータ上に模倣したモデルを 人工ニューラルネットワーク (Artificial Neural Network) とよびます。機械学習の分野では「人工」を省略するのが一般的なので、当講座でも以降は単に ニューラルネットワーク(Neural Network) と記述します。

 ニューラルネットワークは ニューロン を入力層、中間層(隠れ層)、出力層の順に層状に並べて構築します。

 ニューラルネットワーク(Artificial Neural Network)

 入力層は信号をネットワークに送ります。出力層はネットワークから信号を出力します。入力層と出力層の間には複数の中間層(隠れ層)を並べます。そして、入力層から出力層まで、第0層、第1層、第2層 ... というように順序づけることにします(入力層を第1層とする方式もありますが、当サイトでは中間層から第1層と数える方式を採用します)。また、入力信号が上から下へと流れていくイメージで、ある層から見て入力層に近い層を上の層、出力層に近い層を下の層とよぶことにします。

 ある1つのニューロンからの信号は、次の層のすべてのニューロンに重みをつけて渡されます。同じ層にあるニューロン同士では信号のやりとりは行われません。

 ネットワークから 2つの層を選んで、上の層を $a$ 層、下の層を $b$ 層とします。簡単のために、$a$ 層・$b$ 層ともに 2個のニューロンで構成されているとします。

 ミニサイズのニューラルネットワーク(Artificial Neural Network) 改訂版

 $x_0,\ x_1$ は $b$ 層が $a$ 層から受け取る入力です。$x_2$ はダミー入力(疑似信号)で、常に 1 の値をとります。$w_{ji}$ は $b$ 層の $j$ 番目のニューロンが $a$ 層の $i$ 番目のニューロンから受け取る信号に掛かる重みを表しています(下図参照)。

 Python 重みパラメータ行列(改訂版)

 たとえば、$w_{01}$ は「$b$ 層の 0 番目のニューロンが $a$ 層の 1 番目のニューロンから受け取る信号に掛かる重み」を表しています。

 $u_j$ は $b$ 層の $j$ 番目のニューロンの入力総和を表します。
 たとえば、$u_0$ は $b$ 層の 0 番目のニューロンの入力総和であり、$a$ 層のすべてのニューロンからの入力とダミー入力の線形結合で表されます。
 
\[u_0=w_{00}x_0+w_{01}x_1+w_{02}x_2\]
 同様に $u_1$ は $b$ 層の 1 番目のニューロンの入力総和です。
 
\[u_1=w_{10}x_0+w_{11}x_1+w_{12}x_2\]
 ここで行列 $W$ とベクトル $\boldsymbol{u}$ および $\boldsymbol{x}$ を
 
\[W=\begin{bmatrix}
w_{00} & w_{01} & w_{02}\\
w_{10} & w_{11} & w_{12}\end{bmatrix},\quad
\boldsymbol{u}=\begin{bmatrix}u_0\\u_1\end{bmatrix}
,\quad\boldsymbol{x}=\begin{bmatrix}x_0\\x_1\\x_2\end{bmatrix}\]
によって定義すると、$b$ 層の入力総和は
 
\[\boldsymbol{u}=W\boldsymbol{x}\]
と表すことができます。これを活性化関数 $f(u)$ に通して、この層のそれぞれのニューロンからの出力値

\[\boldsymbol{y}=f(\boldsymbol{u})=f(W\boldsymbol{x})\]
を得ることができます。このように行列を使うと、各層のニューロンをまとめて1つのユニットとして扱うことができます(実装する際にはニューロンごとにではなく、層ごとに関数を定義します)。
 


 Python で小さなニューラルネットワークを作ってみましょう。
 最初にネットワークの層の機能をもつ関数を定義しておきます。

# Neural_Network

# In[1]

import numpy as np

# 恒等関数
def identity(x):
    return x

# シグモイド関数
def sigmoid(x):
    f = 1 / (1 + np.exp(-x))
    return f

# 層関数
def layer(xv, wm, func = identity):
    xv = np.append(xv, 1)  # 入力ベクトルにダミーを追加
    u = np.dot(wm, xv)  # 入力総和
    return func(u)  # 活性化関数に入力総和を渡す

 layer()は入力ベクトル xv と重み行列 wm、関数オブジェクト(活性化関数)func を受け取って、出力値を返します。func にはデフォルトで恒等関数オブジェクト identity が指定されているので、このオプション引数を省略すると、入力総和 u をそのまま出力することになります。また、layer() は自動的にダミー入力を組み込むので、xv にダミー入力を加えた配列を渡す必要はありません。

 入力層・中間層・出力層を1層ずつ備えた次のようなモデルを考えます。

 ミニサイズのニューラルネットワーク(Artificial Neural Network)

 適当な入力ベクトルと重み行列を設定して、ネットワークからの出力を確認してみましょう。

# In[2]

# 中間層への入力の重み行列
wm1 = np.array([[1.0, 2.0, 1.5],
               [1.5, 1.0, 1.0]])

# 出力層への入力の重み行列
wm2 = np.array([1.0, -0.5, 1.5])

# 中間層への入力ベクトル
xv0 = np.array([1.0, -0.5])

# 出力層への入力ベクトル
xv1 = layer(xv0, wm1, sigmoid)

# ネットワークからの出力ベクトル
xv2 = layer(xv1, wm2)
xv2 = np.round(xv2, 5)

print("ネットワークからの出力値 {}".format(xv2))
ネットワークからの出力値 1.87718

 
 前回と同じように、$x_0$-$x_1$ 平面上にネットワークの出力値をプロットしてみましょう。

# In[3]

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# 解像度の設定
xn = 33

# x0-x1平面の格子点を作成
xv0_1 = np.linspace(-8.0, 8.0, xn)
xv0_2 = np.linspace(-8.0, 8.0, xn)
X0, X1 = np.meshgrid(xv0_1, xv0_2)

# 中間層への入力の重み
wm1 = np.array([[3.0, 2.0,  2.0],
               [2.0, 3.0, -2.0]])

# 出力層への入力の重み
wm2 = np.array([1.0, 1.0, 0.1])

# ネットワークの出力を格納する変数
Z = np.zeros((xn, xn))

# ネットワークからの出力
for i in range(xn):
    for j in range(xn):
        xv0 = np.array([xv0_1[i], xv0_2[j]])
        xv1 = layer(xv0, wm1, sigmoid)
        Z[j][i] = layer(xv1, wm2)

# 曲面を描画
fig = plt.figure(figsize = (10, 6))
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel("x0", size = 16, labelpad = 10)
ax.set_ylabel("x1", size = 16, labelpad = 10)
ax.set_zlabel("output", size = 16)
ax.plot_surface(X0, X1, Z, color = "green")
plt.show()

 ニューラルネットワーク(Artificial Neural Network)出力3Dプロット

 単体ニューロンに比べると複雑な出力分布になっていますね。ネットワークの中間層を増やしていけば、表現力はさらに向上し、より柔軟なモデルを構築できるようになります。