LSTMネットワークの詳細解説:原理、構造分析、数学的導出、実装コード

一、LSTMの発展背景

LSTM(Long Short Term Memory Network)は、Hochreiter & Schmidhuber(文献引用:[Hochreiter, S, and J. Schmidhuber. "Long short-term memory." Neural Computation 9.8(1997):1735-1780])によって1997年に初めて提案されました。その後、Felix Gers、Fred Cummins、Santiago Fernandez、Justin Bayer、Daan Wierstra、Julian Togelius、Faustino Gomez、Matteo Gagliolo、Alex Glovesらの学者たちによる発展を経て、現在の体系的で完全なLSTMフレームワークが形成されました。本記事で紹介するのは、現在のディープラーニング時代のLSTMです。これは、シーケンスデータ処理において優れた性能を発揮するだけでなく、通常の再帰型ニューラルネットワーク(RNN)が解決困難な長期依存問題も解決できます。

二、LSTMの基本構造と原理

2.1 基本原理の概要

LSTMの基本原理は、複雑に見えますが、簡単に分解すると理解しやすいです。以下の3つの部分に分けて理解できます:

A:これらはLSTMのマクロ構造です(時間軸Tに沿ってT個のBPニューラルネットワークが並んでおり、隠れ層間の状態(重みパラメータ)が伝達可能です)

1. 時間軸に垂直なLSTMネットワーク:各タイムステップごとに、入力層-隠れ層-出力層で構成されるBPニューラルネットワークがあります(これは単純なBPニューラルネットワークです!)

2. 時間軸に沿った履歴情報の伝達経路:隠れ層から伝達される隠れ層状態h(重みパラメータ行列)(RNNと同じ)と細胞状態c(重みパラメータ行列)(これがLSTMの特別な点です)(このステップが、LSTMのような再帰型ニューラルネットワークが長期依存の時間系列データを処理できる理由の一つです)

B:これはLSTMのユニット構造です

3. 単一のLSTMユニットの構造:LSTMの最も重要な機能は、この小さなユニット内(隠れ層のニューロン内)にあります。これがLSTMの本質です

以下で、これら3つの部分をそれぞれ詳しく見ていきましょう:

2.2 LSTMの全体構造(マクロ的な視点)

下図のLSTM構造図からわかるように:各time_stepsには、入力層+隠れ層+出力層で構成されるBPニューラルネットワークがあり、これがLSTM構造の重要な部分です。コードと併せて構造を分析します:

モデル構築プロセスはKerasを使用しています。これは以前はわからなかったのですが、以下の先人のブログを参考にしました:(出典を明記していますので、ご自身でも学習可能です)

LSTM ネットワーク構造の分析(Keras) - 知乎 (zhihu.com)

import tensorflow as tf
from tensorflow import keras
from keras.optimizers import Adam

