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

参考図書
これなら分かる深層学習入門(講談社)
深層学習(講談社) 
これ1冊で最短合格ディープラーニングG検定ジェネラリスト要点整理&問題集(秀和システム)

(1)入力層,中間層

入力層は、説明変数(入力)$\boldsymbol{x}$が入る層である。

中間層(隠れ層)の出力$f(\boldsymbol{x})$は、入力層の出力に重み$\boldsymbol{W}$を掛け合わせ、バイアス$\boldsymbol{b}$を足して活性化関数$f$により変換され、次の層へと情報が伝わる。
$$f(\boldsymbol{x})=\boldsymbol{w}^T\boldsymbol{x}+\boldsymbol{b}$$

(2)NN実装演習

ノード数4の入力層から、3つの中間層への出力を実装する。
式$f(\boldsymbol{x})=\boldsymbol{w}^T\boldsymbol{x}+\boldsymbol{b}$が増えた様なもので、ベクトル$\boldsymbol{w}$を行列$\boldsymbol{W}$に変換する。

```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_vec("重み", W)

b = np.array([0.1, 0.2, 0.3])
print_vec("バイアス", b)

x = np.array([1.0, 5.0, 2.0, -1.0])
print_vec("入力", x)

u = np.dot(x, W) + b
print_vec("総入力", u)
```
結果
![day1_sec1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/91427083-9a2c-a0e5-1195-199057a2c323.png)

(3)確認テスト

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

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

問題2
次のニューラルネットワークをかけ。
入力層：2ノード1層,中間層：3ノード2層,出力層：1ノード1層
![day1_確認テスト1-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/2764ba38-4ff2-20cc-9874-90a18bda2f1b.png)


(4)活性化関数

活性化関数は、入力の和$\boldsymbol{w}^T\boldsymbol{x}(+\boldsymbol{b})$を出力に変換する関数$f$のこと。

ニューラルネットワークでは、活性化関数に非線形関数を用いる。

(例)
シグモイド関数
$$\sigma(x)=\dfrac{1}{1+\mathrm{e}^{-ax}}$$

ステップ関数

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

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

(5)活性化関数実装演習
シグモイド

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

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

plt.scatter(x, y, marker='.')
plt.show()
```
![day1_sigmoid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/5abd831a-fc4b-31ce-8c41-e0223b5ad94e.png)

ReLU

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

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

plt.scatter(x, y, marker='.')
plt.show()
```
![day1_ReLU.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/14b1ce3f-1140-ce70-8df6-b1dd63eeeb89.png)

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

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

(7)出力層

誤差関数は、次の2つが使われることが多い。

二乗誤差
$$E_n(w)=\frac{1}{2}\sum_{i=1}^N(y_i-d_i)^2=\frac{1}{2}||(y-d)||^2$$

クロスエントロピー
$$E_n(w)=-\sum_{i=1}^Nd_i\log y_i$$

(8)活性化関数(出力)
出力層の活性化関数は、隠れ層の出力の大きさを保って変換する。ソフトマックス関数などが代表的である。
$$f(i, \boldsymbol{u})=\frac{e^{u_i}}{\sum^K_{k=1}e^{u_k}}$$
ソフトマックスは、$i$に関する和が1になることから、確率と解釈できる。

(9)誤差関数,活性化関数(出力)実装演習

クロスエントロピー

