デジタル・デザイン・ラボラトリーな日々

アラフィフプログラマーが数学と物理と英語を基礎からやり直す。https://qiita.com/yaju

ディープラーニング(深層学習)を理解してみる(勾配降下法:計算確認)

はじめに

前回の続きです。 yaju3d.hatenablog.jp

前回の計算が本当に合っているのか、Pythonを使って実証してみたいと思います。

プログラム

重みの更新

①現在の重みで推測値を求める

import numpy as np
a = np.array([10, 20])
b = np.array([[1,3,5],[3,1,7]])
print(np.dot(a,b))

[ 70, 50, 190]

全体の誤差を求める。
二乗誤差は、mean_squared_error の名前です。

def mean_squared_error(y, t):
    return 0.5 * np.sum((y - t)**2)

y = np.array([70, 50, 190])
t = np.array([190, 330, 660])

print(mean_squared_error(y, t))

156850.0

②勾配\Delta Eを計算する
勾配は、gradient の名前です。転置は x.T とTを付けるだけなんですね。

def gradient(y, t, x):
    return np.dot((y - t),x.T)
    
t = np.array([190, 330, 660])
y = np.array([70, 50, 190])
x = np.array([[1,3,5],[3,1,7]])

print(gradient(y, t, x))

[-3310 -3930]

③重みを更新する
②で求めた勾配\Delta E を用いて、現在の重みw を更新します。学習係数η は 0.02 としています。

def weight(w, lr, gd):
    return w - lr * gd

t = np.array([190, 330, 660])
y = np.array([70, 50, 190])
x = np.array([[1,3,5],[3,1,7]])
w = np.array([10, 20])

print(weight(w, 0.02, gradient(y, t, x)))

[ 76.2 98.6]

2回目の①現在の重みで推測値を求める

a = weight(w, 0.02, gradient(y, t, x))
b = np.array([[1,3,5],[3,1,7]])
print(np.dot(a,b))

[ 372. 327.2 1071.2]

全体の誤差を求める。
二乗誤差は、mean_squared_error の名前です。

y = np.dot(a,b)
t = np.array([190, 330, 660])

print(mean_squared_error(y, t))

101108.64

というのを繰り返す。

import numpy as np

# 二乗誤差を求める
def mean_squared_error(y, t):
    return 0.5 * np.sum((y - t)**2)

# 勾配を求める
def gradient(y, t, x):
    return np.dot((y - t),x.T)

# 重みを求める
def weight(w, lr, gd):
    return w - lr * gd

# 教師データ
t = np.array([190, 330, 660])
# 重み
w = np.array([10, 20])
# 入力データ(個数)
x = np.array([[1,3,5],[3,1,7]])
y = None
for i in range(100):
    if y is not None:
        # 勾配を求める
        grad = gradient(y, t, x)
        # 重みを求める
        w = weight(w, 0.02, grad)
    # 推測値を求める
    y = np.dot(w, x)
    # 誤差を求める
    l = mean_squared_error(y, t)
    if (i + 1) % 10 == 0:
        print("step=%3d, a1=%6.2f, a2=%6.2f, loss=%.2f" % (i + 1, w[0], w[1], l))
print("Estimated: a1=%6.2f, a2=%6.2f" % (w[0], w[1]))
step= 10, a1= 78.84, a2= 48.86, loss=4531.39
step= 20, a1= 89.41, a2= 32.85, loss=564.82
step= 30, a1= 94.92, a2= 27.91, loss=264.21
step= 40, a1= 97.30, a2= 26.05, loss=217.63
step= 50, a1= 98.28, a2= 25.30, loss=209.89
step= 60, a1= 98.68, a2= 25.00, loss=208.59
step= 70, a1= 98.84, a2= 24.88, loss=208.38
step= 80, a1= 98.91, a2= 24.83, loss=208.34
step= 90, a1= 98.94, a2= 24.81, loss=208.33
step=100, a1= 98.95, a2= 24.80, loss=208.33
Estimated: a1= 98.95, a2= 24.80

実行結果
リンゴ 98.95 ≒ 99、ミカン 24.80 ≒ 25 → 99 x 2 + 25 x 4 ≒ 297

以前、TensorFlowで組んだ際の結果とほぼ同じになったのでこれでいいかな。
リンゴ 98.81 ≒ 99、ミカン 24.90 ≒ 25 → 99 x 2 + 25 x 4 ≒ 297
yaju3d.hatenablog.jp

上記サイトでTensorFlowで組んだのに近付けるために、train_x と train_y を縦ベクトル に変更してみました。

f:id:Yaju3D:20160419005112p:plain f:id:Yaju3D:20160419002003p:plain

import numpy as np

# 二乗誤差を求める
def mean_squared_error(y, t):
    return 0.5 * np.sum((y - t)**2)

# 勾配を求める
def gradient(y, t, x):
    return np.dot((y.T - t.T), x)

# 重みを求める
def weight(w, lr, gd):
    return w - lr * gd

train_x = np.array([[1., 3.], [3., 1.], [5., 7.]])
train_y = np.array([190, 330, 660]).reshape(3, 1)
y = None
for i in range(100):
    if y is not None:
        # 勾配を求める
        grad = gradient(y, train_y, train_x)
        # 重みを求める(横ベクトルを一次元に変換)
        w = weight(w, 0.02, grad).reshape(-1,)
    else:
        # 初期重み
        w = np.array([10, 20])

    # 推測値を求める
    y = np.dot(w, train_x.T)
    # 誤差を求める
    l = mean_squared_error(y, train_y)
    if (i + 1) % 10 == 0:
        print("step=%3d, a1=%6.2f, a2=%6.2f, loss=%.2f" % (i + 1, w[0], w[1], l))
print("Estimated: a1=%6.2f, a2=%6.2f" % (w[0], w[1]))

ディープラーニング(深層学習)を理解してみる(勾配降下法:計算方法)

はじめに

前回の続きです。 yaju3d.hatenablog.jp

幾つかの人工知能関連の本やWebサイトを見ても、数式やプログラムのソースリストは記載されていても、数学が苦手な自分が理解できるようになるまでの説明が無い、そんな中でも下記3つの本(Kindle)がまだ理解できそうな感じで参考になりそうである。

    

基礎的な知識から、やっと実際の計算方法に入っていきます。

勾配降下法

関数 z=x^2 + y^2 について、その最小値を与える xy の値を勾配降下法で求めてみます。
ちなみに、正解は(x, y)=(0, 0)です。
f:id:Yaju3D:20171028224142p:plain

最初に勾配を求めておきましょう。
前回記事の偏微分で説明したように、関数 z=x^{2} + y^{2}偏微分すると指数を係数にした \displaystyle\frac{\partial z}{\partial x}=2x\displaystyle\frac{\partial z}{\partial y}=2y となります。

①勾配式 \displaystyle\left(\frac{\partial z}{\partial x}, \frac{\partial z}{\partial y} \right) = (2x, 2y)

それでは、ステップを追って計算を進めます。

1.初期設定

初期位置と学習係数 \eta を適当に与えます。
今回は初期位置を(3.00,2.00)、学習係数 \eta = 0.1 とします。

No 変位ベ クトル 関数値
i x_i y_i \partial z / \partial x \partial z / \partial y \Delta x \Delta y z
0 3.00 2.00

2.変位ベクトルを算出

現在位置 (x_i, y_i) に対して、勾配式から算出し、勾配降下法の基本式から変位ベクトル \Delta x = (\Delta x_i, \Delta y_i)を求めます。
前回記事の勾配降下法に適用で、2変数関数 z=f(x,y)の勾配降下法の基本式は次のように表しました。
基本式 \displaystyle({\Delta x}, {\Delta y}) = -\eta\nabla f(x,y)

これに①勾配式を当てはめたのが次の式となります。「\cdot」は掛け算の意味です。
②変位ベクトル (\Delta x_i, \Delta y_i) = - \eta(2x, 2y) = (- \eta\cdot2x, - \eta\cdot2y)

No 変位ベ クトル 関数値
i x_i y_i \partial z / \partial x \partial z / \partial y \Delta x \Delta y z
0 3.00 2.00 6.00 4.00 -0.60 -0.40 13.00
各計算結果の求め方

勾配
\partial z / \partial x = 2 \cdot x_0 = 2 \times 3.00 = 6.00
\partial z / \partial y = 2 \cdot y_0 = 2 \times 2.00 = 4.00
変位ベクトル
\Delta x = - \eta \cdot \partial z / \partial x = -0.1 \times 6.00 = -0.60
\Delta y = - \eta \cdot \partial z / \partial y = -0.1 \times 4.00 = -0.40
関数値
z = x_0^2 + y_0^2 = 3.00^2 + 2.00^2 = 9.00 + 4.00 = 13.00

