深層学習 day1

# 1．はじめに
　深層学習day1について学習した内容をまとめました．

# 2．ニューラルネットワーク
　・脳の神経回路網をアルゴリズム化したもの
　・入力層，中間層，出力層で構成されている．
　・中間層が多数になるとDNN(Deep Neural Network)と呼ぶ．
　・回帰や分類に用いることができる．

### 確認テスト1
　問：ディープラーニングはなにをしようとしているか？

　解答：多数の中間層を設けることで，入力に対する欲しい出力を得るための近似関数を作成する．

### 確認テスト2
　問：次の中のどの値の最適化が最終目的か．全て選べ．
　①入力値[X] ②出力値[Y] ③重み[W] ④バイアス[b] 
　⑤総入力[u] ⑥中間層入力[z] ⑦学習率[ρ]

　解答：②重みと④バイアス

### 確認テスト3
　問：次のネットワークを紙にかけ．
　入力層︓2ノード1層，中間層︓３ノード2層，出力層︓1ノード1層

　解答：
![深層学習1_確認テスト_DNN手書き.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2522643/ac0605dc-fe58-5991-e439-06c06370c5ca.jpeg)

# 3．入力層～中間層
　①入力値[X] ②出力値[Y] ③重み[W] ④バイアス[b] ⑤総入力[u] ⑥出力[z] ⑦活性化関数[f]とし，
　入力層のノード数が4，中間層のノード数が1の時，
　中間層の総入力uの式は次のようになる．

```math
\boldsymbol{w}=[w_1，w_2，w_3，w_4] \\
```
```math
\boldsymbol{x}=[x_1，x_2，x_3，x_4] \\
```
```math
u =\boldsymbol{wx} =w_1x_1+w_2x_2+w_3x_3+w_4x_4+b
```
### 確認テスト4
　問：上の式をpythonで書け．

　解答：下記

```python
import numpy as np

# 入力
x = np.random.rand(4)
print("入力", x)

# 重み
W = np.random.rand(4)
print("重み", W)

# バイアス
b = np.random.rand(1)
print("バイアス", b)

#  総入力
u = np.dot(x, W) + b
print("総入力", u)
```
演習結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2522643/769c35b6-bb7f-964e-78d3-bc061c7abf42.png)

### 確認テスト5
　問：1-1のファイルから中間層の出力を定義しているソースを抜き出せ．

　解答：下記

```python
# 2層の総入力
u2 = np.dot(z1, W2) + b2
    
# 2層の総出力
z2 = functions.relu(u2)
```

# 4．活性化関数
　ニューラルネットワークにおいて，次の層への出力の大きさを決める非線形の関数．
　入力値の値によって，次の層への信号のON/OFFや強弱を定める働きをもつ．
　以下の3つは主に中間層に用いられる（シグモイド関数は出力層にも用いられる）．

### ステップ関数
 →しきい値を超えたら発火する関数であり，出力は常に１か０．
 →パーセプトロン（ニューラルネットワークの前身）で利用された関数．
 →0~1間の間を表現できず，線形分離可能なものしか学習できなかった（微分も不可）．

```math
f(x) = \left\{
\begin{array}{ll}
1 & (x \geq 0) \\
0 & (x \lt 0)
\end{array}
\right.
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2522643/601defe3-eeac-410e-30a0-f5c26f0b5888.png)

### シグモイド関数
　→非線形で微分可能な関数．
　→パーセプトロン（ニューラルネットワークの前身）で利用された関数．
　→0~1の間を緩やかに変化する関数で，ステップ関数ではON/OFFしかない状態に対し，
　 信号の強弱を伝えられるようになった．
　→大きな値では出力の変化が微小なため，勾配消失問題を引き起こす事があった．

```math
f(u) = \frac{1}{1+e^{-u}}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2522643/fc365333-dff1-1a54-3704-2d6f4c0db8c2.png)