```python
def cross_entropy_error(d, y):
    if y.ndim == 1:
        d = d.reshape(1, d.size)
        y = y.reshape(1, y.size)
    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.Tx
     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で出力するn対1である点で他の1対1の活性化関数とは違いがある。

(10)確認テスト
問題1
(1)2乗誤差について、なぜ引き算ではなく2乗するのかを述べよ。
引き算のみで誤差を算出すると、目的である誤差が0のときよりも負の時の方が小さいと判断してしまうため。

(2)次式の$1/2$はどのような意味を持つか？
$$E_n(\boldsymbol{w})=\frac{1}{2}\sum_i(y_i-d_i)^2$$
次数の2と1/2が微分の際に消えるので計算コストを抑えられる。

(11)勾配降下法
NNの学習は、誤差$E(\boldsymbol{w})$を最小化することであるが、そのときのパラメタパラメータ$\boldsymbol{w}$の数が多く、解析的に求めるのは難しいので、勾配降下法で逐次的に解を求める。

(例)

勾配降下法
$$w^{(t+1)}=W^{(t)}-\epsilon\nabla E$$
最急降下法とも呼ばれ、基本的な勾配降下法の形である。全データで一発で更新を行う。

SGD
$$w^{(t+1)}=W^{(t)}-\epsilon\nabla E_n$$
前のものでは全データで更新していたところを確率的に選ばれたデータ1つだけで更新を行う。データが膨大なときでも計算コストを抑え、局所的最適解に収束するのを避けやすくする。また、データが増えてもそれらに対応できる、オンライン学習ができるなどの利点もある。

ミニバッチ勾配降下法
$$w^{(t+1)}=W^{(t)}-\epsilon\nabla E_t, E_t=\frac{1}{N_t}\sum_{n \in D_t}E_n$$
前2者の間をとり、いいとこ取りになったもので、計算資源を効率よく活用できる。

(11)勾配降下法実装演習
勾配降下法

```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] 
```
ここでのハイパーパラメータは学習率で、大きいほど収束は速く精度は悪い。Momentumなどでよりよい選択ができる。

(12)確認テスト
問題1
(1)下記勾配降下法に該当するソースコードを配布コードから抜き出せ。
$$w^{(t+1)}=W^{(t)}-\epsilon\nabla E$$

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

(2)下記勾配降下法に該当するソースコードを配布コードから抜き出せ。
$$\nabla E=\frac{\partial E}{\partial \boldsymbol{w}}=\left[\frac{\partial E}{\partial w_1}\cdots\frac{\partial E}{\partial w_M} \right]$$

```python
grad = backward(x, d, z1, y)
```

(13)BP

BP逆伝播法は計算した誤差を、出力から入力へ順に微分し、最低限の計算で効率よくパラメータの更新を解析的に行う方法である。


(14)BP実装演習
SGDにおいてBPを実装する。

```python
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()
    return network

def forward(network, x):    
    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    u1 = np.dot(x, W1) + b1
    z1 = functions.relu(u1)
    u2 = np.dot(z1, W2) + b2
    y = u2    
    return z1, y

