Deep Learning

Deep Learing
# パーセプトロン
## 背景
1957年にアメリカのローゼンブラットという研究者によって考案されたプログラムです。パーセプトロンはニューラルネットワークの起源となるアルゴリズムです。

## パーセプトロンとは
複数の信号を入力として受け取り、ひとつの信号を出力します。
パーセプトロンの信号は「1 or 0」です。
2つの信号を入力として受け取るパーセプトロンは以下のようになる。

![alt](https://www.evernote.com/l/Agun4QnDRFFO3bygz_UsAJy71Oy6JJiM8W0B/image.png)

入力信号： x1,x2
出力信号: y
重み: w1,w2
入力信号がニューロンに送られる際に、それぞれに固有の重みが乗算され、その総和があり限界値(閾値，θ)を超えた場合にのみ1を出力します。

```math
f(x) = \left\{
\begin{array}{ll}
0 & (w1x1 + w2x2 \leq θ) \\
1 & (w1x1 + w2x2 \gt θ)
\end{array}
\right.
```

## パーセプトロンの実装
ANDゲートを例に・・・

```py3
def AND(x1,x2):
    w1,w2,theta = 0.5,0.5,0.7
    tmp = x1*w1 + x2*w2
    if tmp <= theta:
        return 0
    elif tmp > theta:
        return 1
```

## バイアスの導入
θを-bとして先程の式を変形

```math
f(x) = \left\{
\begin{array}{ll}
0 & (b + w1x1 + w2x2 \leq 0) \\
1 & (b + w1x1 + w2x2 \gt 0)
\end{array}
\right.
```

ANDゲートは以下のようになる

```py3
import numpy as np
def AND(x1,x2):
    x = np.array([x1,x2])
    w = np.array([0.5,0.5])
    b = -0.7
    tmp = np.sum(w*x) + b
    if tmp <= 0:
        return 0
    else:
        return 1
```

NANDゲートは以下のようになる

```py3
import numpy as np
def AND(x1,x2):
    x = np.array([x1,x2])
    w = np.array([-0.5,-0.5])
    b = 0.7
    tmp = np.sum(w*x) + b
    if tmp <= 0:
        return 0
    else:
        return 1
```

ORゲートは以下のようになる

```py3
import numpy as np
def AND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.2
    tmp = np.sum(w * x) + b
    if tmp <= 0:
        return 0
    else:
        return 1
```


### 働きの違い
重み => 入力信号の重要度をコントロール
バイアス => 発火のしやすさをコントロール

## パーセプトロンの限界
パーセプトンではXORゲートを表現できません。しかし、**層を重ねる**ことで表現が可能になります。

```py3
def XOR(x1,x2):
    s1 = NAND(x1,x2)
    s2 = OR(x1,x2)
    y = AND(s1,s2)
    return y
```

# ニューラルネットワーク
適切な重みパラメータをデータから自動で学習出来るという性質を持つ。
![alt](https://www.evernote.com/l/Agtw-6tDFkNLIJwvfLASdoKTsj_FG-cmSJ8B/image.png)

## 活性化関数
活性化関数とは入力信号の総和を出力信号に変換する関数のことをいいます。
ニューラルネットワークでは、非線形関数を用いる必要がある。

```math
a = b + w1x1 + w2x2 \\
y = h(a)
```


![alt](https://www.evernote.com/l/Agvxx3vA03lNHIKq0GB0G_oV8r5AEbzMRXgB/image.png)

## ステップ関数
入力が0を越えると1を出力し、それ以外は0を出力する活性化関数の1つ。

```py3
import numpy as np
import matplotlib.pylab as plt
def step_function(x):
    y = x > 0
    return y.astype(np.int)

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

![alt](https://www.evernote.com/l/AguIK2RrerJBsIrLf41Vsrk5Lo1jZk7dT6IB/image.png)


## シグモイド関数
ニューラルネットワークでよく用いられる活性化関数の1つ。

```math
h(x) = \frac{1}{1+e^{-x}}
```

```py3
import numpy as np
import matplotlib.pylab as plt
def sigmoid(x):
    return 1/(1+np.exp(-x))

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

![alt](https://www.evernote.com/l/Agv3iety2PND8pYBePM6oqcue7NOf8QIhdcB/image.png)

## ReLU関数
最近良く用いられる活性化関数。
入力が0を超えていれば、その入力をそのまま出力し、0以下であれば0を出力します。

```math
h(x) = \left\{
\begin{array}{ll}
x & (x \gt 0) \\
0 & (x \leq 0)
\end{array}
\right.
```

```py3
import numpy as np
import matplotlib.pylab as plt
def relu(x):
    return np.maximum(0, x)

x = np.arange(-5.0,5.0,0.1)
y = relu(x)
plt.plot(x,y)
plt.ylim(-1.0,5.5)
plt.show()
```

![alt](https://www.evernote.com/l/AgvbyELRRTdF8JqaTryDLuXHN6by-YZBAA4B/image.png)


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

```py3
# 数値を定義する
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 = forward(network, x)
```

## ソフトマックス関数
分類問題でよく使われる関数

```math
y_k = \frac{e^{a_k}}{\sum_{i = 1}^{n}e^{a_i}}
```

指数関数の計算を行うため、オーバーフロー対策が必要

```math
\begin{align}
y_k & = \frac{Ce^{a_k}}{C\sum_{i = 1}^{n}e^{a_i}} \\
& =  \frac{e^{a_k+\log C}}{\sum_{i = 1}^{n}e^{a_i+\log C}} \\
& =  \frac{e^{a_k+C'}}{\sum_{i = 1}^{n}e^{a_i+C'}} \\
\end{align}
```

C'に入力信号で最大の値を用いることが一般的です。

```py3
import numpy as np
def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c)
    sum_exp_a = np.sum(exp_a)
    y = exp_a/sum_exp_a
    return y
```

# ニューラルネットワークの学習
学習 = 訓練データから最適な重みパラメータの値を自動で獲得すること
ニューラルネットワークが学習を行えるようにするために、損失関数を導入する。
目的 : 損失関数を基準として、その値が最小になる重みパラメータを探すこと
この目的を果たすために**勾配法**を用いる。

## データ駆動
「5」という数字を認識するプログラムを考える。
ゼロから「5」を認識するプログラムを作成することはとてもむずかしいので、データを有効に活用します。
その方法は、画像から**特徴量**を抽出して、特徴量のパターンを機械学習の技術で学習する方法があるンゴ。
特徴量 = 入力データ(入力画像)から本質的なデータ(重要なデータ)を的確に抽出出来るように設計された変換器
ニューラルネットワークでは、画像に含まれる特徴量までも「機械」が学習する。

## 損失関数
ニューラルネットワークが教師データに対してどれだけ一致していないかを表す指標(小さいほうが誤差がすくない)
一般には、2乗和誤差・交差エントロピー誤差などがある

## 2乗和誤差

```math
E = \frac{1}{2}\sum_{k}(y_k - t_k)^2 
```
y<sub>k</sub> : ニューラルネットワークの出力
t<sub>k</sub> : 教師データ
k             : 次元数

教師データは、正解ラベルを1、それ以外を0とします。この方法をone-hot記法という。

```py3
import numpy as np
def mean_squared_error(y,t):
    return 0.5*np.sum((y-t)**2)
```

## 交差エントロピー誤差

```math
E =- \sum_{k}t_k\log y_k 
```

```py3
import numpy as np
def cross_entropy_error(y,t):
    delta = 1e-7
    return -np.sum(t*np.log(y + delta))
```

微小単位を加えることで、np.log(0)=-infとなり計算できなくなることを防ぎます。

## ミニバッチ学習
機械学習の問題は、訓練データを使って学習する→訓練データに対する損失関数を求めて、その値をできるだけ小さくするように設定する　というのを繰り返すという作業。

```math
E = - \frac{1}{N}\sum_{n}\sum_{k}{}t_{nk}\log y_{nk}
```

N個分のデータに拡張し、最後にNで割ることで、正規化しています。
数万の訓練データからある枚数だけを選び出して、ミニバッチごとに学習を行う手法をミニバッチ学習と言います。

## 勾配
(x<sub>0</sub>,x<sub>1</sub>)の両方の偏微分をまとめて、 

```math
(\frac{\partial f}{\partial x_0},\frac{\partial f}{\partial x_1})
```

のように、すべての変数の偏微分をベクトルとしてまとめたものを勾配といいます。

```py3
import numpy as np
def numerical_gradient(f, x):
    h = 1e-4
    grad = np.zeros_like(x)

    for idx in range(x.size):
        tmp_val = x[idx]
        #f(x+h)の計算
        x[idx] = tmp_val + h
        fxh1 = f(x)

        # f(x+h)の計算
        x[idx] = tmp_val + h
        fxh2 = f(x)

        grad[idx] = (fxh1 - fxh2)/(2*h)
        x[idx] = tmp_val #値をもとに戻す

    return grad
```

勾配は各地点において低くなる方向を指します。

## 勾配法
勾配を上手く利用し、関数の最小値を探すのが勾配法です。
勾配法では、現在の場所から勾配方向に一定のき距離だけ進み、その移動先でもまた勾配を求め、その勾配方向へ進むというように繰り返し勾配方向へ移動します。勾配方向へ進むことを繰り返すことで、関数の値を徐々に減らします。

```math
x_0 = x_0 - \eta\frac{\partial f}{\partial x_0} \\
x_1 = x_1 - \eta\frac{\partial f}{\partial x_1}
```

ηは更新の量を表し、ニューラルネットワークの学習では、**学習率**と呼ばれます。1回の学習でどれだけ学習すべきか、どれだけパラメータを更新するかを決める変数です。
上の式は、1回の更新式を示していて、コレを何回も繰り返します。
学習率の値は、0.01や0.001など、前もってなんらかの値を決める必要がある。学習率の値を変更しながら、正しく学習できているかどうか、確認作業を行うのが一般的

```py3
import numpy as np
def numerical_gradient(f, x):
    h = 1e-4
    grad = np.zeros_like(x)

    for idx in range(x.size):
        tmp_val = x[idx]
        #f(x+h)の計算
        x[idx] = tmp_val + h
        fxh1 = f(x)

        # f(x+h)の計算
        x[idx] = tmp_val + h
        fxh2 = f(x)

        grad[idx] = (fxh1 - fxh2)/(2*h)
        x[idx] = tmp_val #値をもとに戻す

    return grad

def gradient_descent(f,init_x,lr=0.01,step_num=100):
    x = inti_x

    for i in range(step_num):
        grad = numerical_gradient(f,x)
        x -= lr * grad

    return x
```

先程の勾配を求める関数を利用しています。
lrは学習率、step_numは勾配法による繰り返しの数を表す。

## ニューラルネットワークに対する勾配
ニューラルネットワークでいう勾配とは、重みパラメータに関する損失関数の勾配です。重みをW、損失関数をLとすると、勾配は、

```math
\frac{\partial L}{\partial W}
```
となります。

## 学習アルゴリズム

<前提>
ニューラルネットワークは、適応可能な重みとバイアスがあり、この重みとバイアスを訓練データに適応するよにに調整することを、「学習」と言う。

<Step1 ミニバッチ>
訓練データの中から、ランダムに一部のデータを選び出す。その選ばれたデータをミニバッチといい、ここではそのミニバッチの損失関数の値を減らすことを目的とする。

<Step2 勾配の算出>
ミニバゥっちの損失関数を減らすために、各重みパラメータの勾配を求める。勾配は、損失関数の値を最も減らす方向を示す。

<Step3 パラメータの更新>
重みパラメータを勾配方向に微小量だけ更新

<Step4 繰り返し>
Step1、Step2、Step3を繰り返す

このような手法は、ミニバッチとして無作為に選ばれたデータを使用しているので、**確率的勾配降下法(SGD)**と呼ばれます。

## 2層ニューラルネットワーク
2層ニューラルネットワークを1つのクラスとして定義します。

```py3
import sys, os
sys.path.append(os.pardir)
from common.functions import *
from common.gradient import numerical_gradient

class TwoLayerNet:
    
    # 初期化する　input_sizeは、入力層のニューロンの数、hidden_sizeは、隠れ層のニューロンの数、output_sizeは、出力層のニューロンの数
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # 重みの初期化
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)#ランダム
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)#ランダム
        self.params['b2'] = np.zeros(output_size)
        
    def predict(self, x):
        W1, W2 = self.params['W1'], self.params['W2'] #重みのパラメータを代入
        b1, b2 = self.params['b1'], self.params['b2']#バイアスのパラメータを代入
        
        a1 = np.dot(x, W1) + b1#第１層の重みx入力信号の計算＋バイアス
        z1 = sigmoid(a1) # 上記で計算したa1をシグモイド関数で変換
        a2 = np.dot(z1, W2) + b2 #第２層の重み*z1の計算+バイアス
        y = softmax(a2) # 上記で計算したa2を出力層であるyに渡す
        
        return y

    # x: 入力データ、t:教師データ
    def loss(self, x, t):#損失関数
        y = self.predict(x)#TwoLayerNet classのinstanceの、predict(x)の結果を返す。
        
        return cross_entropy_error(y, t)# その結果を交差エントロピー誤差に当てはめて、教師データと比べて誤差を計算する。を
    
    # 正解率を計算する。認識精度
    def accuracy(self, x, t):
        y = predict(x) #predict(x)を計算するんだけど、selfいらないのかな？
        y = np.argmax(y, axis=1)# argmaxで、数値が高いラベルのみを取り出す。
        t = np.argmax(t, axis=1)# argmaxで、正解のラベルを取り出す。
        
        accuracy = np.sum(y == t)/float(x.shape[0])#y==tがTrueのものを１として合計して、それをデータの総数で割り算する。
        return accuracy
    
    # x:入力データ, t:教師データ
    # 偏微分、勾配を求める
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t) # lambda 無名関数、lambda x: y xが引数で、yが返り値
        # def loss_W(W):
        #   self.loss(x,t) と同じ意味のはず。
        
        grads = {}# grads（勾配）を初期化する。
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])# １層目の重みの勾配。損失関数と、重みから算出する。
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])# 1層目のバイアスの勾配。損失関数と、重みから算出する。
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])# 2層目の重みの勾配。損失関数と、重みから算出する。
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])# 2層目のバイアスの勾配。損失関数と、重みから算出する。
        
        return grads
```