ラビットチャレンジ　レポート　深層学習 day1、day2

#1. 入力層〜中間層
###概要
入力層
説明変数xが入る。
中間層（または隠れ層とも呼ばれる）
出力f(x)は、入力層からきた説明関数に重みWを掛け合わせ、バイアスbを足して活性化関数fにより返還され、次の層へと情報が渡る。
上記の式は下記を参照

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/c4344ac5-40fd-fe32-0d8d-c99e76249bfa.png)


###演習
```python
# 順伝播（単層・複数ユニット）

# 重み
W = np.array([
    [0.1, 0.2, 0.3], 
    [0.2, 0.3, 0.4], 
    [0.3, 0.4, 0.5],
    [0.4, 0.5, 0.6]
])
print("W", W)
b = np.array([0.1, 0.2, 0.3])
print("b", b)
x = np.array([1.0, 5.0, 2.0, -1.0])
print("x", x)
u = np.dot(x, W) + b
print("u", u)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/d810757c-413f-c7b2-ce86-bd67c0fa270f.png)

###確認テスト

問題1
(1).ディープラーニングは結局何をやろうとしているのか2行以内で述べよ。
明示的なプログラムの代わりに多数の中間層を持つニューラルネットワークを用いて、入力値から目的とする出力値に変換する数学モデルを構築すること。

(2)ニューラルネットワークは、どの値の最適化が最終目的か？
重みとバイアス

問題2
次のニューラルネットワークをかけ。
入力層：2ノード1層
中間層：3ノード2層
出力層：1ノード1層
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/f600c76a-abc2-b066-a67e-d045816f71b0.png)

#2. 活性化関数
###概要
活性化関数は入力信号の総和を出力信号に変換する関数fのこと。
ニューラルネットワークでは、活性化関数には非線形関数を用いないと意味がない。
　→線形関数を用いると、ニューラルネットワーク層を深くする意味がなくなってしまう（いつまでたっても線形だから）。

・シグモイド関数
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/d50270da-0599-e648-fc7d-80ef5e862882.png)

・ステップ関数
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/ec6395ab-15db-ab78-b70d-79beb8846aa2.png)

・ReLU関数
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/9b531279-68a7-4c37-d674-b629750d0f81.png)


###演習

・シグモイド関数

```python
import numpy as np
import matplotlib.pyplot as plt
from common import functions as f

x = np.array([range(-1000, 1000)])
x = x/100
y = f.sigmoid(x)

plt.scatter(x, y, marker='.')
plt.show()

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/83516109-348f-beb2-c38e-c10e70cceaa3.png)


・ステップ関数

```python
import numpy as np
import matplotlib.pyplot as plt
from common import functions as f


x = np.array([range(-1000, 1000)])
x = x/100
y = f.step_function(x)

plt.scatter(x, y, marker='.')
plt.show()

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/568eddd0-8e56-c2b5-1090-085990005298.png)


・ReLU関数

```python
import numpy as np
import matplotlib.pyplot as plt
from common import functions as f


x = np.array([range(-1000, 1000)])
x = x/100
y = f.relu(x)

plt.scatter(x, y, marker='.')
plt.show()

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/e75f6e6e-2581-bcb7-2643-9a93b760a7a4.png)

###確認テスト

問題1
配布されたソースコードより、z=f(u)に該当する箇所を示せ。

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

#3. 出力層
###概要

◆誤差関数
よく使われる誤差関数には、以下の2種類がある

二乗誤差

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/2a54158f-819e-997d-99ee-ba3c1a19659c.png)


クロスエントロピー誤差
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/7033e218-51df-c645-666a-25d9ec4bdf66.png)


◆活性化関数

出力層の活性化関数は、信号の大きさ（比率）をそのままに変換できる。
代表的なのはソフトマックス関数、下記を参照。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/c085999a-ee68-a3c9-9172-8894892f8d84.png)

ソフトマックス関数は、iに関して和をとると1になるという性質を持っている。この性質から、出力層の出力は0〜1の範囲に限定され、そのまま確率として利用できるため分類問題にて使用される。

###演習

・クロスエントロピー

```python
def cross_entropy_error(d, y):
    if y.ndim == 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)
             
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size
```

・ソフトマックス

```python
def softmax(x):
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        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))
```

