【ラビットチャレンジ】 深層学習（後編・day3）

##### ■確認テスト
$５\times５$の入力画像を$3\times3$のフィルタで畳み込んだときの出力画像のサイズを答えよ．ストライドは$2$，パディングは$3$とする．
$$OutputSize = \frac{InputSize + Padding\times2-FilterSize}{Stride}+1 = 3$$

# Section1:再帰型ニューラルネットワークの概念
## RNNとは
時系列データに対応可能なニューラルネットワーク
#### 時系列データ
時間的順序追って一定間隔ごとに観察され，相互に統計的依存関係が認められるようなデータの系列

- 音声データ
- テキストデータなど

** 中間層（隠れ層）が重要 **

### RNNの数学的記述
- 隠れ層への入力 $u_t$
    - 現在時刻の入力 $x^t$ に入力に対する重み $W_{(in)}$ を作用させる
    - １つ前の時刻における隠れ層の出力 $z^{t-1}$ に隠れ層に対する重み $W$ を作用させる
    - バイアス $b$    $$u^t=W_{(in)}x^t+Wz^{t-1}+b$$
    
```python:python
u[:,t+1] = np.dot(X, W_in) + np.dot(z[:,t].reshape(1, -1), W)
```
- 隠れ層の出力 $z^t$
    - 隠れ層への入力 $u_t$ に活性化関数 $f$ を作用させる

```math
\begin{align}
z^t &= f(u^t) \\
&= f(W_{(in)}x^t+Wz^{t-1}+b)
\end{align}
```

```python:python
z[:,t+1] = functions.sigmoid(u[:,t+1])
```

- 現在時刻の出力層への入力 $v^t$
    - 隠れ層の出力 $z^t$ に出力に対する重み $W_{(out)}$ を作用させる
    - バイアス $c$    $$v^t=W_{(out)}z^t+c$$

```python:python
np.dot(z[:,t+1].reshape(1, -1), W_out)
```

- 現在時刻の出力 $y^t$
    - 出力層への入力 $v^t$ に活性化関数 $g$ を作用させる
    
```math
\begin{align}
y^t&=g(v^t)\\
&=g(W_{(out)}z^t+c)
\end{align}
```

```python:python
y[:,t] = function.sigmoid(np.dot(z[:,t+1].reshape(1, -1), W_out))
```

##### ■確認テスト
RNNの３つの重みを答えよ
1. 入力から現在の中間層を定義する際にかけられる重み
2. 中間層から出力を定義する際にかけられる重み
3. 現在の中間層から次の中間層への入力を定義する際にかけられる重み

### RNNの特徴
- 時系列モデルを扱うには，初期の状態と過去の時間 $t-1$ の状態を保持し，そこから次の時間での $t$ を再帰的に求める再帰構造が必要となる．

#### ■ソースコード演習
- バイナリ加算
    - ２進数の加算を予測するAI :smirk:

## BPTT
### BPTTとは
- RNNにおけるパラメータ調整方法の一種．誤差逆伝播法
- Back Propagation Through Time

### BPTTの数学的記述
- 入力に対する重み$$\frac{\partial E}{\partial W_{(in)}}
= \frac{\partial E}{\partial u^t} \biggl[ \frac{\partial u^t}{\partial W_{(in)}}\biggr]^T = \delta ^t [x^t]^T$$

```python:python
np.dot(X.T, delta[:,t].reshape(1, -1))
```

- 出力に対する重み$$\frac{\partial E}{\partial W_{(out)}}
= \frac{\partial E}{\partial v^t} \biggl[ \frac{\partial v^t}{\partial W_{(out)}}\biggr]^T = \delta ^{out, t} [z^t]^T$$

```python:python
np.dot(z[:,t+1].reshape(1, -1), delta_out[:,t].reshape(1,-1))
```

- 前の中間層からの入力に対する重み$$\frac{\partial E}{\partial W}
= \frac{\partial E}{\partial u^t} \biggl[ \frac{\partial u^t}{\partial W}\biggr]^T = \delta ^t [z^{t-1}]^T$$

