ラビットチャレンジ stage3 深層学習（前編） -day2-

#1.勾配消失問題
<dt>1-1.勾配消失問題とは</dt>
　●誤差逆伝播法を使用している際に、学習がうまくできなくなってしまう。　
　
　[確認テスト]
　連鎖率の原理を使い、dz/dxを求めよ

　　$ z = t^2 $ ,$ t = x + y $

  ```math
　　 \frac{dz}{dx} = \frac{dz}{dt} \cdot \frac{dt}{dx} \\

  \frac{dz}{dt} = 2t \\
 
  \frac{dt}{dx} = 1 \\

  \begin{eqnarray}
  \frac{dz}{dt} \frac{dt}{dx} &=& 2t \cdot 1 \\

  &=& 2t \\

  &=& 2(x + y)

  \end{eqnarray}

  ```
　
　●誤差逆伝播法が下位層に進んでいくにつれて、勾配がどんどん緩くなっていく。
　　そのため、勾配降下法による、更新では下位層のパラメータはほとんど変わらず、
　　訓練は最適値に収束しなくなる。


![スクリーンショット 2021-07-17 1.28.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/2e7a6388-ff0d-abed-fcce-c24927993d73.png)


　●上の右の図では、学習回数が増えるに連れて、正解率が上がっている。
　　そのため、うまく学習していると言える。
　　しかし、左の図では学習回数が増えても、正解率が上がっていない。
　　このように学習がうまくできなくなってしまう。
　

<dt>1-2.勾配消失問題がなぜ発生するか</dt>
　
　●勾配消失問題が発生する原因は、ニューラルネットワークの層の数と微分によって、引き起こされる。

![スクリーンショット 2021-07-17 1.34.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/5bb37555-f9db-bfac-2a27-8bd2dabac16d.png)

　●図の赤線で囲ったところを見てみると、出力層から中間層の間で微分の数が増えている。
　　ニューラルネットワークでは、中間層を増やしていってより複雑な学習ができるようにするが、
　　中間層を増やせば増やすほど、微分の回数が増えていく。
　　さらに、微分の値は０〜１の間を取ることが多いため、
　　入力層に向かっていくにつれて、値がどんどん小さくなっていってしまう。

　●勾配消失問題が発生してしまう、代表的な例として、シグモイド関数がある。

　・シグモイド関数

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

![sigmoid1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/50c6cbf5-68ee-c237-91cd-ca11dc1580ee.png)

　　・シグモイド関数の微分

  $$ f(u)' = (1 - f(u)) \cdot f(u) $$

![sigmoid_d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/a07e232f-a99b-4217-dc76-0c71ec0f1981.png)

　●ここで問題なのが、シグモイド関数を微分すると、最大でも0.25の値しか取らない。
　　例えば、中間層の活性化関数がシグモイド関数で、100層もあるとすると、
　　誤差逆伝播の中で、100回も、０.25より小さい値をかける必要がある。
　　そのため、入力層まで遡る頃にはとても小さい数になり、勾配消失が発生してしまう。
　
　[確認テスト]
　　シグモイド関数を微分した時、入力値が０の時に最大値をとる。
　　その値として正しいものを選べ。

　　①0.15　②0.25　③0.35 ④0.45
　　
　　②　0.25

<dt>1-3.勾配消失問題の解決法</dt>

　●活性化関数の選択
　　そもそも、シグモイド関数が勾配消失問題を発生させているので、
　　活性化関数自体を変更することで、勾配消失問題を解決する。

　　代表的な例として、RELU関数がある。

　　・RELU関数

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

![RELU関数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/2339d055-5d9d-18e9-8b56-86c015313c3d.png)

　　RELU関数では、出力が１より大きい場合、微分値は必ず1になり、
　　１より小さい場合は、０になる。
　　そのため、シグモイド関数とは違い、値が小さくなることはなくなり、
　　勾配消失問題が解決できる。
　　その他にも、微分値が０をとった場合は、０を取ったところの重みはあまり使われなくなり、
　　必要な重みの部分だけが、選択的に残るような状態になる。（**スパース化**という)

　　このように、活性化関数には他にも、勾配消失問題を解決できるものがあるため、
　　活性化関数の選択は、解決法の１つとなりうる。

　●重みの初期値設定

　　ニューラルネットワークを最初学習させ始める時、重みは何かしらのルールに基づいて設定する。
　　多くの場合は、乱数を使用して、設定を行う。
　　乱数を使用して、初期値設定を行うときに、**Xavier**というもの等を用いることができる。

 ○Xavier
　　　重みの要素を、前の層のノード数の平方根で除算した値

　　　初期値を設定する際の活性化関数
　　　　・RELU関数
　　　　・シグモイド関数
　　　　・双曲線正接関数
　　　よくS字カーブを描く活性化関数に使用される

　　○He
　　　重みの要素を、前の層のノード数の平方根で除算した値に√２を掛け合わせた値
　
　　　初期値を設定する際の活性化関数
　　　　　・RELU関数

　　[確認テスト]
　　重みの初期値に０を設定すると、どのような問題が発生するか簡潔にまとめよ。

　　全ての重みの値が均一に更新されるため、多数の重みを持つ必要がなくなる。

　●バッチ正規化

　　バッチ正規化とは、ミニバッチ単位で、入力値のデータの偏りを抑制する手法

　　バッチ正規化の使い所
　　活性化関数に値を渡す前後に、バッチ正規化の処理を孕んだ層を加える

　　バッチ正規化層への入力値は、
　　$ u^{(l)} = w^{(l)} z^{(l-1)} + b^{(l)} $ または $z$ 
　
　　・数式

　　１.
 $$ \mu_t = \frac{1}{N_t} \sum_{i=1}^{N_t} \ x_{ni} $$

　　2.
 $$ \sigma_t^2 = \frac{1}{N_t} \sum_{i=1}^{N_t}  (x_{ni} - \mu_t)^2 $$

　　3.
 $$ \hat{x}_ {ni} = \frac{x_{ni} - \mu_t}{\sqrt{\sigma_t^2 + \theta}} $$

　　4.
 $$ y_{ni} = \gamma x_{ni} + \beta $$