3.位置を更新

勾配降下法に従って、現在位置 (x_i, y_i) から移動先 (x_{i+1}, y_{i+1}) の点を次の式から求めます。
②移動先 (x_{i+1}, y_{i+1}) = (x_i, y_i) + (\Delta x_i, \Delta y_i)

No 変位ベ クトル 関数値
i x_i y_i \partial z / \partial x \partial z / \partial y \Delta x \Delta y z
0 3.00 2.00 6.00 4.00 -0.60 -0.40 13.00
1 2.40 1.60

移動先
x_1 = x_0 + \Delta x_0 = 3.00 + (-0.60) = 2.40
y_1 = y_0 + \Delta y_0 = 2.00 + (-0.40) = 1.60

4.2と3の繰り返し

2と3の繰り返し(6~27は省略)、30回繰り返したときの座標 (x_{30}, y_{30}) の値です。
正解の (x, y) = (0, 0) と一致します。

No 変位ベ クトル 関数値
i x_i y_i \partial z / \partial x \partial z / \partial y \Delta x \Delta y z
0 3.00 2.00 6.00 4.00 -0.60 -0.40 13.00
1 2.40 1.60 4.80 3.20 -0.48 -0.32 8.32
2 1.92 1.28 3.84 2.56 -0.38 -0.26 5.32
3 1.54 1.02 3.07 2.05 -0.31 -0.20 3.41
4 1.23 0.82 2.46 1.64 -0.25 -0.16 2.18
5 0.96 0.66 1.97 1.31 -0.20 -0.13 1.40
28 0.01 0.00 0.01 0.01 0.00 0.00 0.00
29 0.00 0.00 0.01 0.01 0.00 0.00 0.00
30 0.00 0.00 0.01 0.00 0.00 0.00 0.00

Excel計算で小数誤差により微妙に値が違います。

バイアスについて

バイアス(bias)とは、一般に真値からの偏り、つまり系統的な誤差を指す。

切片(せっぺん)

ニューラルネットワークのパラメーターは重みとバイアスがセットとなります。
バイアスがイメージしやすいものとして、回帰直線があります。
f:id:Yaju3D:20171105230836p:plain
回帰直線は次のような1次式で表現されます。
回帰方程式 y = ax + b
f:id:Yaju3D:20171106001201p:plain
a を回帰係数(傾き)、bを切片の呼びます。切片が無いと必ず原点を通すことになってしまいます。
傾きが求まったところで切片で上下位置の調整をします。この切片がバイアスのことになります。

閾値(しきいち)

f:id:Yaju3D:20170528144340p:plainf:id:Yaju3D:20170528154127p:plain
左図が本物の神経細胞(ニューロン) で、右図が形式ニューロンです。
簡単に説明すると、入力が2つあり各入力に対して重みが掛け算され、その値が閾値を超えれば出力は「1」、そうでなければ出力は「0」となります。 たとえば、入力が(1,0)、重みが(0.5, 0.7)だとすると、1×0.5 + 0×0.7 = 0.5 を計算して閾値と比較します。
閾値未満だと出力無し(発火なし)、閾値を超えると出力有り(発火あり)となります。

出力信号無し (y=0):w_1x_1+w_2x_2 \lt \theta
出力信号有り (y=1):w_1x_1+w_2x_2 \geqq \theta

ここで \thetaニューロン固有の閾値です。 \theta を左に移行させた発火の式は次のように表現することができます。
発火の式 y=a(w_1x_1+w_2x_2 - \theta)
a は活性化関数となります。

活性化関数にシグモイド曲線を使った場合の閾値 \theta は生物的にはニューロンの個性を表現する値です。
\theta が大きければ興奮しにくく(すなわち鈍感)、小さければ興奮しやすい(すなわち敏感)という感受性を表します。
f:id:Yaju3D:20171106013953p:plain

発火の式 y=a(w_1x_1+w_2x_2 - \theta) にて、\theta だけマイナス記号が付いているのは数学的に美しくありません。美しさが欠けることは数学が嫌うところです。また、マイナスは計算ミスを誘発しやすいという欠点を持ちます。そこで、-\thetab と置き換えたのが次の式となります。
y=a(w_1x_1+w_2x_2 + b)
こうすれば式として美しく、計算ミスも起こりにくくなります。
この b をバイアス(bias)と呼びます。

バイアスの式表現

数式では b とするよりは w_0 として、次の式にします。
y=a(w_0 + w_1x_1+w_2x_2)

一般的に書き直すと、重みをwではなく \theta として、\theta_0 をバイアスとした場合、入力値 x とすると\theta_0x で次元が違うと扱いにくいので、最初の要素に 1 をセットする。
f:id:Yaju3D:20180103202510p:plain

それを x_0=1 と定義して、 x の最初の要素に x_0 を置くほうがより数学上では綺麗となる。
f:id:Yaju3D:20180103210343p:plain

\theta を転置したものと x を掛けたものを計算すると次の式になります。
\theta^Tx = \theta_0x_0 + \theta_1x_1+ \theta_2x_2 + \cdot + \theta_nx_n
更にこれを書き直すと 、f_\theta(x) = \theta^Tx と簡易的な表現の式となる。

やる夫で学ぶ機械学習 - 多項式回帰と重回帰 - · けんごのお屋敷

リンゴとミカン

リンゴとミカンを例題に勾配降下法を計算していきます。上記と違うのは、訓練データがあることです。 f:id:Yaju3D:20160417230234p:plain
f:id:Yaju3D:20160419005112p:plain f:id:Yaju3D:20160419002003p:plain

以前の記事を参考にします。
yaju3d.hatenablog.jp

前提

f:id:Yaju3D:20171029165625p:plain
1層の全結合ニューラルネットワークを用いて、勾配降下法による重みの更新例を示します。
入力層ユニット数は2、出力層はユニット数が1のシンプルなネットワークです。損失関数は二乗誤差を用います。

No 入力 データ 教師データ
i x_1 x_2 t
1 1 3 190
2 3 1 330
3 5 7 660

上表はトレーニングデータセットです。サンプル数は3で、1番目のサンプルを見ると、x_1=1x_2=3が与えられたときの教師データ t=190 (正解)になっています。

勾配\Delta Eの計算式

出力層の出力値 y は、入力層の出力値 x を用いて、次のような一次多項式で表すことができます。
a_1x_1+a_2x_2+b=y
パラメーター a_1,a_2,b

これにトレーニングデータセットを当てはめると、次のような連結方程式が出来上がります。
a_1+3a_2+b=y_1
3a_1+a_2+b=y_2
5a_1+7a_2+b=y_3

この連結方程式は行列とベクトルを用いて次のように表すことが出来ます。

\begin{pmatrix}a_1 & a_2 & b \end{pmatrix} 
\begin{bmatrix}
1 & 3 & 5 \\ 
3 & 1 & 7 \\ 
1 & 1 & 1 \\ 
\end{bmatrix} = \begin{pmatrix}y_1 & y_2 & y_3 \end{pmatrix}  
    w     X       Y
   重み  入力データ 出力データ(推測値)

今回はバイアスbは 0 とするので除外しました。そうしないリンゴとミカンの金額(重み)を求めたいのにバイアスを含む3つ値が求まってしまいます。

\begin{pmatrix}a_1 & a_2 \end{pmatrix} 
\begin{bmatrix}
1 & 3 & 5 \\ 
3 & 1 & 7 \\ 
\end{bmatrix} = \begin{pmatrix}y_1 & y_2 & y_3 \end{pmatrix}  
    w     X       Y
   重み  入力データ 出力データ(推測値)

誤差を含めた式と行列を表現すると下記のようなります。
y=a_1x_1 + a_2x_2 + 誤差 e

\begin{bmatrix}
190 \\ 
330 \\ 
660
\end{bmatrix}=
\begin{bmatrix}
1 & 3 \\ 
3 & 1 \\ 
5 & 7
\end{bmatrix}
\begin{bmatrix}
a_1\\ 
a_2
\end{bmatrix} +
\begin{bmatrix}
e_1\\ 
e_2\\ 
e_3
\end{bmatrix} 

入力データを X、重み(パラメーター)を w、出力データ(推測値) Y としています。wX を用いると、出力データ Y は次の式で表すことができます。
wX=Y
また、教師データは、 t を用いて次のように表します。
t=(190 \quad 330 \quad 660)

