web-dev-qa-db-ja.com

ステートフルLSTMおよびストリーム予測

私はLSTMモデル(KerasとTFで構築された)を、3つの機能を持つ7つのサンプルの複数のバッチでトレーニングしました(サンプルの下のような形状(以下の番号は説明のためのプレースホルダーにすぎません))。各バッチには0のラベルが付いていますまたは1:

データ:

[
   [[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
   [[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
   [[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
   ...
]

つまり、要素が3次元ベクトルである長さ7のmシーケンスのバッチ(したがって、バッチは形状(m * 7 * 3)を持ちます)

目標:

[
   [1]
   [0]
   [1]
   ...
]

私の実稼働環境では、データは3つの機能([1,2,3],[1,2,3]...)を持つサンプルのストリームです。モデルに到着した各サンプルをストリーミングして、バッチ全体を待たずに中間確率を取得したいと思います(7)-以下のアニメーションを参照してください。

enter image description here

私の考えの1つは、不足しているサンプル[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[1,2,3]]のバッチに0を埋め込むことでしたが、それは効率が悪いようです。

LSTM中間状態を永続的に保存し、次のサンプルを待ち、部分的なデータを使用して特定のバッチサイズでトレーニングされたモデルを予測するという正しい方向に私を向けてくれる助けに感謝します。


更新、モデルコードを含む:

opt = optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=10e-8, decay=0.001)
model = Sequential()

num_features = data.shape[2]
num_samples = data.shape[1]

first_lstm = LSTM(32, batch_input_shape=(None, num_samples, num_features), return_sequences=True, activation='tanh')
model.add(
    first_lstm)
model.add(LeakyReLU())
model.add(Dropout(0.2))
model.add(LSTM(16, return_sequences=True, activation='tanh'))
model.add(Dropout(0.2))
model.add(LeakyReLU())
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))

model.compile(loss='binary_crossentropy', optimizer=opt,
              metrics=['accuracy', keras_metrics.precision(), keras_metrics.recall(), f1])

モデルの概要:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
lstm_1 (LSTM)                (None, 100, 32)           6272      
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 100, 32)           0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 100, 32)           0         
_________________________________________________________________
lstm_2 (LSTM)                (None, 100, 16)           3136      
_________________________________________________________________
dropout_2 (Dropout)          (None, 100, 16)           0         
_________________________________________________________________
leaky_re_lu_2 (LeakyReLU)    (None, 100, 16)           0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 1600)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 1601      
=================================================================
Total params: 11,009
Trainable params: 11,009
Non-trainable params: 0
_________________________________________________________________
15
Shlomi Schwartz

もっと簡単な解決策があるかもしれません。

モデルにたたみ込みレイヤーまたは長さ/ステップディメンションに作用する他のレイヤーがない場合は、単に_stateful=True_としてマークできます。

警告:モデルには長さ寸法に作用するレイヤーがあります!!

Flattenレイヤーは、長さの次元をフィーチャーの次元に変換します。これにより、目標を完全に達成できなくなります。 Flattenレイヤーに7つのステップが必要な場合は、常に7つのステップが必要です。

したがって、以下の私の回答を適用する前に、Flattenレイヤーを使用しないようにモデルを修正してください。代わりに、last LSTMレイヤーの_return_sequences=True_を削除できます。

次のコードはそれを修正し、以下の回答で使用するいくつかのことを準備します:

_def createModel(forTraining):

    #model for training, stateful=False, any batch size   
    if forTraining == True:
        batchSize = None
        stateful = False

    #model for predicting, stateful=True, fixed batch size
    else:
        batchSize = 1
        stateful = True

    model = Sequential()

    first_lstm = LSTM(32, 
        batch_input_shape=(batchSize, num_samples, num_features), 
        return_sequences=True, activation='tanh', 
        stateful=stateful)   

    model.add(first_lstm)
    model.add(LeakyReLU())
    model.add(Dropout(0.2))

    #this is the last LSTM layer, use return_sequences=False
    model.add(LSTM(16, return_sequences=False, stateful=stateful,  activation='tanh'))

    model.add(Dropout(0.2))
    model.add(LeakyReLU())

    #don't add a Flatten!!!
    #model.add(Flatten())

    model.add(Dense(1, activation='sigmoid'))

    if forTraining == True:
        compileThisModel(model)
_

これにより、7つのステップでトレーニングし、1つのステップで予測することができます。そうでなければ、それは不可能です。

質問の解決策としてのステートフルモデルの使用

最初に、この新しいモデルにはFlattenレイヤーがないため、再度トレーニングします。