　　　　$\mu_t$：ミニバッチt全体の平均

　　　　$\sigma_t^2$:ミニバッチt全体の標準偏差

　　　　$N_t$:ミニバッチのインデックス

　　　　$\hat{x}_{ni}$：０に値を近づける計算と正規化を施した値
　　　　
　　　　$\gamma$：スケーリングパラメータ

　　　　$\beta$：シフトパラメータ

　　　　$y_{ni}$：ミニバッチのインデックスとスケーリングの積にシフトを加算した値

<dt>1-4.実装</dt>

　●シグモイド関数-ガウス分布

```python
# データの読み込み
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)

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

network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='sigmoid', weight_init_std=0.01)

iters_num = 2000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10

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]

    # 勾配
    grad = network.gradient(x_batch, d_batch)
    
    for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
        network.params[key] -= learning_rate * grad[key]
    
    loss = network.loss(x_batch, d_batch)
    train_loss_list.append(loss)
    
    if (i + 1) % plot_interval == 0:
        accr_test = network.accuracy(x_test, d_test)
        accuracies_test.append(accr_test)        
        accr_train = network.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))
        

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

![sigmoid_gauss.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/a48437e9-2a13-e9ba-f0a4-a3490925f63d.png)


　　シグモイド関数とガウス分布を使用すると正解率は上がらず、
　　上手く学習ができていない。

　●RELU関数-ガウス分布

```python
# データの読み込み
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)

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

network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='relu', weight_init_std=0.01)

iters_num = 2000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10

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]

    # 勾配
    grad = network.gradient(x_batch, d_batch)
    
    for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
        network.params[key] -= learning_rate * grad[key]
    
    loss = network.loss(x_batch, d_batch)
    train_loss_list.append(loss)
    
    if (i + 1) % plot_interval == 0:
        accr_test = network.accuracy(x_test, d_test)
        accuracies_test.append(accr_test)        
        accr_train = network.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))
        
        
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()
```

![RELU_gauss.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/44752c2e-4fc4-95d1-feaf-9e6fc4692294.png)

　　RELU関数とガウス分布では500回までは全然、学習できていなかったが、
　　500回を超えてくると次第に、学習できていった。
　　シグモイド関数からRELU関数に変更しただけで、勾配消失問題が回避され、
　　学習ができることがわかる。

　●シグモイド関数-Xavier

```pyhton
# データの読み込み
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)

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

network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='sigmoid', weight_init_std='Xavier')

iters_num = 2000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10

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]

    # 勾配
    grad = network.gradient(x_batch, d_batch)
    
    for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
        network.params[key] -= learning_rate * grad[key]
    
    loss = network.loss(x_batch, d_batch)
    train_loss_list.append(loss)
    
    if (i + 1) % plot_interval == 0:
        accr_test = network.accuracy(x_test, d_test)
        accuracies_test.append(accr_test)        
        accr_train = network.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))
        

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

![sigmoid_Xavier.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/1f53d178-58ca-9949-04cb-a4191d2ea9d7.png)

　　次にシグモイド関数とXavierの組み合わせ、ガウス分布を使用した際よりも、
　　学習が行われている。

　　●RELU関数-He

```python
# データの読み込み
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)

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

network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='relu', weight_init_std='He')

iters_num = 2000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10

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]

    # 勾配
    grad = network.gradient(x_batch, d_batch)
    
    for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
        network.params[key] -= learning_rate * grad[key]
    
    loss = network.loss(x_batch, d_batch)
    train_loss_list.append(loss)
    
    if (i + 1) % plot_interval == 0:
        accr_test = network.accuracy(x_test, d_test)
        accuracies_test.append(accr_test)        
        accr_train = network.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))
        

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

![RELU_He.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/b2a75ead-d915-89f4-30b1-ba7bb37c3cda.png)


　　最後にRELU関数とHe関数では初めの方からいきなり正解率が上がっている。
　　４つの中では一番学習ができていて、効率も良い。

　　[確認テスト]

　　①.hidden_size_listを変更する。

　　コードは省略

　　　1.シグモイド関数-ガウス分布

![sigmoid_gauss2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/9a953ccf-ddbf-b64c-1fbb-974169603665.png)

　　　2.RELU関数-ガウス分布

![RELU_gauss2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/40e61c48-f34f-f5ae-8a28-569193c67199.png)

　　　3.シグモイド関数-Xavier

![sigmoid_Xavier2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/a18bce34-3d03-5ea8-23f3-38a5299c7408.png)

　　　4.RELU関数-He

![RELU_He2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/28e11d79-5e74-f5d5-0e82-65c34bf03f53.png)


　　１以外は、全体的に、正解率が上がるタイミングが早くなった。
　　

　②同じように「シグモイド関数とHe」、「RELU関数とXavier」の組み合わせを試す。

　　コード省略

　　１.シグモイド関数-He
![sigmoid-he.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/419daf9b-dff2-f254-a097-3baa3f12d03c.png)

　　２.RELU関数-Xavier
![relu_xavier.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/6fdcc3db-d3ff-c828-7254-55bc6e558657.png)

　　　どちらも同じように学習ができている。


　　バッチ正規化

```pyhon
import numpy as np
from collections import OrderedDict
from common import layers
from data.mnist import load_mnist
import matplotlib.pyplot as plt
from multi_layer_net import MultiLayerNet
from common import optimizer