通常の活性化関数はノード1つからの値を別の1つの値に変換する1対1である。しかし、ソフトマックスは出力層のすべてのノードの出力を用いて、各ノード1つずつ出力をする多対1の出力である。

###確認テスト

問題1
(1)2乗誤差について、なぜ引き算ではなく2乗するのかを述べよ。

単なる引き算にしてしまうと、誤差の±により誤差和の値を正確に把握することができない

(2)次式の1/2はどのような意味を持つか？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/b15e1bc4-557f-f49f-8ef2-ee101ca82a8b.png)

微分した時に乗数の2が係数として出てきて、1/21/2と相殺される。微分の計算が楽になる


問題2
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/9e254105-56c3-a1af-5898-51c7ef861ff5.png)

①

```python
return y.T
```


②

```python
np.exp(x)
```


③

```python
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) #プログラムの動きを安定させるため追加
    y = np.exp(x) / np.sum(np.exp(x), axis=0) #総和（全体）np.exp(x)で割ることで、確率としている。

    return y.T

  x = x - np.max(x)  # オーバーフロー対策（プログラムの動きを安定）
  return np.exp(x) / np.sum(np.exp(x)) #　この１行がソフトマックスでやりたいことのすべて
```

問題3
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/7773ea48-9e5a-277c-1f4a-ad3f04fe4b2a.png)

```python
# クロスエントロピー
def cross_entropy_error(d, y):
  if y.ndim == 1:    # 1次元の場合。
    d = d.reshape(1, d.size)    # (1, 全要素数)のベクトルに変形する
    y = y.reshape(1, y.size)    # (1, 全要素数)のベクトルに変形する

  # 教師データが one-hot-vector の場合、正解ラベルのインデックスに変換
  if d.size == y.size:
    d = d.argmax(axis=1)  # argmaxで最大値のインデックスを取得

  batch_size = y.shape[0] # この場合は行の形状なので1
  return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7) / batch_size #分子まで（/ batch_size）が本質的な部分
  # ごく小さい値を加えることで0に落ちない処理をしている
  # np.arangeでバッチサイズ分取り出して対数関数に与えている。
```


#4. 勾配降下法
###概要
人工知能の学習とは、出力層から計算した誤差関数の値E(w)を最小化することである。
ただし、パラメータwのサイズが膨大で、解析的に解くのはほぼ不可能である。
そこで数値的に極小解を求める手法である勾配降下法が用いられる。

勾配降下法の手法には下記がある。

・勾配降下法
一般的な勾配降下法であり、別名最急降下法やニュートン法と呼ばれる。すべてのデータを用い一度に更新を行う。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/882a8ca8-ac6d-ab67-b036-bcda85ca3967.png)


・確率的勾配降下法（SGD）
ランダムに抽出したサンプルの誤差を用いてパラメータを更新する手法である。データが冗長な場合の計算コストの軽減ができたり、望まない局所極小解に収束するリスクを軽減できる。また、学ぶべきデータが逐次増えていく場合に対応できる、オンライン学習可能。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/d075e13a-e50c-af6e-f90a-7d20319928d1.png)


・ミニバッチ勾配降下法
ランダムに分割したデータの集合（ミニバッチ）DtDtに属するサンプルの平均誤差をとる。並列計算に対応できるため、確率的勾配法のメリットを損なわず、計算機の計算資源を有効利用できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/4791cc7a-eafa-52aa-1f66-14d8ff1d06f3.png)


###演習

・勾配降下法

```python
class SGD:
    def __init__(self, learning_rate=0.01):
        self.learning_rate = learning_rate

    def update(self, params, grad):
        for key in params.keys():
            params[key] -= self.learning_rate * grad[key] 
```

###確認テスト

問題1
(1)下記勾配降下法に該当するソースコードを配布コードから抜き出せ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/c107fa97-8355-ffed-dd4b-3387be02dce4.png)

```python
network[key] -= learning_rate * grad[key]
```

(2)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/438f2cf3-02d1-7ab6-8a21-4a454844afdd.png)
オンライン学習とは学習データが入ってくるたびにその都度、新たに入ってきたデータのみを使って学習を行うもの。
学習を行う際に1からモデルを作り直すのではなく、そのデータによる学習で今あるモデルのパラメータを随時更新していく。

