RNNとLSTMに関してまとめてみました。pythonのコピペ用のコードも載せてあります。(2024年2月更新)。使うだけなら簡単ですが、軽く中身を知っているだけでよりうまく使えると思いますので、ぜひ読み物としても見ていってください。
RNN
概要
「再帰型ニューラルネットワーク」と呼ばるもので、時系列データや音声データなどの過去の情報が大事になってくるようなデータに強力なモデルとなっている。
過去に入力された単語列から次に来る単語を予測するなどのパターンを認識するように設計されたニューラルネットワークのモデルともいえる。
<特徴>
テキストや音声などの入出力が可変長の系列データを扱う場合、通常のNNは固定長に変換する場合が多く、情報が失われがちである。
↓
RNNは、可変長の入力を扱える+入力系列の要素間に存在する依存性を扱うことができる
※それまでに入力された系列を考慮した予測が可能
入力系列の要素間に存在する依存性を見るために、過去に入力された系列の情報を保持する機能である、「隠れ状態」(hidden state)をもつ。
※隠れ状態は固定長で、過去に入力した系列の情報が圧縮されいている。
時点tでの隠れ状態$h_t$は1つ前の時点における隠れ状態$h_{t-1}$と現在の入力$x_t$で計算する。最もシンプルなRNNはtanh層1つでできている。以下にイメージ図を載せる。
時間軸に沿ったデータを古い順に入力し。逆伝搬する誤差も過去に遡って反映するため、勾配降下法を用いるが、時間軸に沿って誤差を反映するため、BackPropagation Through-Timeと呼ばれてもいる。
RNNの構築と学習を行う。
RNNのコード(分類)
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense,Input,Embedding,SimpleRNN, GlobalMaxPooling1D
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing.sequence import pad_sequences
from sklearn.metrics import f1_score, precision_score, recall_score
from sklearn.model_selection import train_test_split
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Embedding
from tensorflow.keras.layers import InputLayer
from tensorflow.keras import optimizers
from tensorflow.keras import layers
from tensorflow import keras
from tensorflow.keras import regularizers
word_index = tokenizer.word_index
num_words = len(word_index)
print(num_words)
input_dim = num_words+1 # 入力データの次元数:単語数いれればいい?(把握しきれていない…)
emb_dim = 300
output_dim = 2 # 出力データの次元数:クラス分
num_hidden_units = 64 # 隠れ層のユニット数
batch_size = 128 # ミニバッチサイズ
epochs = 100 # 学習エポック数
def RNN_model():
model = Sequential()
model.add(InputLayer(input_shape=(None,), name='input'))
# Embeddingによりベクトルを変換する
model.add(Embedding(
input_dim=input_dim, # 入力として取り得るカテゴリ数(パディングの0を含む)# vocabulary_size
output_dim=emb_dim, # 出力ユニット数(本来の特徴量の次元数)
# weights=[embedding_matrix], # 埋め込み行列を指定
trainable=True, # 埋め込み行列を固定(学習時に更新)
mask_zero=True)) # 0をパディング用に特別扱いする
#ここがRNN層である。
model.add(SimpleRNN(
num_hidden_units,
return_sequences=False,name="rnn"))
model.add(Dense(100, kernel_regularizer=regularizers.l1_l2(l1=0.01, l2=0.01), activation="relu"))
model.add(layers.Dropout(0.25))
model.add(Dense(output_dim, activation="softmax"))
adam=optimizers.Adam(learning_rate=0.001, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.0, amsgrad=False)
model.compile(optimizer=adam,
loss='sparse_categorical_crossentropy',
metrics=['acc'])
model.summary()
return model
# Preparing callbacks.
model=RNN_model()
callbacks = [
EarlyStopping(patience=3)
]
seed_everything(42)#seed値を固定にするもの:しらべてみてね
# Train the model.
history=model.fit(x=X_train,
y=train_label,
batch_size=batch_size,
epochs=epochs,
validation_split=0.2,
callbacks=callbacks,
shuffle=True)
LSTM
RNNの欠点
時系列データを解析・分析する際にRNNで十分なのでは?と思ったりしたことはないでしょうか?しかし、RNNには欠点があります。
勾配消失問題
誤差を逆伝播する際、過去に遡るにつれて勾配が消えていってしまうという問題が発生
時系列を扱う上での固有問題-重み衝突
NNの重みは、重要なものは大きく、不必要なものを小さくするべきである。
- 入力重み衝突問題
時系列データを扱う場合は、「今の時点では関係ないけれど、将来の時点では関係ある」という入力が与えられた際の、重み大きさは大きくしたいが、長期的な特徴をもつのか短期的な特徴を持つのか分からない状態において、重みを大きくすべきか小さくすべきか決定できない問題が発生します。
- 出力重み衝突問題
セル状態の出力が重み等を介してセル状態の入力として再度伝わるために、「出力」でも「次の時点以降」を考慮しなければいけません。よって出力も同様に、長期的な特徴をもつのか短期的な特徴を持つのか分からない状態において、重みを大きくすべきか小さくすべきか決定できない問題が発生します。
RNNの欠点まとめ
RNNは、入出力を等しく記憶しているために、長期的な依存性を考慮することを苦手としているモデルとなっている。
↓
LSTMは重要なことを記憶し、いらない情報は忘れることができるため、RNNよりも長期の依存性を考慮できるモデルとなっている。
LSTM
勾配消失問題を解決するためや重み衝突といった問題に関してを、LSTMでは、隠れ層の構造を変えることで同様に問題を解決している。
内部構造がRNNとは異なり、LSTMは4つの層から構成されている。
LSTMの詳細と4つの層について
LSTMには、セル状態$C_t$と隠れ状態$h_t$が存在している。
セル状態:情報を忘れさせたり、記憶させたりする仕組み
→その仕組みこそが後述する「ゲート」と呼ばれるもの、CEC(Constant Error Carousel)とも言われ、誤差を内部にとどめ、勾配消失を防ぐものである。
忘却ゲート(Forget Gate)
セル状態のどの部分を忘れさせるかを計算する層:誤差が過剰にセルに残るのを防ぐために、リセットの役割を果たしている
- 入力:前の時刻の隠れ状態$h_t$と入力$x_t$
- 重みをかける
- シグモイド関数で変換(忘れさせたい情報は0に近く、覚えておきたい値に1に近い値になる)
入力ゲート(Input Gate)
入力をどれだけ覚えさせるかを計算する層:入力重み衝突のためのゲート
- 入力:前の時刻の隠れ状態$h_{t-1}$と$入力x_t$
- シグモイド層:どの値をどれだけ更新するかを決めるベクトルを作製
- tanh層:セル状態に覚えさせるベクトルを作製
- 2と3で計算した要素積を計算し、セル状態に覚えさせるベクトルを作製
セル状態の更新
忘却ゲートと入力ゲートの2つで古いセル状態$C_{t-1}$に忘れさせるベクトルと覚えさせるベクトルを作製が完了した。
↓
忘却ゲートからの出力ベクトルと古いセル状態$C_{t-1}$の要素積
+
覚えさせるベクトルを加算しセル状態の更新
↓
セル状態の更新完了
出力ゲート(Output Gate)
どれだけ出力するかを計算する層:出力重み衝突のためのゲート
入力:前の時刻の隠れ状態$h_{t-1}$と入力$x_t$
↓
シグモイド層:どの部分をどれだけ出力するか決める
×
更新したセル状態に対してtanhを演算
↓
出力
簡易コード(分類する場合)
以下のような感じでLSTMの構築と学習を行う。
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense,Input,Embedding,LSTM, GlobalMaxPooling1D
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing.sequence import pad_sequences
from sklearn.metrics import f1_score, precision_score, recall_score
from sklearn.model_selection import train_test_split
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Embedding
from tensorflow.keras.layers import InputLayer
from tensorflow.keras import optimizers
from tensorflow.keras import layers
from tensorflow import keras
from tensorflow.keras import regularizers
word_index = tokenizer.word_index
num_words = len(word_index)
print(num_words)
input_dim = num_words+1 # 入力データの次元数:単語数いれればいい?(把握しきれていない…)
emb_dim = 300
output_dim = 2 # 出力データの次元数:クラス分
num_hidden_units = 100 # 隠れ層のユニット数
batch_size = 128 # ミニバッチサイズ
epochs = 100 # 学習エポック数
def LSTM_model():
model = Sequential()
model.add(InputLayer(input_shape=(None,), name='input'))
# Embeddingによりベクトルを変換する
model.add(Embedding(
input_dim=input_dim, # 入力として取り得るカテゴリ数(パディングの0を含む)
output_dim=emb_dim, # 出力ユニット数(本来の特徴量の次元数)
# weights=[embedding_matrix], # 埋め込み行列を指定
trainable=True, # 埋め込み行列を固定(学習時に更新)
mask_zero=True)) # 0をパディング用に特別扱いする
model.add(LSTM(
num_hidden_units,
return_sequences=False,name="lstm"))
model.add(Dense(100, kernel_regularizer=regularizers.l1_l2(l1=0.01, l2=0.01), activation="relu"))
model.add(layers.Dropout(0.25))
model.add(Dense(output_dim, activation="softmax"))
adam=optimizers.Adam(learning_rate=0.001, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.0, amsgrad=False)
model.compile(optimizer=adam,
loss='sparse_categorical_crossentropy',
metrics=['acc'])
model.summary()
return model
# Preparing callbacks.
model=LSTM_model()
callbacks = [
EarlyStopping(patience=3)
]
seed_everything(42)#seed値を固定にするもの:しらべてみてね
# Train the model.
history=model.fit(x=X_train,
y=train_label,
batch_size=batch_size,
epochs=epochs,
validation_split=0.2,
callbacks=callbacks,
shuffle=True)
動画で勉強する方法
コードがメインのこの記事ですが、中身を軽くは抑えるべきだと個人的には思っています。しかし、その場合、文章が長い本や記事を読まないといけません、、、。しかし、UdemyやYoutube(信憑性は不明)にたくさん講座があります。1例を下に貼っておきます。
↓クリック
こういった動画を一気に流し見して軽く理解をしてからこの記事などを見ると理解度が一気に上がると思いますので是非。
まとめ
RNNとLSTMについて簡単にまとめてみました。使うだけでしたら層をコピペするだけですので、使ってみてください。
技術系(偏りあり)をメインに、雑記系も書いています。モチベーションにもつながりますので、良かったら是非。
ChatGPTについて
大規模言語モデル等の台頭により、LSTMなどを学ぶことも大事ですが新しい技術を取り入れることも大事になっていると考えているため、最近学んだChatGPTでどういう文章を送ればよい答えが返ってくるかということをまとめてみました。
コメント