# バッチ正則化 layer
class BatchNormalization:
    '''
    gamma: スケール係数
    beta: オフセット
    momentum: 慣性
    running_mean: テスト時に使用する平均
    running_var: テスト時に使用する分散
    '''
    def __init__(self, gamma, beta, momentum=0.9, running_mean=None, running_var=None):
        self.gamma = gamma
        self.beta = beta
        self.momentum = momentum
        self.input_shape = None

        self.running_mean = running_mean
        self.running_var = running_var  
        
        # backward時に使用する中間データ
        self.batch_size = None
        self.xc = None
        self.std = None
        self.dgamma = None
        self.dbeta = None

    def forward(self, x, train_flg=True):
        if self.running_mean is None:
            N, D = x.shape
            self.running_mean = np.zeros(D)
            self.running_var = np.zeros(D)
                        
        if train_flg:
            mu = x.mean(axis=0) # 平均
            xc = x - mu # xをセンタリング
            var = np.mean(xc**2, axis=0) # 分散
            std = np.sqrt(var + 10e-7) # スケーリング
            xn = xc / std
            
            self.batch_size = x.shape[0]
            self.xc = xc
            self.xn = xn
            self.std = std
            self.running_mean = self.momentum * self.running_mean + (1-self.momentum) * mu # 平均値の加重平均
            self.running_var = self.momentum * self.running_var + (1-self.momentum) * var #分散値の加重平均
        else:
            xc = x - self.running_mean
            xn = xc / ((np.sqrt(self.running_var + 10e-7)))
            
        out = self.gamma * xn + self.beta 
        
        return out

    def backward(self, dout):
        dbeta = dout.sum(axis=0)
        dgamma = np.sum(self.xn * dout, axis=0)
        dxn = self.gamma * dout
        dxc = dxn / self.std
        dstd = -np.sum((dxn * self.xc) / (self.std * self.std), axis=0)
        dvar = 0.5 * dstd / self.std
        dxc += (2.0 / self.batch_size) * self.xc * dvar
        dmu = np.sum(dxc, axis=0)
        dx = dxc - dmu / self.batch_size
        
        self.dgamma = dgamma
        self.dbeta = dbeta

        return dx    
```

![batch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/9b0d2d7b-e57d-5d58-7ba4-83e4a2890fc3.png)

　　[確認テスト]
 ![スクリーンショット 2021-07-17 7.18.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/58768776-3810-361c-070f-21d72dd0308e.png)

　　(2)
　　　解説
　　　　batch_sizeに一つのミニバッチの数が入力される。
　　　　Nには学習用の全てのデータが入る。
　　　　まずはiにミニバッチのスタートの数を入れる。
　　　　その後、i_endにミニバッチの最後の数を入れる。
　　　　data_xから定義したスタートとエンドまでをスライスで取り出す。

　
#2.学習率の最適化

<dt>2-1.学習率の復習</dt>
　●学習率の値が大きい場合
　　○最適値にいつまでも辿り着かず、発散してしまう。

　●学習率の値が小さい場合
　　○発散することはないが、小さすぎると収束までに時間がかかってしまう。
　　○大域局所最適値に収束しづらくなる。

　●初期の学習率設定方法の指針
　　○初期の学習率を大きく設定し、徐々に学習率を小さくしていく
　　○パラメータごとに学習率を可変させる
　　　⇨学習率最適化手法を利用して、学習率を最適化

<dt>2-1.モメンタム</dt>
　　●誤差をパラメータで微分したものと学習率の積を積を減算したあと、
　　　現在の重みに前回の重みを減算した値と慣性の積を加算する

　　●メリット
　　　○局所的最適解にはならず、大域的最適解となる。
　　　○谷間についてから最も低い位置(最適値)にいくまでの時間が早い

　　●数式

 ```math
  V_t = \mu V_{t-1} - \varepsilon \nabla E \\
  W^{(t+1)} = w^{(t)} + V_t \\
  慣性: \mu
 ```

　　●コード

```python
 self.v[key] = self.momentum* self.v[key] -self.learning_rate* grad[key]

 params[key] += self.v[key]
```

<dt>2-3.AdaGrad</dt>
　●誤差をパラメータで微分したものと再定義した学習率の積を減算する

　●メリット
　　○勾配の緩やかな斜面に対して、最適値に近づける

　●課題
　　○学習率が徐々に小さくなるので、鞍点問題を引き起こす事があった

　●数式

 ```math
  h_0 = \theta \\
  h_t = h_{t-1} + (\nabla E)^2 \\
  w^{(t+1)} = w^{(t)} - \varepsilon \frac{1}{\sqrt{h_t} + \theta} \nabla E
 ```

　　●コード

 ```python
  self.h[key] = np.zeros_like(val)S2)-247

  self.h[key] += grad[key] * grad[key]

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

 ```

<dt>2-4.RMSProp</dt>
　●誤差をパラメータで微分したものと再定義した学習率の積を減算する

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

　●数式

 ```math
  h_t = \alpha h_{t-1} + (1 - \alpha) (\nabla E)^2 \\
  w^{(t+1)} = w^{(t)} - \varepsilon \frac{1}{\sqrt{h_t} + \theta} \nabla E
 ```

　●コード

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


<dt>2-4.Adam</dt>

　●Adamとは
　　　・モメンタムの、過去の勾配の指数関数的減衰平均
　　　・RMSPropの、過去の勾配の2乗の指数関数的減衰平均
　　上記をそれぞれ孕んだ最適化アルゴリズムである。

　●メリット
　　○モメンタムおよびRMSPropのメリットを孕んだアルゴリズムである

　[確認テスト]
　　　・モメンタム、AdaGrad、RMSpropの特徴をそれぞれ簡潔に説明せよ。

　　　モメンタム：大域的最適解となる
　　　AdaGrad：勾配の緩やかな斜面に対して、最適値に近づける
　　　RMSprop：ハイパーパラメータの調整が必要な場合が少ない

<dt>2-5.実装</dt>

　　●SGD

```python
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
from collections import OrderedDict
from common import layers
from data.mnist import load_mnist
import matplotlib.pyplot as plt
from multi_layer_net import MultiLayerNet


# データの読み込み
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)

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

# batch_normalizationの設定 ================================
# use_batchnorm = True
use_batchnorm = False
# ====================================================


network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='sigmoid', weight_init_std=0.01,
                       use_batchnorm=use_batchnorm)

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.01

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10

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]

    # 勾配
    grad = network.gradient(x_batch, d_batch)
    
    for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
        network.params[key] -= learning_rate * grad[key]
        
        loss = network.loss(x_batch, d_batch)
        train_loss_list.append(loss)
    
    
    if (i + 1) % plot_interval == 0:
        accr_test = network.accuracy(x_test, d_test)
        accuracies_test.append(accr_test)        
        accr_train = network.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))

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

