深層学習 Day 3 - Section 2 LSTM のまとめ

この記事は個人的なお勉強用のメモです。

# このお勉強の位置

深層学習 Day 3

Section 1 再帰型ニューラルネットワークの概念
**Section 2 LSTM**　←　これ
Section 3 GRU
Section 4 双方向RNN
Section 5 Seq2Seq
Section 6 Word2vec
Section 7 Attention Mechanism

# 講義

## RNNの課題

時系列を遡ると勾配が消失する。
長い時系列の学習が困難。

LSTMで上記の課題を解決できる。

## 用語

### 勾配爆発

逆伝播すると勾配が指数関数に大きくなってしまうこと。

### 勾配クリッピング

勾配のノルムがしきい値を超えたら、
勾配のノルムを正規化すること。
シンプルなRNNの勾配爆発は、勾配クリッピングによって解決できる。

## 演習チャレンジ（勾配クリッピング）

```python
def gradient_clipping(grad, threshold):
  norm = np.linalg.norm(grad)
  rate = threshold / norm
  if rate < 1:
    return grad * rate
  return grad
```

np.linalg.norm関数：ノルムを計算して出力

講義では 「gradient * rate」が正解と言っているが
「grad * rate」が正解だと思う。

また、素朴な疑問だが、「勾のノルムがしきい値より大きいとき」なので
rateという変数を使うのではなく、

```python
def gradient_clipping(grad, threshold):
  norm = np.linalg.norm(grad)
  rate = threshold / norm
  if norm > threshhold:
    return grad * (norm / threshold)
  return grad
```

の方がわかりやすいと思ったが、後で赤本2の 6.1.4 項を読んで、納得した。
rate 変数を先に作っておけば、複数の勾配を更新するときに
rate 変数を使えて便利なため。

## LSTM

Long short-term memory
RNNとは構造が全く異なる。

### CEC

Constant Error Carousel
勾配を1に保つ。

※ 勾配消失：勾配 < 1
　勾配爆発：勾配 > 1

CECの課題1：
　単にCECを導入すると、重みが一律になってしまう（NNの学習特性が無い）。
解決法：
　入力ゲートと出力ゲートの追加

CECの課題2：
　CECは過去の情報をすべて保管し続けてしまう。
解決法：
　忘却ゲートの追加

CECの課題3：
　CECに保存されている値が他のゲートに影響を与えていない。
　（任意のタイミングで他のノードに伝播させたり、任意のタイミングで忘却させたい。）
解決法：
　覗き穴結合の追加

### 入力ゲートと出力ゲート

重みを持つ入力ゲートと出力ゲートを加えることで
CECの課題を解決。
入力や出力によって重みを変化させる。

### 忘却ゲート

過去の情報が要らなくなったときに、情報を忘却する。


### 演習チャレンジ（LSTM）

```python
def lstm(x, prev_h, prev_c, W, U, b):
  lstm_in = _activation(x.dot(W.T)) + prev_h.dot(U.T) + b)
  a, i, f, o = np.hsplit(lstm_in, 4)
  a = np.tanh(a)
  input_gate = _sigmoid(i)
  forget_gate = _sigmoid(f)
  output_gate = _sigmoid(o)
  c = input_gate * a + forget_gate * c
  h = output_gate * np.tanh(c)
  return c, h
```

新しいセルの状態＝計算されたセルへの入力 × 入力ゲート ＋
　　　　　　　　　1ステップ前のセルの状態 × 忘却ゲート

### 覗き穴結合

CEC自身の値に、重み行列を介して伝播可能にした構造。

- CECから入力ゲートへの覗き穴結合
- CECから出力ゲートへの覗き穴結合
- CECから忘却ゲートへの覗き穴結合


# 実装演習

なし

# 確認テスト

### シグモイド関数の微分

シグモイド関数を微分すると $f(z)(1-f(z))$ になる。
この値が最大になるのは、$f(z)=1-f(z)$ のとき。
すなわち、$f(z)=0.5$ のとき。
このとき、シグモイド関数の値は $0.5\times (1-0.5)=0.25$ になる。

### LSTMのゲート

「映画おもしろかったね。ところで、とてもお腹が空いたから何か____。」

「とても」が無くなっても空欄に影響を及ぼさないのだから、
「とても」は不要な言葉になる。
つまり、忘却ゲートによって忘却されても問題ない。

# 修了テスト～練習問題～

### 問題15（LSTM）

LSTMの構造

(i)：入力ゲート
(f)：忘却ゲート
(o)：出力ゲート


### 問題16と問題17（LSTM）

LSTMのサンプルコード

```python
class LSTM:
  def __init__(self, input_size=input_size, hidden_size=hidden_size):
    self.input_size, self.hidden_size = input_size, hidden_size
    self.weight = np.random.randn(self.input_size + self.hidden_size, self.hidden_size * 4)
    self.bios = np.random.randn(self.hidden_size * 4)

  def forward(self, x, h_prev, c_prev):
    inputs = np.concaterate([x, h_prev], axis=1) # xとh_prevを一つにまとめる。
    inputs = np.matmul(inputs, self.weight) + self.bios
    i, f, o, g = np.hsplit(inputs, 4)
    input_gate, forget_gate, output_gate, g = sigmoid(i), sigmoid(f), sigmoid(o), np.tanh(g)
    c_next = (c_prev * forget_gate) + (g * input_gate) # 
    h_next = np.tanh(c_next) * output_gate
    return h_next, c_next
```

#### 問題16
各ゲートの値はスカラー値である（sigmoid関数の戻り値なので）。
スカラー値を行列に掛けるには、*（アスタリスク）の演算子を使う。

※ 選択肢1の「gorget_gate」は、おそらく「forget_gate」のスペルミス。

#### 問題17

次の隠れ状態 h_next は、次の記憶セル c_next に tanh を作用させて、
出力ゲート output_get を掛けることによって更新する。