損失関数には二乗誤差を使用します。誤差は次のような式で表すことができます。
E=\displaystyle \frac{1}{2} (Y - t)^2
\quad=\displaystyle \frac{1}{2} (wX - t)^2
勾配\Delta Eは、誤差 E を重み w微分すると、勾配\Delta Eを次のような式になります。
\Delta E = \displaystyle \frac{\partial E}{\partial w} = (Y - t)X^T
X^TX の転置行列、(Y - t):誤差信号 \delta

今回、(Y - t)を誤差信号と呼び、記号\delta (デルタ)で表します。
勾配\Delta Eは、誤差信号 \delta と、入力データX から求めることができます。入力データはすでにわかっている値なので、誤差信号の値を求めれば、勾配\Delta Eを求めることができ、そして勾配\Delta Eがわかれば、式 w \leftarrow w-\eta\Delta E で重みを w を更新することができます。\eta:学習係数

初期値の設定

初めに、重みw と学習係数\etaに適当な初期値を設定します。ここでは学習係数\etaは 0.02 とし、重みwの初期値を10円と20円から、バイアスは 0 にして次のように設定します。
w=(a_1 \quad a_2) = (10 \quad 20)

重みの更新

①~③の手順で重みの更新を行います。

①現在の重みで推測値を求める

入力データXと現在の重みwから、推測値Yを求めます。
推測値

wX=\begin{pmatrix}10 & 20\end{pmatrix} 
\begin{bmatrix}
1 & 3 & 5 \\ 
3 & 1 & 7 \\ 
\end{bmatrix}
\quad\quad=(1 \times 10 + 3 \times 20 \quad 3 \times 10 + 1 \times 20 \quad 5 \times 10 + 7 \times 20)  
\quad\quad=(70 \quad 50 \quad 190)  
\quad\quad=Y  

ここで、全体の誤差をいったん計算してみます。損失関数には二乗誤差を使用するので、全体の誤差Eは次のように計算します。

現在の値
教師データ t=(190 \quad 330 \quad 660)
推測値   Y=( 70 \quad  50 \quad 190)
全体の誤差
E=\displaystyle \sum_{n=1}^{3} \frac{1}{2}(y_n - t_n)^2
\quad=\displaystyle \frac{1}{2}(y_1 - t_1)^2 + \frac{1}{2}(y_2 - t_2)^2 + \frac{1}{2}(y_3 - t_3)^2
\quad=\displaystyle \frac{1}{2}(70 - 190)^2 + \frac{1}{2}(50 - 330)^2 + \frac{1}{2}(190 - 660)^2
\quad=\displaystyle \frac{1}{2}(-120)^2 + \frac{1}{2}(-280)^2 + \frac{1}{2}(-470)^2
\quad=\displaystyle 7200 + 39200 + 110450
\quad=\displaystyle 156850

②勾配\Delta Eを計算する

現在の重みwに対する勾配\Delta Eを計算します。
入力データを転置行列X^Tにするのは、行列の掛け算の定義で「行列Aの列数(横の個数)と、行列Bの行数(縦の個数)が等しくないと掛け算できない」ためです。
行列A(Y-t) 1行3列 と 行列B(X^T) 3行2列 にして掛け算させます。行列の積

現在の値
教師データ t=(190 \quad 330 \quad 660)
推測値   Y=( 70 \quad  50 \quad 190)

入力データ X^T = \begin{bmatrix}
1 & 3 \\ 
3 & 1 \\ 
5 & 7 \\ 
\end{bmatrix}  

勾配
\Delta E=(Y-t)X^T
\quad\quad=(70-190 \quad 50-330 \quad 190-660)\begin{bmatrix}
1 & 3 \\ 
3 & 1 \\ 
5 & 7 \\ 
\end{bmatrix}
\quad\quad=(-120 \quad -280 \quad -470)\begin{bmatrix}
1 & 3 \\ 
3 & 1 \\ 
5 & 7 \\ 
\end{bmatrix}
\quad\quad=(-120 \times 1 + -280 \times 3 + -470 \times 5 \quad -120 \times 3 + -280 \times 1 + -470 \times 7)
\quad\quad=(-3310 \quad -3930)
③重みを更新する

②で求めた勾配\Delta Eを用いて、現在の重みwを更新します。学習係数\etaは 0.02 としています。

現在の値
現在の重み w=(10 \quad 20)
勾配   \Delta E=(-3310 \quad -3930)
更新後の重み
w \leftarrow w-\eta\Delta E
\quad=(10 \quad 20) - 0.02 \times (-3310 \quad -3930)
\quad=(10 \quad 20) - (-66.2 \quad -78.6)
\quad=(10 - (-66.2) \quad 20 - (-78.6))
\quad=(76.2 \quad 98.6)

これで重みwが更新されました。ここで、更新後の重みwを使って推測値を求め、全体の誤差を再び計算してみます。

現在の値
教師データ t=(190 \quad 330 \quad 660)

推測値