def backward(x, d, z1, y):    

    grad = {}
    
    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']

    delta2 = functions.d_mean_squared_error(d, y)
    grad['b2'] = np.sum(delta2, axis=0)
    grad['W2'] = np.dot(z1.T, delta2)
    delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)

    delta1 = delta1[np.newaxis, :]
    grad['b1'] = np.sum(delta1, axis=0)
    x = x[np.newaxis, :]
    grad['W1'] = np.dot(x.T, delta1)
    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]['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()
```
横軸:エポック数,縦軸:損失
![day1_確率的勾配降下法.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/c96acbcd-e936-ca76-9cc8-6673b4cd180a.png)


(15)確認テスト
問題1
（1)下記式を表すソースコードを探せ。
$$\frac{\partial E}{\partial \boldsymbol{y}}$$

```python
delta2 = functions.d_mean_squared_error(d, y)
```
$$\frac{\partial E}{\partial \boldsymbol{y}}\frac{\partial \boldsymbol{y}}{\partial \boldsymbol{u}}$$

```python
delta2 = functions.d_mean_squared_error(d, y)
```
$$\frac{\partial E}{\partial \boldsymbol{y}}\frac{\partial \boldsymbol{y}}{\partial \boldsymbol{u}}\frac{\partial \boldsymbol{u}}{\partial w_{ji}^{(2)}}$$

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


(16)勾配消失問題

勾配消失問題は、逆伝播の過程で計算される積でシグモイドの微分値などの1未満0以上の数があることによって入力層までに勾配が0に近づいていってしまうという問題である。入力よりでパラメータが全く更新されないため、学習がうまくいかない。

(17)勾配消失実装演習

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

(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)
train_size = len(x_train)

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)
    grad['b3'] = np.sum(delta3, axis=0)
    grad['W3'] = np.dot(z2.T, delta3)
    delta2 = np.dot(delta3, W3.T) * hidden_d_f(z2)
    grad['b2'] = np.sum(delta2, axis=0)
    grad['W2'] = np.dot(z1.T, delta2)
    delta1 = np.dot(delta2, W2.T) * hidden_d_f(z1)
    grad['b1'] = np.sum(delta1, axis=0)
    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) + '. CR(tr) = ' + str(accr_train))
        print('                : ' + str(i+1) + '. CR(te) = ' + 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()
```
結果
シグモイド
![day2_勾配消失1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/cea88f98-85ef-17e7-4dbf-40bccf2da2b3.png)
ReLU
![day2_勾配消失2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/2e3d10d7-133b-48a8-b78a-66cc1d0273bb.png)

(18)確認テスト
問題1
(1)連鎖律の原理を使い、$z=t^2$、$t=x+y$の時の$dz/dx$を求めよ。

```math
\frac{dz}{dx}=\frac{dz}{dt}\frac{dt}{dz}=2t\times1=2(x+y)
```

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

(19)学習率最適化手法

モメンタムは、前回の重み更新量に従う量を更新に用いるものである。
ミニバッチ:$t-1$,$t$,更新量:$\Delta\boldsymbol{w}^{(t-1)}\equiv\boldsymbol{w}^{(t)}-\boldsymbol{w}^{(t-1)}$,$t$について、

```math
\begin{align}
\boldsymbol{w}^{(t+1)}&=\boldsymbol{w}^{(t)}-\epsilon\nabla E_t+\mu\Delta \boldsymbol{w}^{(t-1)}\\
\Delta\boldsymbol{w}^{(t+1)}&=\mu\Delta\boldsymbol{w}^{(t-1)}-\epsilon\nabla E_t
\end{align}
```
のように更新する。ここで、\mu$がハイパーパラメータとなる。

AdaGradは、学習係数をだんだん小さくしていくものである。学習係数は、パラメータごとに値を設定する。

```math
\begin{align}
-\dfrac{\epsilon}{\sqrt{\sum_{t'=1}^t g_{t',i}^2}}g_{t,i}
\end{align}
```
AdaGradは、始めの勾配が大きい重みの更新量がすぐに小さくなるという欠点がある。

RMSpropは、AdaGradで用いた二乗和ではなく、指数移動平均$v_{t,i}$によるRoot Mean Square(RMS)を用いている。

```math
\begin{align}
v_{t,i}=&\rho\, v_{t-1,i}+(1-\rho)(\nabla E(\boldsymbol{w}^{(t)})_i)^2\\
\boldsymbol{w}^{(t+1)}=&\boldsymbol{w}^{(t)}-\dfrac{\epsilon}{\sqrt{v_{t,i}+\eta}}\nabla E(\boldsymbol{w}^{(t)})_i
\end{align}
```

Adamは、勾配も指数移動平均による値に設定するものである。これはRMSpropの勾配に指数減衰を含んだモメンタムを用いたものと見ることが出来る。このとき、初期値は$m_{0,i}=v_{0,i}=0$としている。

```math
\begin{align}
m_{t,i}=&\rho_1\, m_{t-1,i}+(1-\rho_1)\nabla E(\boldsymbol{w}^{(t)})_i\\
v_{t,i}=&\rho_2\, v_{t-1,i}+(1-\rho_2)(\nabla E(\boldsymbol{w}^{(t)})_i)^2
\end{align}
```
バイアス補正して、

```math
\begin{align}
\hat{m}_{t,i}=\dfrac{m_{t,i}}{(1-\rho_1^t)},\hspace{3mm}\hat{v}_{t,i}=\dfrac{v_{t,i}}{(1-\rho_2^t)}
\end{align}
```

```math
\begin{align}
\boldsymbol{w}^{(t+1)}=&\boldsymbol{w}^{(t)}-\epsilon\dfrac{\hat{m}_{t,i}}{\sqrt{\hat{v}_{t,i}+\eta}}
\end{align}
```
Adamの論文では、パラメータの推奨値として、以下が述べられている。
$$\epsilon=0.001,\hspace{2mm}\rho_1=0.9,\hspace{2mm}\rho_2=0.999,\hspace{2mm}\eta=10^{-8}$$

(20)学習率調整実装演習

モメンタム
```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)
```

(21)確認テスト
問題1
(1)モメンタム・AdaGrad・RMSPropの特徴をそれぞれ簡潔に説明せよ。
モメンタム
学習が大域的最適解に収束し、目的関数の谷にたどり着くと速やかに収束する。

AdaGrad
勾配が緩やかなところで最適解に近づく。

RMSProp
大域的最適解に収束し、多くの場合、ハイパーパラメータが(実質)ない。



(22)過学習
過学習は、訓練誤差は十分小さいが、汎化誤差が大きい状態である。パラメータ(ノード)が過剰に設定されている、訓練データが少ない事などが原因である。

pノルムは、正則化に用いるノルムである。
$$||x||_p=\left(|x_1|^p+\cdots+|x_n|^p\right)^{1/p}$$

ドロップアウトは、確率的に選ばれたノードを学習中に削除し、表現力が低くなった状態で学習することで過学習を防ぐものである。

(23)過学習抑制実装演習
![day2_過学習のまとめ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/1a760c45-7bf0-a598-fe6b-19bf791aed77.png)
L1,L2正則化はそれぞれpノルムでp=1,2のときのノルムを用いて正則化を行うものである。


(24)確認テスト
問題1
(1)下図について、L1正則化を表しているグラフはどちらか？
![day2_L1L2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/db66c5e4-2885-55d8-804c-b2022d0ea9c6.png)
左



(25)CNN

CNNは、全結合層以外にカーネルを用いた畳み込み層とプーリング層を交互に繰り返す構造をもつNNで、画像認識を始めとした2次元データを処理するタスクに適したネットワークである。
![day2_CNN概要.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/e7f9e011-067e-f346-d0aa-097833fd6c7d.jpeg)

畳み込み演算は、画像とカーネルをそれぞれ行列と考えたときにカーネルをずらしながらアダマール積を取っていくような演算である。
$$3\times3+4\times1+4\times2+0\times8+8\times7+9\times5+0\times5+4\times4+3\times1=141$$

![day2_CNN概要2.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/9ca030f2-1546-e49d-c9f8-d88beabda27e.jpeg)

パディングは、画像の外側を適当な値で埋めて画像のサイズをかさ増しする操作である。

ストライドは、畳み込み演算をする際にカーネルを一回にずらす幅のことである

入力サイズ:$W\times H$,カーネルサイズ:$Kw\times Kh$,パディング幅:$p$,ストライド幅:$s$,出力サイズ$OW\times OH$
$$OW=\frac{W+2p−Kw}{s}+1$$
$$OH=\frac{H+2p−Kh}{s}+1$$

チャンネルは、各ピクセルが持つ次元である。

(26)CNN実装演習

```python
class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad

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

        self.dW = None
        self.db = None

    def forward(self, x):
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        out_h = 1 + int((H + 2 * self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2 * self.pad - FW) / self.stride)
        col = im2col(x, FH, FW, self.stride, self.pad)
        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)
        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)

        return dx
```

結果
![day2_CNNリザルト.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/2301ad0b-c5bd-9b36-6f30-e3b2492825c8.png)


(27)確認テスト
問題1
(1)サイズ$6\times6$の入力画像を、サイズ$2\times2$のフィルタで畳み込んだ時の出力画像のサイズを答えよ。なお、ストライドとパディングは1とする。
求める出力幅を$OW$、出力高を$OH$とすると、サイズを求める公式により以下のようになる。
$$OW=\frac{W+2p−Fw}{s}+1=\frac{6+2−2}{1}+1=7$$
$$OH=\frac{H+2p−Fh}{s}+1=\frac{6+2−2}{1}+1=7$$

(28)有名なCNN

(例)

AlexNet
![day2_AlexNet.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090373/777714b1-b392-7d50-fae9-005bbbd4df70.jpeg)
AlexNetは、畳み込みとプーリングが5回繰り返された後に全結合が3層ある。全結合層にドロップアウトが用いられているILSVRC12の優勝モデルである。

(29)AlexNet実装演習

```python
import torch
import torchvision
import torch.nn as nn
import torch.nn.init as init
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import numpy as np
from matplotlib import pyplot as plt
 
train_dataset = torchvision.datasets.CIFAR10(
      root='./data/',
      train=True, 
      transform=transforms.ToTensor(),
      download=True)
 
test_dataset = torchvision.datasets.CIFAR10(
      root='./data/',
      train=False, 
      transform=transforms.ToTensor(),
      download=True)
 
print ('train_dataset = ', len(train_dataset))
print ('test_dataset = ', len(test_dataset))
image, label = train_dataset[0]
print (image.size())
 
train_loader = torch.utils.data.DataLoader(
      dataset=train_dataset,
      batch_size=64, 
      shuffle=True,
      num_workers=2)
 
test_loader = torch.utils.data.DataLoader(
      dataset=test_dataset,
      batch_size=64, 
      shuffle=False,
      num_workers=2)
 
class AlexNet(nn.Module):
 
    def __init__(self, num_classes):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, 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=2, stride=2),
        )
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 4 * 4, 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 * 4 * 4)
        x = self.classifier(x)
        return x
 
num_classes = 10
device = 'cuda' if torch.cuda.is_available() else 'cpu'
net = AlexNet(num_classes).to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4)

num_epochs = 20
train_loss_list, train_acc_list, val_loss_list, val_acc_list = [], [], [], []
 
for epoch in range(num_epochs):
    train_loss, train_acc, val_loss, val_acc = 0, 0, 0, 0

    net.train()
    for i, (images, labels) in enumerate(train_loader):
      images, labels = images.to(device), labels.to(device)      
      optimizer.zero_grad()
      outputs = net(images)
      loss = criterion(outputs, labels)
      train_loss += loss.item()
      train_acc += (outputs.max(1)[1] == labels).sum().item()
      loss.backward()
      optimizer.step()
    
    avg_train_loss = train_loss / len(train_loader.dataset)
    avg_train_acc = train_acc / len(train_loader.dataset)

    net.eval()
    with torch.no_grad():
      for images, labels in test_loader:
        images = images.to(device)
        labels = labels.to(device)
        outputs = net(images)
        loss = criterion(outputs, labels)
        val_loss += loss.item()
        val_acc += (outputs.max(1)[1] == labels).sum().item()
    avg_val_loss = val_loss / len(test_loader.dataset)
    avg_val_acc = val_acc / len(test_loader.dataset)
    
    print ('Epoch [{}/{}], Loss: {loss:.4f}, val_loss: {val_loss:.4f}, val_acc: {val_acc:.4f}' 
                   .format(epoch+1, num_epochs, i+1, loss=avg_train_loss, val_loss=avg_val_loss, val_acc=avg_val_acc))
    train_loss_list.append(avg_train_loss)
    train_acc_list.append(avg_train_acc)
    val_loss_list.append(avg_val_loss)
    val_acc_list.append(avg_val_acc)
    

plt.figure()
plt.plot(range(num_epochs), train_loss_list, color='blue', linestyle='-', label='train_loss')
plt.plot(range(num_epochs), val_loss_list, color='green', linestyle='--', label='val_loss')
plt.legend()
plt.xlabel('epoch')
plt.ylabel('loss')
plt.title('Training and validation loss')
plt.grid()
 
plt.figure()
plt.plot(range(num_epochs), train_acc_list, color='blue', linestyle='-', label='train_acc')
plt.plot(range(num_epochs), val_acc_list, color='green', linestyle='--', label='val_acc')
plt.legend()
plt.xlabel('epoch')
plt.ylabel('acc')
plt.title('Training and validation accuracy')
plt.grid()
```