_trainingModel = createModel(forTraining=True)
trainThisModel(trainingModel)
_

これで、このトレーニング済みモデルを使用して、トレーニング済みモデルを作成したのとまったく同じ方法で新しいモデルを作成できますが、すべてのLSTMレイヤーで_stateful=True_をマークします。そして、訓練されたモデルから重みをコピーする必要があります。

これらの新しいレイヤーは固定のバッチサイズ(Kerasのルール)を必要とするため、1(mストリームではなく1つの単一ストリームが来る)であると想定して、上記のモデル作成に追加しました。

_predictingModel = createModel(forTraining=False)
predictingModel.set_weights(trainingModel.get_weights())
_

そして、ほら。シングルステップでモデルの出力を予測するだけです。

_pseudo for loop as samples arrive to your model:
    prob = predictingModel.predict_on_batch(sample)

    #where sample.shape == (1, 1, 3)
_

連続シーケンスと見なすものの最後に達したと判断したら、predictingModel.reset_states()を呼び出します。これにより、モデルが前のシーケンスの最後で修正されるべきだと考えなくても、新しいシーケンスを安全に開始できます。


状態の保存と読み込み

それらを取得して設定し、h5pyで保存します。

_def saveStates(model, saveName):

    f = h5py.File(saveName,'w')

    for l, lay in enumerate(model.layers):
        #if you have nested models, 
            #consider making this recurrent testing for layers in layers
        if isinstance(lay,RNN):
            for s, stat in enumerate(lay.states):
                f.create_dataset('states_' + str(l) + '_' + str(s),
                                 data=K.eval(stat), 
                                 dtype=K.dtype(stat))

    f.close()


def loadStates(model, saveName):

    f = h5py.File(saveName, 'r')
    allStates = list(f.keys())

    for stateKey in allStates:
        name, layer, state = stateKey.split('_')
        layer = int(layer)
        state = int(state)

        K.set_value(model.layers[layer].states[state], f.get(stateKey))

    f.close()
_

状態の保存/読み込みの動作テスト

_import h5py, numpy as np
from keras.layers import RNN, LSTM, Dense, Input
from keras.models import Model
import keras.backend as K




def createModel():
    inp = Input(batch_shape=(1,None,3))
    out = LSTM(5,return_sequences=True, stateful=True)(inp)
    out = LSTM(2, stateful=True)(out)
    out = Dense(1)(out)
    model = Model(inp,out)
    return model


def saveStates(model, saveName):

    f = h5py.File(saveName,'w')

    for l, lay in enumerate(model.layers):
        #if you have nested models, consider making this recurrent testing for layers in layers
        if isinstance(lay,RNN):
            for s, stat in enumerate(lay.states):
                f.create_dataset('states_' + str(l) + '_' + str(s), data=K.eval(stat), dtype=K.dtype(stat))

    f.close()


def loadStates(model, saveName):

    f = h5py.File(saveName, 'r')
    allStates = list(f.keys())

    for stateKey in allStates:
        name, layer, state = stateKey.split('_')
        layer = int(layer)
        state = int(state)

        K.set_value(model.layers[layer].states[state], f.get(stateKey))

    f.close()

def printStates(model):

    for l in model.layers:
        #if you have nested models, consider making this recurrent testing for layers in layers
        if isinstance(l,RNN):
            for s in l.states:
                print(K.eval(s))   

model1 = createModel()
model2 = createModel()
model1.predict_on_batch(np.ones((1,5,3))) #changes model 1 states

print('model1')
printStates(model1)
print('model2')
printStates(model2)

saveStates(model1,'testStates5')
loadStates(model2,'testStates5')

print('model1')
printStates(model1)
print('model2')
printStates(model2)
_

データの側面に関する考慮事項

最初のモデル(_stateful=False_の場合)では、mの各シーケンスは個別であり、他のシーケンスに接続されていないと見なされます。また、各バッチには固有のシーケンスが含まれていると見なされます。

そうでない場合は、代わりにステートフルモデルをトレーニングすることをお勧めします(各シーケンスが実際に前のシーケンスに接続されていることを考慮して)。そして、あなたは1シーケンスのmバッチが必要になります。 -> m x (1, 7 or None, 3)

4
Daniel Möller

私が正しく理解していれば、mシーケンスのバッチがあり、それぞれの長さが7であり、その要素は3次元ベクトルです(したがって、バッチの形状は(m*7*3)です)。すべての Keras RNN では、return_sequencesフラグをTrueに設定して中間状態にすることができます。つまり、すべてのバッチで、最終的な予測ではなく、対応する7つの出力が得られます。出力iは、0からiまでのすべての入力が与えられた場合のステージiでの予測を表します。

