深層学習day1

#深層学習

##### 確認テスト
ディープラーニングは、結局何をやろうとしているか2行以内で述べよ。また、次の中のどの値の最適化が最終目的か。全て選べ。（1分）
①入力値[ X] ②出力値[ Y]③重み[W]④バイアス[b]⑤総入力[u] ⑥中間層入力[ z]⑦学習率[ρ]

回答
  パラメータを学習データにより最適化し、入力値を入れると正しい出力をするようにする。
  重みとバイアスを最適化する。

次のネットワークを紙にかけ。入力層︓2ノード1層中間層︓３ノード2層出力層︓1ノード1層（5分）

![CamScanner 12-16-2020 11.22 - 第2ページ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118594/54a48737-2ac4-18ee-248f-6f26bcbd56bf.jpeg)
  
## Section1：入力層〜中間層
### 入力層

- ニューラルネットワークの最初の層。
- 入力層はニューラルネットの層数には数えず、入力をそのまま次の層に伝える。
- 入力としてとり得るデータ
    - 連続する実数
    - 確率
    - フラグ値

##### 確認テスト　動物の例
回答
![CamScanner 12-16-2020 11.22.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118594/69ae517c-e081-13eb-f198-a2bc22557577.jpeg)

### 中間層

- 入力層の値に重みをかけてバイアスを加える層。
- 隠れ層 (hidden layer) とも呼ばれる。
- 重みは行列として表現される。

##### 確認テスト　以下の式をPythonで書け
$u=w_1x_1+w_2x_2+w_3x_3+w_4x_4+b=Wx+b$
回答

```python
u = np.dot(x, W) + b
```
##### 確認テスト 1-1のファイルから中間層の出力を定義しているソースを抜き出せ。
回答

```python
# 総入力
u = np.dot(x, W) + b
print_vec("総入力", u)

# 中間層出力
z = functions.relu(u)
print_vec("中間層出力", z)
```
```python
#  総入力
u = np.dot(x, W) + b
print_vec("総入力", u)

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

# 1層の総入力
    u1 = np.dot(x, W1) + b1
    
    # 1層の総出力
    z1 = functions.relu(u1)
    
    # 2層の総入力
    u2 = np.dot(z1, W2) + b2
    
    # 2層の総出力
    z2 = functions.relu(u2)

    # 出力層の総入力
    u3 = np.dot(z2, W3) + b3
    
    # 出力層の総出力
    y = u3
```
```python
# 1層の総入力
    u1 = np.dot(x, W1) + b1

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

    # 2層の総入力
    u2 = np.dot(z1, W2) + b2
    
    # 出力値
    y = functions.softmax(u2)
```
```python
# 隠れ層の総入力
    u1 = np.dot(x, W1) + b1
    # 隠れ層の総出力
    z1 = functions.relu(u1)
    # 出力層の総入力
    u2 = np.dot(z1, W2) + b2
    # 出力層の総出力
    y = u2
```
```python
# 隠れ層の総入力
    u1 = np.dot(x, W1) + b1
    # 隠れ層の総出力
    z1 = functions.relu(u1)
    # 出力層の総入力
    u2 = np.dot(z1, W2) + b2
    # 出力層の総出力
    y = functions.sigmoid(u2)
```

### 実装演習

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

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

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

# 中間層
u = np.dot(x, W) + b
print("中間層", u)
```
```:結果
入力 [ 1.  5.  2. -1.]
中間層 [1.4 2.2 3. ]
```
重みの行列の形状によって入力と中間層の出力のサイズが変わった。

## Section2：活性化関数
ニューラルネットワークにおいて、次の層への出力の大きさを決める非線形の関数
入力値の値によって、次の層への信号のON/OFFや強弱を定める働きをもつ。
非線形の関数を用いて層を重ねることで複雑な表現ができるようになる。

##### 確認（復習）テスト
 線形と非線形の違いを図にかいて簡易に説明せよ。
回答
線形　線形性を満たすため、複数の線形の関数は一つにまとめられる。
![スクリーンショット 2020-12-16 11.49.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118594/18bb85c0-0454-c5a8-63de-524fffe9bcc5.png)
非線形　線形性を満たさない
![スクリーンショット 2020-12-16 12.01.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118594/47c2f8db-0655-1e5a-2df7-fa6d6a926257.png)

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

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

### 実装演習

```python
def sigmoid(x):
    return 1/(1 + np.exp(-x))

