RNNまとめ（+Attention）

# はじめに
この記事はE資格の認定講座で提出したレポートをもとに、代表的なRNNモデルとAttentionについてまとめた記事です。

# 再帰的ニューラルネットワーク（RNN)とは
　RNNとは、時系列データに対して有効なニューラルネットワークの一つ。従来のニューラルネットワークとは異なり、自己回帰的な構造を持ち、主に音声認識、自然言語処理で使用される。
　TensorFlowにSimpleRNNレイヤーがあります。

```py
import tensorflow as tf
from tensorflow.keras.layers import SimpleRNN, Dense
from tensorflow.keras.models import Sequential

# RNNモデルを定義
def rnn_model(input_shape):
    model = Sequential()
    model.add(SimpleRNN(50, activation='tanh', input_shape=input_shape))
    model.add(Dense(1, activation='linear'))
    model.compile(optimizer='adam', loss='mse')
    return model
```

# LSTM(Long Short-Term Memory)
## LSTMとは
LSTMは、RNNの一種で、特に長期的な依存関係を扱うために設計されたニューラルネットワークのアーキテクチャ。LSTMにより時系列を遡れば遡ぼるほど勾配が消失していくというRNNの課題を解決することができる。

## LSTMの構造
　LSTMには3つのゲートがある。
　忘却ゲート：現在の入力と前の状態を受け取り、どの情報を保持するかを決定するゲート
　入力ゲート：現在の入力と前の状態を受け取り、新しい情報をどの程度保持するかを決定するゲート
　出力ゲート：現在の入力と前の状態を受け取り、次の状態をどの程度出力するかを決定するゲート
![LSTMの全体図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3116479/8dca0224-09d8-81a8-f5fb-3b0a178d1c1f.png)

## 実装
TensorFlowにLSTMレイヤーがあります。
```py
import tensorflow as tf
from tensorflow.keras.layers import LSTM, Dense
from tensorflow.keras.models import Sequential

# LSTMモデル
def lstm_model(input_shape):
    model = Sequential()
    model.add(LSTM(50, activation='tanh', input_shape=input_shape))
    model.add(Dense(1, activation='linear'))
    model.compile(optimizer='adam', loss='mse')
    return model
```
スクラッチ実装
```py
# LSTMレイヤの実装
class LSTM:
    # 初期化メソッドの定義
    def __init__(self, Wx, Wh, b):
        self.params = [Wx, Wh, b] # パラメータ
        self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)] # 勾配
        self.cache = None

    # 順伝播メソッドの定義
    def forward(self, x, h_prev, c_prev):
        # パラメータと変数の形状に関する値を取得
        Wx, Wh, b = self.params
        N, H = h_prev.shape

        # 結合したパラメータによる重み付き和の計算
        A = np.dot(x, Wx) + np.dot(h_prev, Wh) + b

        # 各ゲートの重み付き和を取得
        f = A[:, :H]      # forgetゲート
        g = A[:, H:2*H]   # 記憶セル
        i = A[:, 2*H:3*H] # inputゲート
        o = A[:, 3*H:]    # outputゲート

        # ゲート値に変換
        f = sigmoid(f)
        g = np.tanh(g)
        i = sigmoid(i)
        o = sigmoid(o)

        # 出力を計算
        c_next = f * c_prev + g * i  # 記憶セル
        h_next = o * np.tanh(c_next) # 出力データ

        # 逆伝播の計算用に変数を保存
        self.cache = (x, h_prev, c_prev, i, f, g, o, c_next)
        return h_next, c_next

    # 逆伝播メソッドの定義
    def backward(self, dh_next, dc_next):
        # 変数を取得
        Wx, Wh, b = self.params
        x, h_prev, c_prev, i, f, g, o, c_next = self.cache

        # 計算用に活性化記憶セルを計算
        tanh_c_next = np.tanh(c_next)

        # 現レイヤの記憶セルの勾配を計算
        ds = dc_next + (dh_next * o) * (1 - tanh_c_next ** 2)

        # 前レイヤの記憶セルの勾配を計算
        dc_prev = ds * f

        # 活性化後のゲートの勾配を計算
        di = ds * g
        df = ds * c_prev
        do = dh_next * tanh_c_next
        dg = ds * i

        # 活性化前のゲートの勾配を計算
        di *= i * (1 - i)
        df *= f * (1 - f)
        do *= o * (1 - o)
        dg *= (1 - g ** 2)

        # ゲートの勾配を結合
        dA = np.hstack((df, dg, di, do))

        # パラメータの勾配を計算
        dWh = np.dot(h_prev.T, dA)
        dWx = np.dot(x.T, dA)
        db = dA.sum(axis=0)

        # パラメータの勾配を格納
        self.grads[0][...] = dWx
        self.grads[1][...] = dWh
        self.grads[2][...] = db

        # 入力の勾配を計算
        dx = np.dot(dA, Wx.T)
        dh_prev = np.dot(dA, Wh.T)

        return dx, dh_prev, dc_prev
```