![SGD.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/9985ab5b-73a4-a5ce-826c-bb22d324921c.png)

　　SGDではうまく学習ができていないことがわかる。

　●モメンタム

```python

# データの読み込み
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)

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

# batch_normalizationの設定 ================================
# use_batchnorm = True
use_batchnorm = False
# ====================================================

network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='sigmoid', weight_init_std=0.01,
                       use_batchnorm=use_batchnorm)

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.01
# 慣性
momentum = 0.9

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10

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]

    # 勾配
    grad = network.gradient(x_batch, d_batch)
    if i == 0:
        v = {}
    for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
        if i == 0:
            v[key] = np.zeros_like(network.params[key])
        v[key] = momentum * v[key] - learning_rate * grad[key]
        network.params[key] += v[key]

        loss = network.loss(x_batch, d_batch)
        train_loss_list.append(loss)
        
    if (i + 1) % plot_interval == 0:
        accr_test = network.accuracy(x_test, d_test)
        accuracies_test.append(accr_test)        
        accr_train = network.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))
        
        
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()
```

![momentam.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/2f225797-5bb4-6a98-c597-0892dbd47a25.png)

　今回のデータではモメンタムでもうまく学習ができていない。


　●AdaGrad

```python
# AdaGradを作ってみよう
# データの読み込み
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)

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

# batch_normalizationの設定 ================================
# use_batchnorm = True
use_batchnorm = False
# ====================================================

network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='sigmoid', weight_init_std=0.01,
                       use_batchnorm=use_batchnorm)

iters_num = 1000
# iters_num = 500 # 処理を短縮

train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.01

# AdaGradでは不必要
# =============================

momentum = 0.9 

# =============================

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10

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]

    # 勾配
    grad = network.gradient(x_batch, d_batch)
    if i == 0:
        h = {}
    for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
            
        # 変更しよう  
        # ===========================================
        if i == 0:
            h[key] = np.zeros_like(network.params[key])        
        h[key] = momentum * h[key] - learning_rate * grad[key]
        network.params[key] += h[key]

        # ===========================================
        
        loss = network.loss(x_batch, d_batch)
        train_loss_list.append(loss)
        
    if (i + 1) % plot_interval == 0:
        accr_test = network.accuracy(x_test, d_test)
        accuracies_test.append(accr_test)        
        accr_train = network.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))
        
        
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()
```

![AdaGrad.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/4b59c568-109d-615e-21dd-c811fa93430e.png)


　●RMSProp

```python

# データの読み込み
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)

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

# batch_normalizationの設定 ================================
# use_batchnorm = True
use_batchnorm = False
# ====================================================

network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='sigmoid', weight_init_std=0.01,
                       use_batchnorm=use_batchnorm)

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.01
decay_rate = 0.99

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10

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]

    # 勾配
    grad = network.gradient(x_batch, d_batch)
    if i == 0:
        h = {}
    for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
        if i == 0:
            h[key] = np.zeros_like(network.params[key])
        h[key] *= decay_rate
        h[key] += (1 - decay_rate) * np.square(grad[key])
        network.params[key] -= learning_rate * grad[key] / (np.sqrt(h[key]) + 1e-7)

        loss = network.loss(x_batch, d_batch)
        train_loss_list.append(loss)                
        
    if (i + 1) % plot_interval == 0:
        accr_test = network.accuracy(x_test, d_test)
        accuracies_test.append(accr_test)        
        accr_train = network.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))
        
        
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()
```

![RMSProp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/d5820f67-5677-d03d-6b54-a38da124bc26.png)

　　RMSPropではうまく学習できることがわかる。


　●Adam

```python

# データの読み込み
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)

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

# batch_normalizationの設定 ================================
# use_batchnorm = True
use_batchnorm = False
# ====================================================

network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='sigmoid', weight_init_std=0.01,
                       use_batchnorm=use_batchnorm)

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.01
beta1 = 0.9
beta2 = 0.999

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10

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]

    # 勾配
    grad = network.gradient(x_batch, d_batch)
    if i == 0:
        m = {}
        v = {}
    learning_rate_t  = learning_rate * np.sqrt(1.0 - beta2 ** (i + 1)) / (1.0 - beta1 ** (i + 1))    
    for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
        if i == 0:
            m[key] = np.zeros_like(network.params[key])
            v[key] = np.zeros_like(network.params[key])
            
        m[key] += (1 - beta1) * (grad[key] - m[key])
        v[key] += (1 - beta2) * (grad[key] ** 2 - v[key])            
        network.params[key] -= learning_rate_t * m[key] / (np.sqrt(v[key]) + 1e-7)                
        
        
    if (i + 1) % plot_interval == 0:
        accr_test = network.accuracy(x_test, d_test)
        accuracies_test.append(accr_test)        
        accr_train = network.accuracy(x_batch, d_batch)
        accuracies_train.append(accr_train)
        loss = network.loss(x_batch, d_batch)
        train_loss_list.append(loss)        
        
        print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))
                

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()
```
![Adam.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/d3ea7df2-78cb-ff8b-a16f-1077ebe68d1b.png)


#3.過学習
<dt>3-1.過学習の復習</dt>　　
　●訓練誤差だけ小さくなり、テスト誤差が小さくならい現象

![スクリーンショット 2021-07-17 9.05.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/abd0da88-9bd7-f452-508b-336332ce6fe5.png)

　●発生する原因
　　○パラメータの数が多い
　　○パラメータの値が適切ではない
　　○ノードが多い
　　など。。。
　　ネットワークの自由度が高すぎることが原因となる。

<dt>3-2.正則化</dt>
　●ネットワークの自由度(層数、ノード数、パラメータの値etc...)を制約すること
　
　●Weight decay(荷重減衰)
　　過学習の原因
　　　○重みが大きい値をとることで、過学習が発生することがある。
　　　　⇨学習させていくと、重みにばらつきが発生する
　　　　　　重みが大きい値は、学習において重要な値であり、重みが大きいと過学習が起こる

　過学習の解決策
　　○誤差に対して、正則化項を加算することで、重みを抑制する
　　　⇨過学習がおこりそうな重みの大きさ以下で重みをコントロールし、
　　　　　かつ重みの大きさにばらつきを出す必要がある。