### ReLU関数
　→よく使われている活性化関数
　→入力が負であれば0，正であればそのままの値を出力する．
　→勾配消失問題の回避とスパース化に貢献することで良い成果をもたらしている．

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2522643/c87e94a3-3ef1-a3f3-4dd0-cf15a6979123.png)

### 確認テスト6
　問：線形と非線形の違いを図にかいて簡易に説明せよ．

　解答：下記に線形な関数のグラフを示す．
　線形関数は次のような条件を満たす関数である．
　それぞれ上から順に加法性，斉次性と呼ぶ．
　逆に非線形関数は満たさない関数である．

```math
\begin{align}
f(x+y) &= f(x) + f(y) \\ \\
f(kx) &= kf(x)
\end{align}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2522643/648203be-4390-be6a-72db-b18a19c397cd.png)

### 確認テスト7
　問：配布されたソースコードより該当する箇所を抜き出せ．

　解答：下記

```python
z1 = functions.sigmoid(u)
```

### 演習：活性化関数それぞれのグラフを描画するために作成したコード
```python
import matplotlib.pyplot as plt

def step(x):
  y = np.where(x<=0, 0, 1)
  return y

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

def relu(x):
  y = np.maximum(0,x)
  return y

# xの値を生成
x = np.arange(-10, 10, 0.01)

# xに対するyを求める
y = relu(x)

# グラフ描画
plt.plot(x,y)
plt.xlabel("x")
plt.ylabel("y")
plt.show()
```

# 5．出力層
　各ラベルの予測確率，または回帰の値を出力する層である．

### 誤差関数
　正解データ（ラベル）と出力値（予測値）の誤差を示す関数
#### 例：二乗誤差関数

```math
E(w) = \frac{1}{2}\sum_{i=1}^{N}(\hat{y_i}-y_i)^2
```
### 確認テスト8
　問：なぜ引き算でなく二乗するか述べよ．また，1/2はどういう意味を持つか述べよ．

　解答：二乗しないと正負の誤差同士で打ち消しあってしまうから．
　 →二乗することで誤差の符号が正の値になるようにする．
　 1/2は微分したときに出てくる2を約分して計算を楽にするためにある．

#### 出力層における活性化関数
　タスクに応じて用いる活性化関数は異なる．
　・回帰　→　恒等写像

```math
f(u) = u
```
 ・二値分類　→　シグモイド関数（ソフトマックス関数でも機能する）
 ・多値分類　→ソフトマックス関数

```math
f(i,u) = \frac{e^u_i}{\sum_{k=1}^{K}e^{u_k}}
```
### 分類問題における誤差関数
#### 交差エントロピー誤差関数

```math
E(w) = -\sum_{i=1}^{N}y_i\log{\hat{y_i}}
```

### 確認テスト9
　問：ソフトマックス関数のソースコードを1行ずつ説明せよ．

　解答：下記

```python
# softmaxという名前で関数定義
def softmax(x):
    # もしxの次元数が2であれば
    if x.ndim == 2:
        # xを転置する
        x = x.T
        # xの最大値を引く
        x = x - np.max(x, axis=0)
        # 分母に指数関数のxの合計，分子に指数関数のxで計算結果をyに代入　　　
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        # yを転置して返す
        return y.T
    # xの最大値を引く
    x = x - np.max(x)
    # 分母に指数関数のxの合計，分子に指数関数のxで計算結果をyに代入
    return np.exp(x) / np.sum(np.exp(x))
```

### 確認テスト10
　問：交差エントロピー誤差関数のソースコードを1行ずつ説明せよ．

　解答：下記

```python
# d（ラベル），y（出力値）として関数を定義
def cross_entropy_error(d, y):
    #もしyの次元数が1であれば
    if y.ndim == 1:
        # d，yそれぞれの形状を(1,それぞれの大きさ)に変える．
        d = d.reshape(1, d.size)
        y = y.reshape(1, y.size)

    # 教師データがone-hot-vectorの場合，正解ラベルのインデックスに変換
    if d.size == y.size:
        d = d.argmax(axis=1)
    # yの形状の0番目の数を変数batch_sizeに代入する．
    batch_size = y.shape[0]
    # 交差エントロピー誤関数の値を返す（バッチサイズで平均をとっている）．
    return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size