# 重み
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]
])

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

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

# 中間層
u = np.dot(x, W) + b
print("中間層", u)

z = sigmoid(u)
print("出力", z)
```
```:出力
中間層 [1.4 2.2 3. ]
出力 [0.80218389 0.90024951 0.95257413]
```
sigmoid関数によって中間層の出力が大小関係を保ちつつ変わった。

## Section3：出力層
- 出力のために中間層からの値を変換する層。
- 変換するのに用いる関数は誤差逆伝播の際の計算式が綺麗になるように選ぶ。
- 回帰には二乗誤差、文類には交差エントロピーを用いることでy-tという綺麗な値が誤差逆伝播で流れる。

##### 確認テスト
![スクリーンショット 2020-12-16 17.12.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118594/af41d221-8ca5-4b0b-f638-f941113c4b0e.png)
回答
2乗するのは正負を揃えて誤差を表現できるようにするため。
1/2は誤差逆伝播の際に使う計算式を簡単にするため。

##### 確認テスト

![スクリーンショット 2020-12-16 17.15.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118594/3cae66cb-4cea-bb8e-19a2-ac6869fdda24.png)
回答
①np.sum(np.exp(x), axis=0)またはnp.sum(np.exp(x))
②np.exp(x)
③return y.Tまたはreturn np.exp(x) / np.sum(np.exp(x))

```python
def softmax(x): 
    if x.ndim == 2: # 2次元の場合は特別な処理をする
        x = x.T # 転置を取る
        x = x - np.max(x, axis=0) # オーバーフロー対策
        y = np.exp(x) /np.sum(np.exp(x), axis=0) # 軸を0に指定してsoftmaxを求める。
        return y.T # 転置を取る
    x = x - np.max(x) # オーバーフロー対策
    return np.exp(x) / np.sum(np.exp(x)) # 1次元の場合
```

##### 確認テスト

![スクリーンショット 2020-12-16 17.18.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118594/883fa119-5090-348c-976b-3262deb41f76.png)
回答
①return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size
②return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size

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

# 重み
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]
])

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

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

# 中間層
u = np.dot(x, W) + b
print("中間層", u)

z = softmax(u)
print("出力", z)
```
```:出力
中間層 [1.4 2.2 3. ]
出力 [0.12227071 0.27211848 0.60561081]
```
中間層の出力が大小関係を保ちつつ合計が1となる数値に変換された。

## Section4：勾配降下法
- 誤差を最小化するような重みを発見するために用いる方法。
- 重みを少しずつ更新して勾配が最小になる点を探索する。
- ランダムに抽出したサンプルの誤差を最小化する場合、確率的勾配降下法という。

##### 確認テスト
![スクリーンショット 2020-12-16 17.20.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118594/1b979025-5b85-52ed-fc27-0e7245c7c691.png)
回答

```python
network[key]  -= learning_rate* grad[key]
```
```python
grad = backward(x, d, z1, y)
```

##### 確認テスト
オンライン学習とは何か2行でまとめよ
訓練データの一つを取り出してパラメータの更新をする学習方法。
局所界に陥りにくいが外れ値にも過敏に反応する。

##### 確認テスト
![スクリーンショット 2020-12-16 17.26.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118594/af566d17-9af9-9042-85a8-20bad31bd5c3.png)
回答
![CamScanner 12-16-2020 11.22 - 第3ページ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118594/f881704d-dfc9-780d-e580-4d886e7e315d.jpeg)

### 実装演習

```python

# 確率勾配降下法
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
from common import functions
import matplotlib.pyplot as plt

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

# サンプルとする関数
# yの値を予想するAI
def f(x):
    y = 3 * x[0] + 2 * x[1]
    return y

# 初期設定
def init_network():
    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)
    # 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)

    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()
```
![スクリーンショット 2020-12-19 14.13.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118594/d4d4ea10-63b2-ae89-4ce4-5ab2c48b1fad.png)
100エポックまではバラバラな値を出力しているがその後0に近い値を出力するようになった。