　●L1正則化、L2正則化

　　　○数式

 ```math
   E_n(w) + \frac{1}{p} \lambda || x ||_p \\
 
   || x ||_p = (|x_1|^p + \cdots + |x_n|^p)^{\frac{1}{p}}
 ```

　　　1行目では、誤差関数にpノルムを加える。
　　　２行目では、pノルムの計算を行う。

　　　p=1の場合、L1正則化と呼ぶ。（別名：ラッソ回帰)
　　　p=2の場合、L2正則化と呼ぶ。(別名：リッジ回帰)

　　ノルムとは距離を表しており図で書くと以下のようなイメージとなる。

![スクリーンショット 2021-07-17 9.27.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/ce95c12c-5126-3160-cf03-1dcae104c037.png)

 距離の求め方にも色々とある。
　　P1ノルムの距離のことをマンハッタン距離という
　　P２ノルムの距離のことをユークリッド距離という

　　○コード

```python
 np.sum(np.abs(network.params['W' + str(idx)]))

 weight_decay+= weight_decay_lambda*np.sum(np.abs(network.params['W' + str(idx)]))
 
 loss = network.loss(x_batch, d_batch) + weight_decayS3)-163
```

　　[確認テスト]
　　　下図について、L1正則化を表しているグラフはどちらか答えよ

![スクリーンショット 2021-07-17 9.49.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/2cf1be7b-cfe8-f163-d14c-7968556f4e34.png)

　　　右図がL1正則化である。
　　　右図（L1正則化）では、最終的な重みは縦軸上に目指していく。
　　　そのため、1つの重み（横軸の重み）を０のポイントになるように探し出してくれる。


![スクリーンショット 2021-07-17 15.14.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/b3e76fa8-ff18-f090-b6f9-ecb9a67e9c4a.png)

　　（え）　（４）param
　　解説
　　　L２ノルムは、$||p||^2$なので、その勾配が誤差の勾配に加えられる。
　　　つまり、2*param　であるが、係数2は正則化の係数に吸収されても変わらないのでparamが正解である。


![スクリーンショット 2021-07-17 15.25.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/c6bc8e68-10a5-12e0-5780-90f6c2e28219.png)

　　（お）　（３）np.sign(param)
　　解説
　　L1ノルムは、|param|なのでその勾配が誤差の勾配に加えられる。
　　つまり、sign(param)である。
　　signは＋の値だったら1、ーの値だったら−1、0だったら0を出力する符号関数である。


![スクリーンショット 2021-07-17 15.29.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/c21a3c86-bb98-0dfc-3304-10647600b7e2.png)

　　（か）　（４)image[top:bottom, left:right, :] 
　　　解説
　　　　imageの形式が(縦幅, 横幅, チャンネル)であるのも考慮する。

<dt>3-3.ドロップアウト</dt>
　●過学習の課題
　　○ノードの数が多い

　●ドロップアウト
　　○ランダムにノードを削除して、学習させること

　　![スクリーンショット 2021-07-17 15.36.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/1f91a6a3-8c38-8df3-1e07-7426385b6b0e.png)


　●メリット
　　　○データ量を変化させずに、異なるモデルを学習させていると解釈できる

<dt>3-4.実装</dt>

　●正則化やドロップアウトを行っていない状態（過学習を起こしている場合)

```python
import numpy as np
from collections import OrderedDict
from common import layers
from data.mnist import load_mnist
import matplotlib.pyplot as plt
from multi_layer_net import MultiLayerNet
from common import optimizer


(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True)

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

# 過学習を再現するために、学習データを削減
x_train = x_train[:300]
d_train = d_train[:300]

network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10)
optimizer = optimizer.SGD(learning_rate=0.01)

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10


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]

    grad = network.gradient(x_batch, d_batch)
    optimizer.update(network.params, grad)

    loss = network.loss(x_batch, d_batch)
    train_loss_list.append(loss)
        
    if (i+1) % plot_interval == 0:
        accr_train = network.accuracy(x_train, d_train)
        accr_test = network.accuracy(x_test, d_test)
        accuracies_train.append(accr_train)
        accuracies_test.append(accr_test)

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

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

![overfitting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/50b6fcbc-dda3-da5b-c005-bcc153499414.png)

  学習の途中から、trainig setに対しては、正解率が１００％となっている。
　　　　しかし、test set　では途中から学習結果があまり変わらず、過学習を起こしているとわかる。


　●L2正則化

```python
from common import optimizer

(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True)

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

# 過学習を再現するために、学習データを削減
x_train = x_train[:300]
d_train = d_train[:300]


network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10)


iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
learning_rate=0.01

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10
hidden_layer_num = network.hidden_layer_num

# 正則化強度設定 ======================================
weight_decay_lambda = 0.1
# =================================================

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]

    grad = network.gradient(x_batch, d_batch)
    weight_decay = 0
    
    for idx in range(1, hidden_layer_num+1):
        grad['W' + str(idx)] = network.layers['Affine' + str(idx)].dW + weight_decay_lambda * network.params['W' + str(idx)]
        grad['b' + str(idx)] = network.layers['Affine' + str(idx)].db
        network.params['W' + str(idx)] -= learning_rate * grad['W' + str(idx)]
        network.params['b' + str(idx)] -= learning_rate * grad['b' + str(idx)]        
        weight_decay += 0.5 * weight_decay_lambda * np.sqrt(np.sum(network.params['W' + str(idx)] ** 2))

    loss = network.loss(x_batch, d_batch) + weight_decay
    train_loss_list.append(loss)        
        
    if (i+1) % plot_interval == 0:
        accr_train = network.accuracy(x_train, d_train)
        accr_test = network.accuracy(x_test, d_test)
        accuracies_train.append(accr_train)
        accuracies_test.append(accr_test)
        
        print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))               


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

![L2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/d863912e-68d4-4113-1e5c-d2e9c3bcafe7.png)

　　training setの正解率が100%にならなくなった。
　　L2正則化を加えたことによって、一番そこに辿り着けなくなったことにより、
　　training setに対しても、100%にならなくなった。
　　しかしこれも、training set と　test set でまだ離れているので、
　　期待したものとは結果が変わってきている。

