【E資格学習レポート】深層学習前篇

#背景
日本ディープラーニング協会の資格試験（E資格）受験資格を得るためにラビット・チャレンジの講座プログラムを受けることにした。
当該講座を受講する時、科目ごとにレポートを作成しWebに投稿する必要があるためQiitaで作成することとした。
個人的には各科目の全要点などを記述よりも自分が難しいと思うところまたは理解不足なポイントをまとめる形にしたいと考えている。
ちなみにラビット・チャレンジについては以下から参考できる

https://ai999.careers/rabbit/

##入力層〜中間層
####１．要点
ニューラルネットワークでは主に入力層、中間層、出力層で構成される。入力層xは入力として与える特徴量であり、例えば動物分類問題であれば、体長、体重、足の長さ、耳の大きさ、ひげの本数などに該当する。それに対する重みwとの線形結合にバイアスbを加えたものが総入力となる。

```math
u=wx+b
```
中間層では上記総入力uに活性化関数を加えて次に出力する。

####２．実装演習の考察
※後述の出力層とまとめて考察する
####３．確認テストの考察
以下の数式をPythonで記述する

```math
u=w_1x_1+w_2x_2+⋯+b=Wx+b
```
```python
u1 = np.dot(x, W1) + b1
```
※内積の計算はnp.dotを使う

##活性化関数
####１．要点
ニューラルネットワークにおいて、次の層への出力の大きさを決める非線形の関数。入力値の値によって、次の層への信号のON/OFFや強弱を定める働きをもつ。活性化関数は中間層と出力層それぞれに用いられ，以下のようなものがある。

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

シグモイド関数では勾配消失問題を引き起こしやすいためReLU関数が今最も使われている活性化関数である。
ReLU関数の数式、性質は以下図のようになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/94a20fa9-1f5b-cc81-46e6-3f8889ad1805.png)

####２．実装演習の考察
※後述の出力層とまとめて考察する
####３．確認テストの考察
シグモイド関数、Relu関数をPythonで定義する

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

def relu(x):
    return np.maximum(0, x)
```
##出力層
####１．要点
出力層では、ネットワークの最終計算値を出力する。例えば回帰モデルの場合は数学モデルの予測値を出力する、分類モデルの場合は各クラスに属する確率を出力する。
出力層でよく用いられる活性化関数と誤差関数を下記の表にまとめる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/ab115952-9603-9a3f-d7a5-c809e2793e47.png)

####２．実装演習の考察
```python
# 順伝播（3層・複数ユニット）

# ウェイトとバイアスを設定
# ネートワークを作成
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['W3'] = np.array([
        [0.1, 0.3],
        [0.2, 0.4]
    ])
    network['b1'] = np.array([0.1, 0.2, 0.3])
    network['b2'] = np.array([0.1, 0.2])
    network['b3'] = np.array([1, 2])

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

    return network

# プロセスを作成
# x：入力値
def forward(network, x):
    
    print("##### 順伝播開始 #####")

    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']
    
    # 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
    
    print_vec("総入力1", u1)
    print_vec("中間層出力1", z1)
    print_vec("総入力2", u2)
    print_vec("出力1", z1)
    print("出力合計: " + str(np.sum(z1)))

    return y, z1, z2

# 入力値
x = np.array([1., 2.])
print_vec("入力", x)

# ネットワークの初期化
network =  init_network()

y, z1, z2 = forward(network, x)
```
**出力結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/56d31b53-7ce9-5ac8-518c-671ef6751b71.png)
####３．確認テストの考察
二乗誤差関数の数式をPythonで記述する

```math
E_n(W)=\frac{1}{2}\sum_{i=1}^I(y_n-d_n)^2
```
```python
def mean_squared_error(d, y):
    return np.mean(np.square(y - d)) / 2
```
##勾配降下法
####１．要点
勾配降下法は前回のレポートにも記述したように誤差E(w)を最小化するパラメータwを探査するための方法である。下記の式で表現する。

```math
W^{t+1}=W^t+\varepsilon∇E\\
∇E=\frac{\partial E}{\partial W}=\begin{bmatrix}\frac{\partial E}{\partial w_1}・・・\frac{\partial E}{\partial w_M}\end{bmatrix}
```
ここで、εはwの更新量の大きさを定める学習係数(learning rate)と呼ぶ。また、Mはwの成分の数となる。
勾配降下法の学習率の決定、収束性向上のために、以下のアルゴリズムがよく利用されている。

- Momentum
- AdaGrad
- Adadelta
- Adam

確率的勾配降下法は一部のデータのみを用いて、パラメータの更新を逐次行う方法。「オンライン勾配降下法」とも呼ぶ。
ミニバッチ勾配降下法は、バッチ勾配降下法と確率的勾配降下法の中間を取った方法。全データの誤差の和でもなく、１つのデータの誤差でもなく、いくつかのデータの誤差の和を使う方法である。

####２．実装演習の考察
※後述の誤差逆伝播法とまとめて考察する
####３．確認テストの考察
上記勾配降下法の数式がソース上の該当箇所を特定する

```math
W^{t+1}=W^t+\varepsilon∇E
```
```python
network[key]  -= learning_rate * grad[key]
```
```math
∇E=\frac{\partial E}{\partial W}=\begin{bmatrix}\frac{\partial E}{\partial w_1}・・・\frac{\partial E}{\partial w_M}\end{bmatrix}
```
```python
grad = backward(x, d, z1, y)S4)
```
##誤差逆伝播法
####１．要点
誤差逆伝播法とは算出された誤差を、出力層側から順に微分し、前の層前の層へと伝播。最小限の計算で各パラメータでの微分値を解析的に計算する手法。
誤差逆伝播法による計算は下図のようなプロセスになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/0d73a47b-6848-88f1-1c16-173326c30e22.png)
誤差逆伝播法は計算コストが抑えられるメリットがあるが、勾配消失問題を引き起こす原因となる。

####２．実装演習の考察
```python
# 初期設定
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):
    # 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 = 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 = 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()