wX=\begin{pmatrix}76.2 & 98.6 \end{pmatrix} 
\begin{bmatrix}
1 & 3 & 5 \\ 
3 & 1 & 7 \\ 
\end{bmatrix}
\quad\quad=(1 \times 76.2 + 3 \times 98.6  \quad 3 \times 76.2 + 1 \times 98.6 \quad 5 \times 76.2 + 7 \times 98.6  
\quad\quad=(372 \quad 327.2 \quad 1071.2)  
\quad\quad=Y  

全体の誤差
E=\displaystyle \sum_{n=1}^{3} \frac{1}{2}(y_n - t_n)^2
\quad=\displaystyle \frac{1}{2}(y_1 - t_1)^2 + \frac{1}{2}(y_2 - t_2)^2 + \frac{1}{2}(y_3 - t_3)^2
\quad=\displaystyle \frac{1}{2}(372 - 190)^2 + \frac{1}{2}(327.2 - 330)^2 + \frac{1}{2}(1071.2 - 660)^2
\quad=\displaystyle \frac{1}{2}(182)^2 + \frac{1}{2}(-2.8)^2 + \frac{1}{2}(411.2)^2
\quad=\displaystyle 16562 + 3.92 + 84542.72
\quad=\displaystyle 101108.64

全体の誤差は 101108.64 となり、重みの誤差前の 156850 より小さくなっていることが分かります。

更新の繰り返し

以上の①~③の計算で、全サンプル(今回は2サンプル)の更新が1回終わりました。これで1エポックを終了したことになります。
更新後の重みwを用いて、①~③をもう一度繰り返せば、2エポックが終了となります。

最後に

計算の繰り返しはコンピューターの得意なところです。
次回はこの計算が本当に合っているのか、Pythonを使って実証してみたいと思います。

ディープラーニング(深層学習)を理解してみる(勾配降下法:ベクトル、内積、微分、偏微分)

はじめに

前回の続きです。 yaju3d.hatenablog.jp

幾つかの人工知能関連の本やWebサイトを見ても、数式やプログラムのソースリストは記載されていても、数学が苦手な自分が理解できるようになるまでの説明が無い、そんな中でも下記3つの本(Kindle)がまだ理解できそうな感じで参考になりそうである。

    

計算方法に入る前にベクトル等の基礎知識を先に説明していきます。

ベクトル

ベクトルとは大きさと向きを持つ量と定義されます。
f:id:Yaju3D:20171001213101p:plain

ベクトルの矢を座標平面上に置くことで座標のように表現できます。矢の始点を原点に置き、矢の終点の座標でそのベクトルを表します。これをベクトルの成分表示といいます。
成分表示されたベクトル a で下図のように表現します。また3D座標上でもz軸方向を追加して同じように表現します。
f:id:Yaju3D:20171001225146p:plain

下図のように矢印がたくさんある図を見たことがありませんか?
各データは、どこかの一点のデータを基準にして、大きさと向きを持つベクトルとなります。
f:id:Yaju3D:20171001213359p:plain

内積

ベクトルにも足し算と引き算があるように掛け算も…といいたいところですが、微妙に違うのが内積です。内積だとベクトル同士を掛け算っぽくするのに結果はベクトルではなくスカラという1つの値となります。内積以外にも外積がありますが、ここでは説明しません。

過去に3Dの勉強用に書いた記事がありますので、興味があれば読んでみて見ください。

なぜ内積の計算をそうするのかは次のサイトの説明が分かりやすいと思います。
www.yukisako.xyz

内積というのは2つのベクトルの“近さ”を表しているという程度に理解しておけばいいです。
2つのベクトルの近さを表すのにcos\thetaが使われます。

f:id:Yaju3D:20171001231022p:plain

高校までの数学だと「内積の値がcos\thetaを使って表される」と教わるが、逆にcos\thetaの値が内積によって定義されるのだ」と考えるようにするといいでしょう。

cos\thetaの値は2つのベクトルの向きが反対なら「-1」、同じであれば「1」となる。 f:id:Yaju3D:20171002005747p:plain

(ア)2つのベクトルが反対向きのときに内積は最小値
(イ)2つのベクトルが平行でないとき、内積は平行の場合の中間の値
(ウ)2つのベクトルが同じ向きのときに内積は最大値

そして、(ア)の内積が最小値というのが、勾配降下法の基本原理となります。

手書き数字認識でどの数字か分類できるのは、より似ているかで判断しています。
方向が似ているベクトルを「似ている」と判断するなら、2つのベクトルが似ていると内積は大きくなるのです。
f:id:Yaju3D:20171002005805p:plain

内積の計算

平面(2次元)
f:id:Yaju3D:20171002005819p:plain

立方空間(3次元)
f:id:Yaju3D:20171002005835p:plain

内積は2次元や3次元に限らず、何次元のベクトルについても計算することができます。現実世界では4次元や10次元ベクトルで表せるものは存在しないですが、数学上は計算できるところが数学のおもしろいところでもあります。

この内積が何次元(多次元)であっても計算できるということが重要なポイントです。
リンゴとミカンの値段を求める際は2変数です、それにブドウを付け加えると3変数になり、レモンも付け加えると4変数になります。変数が多くなることを多変数と言います。この多変数というのは機械学習における多次元と同じ意味を持っています。

参照

微分

微分とは、ある関数の各点における傾き(変化の割合)のことです。
そして勾配降下法においては、傾きの最小値を求めることが目的です。 f:id:Yaju3D:20171007211313p:plain

最小値

微分において、傾きが水平になる 0 が最小値といいたいところですが、下図右のように最大値においても水平になり 0 となります。
f:id:Yaju3D:20171008132102p:plain

このことは、勾配降下法においては厄介な性質になります。
f:id:Yaju3D:20170917223314p:plain

導関数

関数 y=x^{2}微分して得られた導関数は指数を係数にした {y}'=2x となります。
微分の定義式では極限の記号\displaystyle \lim_{h \to 0}を使って、x の変化量 h を限りなく 0 に近づけます。

\displaystyle \lim_{h \to 0}\frac{f(x+h)-f(x)}{h}={f}'(x)

※現代の数学では、0 で割る(分母が0)ことは禁止事項とされているので、極限まで 0 に近づければいいという屁理屈です。

ちなみに、'の名称は「プライム」で、\limの名称は「リミット」と読み、極限(limit)を取る記号です。

https://sci-pursuit.com/math/differential-1.htmlsci-pursuit.com

偏微分

関数 y=x^{2} は変数が1つでしたが、リンゴとミカンの値段を求める場合は2変数による関数z=f(x,y)となります。このように変数が2つ以上の関数を多変数関数といいます。
多変数関数でも微分法を適用できます。ただし変数が複数あるので、どの変数について微分するかを明示しなければなりません。この意味で、ある特定の変数について微分することを偏微分(partial derivative)といいます。
例えば、2変数x,yから成り立つ関数を考えてみましょう。変数xだけに着目してyは定数と考える微分を「xについての偏微分」と呼び、次の記号で表します。

\displaystyle\frac{\partial z}{\partial x}=\frac{\partial f(x,y)}{\partial x}=\lim_{x \to 0}\frac{f(x+\Delta x,y)-f(x,y)}{\Delta x}

yについての偏微分も同様です。

\displaystyle\frac{\partial z}{\partial y}=\frac{\partial f(x,y)}{\partial y}=\lim_{y \to 0}\frac{f(x,y+\Delta y)-f(x,y)}{\Delta y}

ちなみに、\partialの名称は「デル」、\Deltaの名称は「デルタ」です。

関数 z=x^{2} + y^{2}偏微分すると指数を係数にした \displaystyle\frac{\partial z}{\partial x}=2x\displaystyle\frac{\partial z}{\partial y}=2y となります。

remedics.air-nifty.com

www.youtube.com

勾配降下法に適用

上述で説明してきたベクトルと内積偏微分が勾配降下法にどのように使われるのか?

勾配降下法は、少しずつ下りながら、場所ごとに最も急な勾配を探すことになります。
P0から最も勾配の急な点P1の位置を求める。その位置P1から更に最も勾配の急な点P2の位置を求める。このように繰り返すことで最も早く最小点にたどり着くようになります。 f:id:Yaju3D:20170924211620p:plain

関数z=f(x,y)において、xをΔxだけ、yをΔyだけ変化させたとき、関数z=f(x,y)の変化は下記式となります。
\displaystyle \Delta z = f(x + {\Delta x},y + {\Delta y}) - f(x,y)
これを近似公式から次の関係が成立します。
\displaystyle \Delta z = \frac{\partial f(x,y)}{\partial x}{\Delta x} + \frac{\partial f(x,y)}{\partial y}{\Delta y}

f:id:Yaju3D:20171010235143p:plain

そして、これは2つのベクトルの内積を表す式になっているのです。 f:id:Yaju3D:20171010234406p:plain

ベクトルが反対向きになっている時に、内積が最小値となっている。 f:id:Yaju3D:20171011001024p:plain f:id:Yaju3D:20171011001230p:plain

ベクトルが反対向きは、-1となり、勾配降下法の基本式は次のように表せます。(\etaは正の小さな定数)
\displaystyle({\Delta x}, {\Delta y})=-\eta\left(\frac{\partial f(x,y)}{\partial x}, \frac{\partial f(x,y)}{\partial y} \right)

ちなみに、\etaはイータと読むギリシャ文字です。ローマ字のiに対応します。
なぜループカウンタ変数のほとんどに “i”が使用されるのか? - Qiita

重要なのは、内積は何次元(何変数)のベクトルについても計算することができます。

\displaystyle({\Delta x_1}, {\Delta x_2}, \cdots, {\Delta x_n})=-\eta\left(\frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, \cdots, \frac{\partial f}{\partial x_n} \right)

式が長いので数学のベクトル解析で用いられるハミルトン演算子\nablaのナブラを使って省略します。
\displaystyle \nabla f = \left(\frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, \cdots, \frac{\partial f}{\partial x_n} \right)

2変数関数 z=f(x,y)の勾配降下法の基本式は次のように表せます。
\displaystyle({\Delta x}, {\Delta y}) = -\eta\nabla f(x,y)

3変数関数 z=f(x,y,z)の勾配降下法の基本式は次のように表せます。
\displaystyle({\Delta x}, {\Delta y}, {\Delta z}) = -\eta\nabla f(x,y,z)

次回は計算方法について説明していきます。

参照

ディープラーニング(深層学習)を理解してみる(勾配降下法:最急降下法と確率的勾配降下法)

はじめに

前回の続きです。 yaju3d.hatenablog.jp

勾配降下法をどうして使うのかは理解できたのですが、その計算方法がまだ理解が足りてなくて、微分の本とかを読んでいました。
数学は苦手なんですが、理解はしたい。おまじないとかそういうルールだからとかで済ましたくないんですよね。

勾配降下法の他にも最急降下法とか確率的勾配降下法の用語がありますので、この違いも理解したい。
今回は下記の本(Kindle)を参考にしている。

  

勾配降下法

f:id:Yaju3D:20170917211137p:plain
勾配には、傾斜の程度・斜面という意味があります。
上図では1変数による関数y=f(x)でグラフは放物線となります。

リンゴとミカンの値段を求める場合は2変数による関数z=f(x,y)となり下図のようにワイングラスの底のような形になります。ちなみに3変数以上は図で表わすのは困難です。
f:id:Yaju3D:20170924173848p:plain
下図は関数のグラフの一部を拡大し、斜面に見立てた図となります。その斜面上のある点Pにピンポン玉を置き、そっと手を放すと玉は最も急な斜面を選んで転がり始めます。
f:id:Yaju3D:20170924181321p:plain

この操作を何回も繰り返せば、ピンポン玉は最短な経路をたどってグラフの底、すなわち関数の最小値にたどり着くはずです。この玉の動き(誤差関数という坂道、つまり「勾配」を下へ下へと「降下」していく)を真似たのが勾配降下法です。
f:id:Yaju3D:20170924183956p:plain

正確には最小値を探す場合を勾配降下法(gradient descent mothod)と言い、最大値を探す場合を勾配上昇法(gradient ascent mothod)と言います。descentは、SQLで降順にソートさせる時に略称(desc)で、order by column descと使いますね。

最短で下るには、少しずつ下りながら、場所ごとに最も急な勾配を探すことになります。
P_0から最も勾配の急な点P_1の位置を求める。その位置P_1から更に最も勾配の急な点P_2の位置を求める。このように繰り返すことで最も早く最小点にたどり着くようになります。
f:id:Yaju3D:20170924211620p:plain

数値解析の分野では勾配降下法を最急降下法と呼びますが、勾配降下法の中にもいくつかの方法が存在します。

最急降下法とは

最急降下法は学習データのすべての誤差の合計を取ってからパラメーターを更新します。学習データが多いと計算コストがとても大きくなってしまいます。また、学習データが増えるたびに全ての学習データで再学習が必要となってしまいます。

確率的勾配降下法とは

確率的勾配降下法は学習データをシャッフルした上で学習データの中からランダムに1つを取り出して誤差を計算し、パラメーターを更新をします。勾配降下法ほどの精度は無いが増えた分だけの学習データのみで再学習する(重みベクトルの初期値は前回の学習結果を流用)ため再学習の計算量が圧倒的に低くなります。
運が良ければ最急降下法よりも早く最適解にたどり着けますが、運が悪ければいつまで経っても適した答えを導けません。

ミニバッチ確率的勾配降下法とは

ミニバッチ確率的勾配降下法最急降下法確率的勾配降下法の間を取ったような形となります。 最急降下法では時間がかかりすぎ、確率的勾配降下法では一つ一つのデータにかなり揺さぶられることになるので、学習データの中からランダムにいくつかのデータを取り出して誤差を計算、パラメーターを更新をします。このときの一回に取り出すデータの数をバッチサイズと呼びます。

最急降下法確率的勾配降下法の違い

パラメーター更新の式

最急降下法のパラメーター更新の式
\displaystyle \theta_j := \theta_j - \eta\sum_{i=1}^n(f_{\theta}({x}^{(i)}) - y^{(i)}){x_j}^{(i)}

確率的勾配降下法のパラメーター更新の式
\displaystyle  \theta_j := \theta_j - \eta(f_{\theta} ({x}^{(k)}) - y^{(k)}){x_j}^{(k)}
(式中のkは、パラメーター更新毎にランダムに選ばれたインデックス)

大きな違いとして、確率的勾配降下法ではシグマ\sum (1~nまで合計)が取れています。
その分、計算コストは少なくなっていることが分かりますよね。

最小値へのたどり着き方

最急降下法がほぼ直線的に最小値にたどり着くのに対し、確率的勾配降下法はランダムなのでくねくねしながら最小値にたどり着きます。
f:id:Yaju3D:20170925010546p:plain

局所解に陥る可能性

誤差関数が素直なカーブのみならいいですが、下図のようにいったん下がってまた上がるみたいなうねうねした場合があります。本当は④が最小値なのに別の小さな谷(これを局所解といいます)に捕まってしまうことがあります。
f:id:Yaju3D:20170917223314p:plain

確率的勾配降下法はこのうねうねのいろいろなところから勾配を下ろうとするため、最急降下法よりも局所解に陥る可能性が小さくなります。

定数η(イータ)は学習係数と呼ばれます。このηは人が移動する際の「歩幅」と見立てられます。このηで決められた値に従って次に移動する点が決められるからです。その歩幅が大きいと最小値に達しても、それを飛び越えてしまう危険があります(下図左)。歩幅が小さいと、極小値で停留してしまう危険があります(下図右)。
f:id:Yaju3D:20170925010945p:plain

最後に

言葉で説明だけだと分かりにくいので、実際にPythonでプログラムされているサイトを見つけました。 sinhrks.hatenablog.com

これを、組み直ししてみました。 qiita.com

次回は、勾配降下法を実際に計算してみます。

参照

ディープラーニング(深層学習)を理解してみる(勾配降下法)

はじめに

機械学習をやる上では「勾配降下法」を理解しておきたい。
「勾配降下法」で検索すると自分が書いた記事が見つかります。 yaju3d.hatenablog.jp

資料を元に書いた記事なので当時はよく理解していたわけではないですが、今、読み返すとふむふむと言ったところです。今回はもっと深く追求していきます。

機械学習は最適解を求めるのが目的です。
上記記事の八百屋の件でも、条件が当てはまる最小の値をひたすら繰り返し計算すれば求めることが出来るのですが、これを出来るだけ少ない計算で最小値を見つけるようにしたい。ここで「勾配降下法」を使うことになるわけです。

解けない連立方程式

どうせなので八百屋の問題を使います。

例題 1

リンゴ1個とミカン3個を買うと190円、リンゴ3個とミカン1個を買うと330円するようです。
リンゴ2個とミカン4個を買うといくらになるでしょうか? f:id:Yaju3D:20160417225841p:plain

今回は数式で表してみます。次の連立方程式となります。

\displaystyle \begin{cases}
 a + 3b &=& 190 \\ 
3a +  b &=& 330 \\
\end{cases}

加減法で計算します。

\displaystyle \begin{eqnarray}
   3a + 9b &=& 570 \\ 
-) 3a +  b &=& 330 \\
\\
         8b &=& 240 \\ 
      b &=& 30 