　●L1正則化

```python
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True)

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

# 過学習を再現するために、学習データを削減
x_train = x_train[:300]
d_train = d_train[:300]

network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10)


iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
learning_rate=0.1

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10
hidden_layer_num = network.hidden_layer_num

# 正則化強度設定 ======================================
weight_decay_lambda = 0.005
# =================================================

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]

    grad = network.gradient(x_batch, d_batch)
    weight_decay = 0
    
    for idx in range(1, hidden_layer_num+1):
        grad['W' + str(idx)] = network.layers['Affine' + str(idx)].dW + weight_decay_lambda * np.sign(network.params['W' + str(idx)])
        grad['b' + str(idx)] = network.layers['Affine' + str(idx)].db
        network.params['W' + str(idx)] -= learning_rate * grad['W' + str(idx)]
        network.params['b' + str(idx)] -= learning_rate * grad['b' + str(idx)]        
        weight_decay += weight_decay_lambda * np.sum(np.abs(network.params['W' + str(idx)]))

    loss = network.loss(x_batch, d_batch) + weight_decay
    train_loss_list.append(loss)        
        
    if (i+1) % plot_interval == 0:
        accr_train = network.accuracy(x_train, d_train)
        accr_test = network.accuracy(x_test, d_test)
        accuracies_train.append(accr_train)
        accuracies_test.append(accr_test)
        
        print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))               
                
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()
```

![L!.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/88d13415-95dc-460b-384f-3ce93b5d0b5d.png)


　　こちらについても、L２正則化と同様にtraning set　は１００%にならなくなっている。
　　また、traing set とtest setとの差もあまり変わらない結果となった。


　●ドロップアウト
```python
class Dropout:
    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio
        self.mask = None

    def forward(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)

    def backward(self, dout):
        return dout * self.mask
```

```python

from common import optimizer
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True)

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

# 過学習を再現するために、学習データを削減
x_train = x_train[:300]
d_train = d_train[:300]

# ドロップアウト設定 ======================================
use_dropout = True
dropout_ratio = 0.15
# ====================================================

network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10,
                        weight_decay_lambda=weight_decay_lambda, use_dropout = use_dropout, dropout_ratio = dropout_ratio)
optimizer = optimizer.SGD(learning_rate=0.01)
# optimizer = optimizer.Momentum(learning_rate=0.01, momentum=0.9)
# optimizer = optimizer.AdaGrad(learning_rate=0.01)
# optimizer = optimizer.Adam()

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100

train_loss_list = []
accuracies_train = []
accuracies_test = []

plot_interval=10


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]

    grad = network.gradient(x_batch, d_batch)
    optimizer.update(network.params, grad)

    loss = network.loss(x_batch, d_batch)
    train_loss_list.append(loss)    
    
    if (i+1) % plot_interval == 0:
        accr_train = network.accuracy(x_train, d_train)
        accr_test = network.accuracy(x_test, d_test)
        accuracies_train.append(accr_train)
        accuracies_test.append(accr_test)

        print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))        
        
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()
```

![dropout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/3c0da28d-c187-45b5-b9b7-2d0b4ea34c18.png)

　　ドロップアウトでは、他の２つに比べ、順調に学習ができている。


　●ドロップアウト　＋　L１

```python

from common import optimizer
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True)

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

# 過学習を再現するために、学習データを削減
x_train = x_train[:300]
d_train = d_train[:300]

# ドロップアウト設定 ======================================
use_dropout = True
dropout_ratio = 0.08
# ====================================================

network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10,
                        use_dropout = use_dropout, dropout_ratio = dropout_ratio)

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
learning_rate=0.01

train_loss_list = []
accuracies_train = []
accuracies_test = []
hidden_layer_num = network.hidden_layer_num

plot_interval=10

# 正則化強度設定 ======================================
weight_decay_lambda=0.004
# =================================================

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]

    grad = network.gradient(x_batch, d_batch)
    weight_decay = 0
    
    for idx in range(1, hidden_layer_num+1):
        grad['W' + str(idx)] = network.layers['Affine' + str(idx)].dW + weight_decay_lambda * np.sign(network.params['W' + str(idx)])
        grad['b' + str(idx)] = network.layers['Affine' + str(idx)].db
        network.params['W' + str(idx)] -= learning_rate * grad['W' + str(idx)]
        network.params['b' + str(idx)] -= learning_rate * grad['b' + str(idx)]        
        weight_decay += weight_decay_lambda * np.sum(np.abs(network.params['W' + str(idx)]))

    loss = network.loss(x_batch, d_batch) + weight_decay
    train_loss_list.append(loss)        
        
    if (i+1) % plot_interval == 0:
        accr_train = network.accuracy(x_train, d_train)
        accr_test = network.accuracy(x_test, d_test)
        accuracies_train.append(accr_train)
        accuracies_test.append(accr_test)
        
        print('Generation: ' + str(i+1) + '. 正答率(トレーニング) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. 正答率(テスト) = ' + str(accr_test))               
        
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()
```

![dropout+L!.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/64d4ca60-d48d-342d-4de1-824ddeb283e9.png)

  ドロップアウトとL1正則化を合わせることによって、ドロップアウト単体の時より、
　　　学習のスピードが上がったことがわかる。


#4 畳み込みニューラルネットワーク（CNN）
<dt>4-1.CNNとは</dt>
　●画像の識別や、画像の処理によく用いられる
　　　しかしながら、汎用性も高く画像の識別以外にも使用される。
　　　CNNでは、次元間で、繋がりのあるデータを扱うことができる。
　　　例えば、
　　　　○単一チャンネル
　　　　　・１次元：音声[時刻、**強度**]
　　　　　・２次元：フーリエ変換した音声[時刻、周波数、**強度**]
　　　　　・３次元：CTスキャン[x,y,z,**強度**]
　　　　○複数チャンネル
　　　　　・１次元：アニメのスケルトン[時刻,**(腕の値、膝の値)**]
　　　　　・２次元：カラー画像[x,y,**(R,G,B)**]
　　　　　・３次元:動画[時刻,x,y,**(R,G,B)**]
　
　　　単一チャンネルでは、音声で例えると、ある時刻に対して、
　　　どれだけの空気振動を起こしたかの情報しかない。
　　　一方、複数チャンネルでは、カラー画像で例えると、
　　　あるx軸、y軸の場所では、Rがどれくらい、Gがどれくらい、Bがどれくらいという
　　　２次元のデータに対して、複数（今回は３つ）の情報がある。