# GRU
## GRUとは
　GRUとは、深層学習における一種の再帰型ニューラルネットワーク。LSTMと同様に、シーケンスデータに対するモデリングに適していて、パラメータが多く計算負荷が大きかったLSTMの課題点を解決するために作られた。GRUはパラメータ量がLSTMよりも少ないが、精度はLSTM同等かそれ以上になった。

　GRUにはリセットゲート、更新ゲートの2つのゲートがある。
![GRUの全体像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3116479/6cbf1a4c-13e9-ca3c-73e2-4d3fbbd9b23e.png)

## 実装
TensorFlowにGRUレイヤーがあります。
```py
import tensorflow as tf
from tensorflow.keras.layers import GRU, Dense
from tensorflow.keras.models import Sequential


def gru_model(input_shape):
    model = Sequential()
    model.add(GRU(50, activation='tanh', input_shape=input_shape))
    model.add(Dense(1, activation='linear'))
    model.compile(optimizer='adam', loss='mse')
    return model
```
スクラッチ実装
```py
def gru(x, h, W_r, U_r, W_z, U_z, W, U):
  # ゲートを計算
  # リセットゲート
  r = _sigmoid(x.dot(W_r.T) + h.dot(U_r.T)) 
  # 更新ゲート
  z = _sigmoid(x.dot(W_z.T) + h.dot(U_z.T)) 

  # 次の状態を計算
  h_bar = np.tanh(x.dot(W.T) + (r * h).dot(U.T)) 
  h_new = (1-z) * h + z * h_bar  
  return h_new
```

## 参考
　ゼロから作るDeepLearning2 付録C　GRU
　【脱初心者】GRUの数式とあの図を攻略！超絶分かりやすい図解でステップ解説【E資格合格者のノート公開】　https://i-main.net/emmanote-ai-gru/

# 双方向RNN（BiRNN）
## BiRNNとは
　RNNを２つ組み合わせることで、未来から過去方向も含めて学習できるようにしたモデル。通常のRNNは過去から未来への一方向でしか学習をすることができないが、BiRNNによって未来の情報からの学習も可能になった。文章の推敲や機械翻訳に用いられている。

## 実装
```py
def bindirectional_rnn_net(xs, W_f, U_f, W_b, U_b, V):
  # W_f, U_f：入力から中間層、前の中間層から今の中間層の順方向の重み
  # W_b, U_b：W_f, U_fの逆方向
  # V：順方向、逆方向の中間層から出力層の重み
  xs_f = np.zeros_like(xs)
  xs_b = np.zeros_like(xs)

  for i, x in enumerate(xs):
    xs_f[i] = x
    xs_b[i] = x[::-1]

  hs_f = _rnn(xs_f, W_f, U_f)
  hs_b = _rnn(xs_b, W_b, U_b)
  hs = [np.concatenate([h_f, h_b[::-1]], axis=0)] for h_f, h_b in zip(hs_f, hs_b)
  ys = hs.dot(V.T)
  return ys
```

## 参考
ゼロから作るDeepLearning2 8章 4-1 双方向RNN

# Seq2Seq
## Seq2Seqとは
自然言語処理において、入力と出力の長さが異なるシーケンスを扱うためのモデル。エンコーダとデコーダという2つのニューラルネットワークを使用している。
## Encoder部分
　Encoder部分では、入力されたシーケンスデータを単語の埋め込み表現に変換し、RNNを使用してシーケンスを表す隠れ状態を生成する。隠れ状態は、最終的に固定長のベクトルに変換される。

## Decoder部分
　Decoder部分では、エンコーダーが生成したベクトルを元に、出力となる文章を生成する。

# Attention
## Attentionとは
　入力された情報のうち、重要な情報に焦点を当てて処理するための仕組み。通常、Seq2SeqモデルやTransformerモデルなどの自然言語処理タスクで使用される。
　現在注目を浴びているChatGPTにもAttention機構が使用されている。

## 実装

```py
def scaled_dot_product_attention(q, k, v, mask):
  matmul_qk = tf.matmul(q, k, transpose_b=True)  # (..., seq_len_q, seq_len_k)

  # matmul_qkをスケール
  dk = tf.cast(tf.shape(k)[-1], tf.float32)
  scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)

  # マスクをスケール済みテンソルに加算
  if mask is not None:
    scaled_attention_logits += (mask * -1e9)  

  # softmax は最後の軸(seq_len_k)について
  # 合計が1となるように正規化
  attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)  # (..., seq_len_q, seq_len_k)

  output = tf.matmul(attention_weights, v)  # (..., seq_len_q, depth_v)

  return output, attention_weights
```

## 参考
言語理解のためのTransformerモデル
https://www.tensorflow.org/tutorials/text/transformer?hl=ja#%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%AB%E6%B8%88%E3%81%BF%E5%86%85%E7%A9%8D%E3%82%A2%E3%83%86%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%B3

作って理解する Transformer / Attention
https://qiita.com/halhorn/items/c91497522be27bde17ce
より