```
**実施結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/b4b6b5e8-29be-47aa-9610-407502be7784.png)
####３．確認テストの考察
誤差逆伝播法の数式がソース上の該当箇所を特定する

```math
\frac{\partial E}{\partial y}\frac{\partial y}{\partial u}
```
```python
delta2 = functions.d_mean_squared_error(d, y)
```
```math
\frac{\partial E}{\partial y}\frac{\partial y}{\partial u}\frac{\partial u}{\partial w_{ji}^{(2)}}
```
```python
grad['W2'] = np.dot(z1.T, delta2)
```
##勾配消失問題
####１．要点
勾配降下法とは誤差逆伝播法が下位層(出力層から入力層)に向かって進んでいくにつれて、勾配がどんどん緩やかになっていく。そのため、勾配降下法による更新では下位層のパラメータはほとんど変わらず、訓練は最適値に収束しなくなる。
勾配消失問題の解決策として以下の手法があげられる。
①活性化関数の選択
　→ReLU関数を使用（勾配消失問題の回避とスパース化に効果的）
②重みの初期値設定
　→初期値の設定にXavier、Heなどの手法を用いる
③バッチ正規化
　→活性化関数に値を渡す前後に、バッチ正規化の処理を孕んだ層を加える

####２．実装演習の考察
※後述の学習率最適化手法とまとめて考察する
####３．確認テストの考察
シグモイド関数を微分した時、入力値が0の時に最大値をとる。その値を求める
シグモイド関数：

```math
f(x)=\frac{1}{1 - e^{-x}}
```
シグモイド関数を微分した結果は下式で表すことができる：

```math
f(x)′=(1-f(x))*f(x)
```
従って、xが0の時、シグモイド関数が0.5であり、シグモイド関数微分の最大値は 0.25 となる。

##学習率最適化手法
####１．要点
各最適法の手法とメリットを以下にまとめる
**モメンタム**
誤差をパラメータで微分したものと学習率の積を減算した後、現在の重みに前回の重みを減算した値と慣性の積を加算する。
メリット:
・局所的最適解にはならず、大域的最適解となる。
・谷間についてから最も低い位置(最適値)にいくまでの時間が早い。

**AdaGrad**
誤差をパラメータで微分したものと再定義した学習率の積を減算する
メリット：
・勾配の緩やかな斜面に対して、最適値に近づける

**RMSProp**
誤差をパラメータで微分したものと再定義した学習率の積を減算する
メリット：
・局所的最適解にはならず、大域的最適解となる。
・ハイパーパラメータの調整が必要な場合が少ない

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

####２．実装演習の考察
**各手法の実装イメージ(最適化処理のみ抜粋)**

```python
##普通の勾配降下法
params[key] -= self.learning_rate * grad[key]

##Momentum
self.v[key] = self.momentum* self.v[key] -self.learning_rate* grad[key]
params[key] += self.v[key]

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