(3)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/df8baa72-4ce5-06f4-1fb1-a3ff58a15319.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/271cd2f0-35ef-fb6f-4234-97c49e74d653.png)

#5. 誤差逆伝搬法
###概要
直接数値微分計算をすると、計算量が非常に多くなってしまう。例えば推定したいパラメータの数がnnだった場合、微分を計算しなければならない数はnnで、さらにニューラルネットワーク全体をnn回計算しなければならない。パラメータ数が数千万にもなるニューラルネットワークにおいて、そのような計算を毎回していては学習が進まない。
そこで、誤差逆伝播法が考え出された。算出された誤差を、出力層側から順に微分し、前の層前の層へと伝播する手法である。この手法により、最低限の計算をするだけですみ、効率よく各パラメータの更新量を解析的に計算する手法。

###演習

```python
import numpy as np
import matplotlib.pyplot as plt
from common import functions


# サンプルとする関数
#yの値を予想するAI

def f(x):
    y = 3 * x[0] + 2 * x[1]
    return y

# 初期設定
def init_network():
    # print("##### ネットワークの初期化 #####")
    network = {}
    nodesNum = 10
    network['W1'] = np.random.randn(2, nodesNum)
    network['W2'] = np.random.randn(nodesNum)
    network['b1'] = np.random.randn(nodesNum)
    network['b2'] = np.random.randn()

    # print_vec("重み1", network['W1'])
    # print_vec("重み2", network['W2'])
    # print_vec("バイアス1", network['b1'])
    # print_vec("バイアス2", network['b2'])

    return network

# 順伝播
def forward(network, x):
    # print("##### 順伝播開始 #####")

    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    u1 = np.dot(x, W1) + b1
    z1 = functions.relu(u1)

    ## 試してみよう
    #z1 = functions.sigmoid(u1)

    u2 = np.dot(z1, W2) + b2
    y = u2

    # print_vec("総入力1", u1)
    # print_vec("中間層出力1", z1)
    # print_vec("総入力2", u2)
    # print_vec("出力1", y)
    # print("出力合計: " + str(np.sum(y)))    

    return z1, y

# 誤差逆伝播
def backward(x, d, z1, y):
    # print("\n##### 誤差逆伝播開始 #####")    

    grad = {}

    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']

    # 出力層でのデルタ
    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

# サンプルデータを作成
data_sets_size = 100000
data_sets = [0 for i in range(data_sets_size)]

for i in range(data_sets_size):
    data_sets[i] = {}
    # ランダムな値を設定
    data_sets[i]['x'] = np.random.rand(2)

    ## 試してみよう_入力値の設定
    # data_sets[i]['x'] = np.random.rand(2) * 10 -5 # -5〜5のランダム数値

    # 目標出力を設定
    data_sets[i]['d'] = f(data_sets[i]['x'])

losses = []
# 学習率
learning_rate = 0.07

# 抽出数
epoch = 1000

# パラメータの初期化
network = init_network()
# データのランダム抽出
random_datasets = np.random.choice(data_sets, epoch)

# 勾配降下の繰り返し
for dataset in random_datasets:
    x, d = dataset['x'], dataset['d']
    z1, y = forward(network, x)
    grad = backward(x, d, z1, y)
    # パラメータに勾配適用
    for key in ('W1', 'W2', 'b1', 'b2'):
        network[key]  -= learning_rate * grad[key]

    # 誤差
    loss = functions.mean_squared_error(d, y)
    losses.append(loss)

print("##### 結果表示 #####")    
lists = range(epoch)


plt.plot(lists, losses, '.')
# グラフの表示
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/f3dfd138-5b41-fe64-6710-2b8ac941d592.png)



###確認テスト

確認テスト

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

```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

```


（2)下記式を表すソースコードを探せ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/85b2b9d2-2fe4-0436-bfd8-20425a35a5ea.png)

```python
delta2 = functions.d_mean_squared_error(d, y)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/83f24617-73ed-4309-9159-c480aadb1e47.png)