```python:python
np.dot(z[:,t].reshape(1, -1), delta[:,t].reshape(1,-1))
```
- 入力に対するバイアス$$\frac{\partial E}{\partial b}
= \frac{\partial E}{\partial u^t} \frac{\partial u^t}{\partial b} = \delta ^t$$

```python:python
# コード記載なし（本講座のsimple RNNのコードでは省略）
```

- 出力に対するバイアス$$\frac{\partial E}{\partial c}
= \frac{\partial E}{\partial u^t} \frac{\partial u^t}{\partial c} = \delta ^{out, t}$$

```python:python
# コード記載なし（本講座のsimple RNNのコードでは省略）
```

#### 各層の入出力

「RNNの数学的記述」を参照．

- 隠れ層への入力 $u_t$ ，隠れ層の出力 $z^t$ ，現在時刻の出力層への入力 $v^t$ ，現在時刻の出力 $y^t$

```math
\begin{align}
u^t &= W_{(in)}x^t+Wz^{t-1}+b \\
z^t &= f(u^t) \\
v^t&=W_{(out)}z^t+c\\
y^t&=g(v^t)
\end{align}
```

#### 確認テスト
$y_1$ を $x\_1, s\_0, s\_1, w\_{in}, w, w\_{out}$ を用いて表わせ．
バイアスは任意の文字で定義し，中間層の出力にシグモイド関数 $g(x)$ を作用させよ．$$z_1=g(s_0w+x_1w_{in}+b)$$$$y_1=g(z_1w_{out}+c)$$

### パラメータの更新
# 偏微分(δ)の算出

$$ \frac{\partial E}{\partial u^t}
= \frac{\partial E}{\partial v^t}\frac{\partial v^t}{\partial u^t}
= \frac{\partial E}{\partial v^t}\frac{\partial \\{W\_{(out)}f(u^t)+c \\}}{\partial u^t}
= f'(u^t)W^T_{(out)}\delta^{out, t}=\delta ^t
$$

```python:python
delta[:,t] = (np.dot(delta[:,t+1].T, W.T) + np.dot(delta_out[:,t].T, W_out.T)) * functions.d_sigmoid(u[:,t+1])
```

