ラビットチャレンジ　深層学習　Day1

## ラビットチャレンジのレポートに関するリンクはこちら
[ラビットチャレンジ　応用数学](https://qiita.com/matsu0130/items/6ba0279485ef8904fffb)
[ラビットチャレンジ　機械学習](https://qiita.com/matsu0130/items/12d6ed23e4189fd494cd)
[ラビットチャレンジ　深層学習　Day1](https://qiita.com/matsu0130/items/fa36ff86aa24a3e05810)
[ラビットチャレンジ　深層学習　Day2](https://qiita.com/matsu0130/items/2afa699b2d008e6fe19a)
[ラビットチャレンジ　深層学習　Day3](https://qiita.com/matsu0130/items/adc56e82168f9f86c8ee)
[ラビットチャレンジ　深層学習　Day4前半](https://qiita.com/matsu0130/items/36b867605960e3ea9aca)
[ラビットチャレンジ　深層学習　Day4後半](https://qiita.com/matsu0130/items/68f32635b63f74dc723c)

## 0.ニューラルネットワークの全体像
ニューラルネットワーク（NN）でできること
回帰
結果予想
売上予想
株価予想
ランキング
競馬順位予想
人気順位予想
分類
猫写真の判別
手書き文字認識
花の種類分類

ニューラルネットワーク:回帰
連続する実数値を取る関数の近似

ニューラルネットワーク:分類
性別（男あるいは女）や動物の種類など離散的な結果を予想するための分析

深層学習の実用例
自動売買（トレード）、チャットボット、翻訳、音声解釈、囲碁・将棋AI

**確認テスト1**
ディープラーニングは、結局何をやろうとしているか2行以内で述べよ。
「入力値から出力値に変換する数学モデルについて、人間が特定のモデルのパラメーターを指定するのではなく、中間層を用いることでモデルを構築する。」

**確認テスト2**
以下のニューラルネットワークを紙に書け。
入力層：2ノード1層
中間層：3ノード2層
出力層：1ノード1層
<img width="400" alt="キータ性能評価.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1371880/97584fa2-3548-f5c6-cc0a-36b3c46a8751.png">


## 1. 入力層～中間層
確認テスト1
この図式に動物分類の実例を入れてみよう。
<img width="400" alt="キータ性能評価.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1371880/2f07fd51-37d0-1c7f-a65c-5b6bbb603427.png">


確認テスト2
この数式をPythonで書け。

```python
import numpy as np
nn = np.dot(x, w) + b
```

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

```python
z = functions.sigmoid(u)
print_vec("中間層出力", z)
```

## 2. 活性化関数
ニューラルネットワークにおいて、次の層への出力の大きさを決める非線形の関数。
入力値の値によって、次の層への信号のON/OFFや強弱を定める働きをもつ。
○中間層用の活性化関数
　ReLU関数
　シグモイド（ロジスティック）関数
　ステップ関数

○出力層用の活性化関数
　ソフトマックス関数
　恒等写像
　シグモイド（ロジスティック）関数

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

```python
import numpy as np
x = np.arange(-10,10,0.01)
y = 1 * (x>0)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1371880/37bb51c6-749e-83f9-97c4-0b5fb6e61699.png)



シグモイド関数
0-1の間を緩やかに変化する関数で、ステップ関数ではON/OFFしかない状態に対し、信号の強弱を伝えられるようになり、予想ニューラルネットワーク普及のきっかけとなった。
大きな値では出力の変化が微小なため、勾配消失問題を引き起こす事があった。

```python
x = np.arange(-10,10,1)
y = 1 / (1 + np.exp(-x))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1371880/88efd3ef-a7fd-f8f2-27f0-0a10fcbaa317.png)


ReLU関数
今最も使われている活性化関数
勾配消失問題の回避とスパース化に貢献することで良い成果をもたらしている。

```python
def relu(x):
    return np.maximum(0, x)
y = relu(x)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1371880/852d5428-f3be-12df-6277-07cf64385050.png)

確認テスト1
配布されたソースコードより該当する箇所を抜き出せ。
z1 = functions.relu(u1)
z2 = functions.relu(u2)

## 3. 出力層
分類では、例えば、犬の確率、猫の確率、ネズミの確率のように各クラスに属する確率を出力。
実際にニューラルネットワークの学習において、入力データと訓練データ（正解値）を用意する。
そして出力層の結果と目的変数の正解値を比較し、どのくらい合っているかを表現するのが誤差関数。

**確認テスト1**
・なぜ、引き算でなく二乗するか述べよ
「各ラベルでの誤差は正負の両方の値が発生し、総和をとると、正負が打ち消しあってしまい、全体の誤差を正しく評価できなくなる。2乗して総和をとることで全てのラベルでの誤差が正の値になるようにしている。」

・下式の1/2はどういう意味を持つか述べよ
$$E_{n}(w)=\frac{1}{2}\sum_{j=1}^{J}(y_y-d_j)^2 = \frac{1}{2}||(y-d)||^2$$
「誤差逆伝播するときの微分計算が簡単になるため。本質的な意味はない。」

平均二乗誤差（MSE：Mean Squared Error）
回帰問題の誤差関数
$$MSE = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y_i})^2$$

```python
# ここでは微分を簡単に行えるため１/2をしている
def mean_squared_error(y, y_pred):
  return np.mean((y - y_pred)**2) / 2
```

クロスエントロピー誤差（交差エントロピー誤差）
分類問題の誤差関数
$$H(p,q)=−\sum_{x}p(x)\log{(q(x))}$$


```python
# クロスエントロピー
def cross_entropy_error(y, y_pred):
  if y.ndim == 1:
    y_pred = y_pred.reshape(1, y_pred.size) # (1, 全要素数)のベクトルに変形する
    y = y.reshape(1, y.size)                # (1, 全要素数)のベクトルに変形する
 
  batch_size = y.shape[0]
  return -np.sum(y * np.log(y_pred + 1e-7) / batch_size

# 教師データが one-hot-vector の場合、正解ラベルのインデックスに変換
def cross_entropy_error(y, y_pred):
  if y_pred.size == y.size:
    y_pred = y_pred.argmax(axis=1)

  batch_size = y.shape[0]
  # np.arangeでバッチサイズ分取り出して対数関数に与えている。
  return -np.sum(np.log(y[np.arange(batch_size), y_pred] + 1e-7) / batch_size
```
出力層の活性化関数
回帰　→　恒等写像
ニ値分類　→　シグモイド関数
多クラス分類　→　ソフトマックス関数

確認テスト2
①~③の数式に該当するソースコードを示し、一行づつ処理の説明をせよ。
①　return y.T　-> 関数の出力結果
②　np.exp(x) -> 
③　np.sum(np.exp(x),axis=0) -> 

```python
# ソフトマックス関数
def softmax(x):
  if x.ndim == 2:
    x = x.T
    # プログラムの動きを安定させるため追加
    x = x - np.max(x, axis=0) 
    # np.sum(np.exp(x))で、確率としている。
    y = np.exp(x) / np.sum(np.exp(x), axis=0) 
    return y.T

  x = x - np.max(x)  # オーバーフロー対策
  return np.exp(x) / np.sum(np.exp(x)) 
```
確認テスト3
①~②の数式に該当するソースコードを示し、一行づつ処理の説明をせよ。
①　cross_entropy_error(d, y)
②　-np.sum(np.log(y[np.arange(batch_size), d] + 1e-7))

### 4. 勾配降下法
勾配降下法は、ニューラルネットワークを学習させる手法で、以下の3種類などがある。
・勾配降下法
・確率的勾配降下法
・ミニバッチ勾配降下法

深層学習の目的
→学習を通して誤差を最小にするネットワークを作成すること
→勾配降下法を利用してパラメーターを最適化

実際に
$$
f(x_0, x_1) = x_0^2 + x_1^2
$$
を偏微分して勾配法を実装する

```python
# 勾配法の実装
import numpy as np

# 関数の定義
def function(x):
  return x[0]**2 + x[1] **2

def numerical_gradient(f, x):
  h = 1e-4 
  grad = np.zeros_like(x)

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

    x[idx] = tmp_val - h
    fxh2 = f(x)

    grad[idx] = (fxh1 - fxh2) / (2*h)
    x[idx] = tmp_val

  return grad

x = np.array([3.0,4.0])
numerical_gradient(function, x)
```

```
>>array([6., 8.])
```
**学習率**
学習率が大きいと、最適解を飛び越えて値が大きくなり、学習がうまく進まない。このことを「発散」という。
逆に、学習率が小さいと発散する確率が減るが、学習が終わるまでの時間がかかる。また、学習率が小さいと、局所最適解（極小値）を求めてしまう可能性がある。

学習率（η）を用いて勾配式を書くと以下の式になる（関数は先ほどの式を使用している）

$$
x_0 = x_0 - η\frac{∂f}{∂x_0}
$$
$$
x_1 = x_1 - η\frac{∂f}{∂x_1}
$$

```python
# 勾配法による最適化
def gradienr_descent(f, init_x, lr=0.01, step_num=100):
  """
  f -> 最適化したい関数
  init_x -> 初期値
  lr -> 学習率
  step_num -> 勾配法による繰り返しの数
  """
  x = init_x

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

init_x = np.array([-3.0,4.0])
gradienr_descent(function, init_x, lr=0.1)
```
**確率的勾配降下法(SGD)**
・データが冗長な場合は計算コストを軽減
・毎回毎回違いデータを使って学習するため、望まない局所最適解に収束するリスクを軽減
・オンライン学習できる
以下の式で重みの更新を行う
$$
\mathbf{W} ← \mathbf{W} - η\frac{∂L}{∂\mathbf{W}}
$$
しかしこの方法だと関数の形状が等方的でない場合、非効率になってしまう。
そこで**Momentum、AdaGrad、Adam**という手法を用いる。

・Momentum
$$
\mathbf{v} ← α\mathbf{v} - η\frac{∂L}{∂\mathbf{W}} 
$$
$$
\mathbf{W} ← \mathbf{W} + \mathbf{v}
$$

・AdaGrad
$$
\mathbf{h} ← α\mathbf{h} - η\frac{∂L}{∂\mathbf{W}}⊙η\frac{∂L}{∂\mathbf{W}} 
$$
$$
\mathbf{W} ← \mathbf{W} - η\frac{1}{\sqrt{\mathbf{h}}}\frac{∂L}{∂\mathbf{W}}
$$

・Adam
$$
\mathbf{m_{t+1}} = ρ_1\mathbf{m_t} + (1 - ρ_1)η\frac{∂L}{∂\mathbf{W_t}}
$$

$$
\mathbf{v_{t+1}} = ρ_2\mathbf{v_t} + (1 - ρ_2)η\frac{∂L}{∂\mathbf{W_t}}⊙η\frac{∂L}{∂\mathbf{W_t}} 
$$
バイアスの修正を行う

$$
m_{t+1} = \frac{\mathbf{m_{t+1}}}{1 - ρ_{1}^t}
$$

$$
v_{t+1} = \frac{\mathbf{v_{t+1}}}{1 - ρ_{2}^t}
$$

以上を用いて

$$
\mathbf{W_{t+1}} = \mathbf{W} - η\frac{1}{\sqrt{v_{t+1} + ε}}⊙m_{t+1}
$$

**ミニバッチ勾配降下法**
確率的勾配降下法のメリットを損なわず、計算機の計算資源を有効利用できる
→CPUを利用したスレッド並列化やGPUを利用したSIMD並列化

確認テスト1
該当するソースコードを探してみよう。

```python
network[key] -= learning_rate * grad[key]
grad = backward(x, d, z1, y)
```
確認テスト2
オンライン学習とは何か2行でまとめよ
「モデルにその都度データ与えて学習させる学習手法。学習データが入ってくる都度パラメータを更新し、学習を進めていく。」

### 5. 誤差逆伝播法
直接微分計算をすると、計算量が非常に多くなってしまう。そこで、誤差逆伝播法を利用する。
算出された誤差を、出力層側から順に微分し、前の層前の層へと伝播。最小限の計算で各パラメータでの微分値を解析的に計算する手法。

```python
    # 出力層でのデルタ
    # functions.d_mean_squared_errorが誤差関数を微分したもの
    delta2 = functions.d_mean_squared_error(d, y)
    # b2の勾配
    grad['b2'] = np.sum(delta2, axis=0)
    # W2の勾配
    grad['W2'] = np.dot(z1.T, delta2)
    # 中間層でのデルタ
    #delta1 = np.dot(delta2, W2.T) * functions.d_relu(z1)

    # １回使った微分を使いまわしている。
    delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)

    delta1 = delta1[np.newaxis, :]
    # b1の勾配
    grad['b1'] = np.sum(delta1, axis=0)
    x = x[np.newaxis, :]
    # W1の勾配
    grad['W1'] = np.dot(x.T, delta1)

    # print_vec("偏微分_重み1", grad["W1"])
    # print_vec("偏微分_重み2", grad["W2"])
    # print_vec("偏微分_バイアス1", grad["b1"])
    # print_vec("偏微分_バイアス2", grad["b2"])

    return grad
```

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

確認テスト2
2つの空欄に該当するソースコードを探せ

```python
delta2 = functions.d_mean_squared_error(d, y)
grad['W2'] = np.dot(z1.T, delta2)
```