# 二層単一出力LSTM
def create_lstm_model(hidden_units):
    model = keras.Sequential()  # 空のSequentialモデルをまず作成し、そこに層を段階的に追加します
    
    # 入力層
    # kerasのlayers関数を使用してモデル層を構築します
    # kerasの利点は、モデルの構造、各層の活性化関数、入出力形式、およびその他の情報を明確に明示的に定義できる点です
    model.add(keras.layers.LSTM(
        units=hidden_units,  # この層のニューロン数を定義
        activation="relu",  # 活性化関数を定義。定義しない場合はデフォルトで"tanh"です。要件と実験結果に基づいて定義することが推奨されます
        return_sequences=True,  # デフォルトは"False"。Trueに設定すると、この層は各タイムステップの隠れ状態を出力します。これはLSTM層をスタックするために必要です
                               # つまり、注意!!!次の層もLSTM層である場合!層のスタックを保証するために、この層の各タイムステップは出力され、次のLSTM層の入力となります。
                               # 多層LSTMにとって非常に重要です。一つのLSTM層を別のLSTM層に接続し、異なるタイムステップ間で情報が伝達できるようにします。
        input_shape=(X_train.shape[1], X_train.shape[2])  # 入力データの形式を定義(times_step, features)
                                                         # ここではX_trainのデータ形式はテンソルであり、(n_samples, times_step, feature)
                                                         # したがって、X_train.shape[0]はn_samples、以下同様です
                                                         # 最初の層で定義すれば、後の層では入力形状を調整する必要はありません。前の層の出力形状に基づいて自動的に調整されます
        ))
    model.add(keras.layers.Dropout(0.2))  # ドロップアウト層を定義し、20%のニューロンをドロップアウトして過学習を防止します
    
    # 隠れ層
    # 同様にkerasのlayersを使用してモデル層を構築します
    model.add(keras.layers.LSTM(
        units=hidden_units,  # この層のニューロン数を定義
        activation="relu",  # 活性化関数を定義。定義しない場合はデフォルトで"tanh"です。要件と実験結果に基づいて定義することが推奨されます
        return_sequences=False,  # デフォルトは"False"。最後のタイムステップの出力のみを返します。次の層はDense出力層であるため、Dense層は固定サイズの入力を期待します。
                                # したがって、完全なシーケンスではなく、次のDense層に直接接続できます。
        ))
    model.add(keras.layers.Dropout(0.2))  # ドロップアウト層を定義し、20%のニューロンをドロップアウトして過学習を防止します
    
    # 出力層
    # ここでは、各times_stepsの出力が一つの値(ストレス)であることを期待しているため、Dense層を使用してこの入力を受け取り、
    # 必要な最後のタイムステップの予測値を直接出力します
    model.add(keras.layers.Dense(units=1))  # ここでunit=1と設定しているのは、このDense層を出力層として使用し、直接一つの次元の予測連続値を出力するためです。
                                          # 活性化関数は設定していません。これは連続予測値を必要とするためで、回帰のような効果を持ちます。線形活性化関数(デフォルト)を使用します。
                                          # 二値分類問題の場合はsigmoid関数を追加し、多クラス分類問題の場合はsoftmax関数を追加できます。詳細はご自身で学習してください。
    
    model.compile(loss='mse', optimizer=Adam(learning_rate=0.0001, clipvalue=0.2))  # モデルをコンパイルして構築します。
                                                                                     # 損失関数はmseで、回帰問題でよく使用されます。
                                                                                     # 最適化アルゴリズムはAdam最適化アルゴリズムで、学習率は0.0001です。
                                                                                     # そして、勾配クリッピングの閾値を0.2に設定して勾配の大きさを制御します。
    
    model.summary()  # モデル構造の概要を表示します:各層の名前、出力形状、および学習可能なパラメータの数を含みます。
    return model

上記のコードは、二層のLSTM+一層のDenseからなるLSTMモデル構造を定義しています。下の図を参考にして直感的な理解を得てください。どのパラメータがわからない場合は、さらに詳しく調べてください(注意:決して無秩序に行動しないでください。習慣として、モデルを構築した後、モデル構造の概要を確認するようにしてください!すべてのパラメータはその機能を詳細に学び、メモを取って記録してください!さもなければ、原理や構造、コードのパラメータの詳細を十分に理解せずに、なぜそれを使用できるのでしょうか?必ず覚えておいてください!)

上図からもわかるように、LSTMがタイムステップ(time steps)に沿って進行するにつれて、隠れ層(hidden layers)の進行方向の出力には、隠れ層状態h(このタイムステップの隠れ層重みベクトル)だけでなく、細胞状態cもあります。 この細胞状態cは、歴史的な有効情報を保存する役割を果たします。これがLSTMが長期依存のシーケンスデータを処理でき、長期記憶を持つ理由です!

では、この神秘的なユニットの構造を解説します!

2.3 LSTMのユニット構造(ミクロ的な視点)

下図はLSTMユニットの内部構造の図示です。わかるように:

細胞状態c(cell)は、歴史的な情報を保存するためのストレージのようなもので、その情報の入出力を制御する3つのゲートメカニズムがあります。これらの3つのゲートは、忘却ゲート(forget gate)、入力ゲート(input gate)、出力ゲート(output gate)です; 簡単に言うと、以下のような構造です:

これらのスイッチは、どのようにアルゴリズムで実現されているのでしょうか?これがゲート(gate)の概念が使われます。ゲートは、実際には全結合層で、その入力はベクトルであり、出力は0から1の間の実数ベクトルです。Wをゲートの重みベクトル、bをバイアス項とすると、ゲートは以下のように表現できます:

ゲートの使用は、ゲートの出力ベクトルを要素ごとに乗算して、制御したいベクトルと乗算することです。ゲートの出力は0から1の間の実数ベクトルであるため、ゲートの出力が0の場合、どのベクトルと乗算しても0ベクトルになります。これは、何も通過できないことを意味します;出力が1の場合、どのベクトルと乗算しても何も変化しません。これは、何でも通過できることを意味します。δ(シグモイド関数)の値域が(0,1)であるため、ゲートの状態は半開半閉です。

LSTMでは、2つのゲートで細胞状態cの内容を制御します一つは忘却ゲート(forget gate)で、前のタイムステップの細胞状態が現在のタイムステップctにどれだけ保持されるかを決定します;もう一つは入力ゲート(input gate)で、現在のタイムステップのネットワーク入力xtが細胞状態ctにどれだけ保存されるかを決定しますLSTMは出力ゲート(output gate)を使用して、細胞状態ctがLSTMの現在の出力値htにどれだけ出力されるかを制御します。したがって、2つのゲートがCtの内容を決定し、1つのゲートがCtが出力できる内容を決定します。これらの2つの概念は異なります!