##RSMprop
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
learning_rate_t  = learning_rate * np.sqrt(1.0 - beta2 ** (i + 1)) / (1.0 - beta1 ** (i + 1))
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)
```
**各手法の学習結果イメージ**
今回の実施結果としては、普通の勾配降下法、Momentum、AdaGradでは学習がうまく進めず勾配消失問題が発生したが、RSMprop、Adamでは勾配消失問題発生せずに良い学習結果が得られた。
普通の勾配降下法：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/914c2dd1-58d4-2e39-be68-95d7078c3191.png)
Momentum：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/d098c4b0-e163-bd58-acea-dda0b852ca77.png)
AdaGrad：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/92c09d8d-75df-83f6-862d-bf9020cd2088.png)
RSMprop：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/06a29149-5862-8fa6-200e-457de9f1cbc8.png)
Adam：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/f2b444cc-4bd9-ac07-bf3b-aa64268069c6.png)
####３．確認テストの考察
**モメンタム・AdaGrad・RMSPropの特徴をそれぞれ簡潔に説明：**
**・モメンタム**
慣性項のパラメータαを加え、前回の更新量にα倍して加算することでパラメータの更新をより慣性的なものにするという狙いがある。
但しハイパーパラメータが２つあり、最適化が難しいという問題がある。
**・AdaGrad**
epoch回数を重ねていくごとに計算される学習係数は小さくなっていく。
また、傾きが小さくなっていくごとに学習係数は0に漸近していき収束するであろうことが期待できる。
**・RMSProp**
AdaGradを改良したアルゴリズム。AdaGradより直近の勾配情報を優先して計算するという改良が実現されている。

##過学習
####１．要点
機械学習において、テスト誤差と訓練誤差とで学習曲線が乖離することを過学習という。複雑なネットワークに対して学習データが不十分の場合過学習が起きやすいと言われている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/68d787f5-0325-13f8-4627-c2b622355500.png)
過学習を防ぐ手段としてはL1正則化（ラッソ回帰）, L2正則化（リッジ回帰）、ドロップアウトが挙げられる。
####２．実装演習の考察
過学習と各正則化手法の学習結果を確認する（ソースコード省略）
過学習が起きた場合：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/8d254f29-c265-8c79-6c40-0c8655efa90d.png)
L1正則化：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/68a9b708-0029-fa27-023d-68efc339b74c.png)
L2正則化：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/9e2ec7ac-995e-3365-4e70-4e9caa4eb244.png)
ドロップアウト：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/29b9be58-6f33-f2b5-c828-d06be1c2d77c.png)
ドロップアウト＋L1正則化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/0bef9b24-05a1-abc2-2ab5-d0c38d5d19f6.png)
####３．確認テストの考察
**L1正則化とL2正則化の違いを比較する：**

|L1正則化　　　　　　　　　　　　　　|L2正則化　　　　　　　　　　　　　　　|
|---|---|
|スパース性がある（変数選択ができる）|スパース性がない|
|解析的に解が求まらない（数値計算で推定）|解析的に解が求まる|
|過学習の抑制 〇|過学習の抑制 ◎|
|精度 〇|精度 ◎|

##畳み込みニューラルネットワークの概念
####１．要点
畳み込みネットワーク（convolutional neural network : CNN）は主に画像認識で用いられ、その圧倒的な精度ゆえにディープラーニングが注目される1つの要因を作り出した手法でもある。
CNNは入力層、畳み込み層、プーリング層、全結合層、出力層で構成される。
畳み込み層ではMatrix Kernelを用いたフィルタリング処理を行う。畳み込み層の特徴としては複数次元のデータを学習できる。
プーリング層は入力画像におけるフィルタ形状の位置ずれを吸収するように機能する。
畳み込み層の演算イメージ：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/a8aafaa5-f74c-3c06-0c83-ba3a6401dfb2.png)
プーリング層の演算イメージ：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/f259e701-2c3b-3570-a165-6e2509d2a494.png)
####２．実装演習の考察
他次元配列のデータを2次元データに変換するim2col処理を確認する（ソース省略）
処理結果：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413609/ff876bef-96d2-41b6-9d09-c4ab861a73dd.png)
####３．確認テストの考察
**サイズ6×6の入力画像を、サイズ2×2のフィルタで畳み込んだ時の出力画像のサイズを計算する。なおスライドとパディングは1とする。**
出力画面サイズ＝（元サイズ＋２*パディングーフィルタサイズ）／スライド　＋１
　　　　　　　＝(6+2-2)/1 +1＝7

##最新のCNN
####１．要点
以下に最近の有名なCNNをいくつか紹介する。
**・AlexNet**
AlexNetは，2012年のILSVRCにおいて，従来の画像認識のデファクトスタンダードであったSIFT + Fisher Vector + SVM5というアプローチに大差をつけて優勝し，一躍深層学習の有効性を知らしめたモデルである．現在では比較的小規模なモデルということもあり，ベースラインとして利用されることもある。
**・GoogleNet**
GoogLeNet は、VGGNet とは独立に開発されたアーキテクチャであり、2014 年の画像分類チャレンジコンテスト ISLVRC-2014 で 1 位を獲得した。GoogLeNet のアーキテクチャは、AlexNet、ZFnet などの既存のアーキテクチャとは大きく異なり、1×1 Convolution、global average poolingおよび Inception モジュールなどの技術が新たに導入された。GoogLeNet は、この Inception モジュールを取り入れたことで、層を深くすることができるようになり、全体で 22 層で構成されている。

下記記事に最新のCNNについて詳しく紹介されている。

https://qiita.com/yu4u/items/7e93c454c9410c4b5427
####２．実装演習の考察
なし
####３．確認テストの考察
なし

##参考文献
深層学習の勉強をしていく中で、Deep Learningの定番の本と言われている「ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装」が参考になった。

https://www.amazon.co.jp/%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E4%BD%9C%E3%82%8BDeep-Learning-%E2%80%95Python%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%83%A9%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0%E3%81%AE%E7%90%86%E8%AB%96%E3%81%A8%E5%AE%9F%E8%A3%85-%E6%96%8E%E8%97%A4-%E5%BA%B7%E6%AF%85/dp/4873117585