しかし、最後に一度にすべてを取得します。私の知る限り、Kerasは、バッチの処理中にスループットを取得するための直接インターフェースを提供していませんCUDNN- optimizedバリアントのいずれかを使用している場合、これはさらに制約を受ける可能性があります。あなたができることは、基本的にバッチを形状(m*1*3)の7つの連続したバッチと見なし、それらをLSTMに徐々にフィードして、各ステップでの隠された状態と予測。そのためには、return_stateTrueに設定して手動で行うか、単にstatefulto Trueを設定してオブジェクトに追跡させることができます。


次のPython2 + Kerasの例は、必要なものを正確に表す必要があります。具体的には:

  • lSTM中間状態全体を永続的な方法で保存できる
  • 次のサンプルを待つ間
  • また、任意で不明な特定のバッチサイズでトレーニングされたモデルを予測します。

そのため、最も簡単なトレーニングのためのstateful=Trueの例と最も正確な推論のためのreturn_state=Trueの例が含まれているため、両方のアプローチのフレーバーが得られます。また、シリアル化されていて、あまり知られていないモデルを取得することも前提としています。この構造は、Andrew Ngのコースの構造と密接に関連しており、このトピックでは私よりも信頼性が高くなっています。モデルのトレーニング方法を指定しないので、多対1のトレーニング設定を想定しましたが、これは簡単に適応できます。

from __future__ import print_function
from keras.layers import Input, LSTM, Dense
from keras.models import Model, load_model
from keras.optimizers import Adam
import numpy as np

# globals
SEQ_LEN = 7
HID_DIMS = 32
OUTPUT_DIMS = 3 # outputs are assumed to be scalars


##############################################################################
# define the model to be trained on a fixed batch size:
# assume many-to-one training setup (otherwise set return_sequences=True)
TRAIN_BATCH_SIZE = 20

x_in = Input(batch_shape=[TRAIN_BATCH_SIZE, SEQ_LEN, 3])
lstm = LSTM(HID_DIMS, activation="tanh", return_sequences=False, stateful=True)
dense = Dense(OUTPUT_DIMS, activation='linear')
m_train = Model(inputs=x_in, outputs=dense(lstm(x_in)))
m_train.summary()

# a dummy batch of training data of shape (TRAIN_BATCH_SIZE, SEQ_LEN, 3), with targets of shape (TRAIN_BATCH_SIZE, 3):
batch123 = np.repeat([[1, 2, 3]], SEQ_LEN, axis=0).reshape(1, SEQ_LEN, 3).repeat(TRAIN_BATCH_SIZE, axis=0)
targets = np.repeat([[123,234,345]], TRAIN_BATCH_SIZE, axis=0) # dummy [[1,2,3],,,]-> [123,234,345] mapping to be learned


# train the model on a fixed batch size and save it
print(">> INFERECE BEFORE TRAINING MODEL:", m_train.predict(batch123, batch_size=TRAIN_BATCH_SIZE, verbose=0))
m_train.compile(optimizer=Adam(lr=0.5), loss='mean_squared_error', metrics=['mae'])
m_train.fit(batch123, targets, epochs=100, batch_size=TRAIN_BATCH_SIZE)
m_train.save("trained_lstm.h5")
print(">> INFERECE AFTER TRAINING MODEL:", m_train.predict(batch123, batch_size=TRAIN_BATCH_SIZE, verbose=0))


##############################################################################
# Now, although we aren't training anymore, we want to do step-wise predictions
# that do alter the inner state of the model, and keep track of that.


m_trained = load_model("trained_lstm.h5")
print(">> INFERECE AFTER RELOADING TRAINED MODEL:", m_trained.predict(batch123, batch_size=TRAIN_BATCH_SIZE, verbose=0))

# now define an analogous model that allows a flexible batch size for inference:
x_in = Input(shape=[SEQ_LEN, 3])
h_in = Input(shape=[HID_DIMS])
c_in = Input(shape=[HID_DIMS])
pred_lstm = LSTM(HID_DIMS, activation="tanh", return_sequences=False, return_state=True, name="lstm_infer")
h, cc, c = pred_lstm(x_in, initial_state=[h_in, c_in])
prediction = Dense(OUTPUT_DIMS, activation='linear', name="dense_infer")(h)
m_inference = Model(inputs=[x_in, h_in, c_in], outputs=[prediction, h,cc,c])