## Section5：誤差逆伝播法
- 算出された誤差を、出力層側から順に微分し、前の層前の層へと伝播する。
- 合成関数の微分法則を用いることで各層の入力とその後の層からの勾配を元に勾配を求めていく。
- 簡単な計算で済むため計算コストを減らすことができる。

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

```python
z1 = functions.sigmoid(u)
```
z1を保存している

##### 確認テスト
![スクリーンショット 2020-12-16 14.50.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118594/67ab944d-0e7e-5a03-d2c3-92b91e39c8a5.png)

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

### 実装演習

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

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


# In[ ]:


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

    network = {}
    network['W1'] = np.array([
        [0.1, 0.3, 0.5],
        [0.2, 0.4, 0.6]
    ])

    network['W2'] = np.array([
        [0.1, 0.4],
        [0.2, 0.5],
        [0.3, 0.6]
    ])

    network['b1'] = np.array([0.1, 0.2, 0.3])
    network['b2'] = np.array([0.1, 0.2])

    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)
    u2 = np.dot(z1, W2) + b2
    y = functions.softmax(u2)

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

    return y, z1

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

    grad = {}

    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    #  出力層でのデルタ
    delta2 = functions.d_sigmoid_with_loss(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)
    # b1の勾配
    grad['b1'] = np.sum(delta1, axis=0)
    #  W1の勾配
    grad['W1'] = np.dot(x.T, delta1)

    print_vec("偏微分_dE/du2", delta2)
    print_vec("偏微分_dE/du2", delta1)

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

    return grad

# 訓練データ
x = np.array([[1.0, 5.0]])
# 目標出力
d = np.array([[0, 1]])
#  学習率
learning_rate = 0.01
network =  init_network()
y, z1 = forward(network, x)

# 誤差
loss = functions.cross_entropy_error(d, y)

grad = backward(x, d, z1, y)
for key in ('W1', 'W2', 'b1', 'b2'):
    network[key]  -= learning_rate * grad[key]

print("##### 結果表示 #####")


print("##### 更新後パラメータ #####")
print_vec("重み1", network['W1'])
print_vec("重み2", network['W2'])
print_vec("バイアス1", network['b1'])
print_vec("バイアス2", network['b2'])
```
```:出力
##### ネットワークの初期化 #####
*** 重み1 ***
[[0.1 0.3 0.5]
 [0.2 0.4 0.6]]

*** 重み2 ***
[[0.1 0.4]
 [0.2 0.5]
 [0.3 0.6]]

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

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

##### 順伝播開始 #####
*** 総入力1 ***
[[1.2 2.5 3.8]]

*** 中間層出力1 ***
[[1.2 2.5 3.8]]

*** 総入力2 ***
[[1.86 4.21]]

*** 出力1 ***
[[0.08706577 0.91293423]]

出力合計: 0.9999999999999999

##### 誤差逆伝播開始 #####
*** 偏微分_dE/du2 ***
[[ 0.08706577 -0.08706577]]

*** 偏微分_dE/du2 ***
[[-0.02611973 -0.02611973 -0.02611973]]

*** 偏微分_重み1 ***
[[-0.02611973 -0.02611973 -0.02611973]
 [-0.13059866 -0.13059866 -0.13059866]]

*** 偏微分_重み2 ***
[[ 0.10447893 -0.10447893]
 [ 0.21766443 -0.21766443]
 [ 0.33084994 -0.33084994]]

*** 偏微分_バイアス1 ***
[-0.02611973 -0.02611973 -0.02611973]

*** 偏微分_バイアス2 ***
[ 0.08706577 -0.08706577]

##### 結果表示 #####
##### 更新後パラメータ #####
*** 重み1 ***
[[0.1002612  0.3002612  0.5002612 ]
 [0.20130599 0.40130599 0.60130599]]

*** 重み2 ***
[[0.09895521 0.40104479]
 [0.19782336 0.50217664]
 [0.2966915  0.6033085 ]]

*** バイアス1 ***
[0.1002612 0.2002612 0.3002612]

*** バイアス2 ***
[0.09912934 0.20087066]
```
学習によって重みとバイアスが最適化された。