$$ \delta^{t-1} = \frac{\partial E}{\partial u^{t-1}}
= \frac{\partial E}{\partial u^t}\frac{\partial u^t}{\partial u^{t-1}}
= \delta ^t \biggl\\{ \frac{\partial u^t}{\partial z^{t-1}}\frac{\partial z^{t-1}}{\partial u^{t-1}} \biggl\\}
=\delta ^t \big\\{ Wf'(u^{t-1}) \big\\}
$$

$$ \delta ^{t-z-1}=\delta^{t-z} \big\\{ Wf'(u^{t-z-1}) \big\\}$$

#### パラメータの更新式

- 入力に対する重み
    
```math
W^{t+1}_{(in)} = W^{t}_{(in)}-\epsilon \frac{\partial E}{\partial W_{(in)}} = W^{t}_{(in)}-\epsilon \sum_{z=0}^{T_t}\delta^{t-z}[x^{t-z}]^T
```

```python:python
W_in -= learning_rate * W_in_grad
```

- 出力に対する重み
    - 第２項は$\sum$をとっているが，微分して1項だけ残る．
    
```math
W^{t+1}_{(out)} = W^{t}_{(out)}-\epsilon \frac{\partial E}{\partial W_{(out)}} = W^{t}_{(out)}-\epsilon \delta^{out, t}[z^t]^T
```

```python:python
W_out -= learning_rate * W_out_grad
```

- 前の中間層からの入力に対する重み

```math
W^{t+1} = W^{t}-\epsilon \frac{\partial E}{\partial W} = W^{t}-\epsilon \sum_{z=0}^{T_t}\delta^{t-z}[z^{t-z-1}]^T
```

```python:python
W -= learning_rate * W_grad
```

- 入力に対するバイアス

```math
b^{t+1} = b^{t}-\epsilon \frac{\partial E}{\partial b} = b^{t}-\epsilon \sum_{z=0}^{T_t}\delta^{t-z}
```

- 出力に対するバイアス
    - 第２項は$\sum$をとっているが，微分して1項だけ残る．

```math
c^{t+1} = c^{t}-\epsilon \frac{\partial E}{\partial c} = c^{t}-\epsilon \delta^{out, t}
```

### BPTTの全体像

```math
\begin{align}
E^t &= loss(y^t, d^t) \\
&= loss\big(g(W_{(out)}z^t+c), d^t\big) \\
&= loss\bigg(g\big(W_{(out)}f(W_{(in)}x^t+Wz^{t-1}+b)+c\big), d^t\bigg)
\end{align}
```

```math
\begin{align}
&W_{(in)}x^t+Wz^{t-1}+b \\
&= W_{(in)}x^t+Wf(u^{t-1})+b \\
&= W_{(in)}x^t+Wf(W_{(in)}x^{t-1}+Wz^{t-2}+b)+b
\end{align}
```

# Section2：LSTM

- LSTM = Long short-term memory

### RNNの課題
- 勾配消失
時系列を遡れば遡るほど勾配が消失していくため，長い時系列の学習が困難．
→それをRNNの構造を変えることで解決するのが，LSTM．

- 勾配爆発
勾配が，層を伝搬するごとに指数関数的に大きくなっていく．

## CEC
- Constant Error Carousel．
勾配消失および勾配爆発の解決方法として，勾配が，1であれば解決できる．

$$ \delta^{t-z-1}=\delta^{t-z} \bigl\\{Wf'(u^{t-z-1})\bigr\\}=1 $$$$\frac{\partial E}{\partial c^{t-1}} = \frac{\partial E}{\partial c^t}\frac{\partial c^t}{\partial c^{t-1}} = \frac{\partial E}{\partial c^t}\frac{\partial }{\partial c^{t-1}} \\{a^t-c^{t-1}\\}= \frac{\partial E}{\partial c^t}$$

- CECの課題
    - 入力データについて，時間依存度に関係なく重みが一律． → ニューラルネットワークの学習特性がないということ．
    - 入力層から隠れ層への重み：入力重み衝突
    - 隠れ層から出力層への重み：出力重み衝突

## 入力ゲートと出力ゲート
- 入力・出力ゲートを追加することで，それぞれのゲートへの入力値の重みを，重み行列W,Uで可変可能とすることで，CECの課題を解決する．

## 忘却ゲート
- LSTMの現状
    - CECは，過去の情報がすべて保管されている．
        - 過去の情報が要らなくなった場合，削除することはできず保管され続ける．
- 解決策
    - 過去の情報が要らなくなった場合，そのタイミングで情報を忘却する機能が必要． → 忘却ゲートの誕生．

#### 確認テスト
「映画面白かったね，ところで，とてもお腹が空いたから何か〜〜」という文章をLSTMに入力し「〜〜」に当てはまる単語を予測したいとする．文中の「とても」という言葉はその予測においてなくなっても影響を及ぼさないと考えられる．このような場合，どのゲートが作用すると考えられるか．
→ 忘却ゲートを作用させる．

## 覗き穴結合
- 課題
    - CECの保存されている過去の情報を，任意のタイミングで他のノードに伝搬させたり，あるいは任意のタイミングで忘却させたい．CEC自身の値は，ゲート制御に影響を与えていない．
    → CEC自身の値に，重み行列を介して伝搬可能にした構造である，覗き穴結合を用いる．


# Section3：GRU
Gated Recurrent Unit
- LSTMでは，パラメータ数が多く，計算負荷が高くなる問題があった． → GRU
- GRU：LSTMのパラメータを大幅に削減し，精度は同等またはそれ以上が望めるようになった構造（実務上は，LSTMの方が効果的である場合もある．）．計算不可が低減される．

#### 確認テスト
LSTMとCECが抱える課題について，それぞれ簡潔に述べよ．
- LSTMは，パラメータ数が多くて，計算時間がかかる．
- CECは勾配が1で固定されるため，最適なパラメータを学習するという概念自体が無くなる（学習が行われない．）

#### 演習チャレンジ
GRUにおいて，新しい中間状態は，１ステップ前の中間表現と計算された中間表現の線形和で表現される．つまり，更新ゲート`z`を用いて，`(1-z) * h + z * h_bar`と書ける．

#### 確認テスト
LSTMとGRUの違いを簡潔に述べよ．

- LSTM
    - パラメータが多く，計算負荷が高くなる．
- GRU
    - LSTMのパラメータを省略し計算負荷が低減されている．タスクによってはLSTMよりよい性能を発揮する．

どちらが優秀かはタスクによるので，実務においてはどちらのモデルとも走らせて性能を比較する．

# Section4：双方向RNN
- 過去の情報だけでなく，未来の情報を加味することで，精度を向上させるモデル．
    - 文章の遂行や，機械翻訳などに用いられる．

#### 演習チャレンジ
双方向RNNでは，順方向と逆方向に伝播したときの中間層表現をあわせたものが特徴量となるので，`np.concatenate([h_f, h_b[::-1]], axis=1)`である．

# Section5：Seq2Seq
- Seq2Seqとは
    - Encoder-Decoderモデルの一種．
    - 機械対話や機械翻訳などに使用される．

## Encoder RNN
- ユーザーがインプットしたテキストデータを，単語などのトークンに区切って渡す構造．
    - Taking：文章を単語などのトークン毎に分割し，トークン毎のIDに分割する．
    - Embedding：IDから，そのトークンを表すぶんさんひょうげん分散表現ベクトルに変換する（数値化）．
    - Encoder RNN：ベクトルを順番にRNNに入力していく．
- Encoder RNNの処理手順
    - vec1をRNNに入力し，hidden stateを出力する．このhidden stateと次の入力vec2をまたRNNに入力してまたhidden stateを出力するという流れを繰り返す．
    - 最後のvecを入れたときのhidden stateをfinal stateとしてとっておく．このfinal stateがthought vectorと呼ばれ，入力した文の意味を表すベクトルとなる．

## Decoder RNN
- システムがアウトプットデータを単語などのトークン毎に生成する構造．
    - 前もって学習させておく．

- Decoder RNNの処理
    - Decoder RNN：Encoder RNNのfinal state(thought vector)から，各tokenの生成確率を出力していく．final stateをDecoder RNNのinitial stateとして設定し，Embeddingを入力する．
    - Sampling：生成確率に基づいてtokenをランダムに選ぶ．
    - Embedding：Samplingで選ばれたtokenをEmbeddingしてDecoder RNNへの次の入力とする．
    - Detokenize：上記処理を繰り返し，得られたtokenを文字列に戻す．

#### 確認テスト
seq2seqについて説明しているものを選べ．
→RNNを用いらEncoder-Decoderモデルの一種であり，機械翻訳などのモデルに使われる．

## HRED
Seq2Seqの課題としては，一問一答しかできない．
→ 問に対して文脈も何もなく，ただ応答が行われ続ける．
→ 人間ぽさを出すために，HREDを用いる．

- HREDとは
    - 過去$n-1$個の発話から次の発話を生成する．
    - Seq2Seq + Context RNN

- Context RNNとは
    - Encoderのまとめた各文章の系列をまとめて，これまでの会話コンテキスト全体を表すベクトルに変換する構造． → 過去の発話の履歴を加味した返答をできる．

- HREDの課題
    - HREDは確率的な多様性が字面にしかなく，会話の「流れ」のような多様性がない．
        - 同じコンテキストを与えられると，会話の流れが同じなので，同じ回答しか出せない．
    - HREDは短く情報量に乏しい答えをしがちである．
        - 短いよくある答えを学ぶ傾向がある．（「うん」，「そうだね」，「・・・」など）

## VHRED
- VHREDとは
    - HREDにVAEの潜在変数の概念を追加したもの．

#### 確認テスト
- seq2seqとHREDの違いを述べよ
    - seq2seqは一問一答しかできない．HREDは文脈に則して返答ができる．

- HREDとVHREDの違いを述べよ
    - HREDは同じ文脈であると同じ返答をしてしまう．VHREDは，同じ文脈であってもランダムに異なる返答ができる．

## オートエンコーダ
- オートエンコーダとは
    - 教師なし学習の一つ．そのため学習時の入力データは訓練データのみで教師データは利用しない．
- オートエンコーダの具体例
    - MNISTの場合，28x28の数字の画像を入れて，雄ねじ画像を出力するニューラルネットワークのこと．

- オートエンコーダ構造
    - Encoder：入力データから潜在変数 $z$ に変更するニューラルネットワーク
    - Decoder：潜在変数 $z$ をインプットとして元画像を復元するニューラルネットワーク
- オートエンコーダのメリット
    - 次元削減が行える．（$z$ のデータが入力データより小さい場合，次元削減とみなすことができる．）

## VAE
通常のオートエンコーダの場合，何かしらの潜在変数 $z$ にデータを押し込めているものの，その構造がどのような状態かわからない．
→ VAEはこの潜在変数 $z$ に確率分布 ＄z=N(0,1)$を仮定したもの．
→ データを潜在変数 $z$ の確率分布という構造に押し込めることが可能になる．

#### 確認テスト
VAEは，自己符号化器の潜在変数に確率分布を導入したもの．

#### 牽引企業
AWS，GCPなど．AWSは使いやすさ，価格面でよい．学習するときだけ使用する．
Googleは，TensorFlowが使いやすい．TensorFlowServingなどに興味あり．
ハードウェアはNVIDIA．
自動運転はTOYOTAが頑張っている．自動運転向きの都市計画など．
お金という文脈でアンテナを広げてみる．

# Section6：Word2vec

RNNでは，単語のような可変長の文字列をNNに与えることはできない．
→ 固定長形式で単語を表す必要がある．

- word2vec
    - 学習データからボキャブラリを作成する．
        - I want to eat apples. I like apples. → {apples, eat, I, to want}

- one-hotベクトル
    - applesを入力する場合は，入力層には以下のベクトルが入力される．
        - [apples, eat, I, to, want, ...] = [1, 0, 0, 0, 0, ...]

- メリット
    - 大規模データの分散表現の学習が，現実的な計算速度とメモリ量で実現可能にした．
        - ボキャブラリ x 任意の単語ベクトル次元で**重み行列**が生成される．
        - （ボキャブラリ x ボキャブラリだけの重み行列が生成されるのではなく．） 

# Section7：Attention Mechanism

- seq2seqは長い文章への対応が難しい．
    - seq2seqは２単語でも，１００単語でも，固定次元ベクトルの中に入力しなければならない．
    - 文章が長くなるほどそのシーケンスの内部表現の次元も大きくなっていく仕組みが必要． → Attention Mechanism

- Attention Mechanism
    - 「入力と出力のどの単語が関連しているのか」の関連度を学習する仕組み．

「私はペンを持っている」 ⇔ "I have a pen."
"a"については関連度が低く，"I"については「私」と関連度が高い．

#### 確認テスト
- RNNとword2vecの違い
    - RNNはボキャブラリ x ボキャブラリだけの重み行列が生成される．vec2vecはボキャブラリ x 任意の単語ベクトル次元で重み行列が生成される．
- seq2seqとAttention Mechanismの違い
    - seq2seqは固定次元ベクトルのものしか学習できなかったのに対し，Attention Mechanismは重要度の概念を用いることによって長い文章を入力したときにでも翻訳が成り立つ．重要度，関連度の学習が進むほど精度が向上する． 

#### 演習チャレンジ
構文木のleftとrightを組み合わせたもの`concatenate`に対して重みとの内積を取る．
`W.dot(np.concatenate([left, right]))`

#### 実務経験について
学習にはデータが必要なので，データが溢れたところに飛び込んで，実務経験を積むのが良い．
生成したモデルを使っていかにサービスを提供するかを学ぶことも必要．サーバーの管理とか，どのようにモデルを回していくか，バックエンドやクライアントとの関係などの経験を積むことも意識する．


<br>
<br> 

[DeepLearning ラビットチャレンジ](http://ai999.careers/rabbit/　"ラビットチャレンジリンク")
<img src="http://ai999.careers/tools/wp/wp-content/uploads/2018/12/card.png" width=50%>