```

### 演習（多値分類）
```python
# 多クラス分類

import numpy as np

def print_vec(text, vec):
    print("*** " + text + " ***")
    print(vec)
    #print("shape: " + str(x.shape))
    print("")

def relu(x):
  y = np.maximum(0,x)
  return y

def softmax(x):
    # もしxの次元数が2であれば
    if x.ndim == 2:
        # xを転置する
        x = x.T
        # xの最大値を引く
        x = x - np.max(x, axis=0)
        # 分母に指数関数のxの合計，分子に指数関数のxで計算結果をyに代入　　　
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        # yを転置して返す
        return y.T
    # xの最大値を引く
    x = x - np.max(x)
    # 分母に指数関数のxの合計，分子に指数関数のxで計算結果をyに代入
    return np.exp(x) / np.sum(np.exp(x))

def cross_entropy_error(d, y):
    #もしyの次元数が1であれば
    if y.ndim == 1:
        # d，yそれぞれの形状を(1,それぞれの大きさ)に変える．
        d = d.reshape(1, d.size)
        y = y.reshape(1, y.size)

    # 教師データがone-hot-vectorの場合，正解ラベルのインデックスに変換
    if d.size == y.size:
        d = d.argmax(axis=1)
    # yの形状の0番目の数を変数batch_sizeに代入する．
    batch_size = y.shape[0]
    # 交差エントロピー誤関数の値を返す（バッチサイズで平均をとっている）．
    return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size

# ウェイトとバイアスを設定
# ネートワークを作成
def init_network():
    print("##### ネットワークの初期化 #####")

    #_各パラメータのshapeを表示
    #_ネットワークの初期値ランダム生成
    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.7, 1.0],
        [0.2, 0.5, 0.8, 1.1],
        [0.3, 0.6, 0.9, 1.2]
    ])
    network['b1'] = np.array([0.1, 0.2, 0.3])
    network['b2'] = np.array([0.1, 0.2, 0.3, 0.4])
    
    print_vec("重み1", network['W1'] )
    print_vec("重み2", network['W2'] )
    print_vec("バイアス1", network['b1'] )
    print_vec("バイアス2", network['b2'] )

    return network

# プロセスを作成
# x：入力値
def forward(network, x):
    
    print("##### 順伝播開始 #####")
    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    
    # 1層の総入力
    u1 = np.dot(x, W1) + b1

    # 1層の総出力
    z1 = relu(u1)

    # 2層の総入力
    u2 = np.dot(z1, W2) + b2
    
    # 出力値
    y = softmax(u2)
    
    print_vec("総入力1", u1)
    print_vec("中間層出力1", z1)
    print_vec("総入力2", u2)
    print_vec("出力1", y)
    print("出力合計: " + str(np.sum(y)))
        
    return y, z1

## 事前データ
# 入力値
x = np.array([1., 2.])

# 目標出力
d = np.array([0, 0, 0, 1])

# ネットワークの初期化
network =  init_network()

# 出力
y, z1 = forward(network, x)

# 誤差
loss = cross_entropy_error(d, y)

## 表示
print("\n##### 結果表示 #####")
print_vec("出力", y)
print_vec("訓練データ", d)
print_vec("誤差",  loss)
```
実装結果

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

*** 重み2 ***
[[0.1 0.4 0.7 1. ]
 [0.2 0.5 0.8 1.1]
 [0.3 0.6 0.9 1.2]]

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

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

##### 順伝播開始 #####
*** 総入力1 ***
[0.6 1.3 2. ]

*** 中間層出力1 ***
[0.6 1.3 2. ]

*** 総入力2 ***
[1.02 2.29 3.56 4.83]

*** 出力1 ***
[0.01602796 0.05707321 0.20322929 0.72366954]

出力合計: 1.0

##### 結果表示 #####
*** 出力 ***
[0.01602796 0.05707321 0.20322929 0.72366954]

*** 訓練データ ***
[0 0 0 1]

*** 誤差 ***
0.3234202933601941
```