\end{eqnarray}
\displaystyle \begin{eqnarray}
a + 3b &=& 190 \\  
a + 3\times30 &=& 190 \\
a &=& 190-90 \\
a &=& 100
\end{eqnarray}


a=100b=30 と分かりましたので、値を入れると答えが 320 と求まります。

f:id:Yaju3D:20160417230109p:plain f:id:Yaju3D:20160417230118p:plain

例題 2

例題 1 に対し、更にリンゴ5個とミカン7個を買うと660円を追加した場合
リンゴ2個とミカン4個を買うといくらになるでしょうか?
f:id:Yaju3D:20160417230234p:plain

先程と同様に連立方程式を書いてみます。

\displaystyle \begin{cases}
 a + 3b &=& 190 \\ 
3a +  b &=& 330 \\
5a + 7b &=& 660
\end{cases}


先程の答えの a=100b=30 を入れても答えが 100\times5 + 30\times7 = 500 + 210 = 710となり答えの 660にはなりません。

f:id:Yaju3D:20160417230455p:plain

中学校で勉強する連立方程式は、未知数(パラメータの数)が2個であれば、方程式の数も2本でなければなりません。例題 2は、方程式の数が多すぎるということになります。 とは言え、実際の世の中の現象は、このようにデータがバラバラなケースのほうが多いですよね。
そこで出来るだけ近い値である近似値を求めることにするわけです。

近似値を求める

今回、近似値を求めるのには最小2乗法を使います。

最小二乗法とは、モデル関数を f(x) とするとき、

\displaystyle \sum_{i=1}^n\{y_i-f(x)\}^2

が最小となるような f(x) を求めることである。 sci-pursuit.com

下図に合わせて、最小二乗法の式を変更してみます。

x_i がリンゴの個数、y_i がミカンの個数、z_i が合計値とする。

a がリンゴの価格、b がミカンの価格の最小値を求める。

\displaystyle \frac{1}{3}\sum_{i=1}^n\{(ax_i + by_i)-z_i\}^2

最後に平均しているので3で割っていますが、最小値を求める上で平均ではなく合計したままでも、最小二乗法の公式通りに2で割っても最終的に求まる結果は変わらないです。

f:id:Yaju3D:20160417230639p:plain
条件が当てはまる最小の値をひたすら繰り返し計算する。
f:id:Yaju3D:20160417230656p:plain f:id:Yaju3D:20160417230833p:plain

たくさん計算した結果、最小の値として、a=90b=30 が求まりました。

f:id:Yaju3D:20171029162934p:plain

少ない計算で最小値を求める

最小二乗法では1回の計算で(一発で)パラメータを求める方式です。データを追加する度に再度計算し直すことになります。少ないデータ量の時はいいですが、データ量が膨大になるとシステムの対応が難しくなってきます。

もし、データを追加するたびにパラメータを更新していく方式ができるなら、変化点だけの少ない計算で最小値を求めることができるようになります。
f:id:Yaju3D:20160417230848p:plain
それが、「勾配降下法」という方法となります。

続きは後日書きます。

ディープラーニング(深層学習)を理解してみる(TensorFlow Playgroundを試す)

はじめに

前回はパーセプトロンを多層にすることで、線型分離可能でない問題を解けることを学びました。
yaju3d.hatenablog.jp

これを視覚として見ながら学びたいということで見つけたのが「TensorFlow Playground」となります。
f:id:Yaju3D:20170624220010p:plain