<dt>4-2.畳み込み層の構造図</dt>

![スクリーンショット 2021-07-17 16.27.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/8e04aeb4-2b97-c4dd-7797-6cd4e4fc42b6.png)


　　CNNを使った代表的なものにLeNetというものがある。

![スクリーンショット 2021-07-17 16.29.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/d6a18e17-bba4-72b6-ccc6-2275f37e0b29.png)

　　LeNetでは、入力の画像データは32×３２pxの画像である。
　　最終的に、１０種類の値を出力する。（例えば入力された画像は0~9のどの数字ですか?など)
　　入力から出力までの間に、畳み込み層やプーリング層、全結合層の処理を行う。
　　次から一つ一つの層の説明を行っていく。

<dt>4-3.全体像</dt>

![スクリーンショット 2021-07-17 16.43.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/9f4f54c8-8d42-667b-887c-9b5126c25a43.png)

 　上の画像では、４×４×３の情報が入力として渡される。
　　　イメージとしては、4px×4pxのカラー画像で、RGBの３チャンネルを持っている。

　　　畳み込み層では、入力値に対して、フィルターを通して、出力結果を出す。

　　　![スクリーンショット 2021-07-17 16.55.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/e31fba95-d8e9-642f-8424-9896b6651dfa.png)

　　　画像では様々な色を使ってごちゃごちゃしてるが、
　　　要は、3×3のフィルターを左上、右上、左下、右下にあてて、それぞれ数値を出し、
　　　移動分(今回は2×2）の出力を行う。
　　
　　　フィルターを通して新たな出力を得ているが、フィルターの当て方を見ると
　　　周りのデータを取得していると言える。
　　　そのため、周りの情報と繋がりを保ったまま、
　　　情報を処理できることが、CNNの特徴と言える。

![スクリーンショット 2021-07-17 17.06.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/40ce236c-2166-1385-90cc-eab3b0d2349d.png)

　　全結合層というのは、今までの学習で出てきたニューラルネットワークのことである。
　　畳み込み層との違いとしては、畳み込み層は次元の繋がりを保ったままの情報を出力していく。
　　それに対し、全結合層では、次元の繋がりをまとめて、本来ほしかった情報に出力するようになる。

<dt>4-4.畳み込み層</dt>
　●畳み込み層では、縦、横、チャンネルの３次元のデータをそのまま学習し、
　　　次に伝えることができる。
　　　⇨次元の空間情報も学習できるような層が畳み込み層である。

　●演算概念

![スクリーンショット 2021-07-17 17.12.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/c2788fcd-7e12-6ce1-7ae0-d8d76494ed87.png)

　　畳み込み層ではフィルターを使うがニューラルネットワークで学習した、重みと考えて良い。
　　初期の状態では、乱数を入れる。
　　計算も簡単で、入力画像とフィルターの対応する所の積をとり、
　　１つにまとめることで次の層への出力となる。


![スクリーンショット 2021-07-17 17.21.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/2ce465d4-98b6-31e4-6c92-20e7543f1490.png)

　　先程の画像では、入力画像にフィルターを当てただけなので、
　　ニューラルネットワークでいう所の、x(入力） w（重み）を行ったことになる。
　　ニューラルネットワークではもう一つ　＋b というものがあった。
　　バイアスを足してあげるという処理だが、畳み込み層でも同じようにできる。

　●パディング
　　先程の演算では４×４の入力画像を、２×２の出力画像に変換した。
　　仮にフィルターを２×２のフィルターにしても、出力画像は3×３となり、
　　画像が小さくなってしまう。
　　もし畳み込み層を大量に組み込んだニューラルネットワークがあれば、
　　画像はどんどん小さくなりすぎてしまう。

　　ここで登場するのが、パディングである。

![スクリーンショット 2021-07-17 17.31.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/446c276d-5860-5c0c-ab3d-8b855c46f903.png)

　　パディングでは、縦のサイズ、横のサイズをあらかじめ、広げて、フィルターを通す。
　　そうすることで、出力画像は大きさを変えずに出力することができるようになる。
　　広げた場所の入力値については、全て０で埋めたり、一番近い所の数字を埋めたりできる。


　●ストライド
　　先程までは、フィルターを１つずつ動かしていた。
　　これをストライド１という、ストライドを２に変えると、
　　フィルターは２つずつ動く事になる。

　　![スクリーンショット 2021-07-17 17.35.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/4e5f7143-915f-23fb-10b1-d719a5f310c0.png)


●チャンネル
　チャンネルはフィルターの数を意味する。
　イメージとしてはチャンネルを増やすことによって、様々な見方ができるようになる。

![スクリーンショット 2021-07-17 17.38.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/c6902a07-5498-a965-d283-add4d3735b81.png)


　●畳み込み層ができた理由
　　全結合層では、例えば画像の場合、縦、横、チャンネルの3次元データだが、
　　1次元のデータとして処理される。
　　せっかくRGBという各チャンネルの繋がりがあったのに、
　　この繋がりが学習に反映されないため、畳み込み層ができた。


　●実装
　　○畳み込み層

```python
class Convolution:
    # W: フィルター, b: バイアス
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad
        
        # 中間データ（backward時に使用）
        self.x = None   
        self.col = None
        self.col_W = None
        
        # フィルター・バイアスパラメータの勾配
        self.dW = None
        self.db = None

    def forward(self, x):
        # FN: filter_number, C: channel, FH: filter_height, FW: filter_width
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        # 出力値のheight, width
        out_h = 1 + int((H + 2 * self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2 * self.pad - FW) / self.stride)
        
        # xを行列に変換
        col = im2col(x, FH, FW, self.stride, self.pad)
        # フィルターをxに合わせた行列に変換
        col_W = self.W.reshape(FN, -1).T

        out = np.dot(col, col_W) + self.b
        # 計算のために変えた形式を戻す
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)

        self.x = x
        self.col = col
        self.col_W = col_W

        return out

    def backward(self, dout):
        FN, C, FH, FW = self.W.shape
        dout = dout.transpose(0, 2, 3, 1).reshape(-1, FN)

        self.db = np.sum(dout, axis=0)
        self.dW = np.dot(self.col.T, dout)
        self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)

        dcol = np.dot(dout, self.col_W.T)
        # dcolを画像データに変換
        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)

        return dx
```

　畳み込み層ではデータを高速で計算するためにある工夫が行われる。
　それが、column to image (以下、col2im)と呼ばれるものになる。

```python
# ２次元配列を画像データに変換
def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
    # N: number, C: channel, H: height, W: width
    N, C, H, W = input_shape
    # 切り捨て除算    
    out_h = (H + 2 * pad - filter_h)//stride + 1
    out_w = (W + 2 * pad - filter_w)//stride + 1
    col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2) # (N, filter_h, filter_w, out_h, out_w, C)

    img = np.zeros((N, C, H + 2 * pad + stride - 1, W + 2 * pad + stride - 1))
    for y in range(filter_h):
        y_max = y + stride * out_h
        for x in range(filter_w):
            x_max = x + stride * out_w
            img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]

    return img[:, :, pad:H + pad, pad:W + pad]
```

　　　col2imでは畳み込み演算を行う際に、フィルターを通す箇所の抽出結果を
　　　縦に並べることによって、後の重みを掛けたりする計算を早くする。

　　　例）

 ```math
  \begin{array}{llll}
   [ 1 ,& 2 ,& 3 ,& 4] \\
   [ 5 ,& 6 ,& 7 ,& 8] \\
   [ 9 ,& 10 ,& 11 ,& 12] \\
   [ 13 ,& 14 ,& 15 ,& 16] \\ 
  \end{array}
 ```

 上のような画像に２×２のフィルターを通した場合、
　　例えば、左上を抽出すると、[1, 2, 5, 6]になる。
　　この[1, 2, 5, 6]を縦にまとめる処理を同じように他のところでも行うと、

 ```math
  \begin{array}{lllllllll}
   [1,& 2,& 3,& 5,& 6,& 7,& 9,& 10,& 11]\\
   [2,& 3,& 4,& 6,& 7,& 8,& 10,& 11,& 12]\\
   [5,& 6,& 7,& 9,& 10,& 11,& 13,& 14,& 15]\\
   [6,& 7,& 8,& 10,& 11,& 12,& 14,& 15,& 16]\\
  \end{array}
 ```

　　となり、後に計算が高速にできるようになる。

<dt>4-5.プーリング層</dt>
　●考え方としては、畳み込み層と同じように画像を少しずつずらしながら処理を行う。
　　　しかし、畳み込み層と違い、プーリング層には重みが存在しない。
　　　対象領域のMax値または、平均値を取得していく。

![スクリーンショット 2021-07-17 18.18.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/97626f01-7bbe-b898-4a49-836c996cca50.png)

　上の画像では、Max poolingを使用している。
　Max poolingでは対象領域の最大値（今回は９）を出力とする。


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

　7×7

　公式

```math
  OH = \frac{画像の高さ + 2 \times パディングの高さ　+ フィルターの高さ}{ストライド} + 1 \\

  \\
 
  OW = \frac{画像の幅 + 2 \times パディングの幅 + フィルターの幅}{ストライド} + 1 \\
```

　●実装
　　○プーリング層

```python
class Pooling:
    def __init__(self, pool_h, pool_w, stride=1, pad=0):
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad
        
        self.x = None
        self.arg_max = None

    def forward(self, x):
        N, C, H, W = x.shape
        out_h = int(1 + (H - self.pool_h) / self.stride)
        out_w = int(1 + (W - self.pool_w) / self.stride)
        
        # xを行列に変換
        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        # プーリングのサイズに合わせてリサイズ
        col = col.reshape(-1, self.pool_h*self.pool_w)
        
        #maxプーリング
        arg_max = np.argmax(col, axis=1)
        out = np.max(col, axis=1)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)

        self.x = x
        self.arg_max = arg_max

        return out

    def backward(self, dout):
        dout = dout.transpose(0, 2, 3, 1)
        
        pool_size = self.pool_h * self.pool_w
        dmax = np.zeros((dout.size, pool_size))
        dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
        dmax = dmax.reshape(dout.shape + (pool_size,)) 
        
        dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
        dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)
        
        return dx
```

#5.AlexNet
<dt>5-1.モデルの構造
　●５層の畳み込み層及びプーリング層など、それに続く３層の全結合層で構成される。

![スクリーンショット 2021-07-17 18.34.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221503/53384643-a14a-9d95-e4e9-dbf76ad41ef6.png)

<dt>5-2.処理のながれ</dt>

　　１.入力には224×224の画像を用いる。
　　　　　　チャンネルは３
　　2.入力された画像を11×11のフィルターで畳み込みを行う
　　　　　　この際、チャンネルは９６個にする。
　　3.55×５５の画像をmax poolingで縮める。
　　　　　この際、チャンネルは256個
　　4.27×２７の画像を3×3のフィルターで畳み込む
　　　　　この際、チャンネルは３８４個
　　５.最後に、また3×３のフィルターで畳み込む
　　　　　この際、画像の大きさが変わっていないので、パディングを行なっていると考えられる。
　　６.最終的に256種類の13×13のデータに圧縮し、全結合層へ渡す。
　　　　　　データの渡し方には様々あるが、代表的なFrattenというものは
　　　　　　13×13×256のデータを横一列にまとめて渡すという処理を行う。
　　　　　　その他にも、「Global Max pooling」 「Global Avg pooling」 などがある。
　　７.受け取ったデータを4096個の数字に出力する。
　　８.最後に１000個の数字を出力する。

 AlexNetでは、サイズ4096の全結合層では、過学習を防ぐため、
　　ドロップアウトを行っている。