# 6．勾配降下法
　深層学習の目的は，誤差関数Eを最小にすることであった．
　→勾配降下法：誤差関数Eが最小になるようなパラメータを探索し，最適化する．
　全サンプルの平均誤差を用いる．
　式は以下になる．

```math
w^{t+1} = w^t - \eta\frac{\partial E}{\partial w}
```
### 確認テスト11
　問：勾配降下法の式に該当するソースコードを探してみよう．

　解答：下記

```python
network[key] -= learning_rate * grad[key]
grad = backward(x, d, z1, y)
```
### 確率的勾配降下法（SGD：Stochastic Gradient Descent）
　勾配降下法では，全サンプルを学習に利用していたが，
　SGDでは，全サンプルの中からランダムに選ばれた誤差のみを学習に用いる．
　メリットとして以下が挙げられる．
　・データが冗⻑な場合の計算コストの軽減
　・望まない局所的最適解に収束するリスクの軽減
　・オンライン学習ができる

### ミニバッチ勾配降下法
　オンライン学習とバッチ学習の中間的な特徴を持つ学習手法
　→ランダムに分割したデータの集合（ミニバッチ）に属するサンプルの平均誤差を利用
　確率的勾配降下法のメリットを損なわず，計算機の計算資源を有効利用できる．
　→CPUを利用したスレッド並列化やGPUを利用したSIMD並列化
　式は以下になる．

```math
\begin{align}
w^{t+1} &= w^t - \eta\frac{\partial E}{\partial w} \\
E_t &= \frac{1}{N_t}\sum_{n\in{D_t}}^{} E_n \\
N_t &= |D_t|
\end{align}
```

### 確認テスト12
　問：オンライン学習とは何か2行でまとめよ．

　解答：学習データが入ってくるたびにパラメータを更新し，学習を進めていく方法である．
　     1回の学習あたりのコストが低い，外れ値の影響が受けやすいといった特徴がある．

### 確認テスト13
　問：勾配降下法の式の意味を図に書いて説明せよ．

　解答：図を以下に示す．
　傾きが負であれば重みを増やし，傾きが正であれば重みを減らすことで
　誤差関数Eを最小化する（どれぐらい変化させるかは学習率で調整）．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2522643/63bf1446-7da3-40ce-87b8-0732652cdad8.png)

# 7．誤差逆伝搬法
　算出された誤差を出力層側から順に微分し，後ろの層へと伝播する．
　→最小限の計算で各パラメータでの微分値を解析的に計算する手法
　微分時には**数値微分**が用いられる．
　式は以下に示す．

```math
\frac{\partial E}{\partial w_m} \approx \frac{E(w_m+h)-E_m(w_m-h)}{2h}
```
　これに加え，チェインルール（連鎖率）を用いることで，
　不要な再帰的計算を避けて微分を算出できる．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2522643/cf970e4f-559e-68c7-5809-5d1678de2f83.png)


### 確認テスト14
　問：誤差逆伝播法では，不要な再帰的処理を避ける事が出来る．
　既に行った計算結果を保持しているソースコードを抽出せよ．

　解答：下記

```python
delta2 = functions.d_mean_squarred_error(d, y)
delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)
```

### 確認テスト15
　問：以下の図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2522643/f4a4b84a-1c4a-6d52-c63d-0fb1a1884741.png)

　解答：下記

```python
delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)
grad['W1'} = np.dot(x.T, delta1)
```
# 8．参考
ニューラルネットワークや勾配降下法，誤差逆伝搬法について：
絶対に理解させる誤差逆伝播法【深層学習】
https://www.youtube.com/watch?v=0itH0iDO8BE