#  Let's confirm that this model is able to load the trained parameters:
# first, check that the performance from scratch is not good:
print(">> INFERENCE BEFORE SWAPPING MODEL:")
predictions, hs, zs, cs = m_inference.predict([batch123,
                                               np.zeros((TRAIN_BATCH_SIZE, HID_DIMS)),
                                               np.zeros((TRAIN_BATCH_SIZE, HID_DIMS))],
                                              batch_size=1)
print(predictions)


# import state from the trained model state and check that it works:
print(">> INFERENCE AFTER SWAPPING MODEL:")
for layer in m_trained.layers:
    if "lstm" in layer.name:
        m_inference.get_layer("lstm_infer").set_weights(layer.get_weights())
    Elif "dense" in layer.name:
        m_inference.get_layer("dense_infer").set_weights(layer.get_weights())

predictions, _, _, _ = m_inference.predict([batch123,
                                            np.zeros((TRAIN_BATCH_SIZE, HID_DIMS)),
                                            np.zeros((TRAIN_BATCH_SIZE, HID_DIMS))],
                                           batch_size=1)
print(predictions)


# finally perform granular predictions while keeping the recurrent activations. Starting the sequence with zeros is a common practice, but depending on how you trained, you might have an <END_OF_SEQUENCE> character that you might want to propagate instead:
h, c = np.zeros((TRAIN_BATCH_SIZE, HID_DIMS)), np.zeros((TRAIN_BATCH_SIZE, HID_DIMS))
for i in range(len(batch123)):
    # about output shape: https://keras.io/layers/recurrent/#rnn
    # h,z,c hold the network's throughput: h is the proper LSTM output, c is the accumulator and cc is (probably) the candidate
    current_input = batch123[i:i+1] # the length of this feed is arbitrary, doesn't have to be 1
    pred, h, cc, c = m_inference.predict([current_input, h, c])
    print("input:", current_input)
    print("output:", pred)
    print(h.shape, cc.shape, c.shape)
    raw_input("do something with your prediction and hidden state and press any key to continue")

追加情報:

状態の永続化には2つの形式があるので、
1。各シーケンスで同じである、モデルの保存/トレーニングされたパラメーター
2。 acは、シーケンス全体で進化し、「再開」される可能性がある状態です

LSTMオブジェクトの根性を見るのは興味深いです。 Pythonの例では、aおよびcの重みは明示的に処理されますが、トレーニングされたパラメータは処理されず、内部でどのように実装されているか、またはそれらの意味が明確ではない場合があります。次のように検査できます。

for w in lstm.weights:
    print(w.name, w.shape)

私たちの場合(32の隠された状態)は次を返します:

lstm_1/kernel:0 (3, 128)
lstm_1/recurrent_kernel:0 (32, 128)
lstm_1/bias:0 (128,)

128の次元数を観察します。なぜですか? このリンク は、Keras LSTMの実装を次のように説明しています。

enter image description here

Gは再帰的なアクティブ化、pはアクティブ化、Wsはカーネル、Usは再帰的なカーネル、hは出力でもある隠し変数で、*という表記は要素ごとの乗算です。

128=32*4は、4つのゲートのそれぞれの内部で発生するアフィン変換のパラメータであり、連結されていることを説明しています。

  • 形状のマトリックス(3, 128)(名前はkernel)は、指定されたシーケンス要素の入力を処理します
  • 形状(32, 128)の行列(名前はrecurrent_kernel)は、最後の再発状態hの入力を処理します。
  • 他のNNセットアップでは通常どおり、形状(128,)biasという名前)のベクトル。
4
fr_andres

注:この回答は、トレーニングフェーズのモデルがステートフルでないことを前提としています。ステートフルRNNレイヤーとは何かを理解し、トレーニングデータに対応するステートフル性のプロパティがあることを確認する必要があります。つまり、シーケンス間に依存関係があることを意味します。つまり、1つのシーケンスは、モデルで検討したい別のシーケンスのフォローアップです。モデルとトレーニングデータがステートフルである場合、RNNレイヤーの最初からstateful=Trueを設定することを含む他の答えはより単純であると思います。

更新:トレーニングモデルがステートフルかどうかに関係なく、いつでもその重みを推論モデルにコピーして、ステートフルを有効にすることができます。したがって、stateful=Trueの設定に基づくソリューションは、私のソリューションよりも短く、優れていると思います。これらの唯一の欠点は、これらのソリューションのバッチサイズを修正する必要があることです