2.3.1 ユニット内部構造--動的図による理解

1. 忘却ゲート(forget gate)の役割は、前のタイムステップの隠れ層状態が現在のタイムステップにどれだけ保持されるかを決定することです(過去の記憶を保持する)

2. 入力ゲート(input gate)の役割は、現在のタイムステップのネットワーク入力が細胞状態にどれだけ保存されるかを決定することです(現在の経験を大切にする)出力ゲート(output gate)は、現在のタイムステップの細胞状態がLSTMの現在の隠れ層状態htにどれだけ保存されるかを制御するために使用されます(未来に希望を残す)

3. 細胞状態cellの更新(前のタイムステップのCt-1と忘却ゲートの点乗算、および入力ゲートの加算によって、現在のタイムステップのCtに更新されます)

4. 出力ゲート(output gate)は、現在のタイムステップの細胞状態CtがLSTMの現在の隠れ層状態htにどれだけ保存されるかを制御するために使用されます(未来に希望を残す)

2.3.2 ユニット内部構造--数式による導出理解

以下で、これらの3つのゲートとLSTMユニット内の詳細を数式で図解して導出します:

まず、ユニット内の数式を示します:(典型的なニューラルネットワークの出力:出力=活性化関数(重み*[入力])

ここで:f_t, i_t, C_t, o_t, h_tはそれぞれ、現在のタイムステップの忘却ゲート(forget)、入力ゲート(input)、細胞状態(cell)、出力ゲート(output)、隠れユニットベクトル(hidden)を表します。対応するWの下付き文字は、それぞれの重みパラメータです(補足:重みパラメータは、ほとんどのニューラルネットワークモデルで学習するための最も重要なパラメータであり、学習、調整、調整を通じて最適な重み行列パラメータを得るためにあります)

上記の数式をまとめた図:(ここではある先人の書いたLSTMの図解を引用しています——一文でLSTMを完全に理解する——CSDNブログ、非常に優れています。出典を明記していますので、元のブログをご覧ください!)

数式の分解:

したがって、忘却ゲートと出力ゲートは、点乗算のゲート制御操作を行います。0を掛けると、それを忘れて不要にします/1を掛けると、出力して行かせます(忘却ゲートは以前のタイムステップの不必要なデータを制御し、出力ゲートはCtが出力できるデータを制御します)入力ゲートだけが、実際の情報融合を行い、この時点の入力xと前のタイムステップの出力ht-1を処理して加算融合します。perfect!

OKOK、ここまで来れば、皆さんはLSTMユニットの内部構造の動作メカニズムと底層の数学的ロジックを理解できたはずです!wowowowowowo!

三、LSTMモデルの構築とトレーニングコード

コードを示す前に、LSTMのデータ構造について皆さんと議論したいと思います。周知の通り、**LSTMの入力データは3Dのテンソルです(テンソルとは何かについては、このブロガーのブログを詳しくご覧ください:**ノート | テンソル(tensor)とは何か & 深層学習 - 知乎 (zhihu.com)

要するに、lstmのデータ形式は以下の図のようなものです:(n_samples, time_steps, features);サンプル数、タイムステップ長、サンプル特徴数で構成される3Dテンソルです。明らかに、LSTMがどのように段階的に前進学習するかも容易に想像できます。

理解しやすくするために、spyderの変数ブラウザでコード内のLSTMのデータ構造をもう一度ご覧ください:明らかに、私の入力データXはすべて3Dのテンソルです。x_testを例にすると:サンプル数が142336、タイムステップ数が10、特徴数が15で、私のlstmネットワークの入力データを構成しています。予測するデータyは一つの次元のテンソルです。なぜなら、私の予測結果は一連のデータだからです。

lstmがどのように進行するかについては、x_testのデータ構造を開いてご覧ください:左下の赤で囲んだところがx_testのshapeです。それは(142356, 10, 15)の3Dテンソルです。軸の選択は0/1/2で、これらの3つの次元のどれを軸としてデータを表示するかを選択できます(毕竟三维数据在这里只能平面展开啦!);隣のものは、この軸でデータを展開するためのインデックスです。ここではサンプル数を軸としており、インデックス0が最初の入力データ、1が2番目の入力データ、2が3番目の入力データです。はっきりとわかります:lstmの場合、各入力データ(つまり、各一定期間の入力)は以下のようなデータであり、入力ごとにBPネットワーク内に入力され、その後さらに前進し、一度に一つのタイムステップずつ進むことで、シーケンスデータ全体を学習する能力を達成します。 どうですか?非常にniceではないでしょうか!

以下にLSTMの進行における入力データ構造の図示を作成しました。皆さんが理解できることを願っています!(私のテストデータは少し特殊で、最初の数百行は最初の列だけが異なります。したがって、インデックス検索に従って、最初の列のデータのみが段階的に変化しているのがわかります)

id="jN2AhHOK-1713060367141" frameborder="0" src="https://live.csdn.net/v/embed/378674" allowfullscreen="true" data-mediaembed="csdn"> 2024-04-14 10-01-57 - Trim

ここでは、すでにKerasを使用したモデル構築のコードを詳細に示しました。ここでは、トレーニングコードを簡単に補足します:

import numpy as np
from numpy import savetxt
import pandas as pd
from pandas.plotting import register_matplotlib_converters
from pylab import rcParams
import matplotlib.pyplot as plt
from matplotlib import rc
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler, StandardScaler
from sklearn.metrics import r2_score
import tensorflow as tf
from tensorflow import keras
from keras.optimizers import Adam, RMSprop
import os

# GPU設定
tf.config.set_visible_devices(tf.config.list_physical_devices('GPU'), 'GPU')

# matplotlib設定
register_matplotlib_converters()
plt.rcParams["font.sans-serif"] = [""]  # デフォルトフォントを指定
pd.set_option('display.max_columns', None)  # 結果にすべての列を表示
pd.set_option('display.max_rows', None)  # 結果にすべての行を表示

# データ前処理
# 1. トレーニングセット(New-train)データ処理
source = 'New-train.csv'
df_train = pd.read_csv(source, index_col=None)
df_train = df_train[['設定するデータテーブルヘッダー']] 

source = 'New-test.csv'
df_test = pd.read_csv(source, index_col=None)
df_test = df_test[['設定するデータテーブルヘッダー']] 

train_size = int(len(df_train))
test_size = int(len(df_test))
train = df_train.iloc[0:train_size]
test = df_test.iloc[0:test_size]
print(len(train), len(test))

def prepare_training_data(X, y, time_steps=1):
    Xs, ys = [], []
    for i in range(len(X) - time_steps):
        v = X.iloc[i:(i + time_steps)].values
        Xs.append(v)
        ys.append(y.iloc[i + time_steps])
    return np.array(Xs), np.array(ys)


time_steps = 10
X_train, y_train = prepare_training_data(train.loc[:, '設定するデータテーブルヘッダー'], train.*, time_steps)
x_test, y_test = prepare_training_data(test.loc[:,'設定するデータテーブルヘッダー'], test.*, time_steps)


# モデル構築
def build_lstm_model(hidden_units):
    model = keras.Sequential()
    model.add(keras.layers.LSTM(units=hidden_units, activation="relu", return_sequences=True, 
    input_shape=(X_train.shape[1], X_train.shape[2])))
    model.add(keras.layers.Dropout(0.2))
    model.add(keras.layers.LSTM(units=hidden_units, activation="relu"))
    model.add(keras.layers.Dropout(0.2))
    model.add(keras.layers.Dense(1))  # この行のコードは、Kerasモデルに1つのニューロンを持つDense層を追加し、単一の連続出力値(strain)を生成します。
    model.compile(loss='mean_squared_error', optimizer=Adam(learning_rate=0.0001, clipvalue=0.2))
    return model

# モデルトレーニング
def train_model(model):
    # 早期停止メカニズム、過学習を防止
    early_stop = keras.callbacks.EarlyStopping(
        monitor='val_loss',
        min_delta=0.0,  # min_delta=0.0は、トレーニングプロセス中に指標に改善がない場合、たとえ非常に小さな改善であっても、改善がないと見なされることを意味します
        patience=2000)  # 連続2000エポックで指標がmin_deltaを超える改善がない場合、トレーニングが早期に停止されます
    
    history = model.fit(
        X_train, y_train,  # model.fit()メソッドを呼び出すと、モデルはトレーニングデータに基づいてパラメータを更新し、トレーニングプロセス中に徐々にモデルの性能を最適化します
        epochs=400,         # トレーニングが完了すると、モデルのパラメータはトレーニングプロセス中に得られた最適値に更新されます
        validation_split=0.1,   
        batch_size=12600,
        shuffle=False,
        callbacks=[early_stop])
    return history

lstm_model = build_lstm_model(64)
# ここではトレーニングモデルのコードのみを記述しています。予測については、ご自身のデータ構造と希望する効果に基づいて書く必要があります。

タグ: LSTM ニューラルネットワーク 深層学習 Keras 時系列分析

6月9日 22:30 投稿