はじめに
この記事はE資格の認定講座で提出したレポートをもとに、代表的なRNNモデルとAttentionについてまとめた記事です。
再帰的ニューラルネットワーク(RNN)とは
RNNとは、時系列データに対して有効なニューラルネットワークの一つ。従来のニューラルネットワークとは異なり、自己回帰的な構造を持ち、主に音声認識、自然言語処理で使用される。
TensorFlowにSimpleRNNレイヤーがあります。
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つのゲートがある。
忘却ゲート:現在の入力と前の状態を受け取り、どの情報を保持するかを決定するゲート
入力ゲート:現在の入力と前の状態を受け取り、新しい情報をどの程度保持するかを決定するゲート
出力ゲート:現在の入力と前の状態を受け取り、次の状態をどの程度出力するかを決定するゲート
実装
TensorFlowにLSTMレイヤーがあります。
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
スクラッチ実装
# 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同等かそれ以上になった。
実装
TensorFlowにGRUレイヤーがあります。
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
スクラッチ実装
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を2つ組み合わせることで、未来から過去方向も含めて学習できるようにしたモデル。通常のRNNは過去から未来への一方向でしか学習をすることができないが、BiRNNによって未来の情報からの学習も可能になった。文章の推敲や機械翻訳に用いられている。
実装
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機構が使用されている。
実装
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
より