単一のシーケンスでのLSTMレイヤーの出力は、固定されているウェイトマトリックスと、前に処理されたタイムステップ。ここで、長さmの単一シーケンスのLSTMレイヤーの出力を取得するための1つの明白な方法は、シーケンス全体を一度にLSTMレイヤーにフィードすることです。ただし、前に述べたように、その内部状態は前のタイムステップに依存するため、この事実を利用して、チャンクの処理の最後にLSTMレイヤーの状態を取得し、それをLSTMに渡すことにより、単一シーケンスのチャンクをチャンクごとにフィードできます。次のチャンクを処理するためのレイヤー。より明確にするために、シーケンスの長さが7である(つまり、固定長の特徴ベクトルの7つのタイムステップがある)とします。例として、このシーケンスを次のように処理することができます。

  1. タイムステップ1と2をLSTMレイヤーに送ります。最終状態を取得します(これをC1と呼びます)。
  2. タイムステップ3、4、5と状態C1を初期状態としてLSTMレイヤーにフィードします。最終状態を取得します(これをC2と呼びます)。
  3. タイムステップ6と7、および状態C2を初期状態としてLSTMレイヤーにフィードします。最終出力を取得します。

その最終出力は、7つのタイムステップ全体を一度にフィードした場合、LSTMレイヤーによって生成される出力と同等です。

したがって、Kerasでこれを実現するには、LSTMレイヤーのreturn_state引数をTrueに設定して、中間状態を取得できます。さらに、入力レイヤーを定義するときに固定タイムステップ長を指定しないでください。代わりにNoneを使用して、モデルに任意の長さのシーケンスを供給し、各シーケンスを段階的に処理できるようにします(トレーニング時間の入力データが固定長のシーケンスである場合は問題ありません)。

推論時にこのチャック処理機能が必要なため、トレーニングモデルで使用されるLSTM層を共有し、初期状態を入力として取得し、結果の状態を出力として提供できる新しいモデルを定義する必要があります。以下は、実行可能な一般的なスケッチです(モデルのトレーニング時にLSTMレイヤーの返された状態は使用されません。テスト時にのみ必要です)。

# define training model
train_input = Input(shape=(None, n_feats))   # note that the number of timesteps is None
lstm_layer = LSTM(n_units, return_state=True)
lstm_output, _, _ =  lstm_layer(train_input) # note that we ignore the returned states
classifier = Dense(1, activation='sigmoid')
train_output = classifier(lstm_output)

train_model = Model(train_input, train_output)

# compile and fit the model on training data ...

# ==================================================

# define inference model
inf_input = Input(shape=(None, n_feats))
state_h_input = Input(shape=(n_units,))
state_c_input = Input(shape=(n_units,))

# we use the layers of previous model
lstm_output, state_h, state_c = lstm_layer(inf_input,
                                           initial_state=[state_h_input, state_c_input])
output = classifier(lstm_output)

inf_model = Model([inf_input, state_h_input, state_c_input],
                  [output, state_h, state_c])  # note that we return the states as output

これで、inf_modelに、シーケンスのタイムステップが現在利用できる分だけフィードできます。ただし、最初はすべてゼロのベクトル(状態のデフォルトの初期値)を状態に与える必要があることに注意してください。たとえば、シーケンスの長さが7の場合、新しいデータストリームが利用可能になるとどうなるかを次に示します。

state_h = np.zeros((1, n_units,))
state_c = np.zeros((1, n_units))

# three new timesteps are available
outputs = inf_model.predict([timesteps, state_h, state_c])

out = output[0,0]  # you may ignore this output since the entire sequence has not been processed yet
state_h = outputs[0,1]
state_c = outputs[0,2]

# after some time another four new timesteps are available
outputs = inf_model.predict([timesteps, state_h, state_c])

# we have processed 7 timesteps, so the output is valid
out = output[0,0]  # store it, pass it to another thread or do whatever you want to do with it

# reinitialize the state to make them ready for the next sequence chunk
state_h = np.zeros((1, n_units))
state_c = np.zeros((1, n_units))

# to be continued...

もちろん、これをある種のループで行うか、データストリームを処理するための制御フロー構造を実装する必要がありますが、一般的な考え方は理解できると思います。

最後に、具体的な例はシーケンスツーシーケンスモデルではありませんが、 公式Keras seq2seqチュートリアル を読むことを強くお勧めします。

2
today

私の知る限り、Tensorflowの静的グラフのため、トレーニング入力長とは異なる長さの入力をフィードする効率的な方法はありません。

パディングはそれを回避するための公式な方法ですが、効率とメモリの消費量が少なくなります。 Pytorchを調べることをお勧めします。Pytorchは問題を解決するのに簡単です。

Pytorchでlstmをビルドするには 素晴らしい投稿 がたくさんあります。動的グラフを見ると、その利点を理解できます。

0
Shawn