しかし、初見では何がなんだか分からないので「TensorFlow Playground」のことを書いている幾つかのブログを参考に学んでいきます。

TensorFlow Playgroundとは

TensorFlow Playgroundは、A Neural Network Playgroundとも呼ばれ、Daniel SmilkovさんとShan Carterさんが開発したニューラルネットワークの仕組みを理解するための教育コンテンツです。

TensorFlow Playgroundの日本語訳を書いてくれているのが「TensorFlow Playgroundの仕組み」です。

目的

下図のように青とオレンジの正しい区切りの位置を見つけるということです。
直線だと簡単に分離できる問題もあれば、円形という複雑な問題もあるわけです。
f:id:Yaju3D:20170604230608p:plain

使い方

例題 Circle(円)、Gaussian(ガウシアン)、Exclusive or(排他or:XOR)、Spiral(螺旋)
f:id:Yaju3D:20170626230724p:plain

全体レイアウト

f:id:Yaju3D:20170625120546p:plain

  • ①は、DATA(データ)は例題となります。例題は4つあり初期値は円となっています。
  • ②は、FEATURES(特徴)は、入力するデータの特徴となります。特徴は7つあり初期値は上2つとなっています。
  • ③は、HIDDEN LAYERS(隠れ層)は中間層となります。中間層の数を「+」と「-」ボタンで増減でき初期値は2段となっています。
  • ④は、neurons(ニューロン)は、各隠れ層のノードとなります。
    ※各ノードをマウスオーバーすると⑥で拡大して見れます。ニューロンは重みの影響を受け、重みは線の太さと色で表されます。
    • ④-1が先頭の中間層で初期値は4ニューロンとなります。
    • ④-2が最後の中間層で初期値は2ニューロンとなります。
  • ⑤は、再生ボタンとなります。決定したモデルを実際に動かします。
  • ⑥は、出力結果となります。
  • ⑦は、Test loss(誤差(テスト))とTraining loss(誤差(トレーニング))の数値となります。

実際に動かしてみよう

デフォルト状態で再生してみます。右側の出力結果の平面上に点在したオレンジと青の点々に沿うようにヒートマップの色が変化していく様子が見れるはずです。 そして点々の色とヒートマップの色の分布が互いに近づいてくるに従って誤差の数値が小さくなる様子も確認できるはずです。

青枠⑦のTest loss(誤差(テスト))とTraining loss(誤差(トレーニング))の数値を見ます。これが少ないほど良い結果になります。
Epochが50くらいで図上では分離されていますが、誤差の数値的には0.022とまだあります。
Epochが100くらいで誤差の数値的には0.008くらいまで下がります。
Epochが500くらいまでいくと誤差が0.0001まで下がります。

パラメーターをいじってみる

④-1の先頭の中間層を4ニューロンから3ニューロンに減らしてみます。
f:id:Yaju3D:20170625134326p:plain
これだとあまり変化なく、Epochが500くらいまでいくと誤差が0.0001まで下がります。
さらに2層まで減らすとEpochが1000までいっても収束しません。

今度は、中間層を1つ増やしてみます。
f:id:Yaju3D:20170625145117p:plain
Epochが200くらいまでいくと誤差が0.0001まで下がるので、収束は速くなります。

各パラメーターの説明

TensorFlow Playgroundの日本語訳「TensorFlow Playgroundの仕組み」から一部抜粋します。

上側パラメーター
  • Epoch(エポック)は、学習させる回数です。用意された全トレーニングデータをニューラルネットワークに投入しパラメータを更新、修正し終えると機械学習の1サイクル(1エポック)が完了します。 統計モデルの種類が分類の場合、トレーニングデータは500個なので、この500個全部をトレーニングに使い切ると1エポック完了したことになります。
  • Learning rate(学習率)は、一度の学習でどの程度重みやバイアスの値を修正するかの率を表します。学習率が大きいと1度に修正される重み、バイアスの量が大きくなります。 小さくすると重み、バイアスの値を1度に少しだけ修正しようとします。
  • Activation(活性化関数)は、ニューロンの出力をフィルタリングします。tanh、ReLU、Sigmoid(シグモイド関数)、Liner(線形関数)があります。
  • Regularization(正則化)は、過学習を防ぐための仕組みです。None, L1, L2 があります。
  • Regularization rate(正則化項)は、正則化されたニューラルネットワークに対して正則化項を加えることで、より過学習を防ぐようになります。
  • Problem type(統計モデルの種類)は、「Classification(分類)」と「Regression(回帰)」の2種類があります。
    • Classification(分類)は、入力した画像が猫であるか猫でないかを0/1で判断する。分類の図は点々がブルー(+1)かオレンジ(-1)かの二通りの出力結果のデータの分布となります。
    • Regression(回帰)は、得点の値について-1,1だけのとびとびの離散的な値だけでなく、 その間の-0.529や0.29039などの-1から+1までの連続的な中間値も考慮に入れます。入力した画像が猫である確率を求める場合に使います。回帰の図は分類と同様に点々がブルー(+1)かオレンジ(-1)であることには変わりありませんが、白味がかった-1と+1の間の中間の値のデータも含まれます。
左側パラメーター
  • Ratio of training to test data(トレーニングデータの割合)は、「Classification(分類)」が500個、「Regression(回帰)」が1200個のデータのサンプル数となっていますので、例えばトレーニングデータの割合を60%とすると、500個のデータサンプルの内300個をトレーニングデータに、 200個のデータをテストデータに回します。
  • Noise(ノイズ)は、円やガウシアンなどの選択したデータセットに対してかけるノイズ(0〜50まで5刻みで設定)で数値は%単位です。
  • Batch size(バッチサイズ)は、1回の学習(重み、バイアスの更新)を何個のトレーニングデータで実施するかを決める数値です。例えば1エポックで500回学習とした場合にバッチサイズが25にすると、1エポックで20回学習することになります。
  • REGENARATE(再生成)は、データを再生成します。
右側下パラメーター

f:id:Yaju3D:20170626231602p:plain

過学習

過学習が発生すると、十分に小さなTraining loss(誤差(トレーニング))に対して、Test loss(誤差(テスト))が小さくならない現象が発生します。TensorFlow Playgroundで意図的に過学習を起こすことは簡単です。画面左側の「Ratio of training to test data(トレーニングデータの割合)」を10%まで下げてみます。
f:id:Yaju3D:20170626234708p:plain

スライド

例題

Problem type(統計モデルの種類)の「Classification(分類)」だけ他の例題を自分なりにやってみました。
これが正しいわけでもなく、再更新すると違う結果になったりすることもあります。

Gaussian(ガウシアン)

一番単純です。Activation(活性化関数)をLiner(線形関数)にしています。
f:id:Yaju3D:20170626232335p:plain

Exclusive or(排他or:XOR)

XORは線形分離不可能な問題です。特徴量にX1X2があるのでそのまま使っています。
f:id:Yaju3D:20170626233038p:plain

別方法として特徴量は先頭2つでやってみました。Activation(活性化関数)をReLUにすると直角的になります。
f:id:Yaju3D:20170627215236p:plain

Spiral(螺旋)

特徴量を全て使っています。過学習を防ぐためRegularization(正則化)をL1、Regularization rate(正則化項)を0.001にしてます。
なかなかTest loss(誤差(テスト))が下がらなくて、0.008が限界でした。
f:id:Yaju3D:20170626233502p:plain

特徴量を減らして、中間層を増やしてみました。Test loss(誤差(テスト))は、0.006になりました。
f:id:Yaju3D:20170627234331p:plain

参照

ディープラーニング(深層学習)を理解してみる(パーセプトロンと論理演算)

はじめに

TensorFlow関連の記事を書いている割には、ディープラーニング(深層学習)について理解度が足りてないということもあって基礎的なところから理解してみようと思いました。幸い、ここ1年で本やブログ記事が増えてきたので助かります。

参考にした下記の2つ本となります。あと、この本の内容を元にした幾つかのブログの記事などです。
自分なりに理解したことをまとめて書いていきますが、詳細はリンク先を参照してください。

線形と非線形

機械学習をやる上で「線形」と「非線形」という言葉が出てきますので、先に説明しておきます。

線形

簡単に言えば、直線的な線のことです。
一次関数「例 y = ax + b」のように定数倍と足し算・引き算で表されたものとなります。

【追記 2017/07/21】 y = ax + bは、線形ではないとのことです、線形の定義では原点を通るとのこと。 そもそも線形ってどういうこと - Qiita
linear function 数学などに関する備忘録/ウェブリブログ

非線形

線形以外で曲線的や変化が激しい線のことです。
二次関数「例 y = x2」のように放物線などで表されたものです。