```python
delta2 = functions.d_mean_squared_error(d, y)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/d8e820a1-e614-40a7-2164-1ce63fce4b41.png)

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


#6. 勾配消失問題
###概要
出力層〜入力層における誤差関数勾配の総和の内部は活性化関数の微分
の積となっている。
もし各層における誤差関数の勾配が小さい値（0に近い）場合、
その積を全層における誤差関数勾配として得る為、
誤差関数勾配は小さい値のかけ算を繰り返すことで、0に近い値になってしまい学習が
進まなくなってしまう。これが勾配消失問題。

###演習

意図的に勾配消失させる

```python
import numpy as np
import matplotlib.pyplot as plt
from common import functions


import numpy as np
from common import layers
from collections import OrderedDict
from common import functions
from data.mnist import load_mnist
import matplotlib.pyplot as plt

# mnistをロード
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)
train_size = len(x_train)

print("データ読み込み完了")

# 重み初期値補正係数
wieght_init = 0.01
#入力層サイズ
input_layer_size = 784
#中間層サイズ
hidden_layer_1_size = 40
hidden_layer_2_size = 20

#出力層サイズ
output_layer_size = 10
# 繰り返し数
iters_num = 2000
# ミニバッチサイズ
batch_size = 100
# 学習率
learning_rate = 0.1
# 描写頻度
plot_interval=10

# 初期設定
def init_network():
    network = {} 
    network['W1'] = wieght_init * np.random.randn(input_layer_size, hidden_layer_1_size)
    network['W2'] = wieght_init * np.random.randn(hidden_layer_1_size, hidden_layer_2_size)
    network['W3'] = wieght_init * np.random.randn(hidden_layer_2_size, output_layer_size)

    network['b1'] = np.zeros(hidden_layer_1_size)
    network['b2'] = np.zeros(hidden_layer_2_size)
    network['b3'] = np.zeros(output_layer_size)

    return network

# 順伝播
def forward(network, x):
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']
    hidden_f = functions.sigmoid

    u1 =  np.dot(x, W1) + b1
    z1 = hidden_f(u1)
    u2 =  np.dot(z1, W2) + b2
    z2 = hidden_f(u2)
    u3 =  np.dot(z2, W3) + b3
    y = functions.softmax(u3)

    return z1, z2, y

# 誤差逆伝播
def backward(x, d, z1, z2, y):
    grad = {}

    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']
    hidden_d_f = functions.d_sigmoid
    last_d_f = functions.d_softmax_with_loss


    # 出力層でのデルタ
    delta3 = last_d_f(d, y)
    # b3の勾配
    grad['b3'] = np.sum(delta3, axis=0)
    # W3の勾配
    grad['W3'] = np.dot(z2.T, delta3)
    # 2層でのデルタ
    delta2 = np.dot(delta3, W3.T) * hidden_d_f(z2)
    # b2の勾配
    grad['b2'] = np.sum(delta2, axis=0)
    # W2の勾配
    grad['W2'] = np.dot(z1.T, delta2)
    # 1層でのデルタ
    delta1 = np.dot(delta2, W2.T) * hidden_d_f(z1)
    # b1の勾配
    grad['b1'] = np.sum(delta1, axis=0)
    # W1の勾配
    grad['W1'] = np.dot(x.T, delta1)

    return grad

# パラメータの初期化
network = init_network()

accuracies_train = []
accuracies_test = []

# 正答率
def accuracy(x, d):
    z1, z2, y = forward(network, x)
    y = np.argmax(y, axis=1)
    if d.ndim != 1 : d = np.argmax(d, axis=1)
    accuracy = np.sum(y == d) / float(x.shape[0])
    return accuracy

for i in range(iters_num):
    # ランダムにバッチを取得    
    batch_mask = np.random.choice(train_size, batch_size)
    # ミニバッチに対応する教師訓練画像データを取得    
    x_batch = x_train[batch_mask]
    # ミニバッチに対応する訓練正解ラベルデータを取得する
    d_batch = d_train[batch_mask]



    z1, z2, y = forward(network, x_batch)
    grad = backward(x_batch, d_batch, z1, z2, y)

    if (i+1)%plot_interval==0:
        accr_test = accuracy(x_test, d_test)
        accuracies_test.append(accr_test)

        accr_train = accuracy(x_batch, d_batch)
        accuracies_train.append(accr_train)

        print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))

    # パラメータに勾配適用
    for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
        network[key]  -= learning_rate * grad[key]


lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test,  label="test set")
plt.legend(loc="lower right")
plt.title("accuracy")
plt.xlabel("count")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
# グラフの表示
plt.show()

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/e47d6b50-76f2-bfe9-94bb-ecad92f98dad.png)

Relu関数にすると勾配消失が起こらなくなる、つまり勾配消失と
活性化関数にはかかわりがあると言える。

###確認テスト

問題1
(1)連鎖律の原理を使い、z=t2、t=x+yの時のdz/dxを求めよ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/efba960b-0e49-f87e-ff15-5ec1ec383c5c.png)


問題2
(1)シグモイド関数を微分した時、入力値が0の時に最大値をとる、その値を答えよ。

0.25

#7. 学習最適化手法
###概要

・モメンタム
勾配の移動平均を出して振動を抑える（過去の勾配たちを考慮することで急な変化を抑える）。
誤差をパラメータで微分した値と学習率εε の積をひき算（←勾配降下法と同様)した後、現在の重みに前回の重みを減算した値と慣性（ハイパーパラメータ）μμ のかけ算を加える。
・AdaGrad
次元ごとに学習率を変化させるようにしたもの。
誤差をパラメータで微分したものと再定義した学習率をかけ算して引く。
学習率が徐々に小さくなっていく

・RMSprop
AdaGradの改良版
一度学習率が00に近づくとほとんど変化しなくなるAdaGradの問題を改良したもの。
前回までの勾配情報をどの程度使うのかαでコントロールする。

・Adam
Adamは、今までの学習率最適化手法のいいとこどりで、良くなかった部分がつぶれるようにした最強の方法。そのため一番使われる。具体的には、
モメンタム：過去の勾配の指数的減衰平均（進が良い方向に一気に進む）
RMSprop：過去の勾配の２乗の指数的減衰平均（学習率の調整をうまくする。）
の２つを含んだ最適解アルゴリズムで、鞍点を抜ける能力がある。


###演習

モメンタム

```python
class Momentum:
    def __init__(self, learning_rate=0.01, momentum=0.9):
        self.learning_rate = learning_rate
        self.momentum = momentum
        self.v = None

    def update(self, params, grad):
        if self.v is None:
            self.v = {}
            for key, val in params.items():                                
                self.v[key] = np.zeros_like(val)

        for key in params.keys():
            self.v[key] = self.momentum * self.v[key] - self.learning_rate * grad[key] 
            params[key] += self.v[key]

```

AdaGrad

```python
class AdaGrad:
    def __init__(self, learning_rate=0.01):
        self.learning_rate = learning_rate
        self.h = None

    def update(self, params, grad):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)

        for key in params.keys():
            self.h[key] += grad[key] * grad[key]
            params[key] -= self.learning_rate * grad[key] / (np.sqrt(self.h[key]) + 1e-7)

```

RMSprop

```python
class RMSprop:
    def __init__(self, learning_rate=0.01, decay_rate = 0.99):
        self.learning_rate = learning_rate
        self.decay_rate = decay_rate
        self.h = None

    def update(self, params, grad):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)

        for key in params.keys():
            self.h[key] *= self.decay_rate
            self.h[key] += (1 - self.decay_rate) * grad[key] * grad[key]
            params[key] -= self.learning_rate * grad[key] / (np.sqrt(self.h[key]) + 1e-7)

```

Adam

```python
class Adam:
    def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999):
        self.learning_rate = learning_rate
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0
        self.m = None
        self.v = None

    def update(self, params, grad):
        if self.m is None:
            self.m, self.v = {}, {}
            for key, val in params.items():
                self.m[key] = np.zeros_like(val)
                self.v[key] = np.zeros_like(val)

        self.iter += 1
        lr_t  = self.learning_rate * np.sqrt(1.0 - self.beta2 ** self.iter) / (1.0 - self.beta1 ** self.iter)         

        for key in params.keys():
            self.m[key] += (1 - self.beta1) * (grad[key] - self.m[key])
            self.v[key] += (1 - self.beta2) * (grad[key] ** 2 - self.v[key])

            params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)
```

###確認テスト

問題1
(1)モメンタム・AdaGrad・RMSPropの特徴をそれぞれ簡潔に説明せよ。
◆モメンタム
・局所的最適解にはならず、大域的最適解になる
・谷間についてから最も低い位置（最適値）に行くまでの時間が早い。