イメージ

左図が線形、右図が非線形となります。「★」と「○」を区分ける際に直線で区分けるのは簡単ですが、曲線的でないと区分けることが出来ないのは難しいです。
f:id:Yaju3D:20170604200125p:plain

ディープラーニング(深層学習)とは

ディープラーニング機械学習の一種で、神経細胞を模したパーセプトロン(ニューロンモデル)と言う小さな計算機をたくさん用意し多層にして構築したものです。よって、多層構造のニューラルネットワークを用いた機械学習と言えます。 ※パーセプトロンニューラルネットワークの基本単位となります。

現在のディープラーニングに到るまでを順を追って書いています。

形式ニューロン

f:id:Yaju3D:20170528144340p:plain f:id:Yaju3D:20170528154127p:plain
左図が本物の神経細胞(ニューロン) で、右図が形式ニューロンです。
形式ニューロンWikipediaから引用します。

形式ニューロン(けいしきニューロン、英: formal neuron)や Threshold Logic Unit とは、1943年に神経生理学者・外科医であるウォーレン・マカロックと論理学者・数学者であるウォルター・ピッツが発表した最初の人工ニューロン(英: artificial neuron)[1]。伝達関数としてはヘヴィサイドの階段関数を使い、入出力の値は 0 または 1 の二値だけをとる。

簡単に説明すると、入力が2つあり各入力に対して重みが掛け算され、その値が閾値を超えれば出力は「1」、そうでなければ出力は「0」となります。 たとえば、入力が(1,0)、重みが(0.5, 0.7)だとすると、1×0.5 + 0×0.7 = 0.5 を計算して閾値と比較します。

形式ニューロンの場合、入力が「0」か「1」、重みが「実数」、出力が「0」か「1」となります。

パーセプトロン

心理学者のドナルド・ヘッブが、1949年に学習や記憶を進めるのは集中力や根性でなくシナプス(神経細胞同士の接合部)であると生理学的な提起をしました。「ヘッブの法則」というもので、シナプス(神経細胞同士の接合部)は、よく使うものほど強化され使わなくなると徐々に結合が弱くなります。

このヘップの法則を形式ニューロンのような神経回路モデルにあてはめることができないかということから考え出されたのが「パーセプトロン」となります。

単純パーセプトロン

f:id:Yaju3D:20170528193545p:plain
1958年にローゼンブラットが提案したパーセプトロンは形式ニューロンを発展させ、いくつか並列に組み合わせてから出力ニューロンで束ねるという2層(入力層と出力層)の構造をとることで、入力と出力のペアを学習することを示しました。

形式ニューロンと違う点としては、入力は「実数」であることと学習という過程があります。
レーニングデータを複数を用意することによって、間違った答えを出力した場合、ある計算式に従って重みやバイアスの値を変化させます。
f:id:Yaju3D:20170604230608p:plain

基本的には線型分離可能な問題しか解くことができない(ミンスキーとパパートにより指摘)という欠点があります。

余談ですが、ローゼンブラットが提案したパーセプトロン非線形分離問題も扱える3層でしたが、その後の研究は中間層を省いた2層による単純パーセプトロンの研究が進んだため、ミンスキーとパパートの共著「パーセプトロン」(1969年出版)による批判も単純パーセプトロンだけに当てはまるものだったのです。パーセプトロンの熱は一気に冷めてしまい、パーセプトロンは線形分離可能問題しか扱えないというレッテルが貼られたのでした。

多層パーセプトロン

世の中の解きたい問題は大抵パーセプトロン(線型分離可能な問題)では解けないタイプの問題であったため、パーセプトロンの研究は廃れていきました。それ以降は人工知能の研究は学習型ではなく、知識を扱うプロダクションルール型に移行していった。 しかし、約15年後の1986年に認知心理学者ラメルハートが再発見した3層モデルのバックプロパゲーション(誤差逆伝播学習法)を発表したことで、再び脚光を浴びたのである。
再発見とは、1967年に甘利俊一先生が隠れ層のある3層以上の計算モデルを既に発表していた。
f:id:Yaju3D:20170528195430p:plain

単純パーセプトロンに真ん中の層を追加しました。この層を隠れ層(hidden layer)または中間層と呼びます。実はこれだけで線形分離可能な問題しか解けないという欠点を解決できたのです。
単純パーセプトロンでは重みを調整するのが1層で簡単だったのですが、今回は2層でそれぞれの層の調整するのが難しという問題がありました。しかし、誤差逆伝搬(所望の出力結果になるよう誤差を出力層から入力層に向かった逆方向に重みを調整するもの)と呼ばれる方法を使うこと良い重みの調整方法を手に入れることができたため、3層構造でも学習可能になったのです。

f:id:Yaju3D:20170528222639p:plain
再びニューラルネットワークのブームが訪れ、あらゆるものに応用が試みられて3層より層を深くして学習効果を上げることも試されました。 しかし、バックプロパゲーションは4層以上にすると満足な精度が得られませんでした。
バックプロパゲーションを多層にすると勾配消失と過学習が発生することで、うまく学習できなくなるのです。

当時は、学習時間が非常にかかることや過学習を防ぐための理論がなく、サポートベクターマシン(SVM)など他の手法が注目されていたこともあり、再びニューラルネットワークは冬の時代を迎えたのでした。

ディープラーニング

冬の時代にも着々と進められた技術的進歩の中で、2006年にカナダのトロント大学のジェフリー・ヒントン教授が「ディープビリーフネットワーク」を発表した。これは層が深くなっても学習できていた。
f:id:Yaju3D:20170529004111p:plain
従来のニューラルネットワーク(左図)はパラメーターの更新を4層以上一度にやっていたが、ディープビリーフネットワーク(右図)では層毎に学習が完了してから次の層を付け足したことにある。1層が終わってから2層目と順に学習し、全ての学習が終わってから全層を結合してバックプロパゲーションをかけたのである。
2006年にヒントン教授の論文では層が深いニューラルネットを総称して「ディープネットワーク」と呼び、2007年にアンドリュー・ング氏が論文で「高次元データの階層的な表現の学習」に「ディープラーニング」という言葉を用いた。

2012年にアンドリュー・ング氏が畳込みニューラルネットワーク(CNN)で1000万本のYouTubeビデオ画像を学習し、ディープラーニングが学習した猫の画像を発表した。以降、ディープラーニングは爆発的なブームとなり、全てのものに応用が試みられるようになったのである。 f:id:Yaju3D:20170604203231p:plain f:id:Yaju3D:20170529010051p:plain

現在のディープラーニングには「畳込みニューラルネットワーク」、「再起型ニューラルネットワーク」、「積層自己符号化器」、「ディープビリーフネットワーク」、「ディープボルツマンマシン」など多くの種類があります。

畳込みニューラルネットワーク(CNN)

現在、画像分類の分野で主流となっているのは「畳込みニューラルネットワーク(CNN)」です。この畳込みニューラルネットワークは、福島邦彦先生の「ネオコグニトロン」が元になっています。

f:id:Yaju3D:20170529013306p:plain
畳込みニューラルネットワークは、その名の通り畳込み(コンボリューション:Convolution)を行って情報を圧縮していくネットワークで、5層(入力層、畳込み層、プーリング層、全結合層、出力層)から構成されています。畳込み層とプーリング層は複数繰り返して深い層を形成し、その後の全結合層も同様に何層か続きます。

畳込み層では、カーネルという小さな正方形をフィルターとして適用しながら、入力された画像をより小さい画像に変換します。
f:id:Yaju3D:20170604222728p:plain f:id:Yaju3D:20160417025404g:plain
引用元:http://deeplearning.stanford.edu/wiki/index.php/Feature_extraction_using_convolution

プーリング層では、畳込み層から出力された特徴マップを縮小します。最大値プーリングの場合、注目領域の最大値を特徴マップの値とします。
f:id:Yaju3D:20170611164450p:plain
引用元: http://cs231n.github.io/convolutional-networks/#pool

画像認識での話ですが、プーリングは位置と回転に不変性を与えます。ある領域についてプーリングを行うと、画像が数ピクセルだけ移動や回転をしてもその出力はほぼ同じになります。それは最大プーリングが、微妙なピクセルの違いを無視して同じ値を抽出してきてくれるからです。
自然言語処理における畳み込みニューラルネットワークを理解する

浅いレイヤーは単純なパーツ(小さい部品より具体的なもの)を学習、深いレイヤーはパーツを組み合わせた全体の学習を行います。
f:id:Yaju3D:20170604205436p:plain

qiita.com

ネオコグニトロン