◆AdaGrad
・勾配の緩やかな斜面に対して、最適値に近づける。

◆RMSProp
・局所的最適解にはならず、大域的最適解となる。
・ハイパーパラメータの調整が必要な場合が少ない

#8. 過学習
###概要
過学習とはテストごとの訓練誤差と学習誤差が乖離すること

◆原因
・パラメータの数が多い
・パラメータの値が適切ではない
・ノードの数が多い
等々

データややりたいタスクに対してニューラルネットワークが複雑すぎると
学習が進むと重みの値にばらつきが発生する。
重みが大きいほど重要な値であることを示すが大きいことによって過学習の原因ともなる。


###演習

###確認テスト
問題1
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/e1a92802-640f-92d2-f418-4b615ab33567.png)

答えは(a)

問題2
下図について、L1正則化を表しているグラフはどちらか答えよ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201068/f6194c56-3793-636f-9079-120b76729288.png)

答えは右図のLasso推定量

#9. 畳み込みニューラルネットワークの概念
###概要
畳み込みニューラルネットワーク（Convolutional Neural Network：CNN）は、主に画像認識等で使用されているニューラルネットワーク。
画像だけにしか使えないわけではなく、1次元CNNを使うことで時系列データを扱うことも出来る。


◆畳み込み層
画像とフィルタ(カーネル)の積の総和にバイアスを加えることで算出することで特徴を抽出する。
例えば横線を抽出するフィルタに対する畳み込みの結果が大きければ横線の特徴を抽出(横線が画像に存在する)し、小さければ横線がないということになる。

フィルタは3x3, 5x5等のサイズ。

◆パディング
画像の周囲に固定値(0を使うことが多い)を埋め込むこと。
パディングなしで畳み込みをすると元の画像より小さくなるが、パディングをすることでサイズを維持することが出来る。

また、パディングなしの場合、画像の端の方は他の部分と比べて畳み込みに使われる回数が少なくなり、特徴として抽出されにくいが、パディングをすることによってより端の方も特徴を抽出できるようになる。

パディングに使う値は0でなくとも良いが、学習に影響を与える可能性があるため0とすることが一般的。

◆ストライド
フィルタをどれだけずらして畳み込みを行うかということ。
画像に対してフィルタを少しずつずらしながら畳み込みは行っていくが、この時、ストライドを1とした場合、次の畳み込みは1ピクセルずらして行うことになる。

◆チャンネル
空間的な奥行き。
チャンネルが1つだと例えばカラー画像でもRGBの違いを区別せずに畳み込みすることになり、正確な結果とならない。

◆プーリング層
対象領域の中から1つの値を取得する層。
畳み込んだ後に行うことでそれらしい特徴を持った値のみを抽出できる

◆最大値プーリング
3x3等の対象領域の中で最大値を取得する

◆平均値プーリング
3x3等の対象領域の中で平均値を取得する


###演習

###確認テスト
(1)サイズ6×6の入力画像を、サイズ2×2のフィルタで畳み込んだ時の出力画像のサイズを答えよ。なお、ストライドとパディングは1とする。

7×7

#10. 最新のCNN
###概要

◆AlexNet
2012年にILSVRC（ImageNet Large Scale Visual Recognition Challenge）で優勝したモデル。
ILSVRC：AIを用いて画像認識の正確性を競うコンテスト

AlexNetはそれまで（2011年まで）主要だった、サポートベクターマシーンを使ったモデルをなぎ倒して優勝したモデル。
なお、2012年2位だったのは、サポートベクターマシーンを使った東京大学の原田研究室（中心は牛久祥孝氏）

5層の畳み込み層およびプーリング層、それに続いて2層の全結合層という構造。

ドロップアウトを使用して過学習を抑えている。

###演習

```python
import torch
import torch.nn as nn

class AlexNet(nn.Module):

    def __init__(self, num_classes=1000):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), 256 * 6 * 6)
        x = self.classifier(x)
        return x

```

###確認テスト
(1)サイズ5×5の入力画像を、サイズ3×3のフィルタで畳み込んだ時の出力画像のサイズを答えよ。なおストライドは2、パディングは1とする。

3×3