1979年に福島邦彦先生が後のディープラーニング計算に使われる「ネオコグニトロン」を発表した。現在のパターン認識を行うディープラーニングの構造は、ネオコグニトロンそのものであり、S細胞をコンボリューション、C細胞をプーリングと呼んでいるだけである。 ディープラーニングとの違いは学習の仕方で、ディープラーニングバックプロパゲーションを採用しており、ネオコグニトロンは新しい入力パターンに反応する細胞がないときは新しく細胞を加えるという「add-if silent」学習則を採用している。
ネオコグニトロンは当初特許出願されていたものの、予算が厳しく特許権を放棄した。特許権を継続していれば今頃は莫大な収入を得ていたかも知れませんが、特許権が無いことでディープラーニングが開花したのかも。

パーセプトロンと論理演算

人間の脳は神経細胞(ニューロン)のネットワークで構成されています。この最小単位の神経細胞(ニューロン)を模倣したモデルがパーセプトロンとなります。このモデルは入力値が閾値を超えた場合に「1」を出力(この場合を「ニューロンが発火する」と表現)し、そうでない場合は「0」を出力します。このように入力値から出力値を生むまでの計算を活性化関数と呼びます。

活性化関数

活性化関数とはニューロンが入力から出力を生むまでの計算のことで、活性化関数には非線形関数が用いられます。線形の活性化関数では非線形分離できないためです。
パーセプトロンでは活性化関数にステップ関数(入力に対してある閾値を境に階段(ステップ)のように出力が1か0か決まる)を使っていた。
f:id:Yaju3D:20170603024904p:plain

ニューラルネットワークは学習をする際に微分を含む計算を行うため、微分するとゼロになるステップ関数は都合が悪いことからシグモイド曲線(sigmoid)などが使用されている。
微分するとゼロになると都合が悪いのは、値を微分値で更新しても同じ値のままになってしまうからです。

シグモイド曲線は入力した値を0から1の間に収めてくれる関数の1つです。多くの自然界に存在する事柄は、このようなS字曲線を取ります。欠点として1に近づくほど1そのものにならない性質があります。
f:id:Yaju3D:20170602002203p:plain

論理演算

コンピュータの演算には四則演算(+、-、×、÷)のほかに論理演算(AND、OR、NOT、XOR)があります。論理演算とは、2つ以上の1または0入力値に対して、1つの演算結果(1または0)を出力する演算のことです。
論理演算と四則演算の大きな違いは、論理演算が2進数の1桁(=1bit)を対象としていることと、演算結果が決して桁上がりしないことです。コンピュータは、内部的にすべてのデータを2進数で取り扱っているからこそ、コンピュータの世界では論理演算が重要なのです。

清水亮著「はじめての深層学習(ディープラーニング)プログラミング」の「2.2 論理演算を学習をさせてみる」として、Chainerを使用して論理演算の「AND」と「OR」と「NAND」および「XOR」を学習させています。
※初版本を購入したため、ソースリストにも誤りがありました。参照:お詫びと訂正

前回の記事「Chainer ver2.xをWindowsにインストールしてみた」でChainerを入れたこともあって、そのまま利用しています。tensorflow版は「tensorflow 論理演算」で検索してみて下さい。

仕組みそのものを知りたい方は「ゼロから作るDeep Learning」の本が「Python + numpy」のみで説明しています。参照:ゼロから作るDeep Learning 2章「パーセプトロン」

#!/usr/bin/env python
# coding: utf-8

import numpy as np
import chainer.functions as F
import chainer.links as L
from chainer import Variable, optimizers,Chain

class Model(Chain):
    def __init__(self):
        super(Model, self).__init__(
            l1 = L.Linear(2, 1),
            )
    def __call__(self, x):
        # h = self.l1(x)
        # sigmoid function
        h = F.sigmoid(self.l1(x))
        return h
    
model = Model()
optimizer = optimizers.MomentumSGD(lr=0.01, momentum=0.9)
optimizer.setup(model)

x = Variable(np.array([[0,0],[0,1],[1,0],[1,1]], dtype=np.float32))
# OR
t = Variable(np.array([[0],[1],[1],[1]], dtype=np.float32))
# AND
#t = Variable(np.array([[0],[0],[0],[1]], dtype=np.float32))
# NAND
#t = Variable(np.array([[1],[1],[1],[0]], dtype=np.float32))

for i in range(0, 3000):
    optimizer.zero_grads()
    y = model(x)
    loss = F.mean_squared_error(y, t)
    loss.backward()
    optimizer.update()
    
    print("loss:", loss.data)

print(y.data)
#print(y.data.round())

「OR」の実行結果 四捨五入すれば上から[ 0, 1, 1, 1]となります。

︙
loss: 0.01094332616776228
loss: 0.01093930285423994
[[ 0.15879413]
 [ 0.90393192]
 [ 0.90352207]
 [ 0.99786234]]

このソースコードでは「OR」が実行されるようになっています。「AND」または「NAND」を実行するには対象コメントを外して、他のところは逆にコメントを付けてください。あと、急に「NAND」が出てきましたが何故かは後述します。

f:id:Yaju3D:20170601010201p:plain f:id:Yaju3D:20170601010218p:plain f:id:Yaju3D:20170601010235p:plain

この中に「XOR」が無いのには理由があります。上図が示すように「OR」、「AND」、「NAND」については1本の判別直線で表現することが出来ます。つまり単純パーセプトロンで線型分離可能となっているわけです。

f:id:Yaju3D:20170601011628p:plain

ところが「XOR」の場合は非線形(もしくは2本の判別直線)にしないと判別することが出来ません。 これを判別できるようにするには、単純パーセプトロンの中間層(隠れ層)を追加して多層パープセトロンにすればいいのです。
f:id:Yaju3D:20170605010116p:plain

【2018/11/04追記】多重パーセプトロン <計算例> XOR回路を表現する
下記サイトのXOR回路をNAND回路とOR回路の2つに分ける(多重化)することで答えが求まるのが、分かりやすい。
arduinopid.web.fc2.com

修正したのは上側部分で変数l2の追加と変数tをXORに変更したくらいです。

#!/usr/bin/env python
# coding: utf-8

import numpy as np
import chainer.functions as F
import chainer.links as L
from chainer import Variable, optimizers,Chain

class Model(Chain):
    def __init__(self):
        super(Model, self).__init__(
            l1 = L.Linear(2, 2),
            l2 = L.Linear(2, 1),
            )
    def __call__(self, x):
        # sigmoid function
        h = F.sigmoid(self.l1(x))
        return self.l2(h)
    
model = Model()
optimizer = optimizers.MomentumSGD(lr=0.01, momentum=0.9)
optimizer.setup(model)

x = Variable(np.array([[0,0],[0,1],[1,0],[1,1]], dtype=np.float32))
# XOR
t = Variable(np.array([[0],[1],[1],[0]], dtype=np.float32))

for i in range(0, 3000):
    optimizer.zero_grads()
    y = model(x)
    loss = F.mean_squared_error(y, t)
    loss.backward()
    optimizer.update()
    
    print("loss:", loss.data)

print(y.data)
#print(y.data.round())

「XOR」の実行結果 四捨五入すれば上から[ 0, 1, 1, 0]となります。

︙
loss: 0.010527125559747219
loss: 0.01044147927314043
[[ 0.05130053]
 [ 0.8998214 ]
 [ 0.89989913]
 [ 0.13812399]]

NANDについて

余談ではあるのですが、「NAND」は「NOT + AND」の組合せたもので[ 1, 1, 1, 0]となります。
ソフトウェア側だとだから何だと思われるのですが、ハードウェア側の論理回路から見ると、すべての論理演算(OR,AND,NOT,XOR)はNAND回路を組み合わせれば出来てしまうのです。同様に「NOR」でもすべての論理演算が作れます。

詳しくは下記サイトを見て下さい。

最後に

脳を真似たモデルが最終的には凄い結果を出せるようになったわけで、生物って凄いなーとつくづく思います。

人間の身体全体が60兆個の細胞でできている中で、脳細胞は140億個と言われています。
PCのメモリ容量が2GB(160億ビット)として2値(0か1)なのでデータ量は2160億通り、脳のニューロンモデルが2値(発火しないか発火する)とすると2140億通りとすると、もう充分ではないかと思えるわけですが、実際はグリア細胞というニューロン細胞以外の神経細胞のために出力はディジタルではなくアナログ信号に近いとのことです。

生物は奥が深い。

この資料は良かったです。ディープラーニングが人工ニューロンから計算グラフに変わってきている。

参照

スポンサーリンク