Keras LSTMについて


311

私はLSTMの理解を調整するために努力しており、この記事でKerasに実装されているChristopher Olahによって指摘されています。Jaras BrownleeがKerasチュートリアルとして書いブログをフォローしています。主に混乱しているのは、

  1. [samples, time steps, features]およびへのデータ系列の再形成
  2. ステートフルLSTM

以下に貼り付けたコードを参照して、上記の2つの質問に集中してみましょう。

# reshape into X=t and Y=t+1
look_back = 3
trainX, trainY = create_dataset(train, look_back)
testX, testY = create_dataset(test, look_back)

# reshape input to be [samples, time steps, features]
trainX = numpy.reshape(trainX, (trainX.shape[0], look_back, 1))
testX = numpy.reshape(testX, (testX.shape[0], look_back, 1))
########################
# The IMPORTANT BIT
##########################
# create and fit the LSTM network
batch_size = 1
model = Sequential()
model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
for i in range(100):
    model.fit(trainX, trainY, nb_epoch=1, batch_size=batch_size, verbose=2, shuffle=False)
    model.reset_states()

注:create_datasetは、長さNのシーケンスを取り、N-look_back各要素がlook_back長さシーケンスである配列を返します。

タイムステップと機能とは何ですか?

見てわかるように、TrainXはTime_stepsとFeatureがそれぞれ最後の2つの次元(この特定のコードでは3と1)である3次元配列です。下の画像に関して、これmany to oneはピンクのボックスの数が3であるケースを検討しているということですか?または、チェーンの長さが文字通り3であることを意味しますか(つまり、3つの緑色のボックスのみが考慮されます)。ここに画像の説明を入力してください

多変量シリーズを検討する場合、機能の引数は関連しますか?たとえば、2つの金融株を同時にモデル化しますか?

ステートフルLSTM

ステートフルLSTMは、バッチの実行間でセルメモリ値を保存することを意味しますか?これが当てはまる場合batch_sizeは1であり、トレーニングの実行の合間にメモリーがリセットされるため、ステートフルであると言ったのは何のことか。これは、トレーニングデータがシャッフルされていないという事実に関連していると思いますが、方法がわかりません。

何かご意見は?画像参照:http : //karpathy.github.io/2015/05/21/rnn-effectiveness/

編集1:

赤と緑のボックスが等しいという@vanのコメントについて少し混乱しました。確認のために、次のAPI呼び出しは展開された図に対応していますか?特に2番目の図に注意してください(batch_size任意に選択したものです)。 ここに画像の説明を入力してください ここに画像の説明を入力してください

編集2:

Udacityのディープラーニングコースを終了し、time_step引数についてまだ混乱している人は、次のディスカッションを参照してください。https://discussions.udacity.com/t/rnn-lstm-use-implementation/163169

更新:

それはmodel.add(TimeDistributed(Dense(vocab_len)))私が探していたものでした。次に例を示します。https//github.com/sachinruk/ShakespeareBot

Update2:

LSTMに関する私の理解のほとんどをここにまとめました:https : //www.youtube.com/watch?v= ywinX5wgdEU


7
最初の写真は(batch_size、5、1)である必要があります。2番目の写真は(batch_size、4、3)でなければなりません(後続のシーケンスがない場合)。そして、なぜ出力はまだ「X」のままですか?「Y」にしますか?
Van

1
ここでは、X_1、X_2 ... X_6は単一の数値であると想定しています。そして、3つの数値(X_1、X_2、X_3)は形状(3、)のベクトルを作成します。1つの数値(X_1)は、形状(1)のベクトルを作成します。
Van

2
@Van、あなたの仮定は正しいです。それは興味深いので、基本的にモデルはtime_stepsの数を超えるパターンを学習しません。したがって、1000の長さの時系列があり、100日ごとにパターンを視覚的に確認できる場合、time_stepsパラメータを少なくとも100に設定する必要があります。これは正しい観測ですか?
sachinruk

3
はい。また、1日に3つの関連する特徴を収集できる場合は、2番目の写真で行ったように、特徴のサイズを3に設定できます。その状況では、入力形状は(batch_size、100、3)になります。
Van

1
あなたの最初の質問に答えるには、私が単一の時系列を取っていたからです。たとえば、株価なので、XとYは同じシリーズのものです。
sachinruk

回答:


173

まず第一に、あなたは偉大なチュートリアル(選択した12を起動します)。

Time-stepの意味Time-steps==3X.shape(データシェイプの記述)では、3つのピンク色のボックスがあることを意味します。Kerasでは、各ステップで入力が必要になるため、緑のボックスの数は通常、赤いボックスの数と同じになります。構造をハックしない限り。

多対多vs多対1:ケラスでは、or またはをreturn_sequences初期化するときにパラメーターがあります。場合である(デフォルトで)、それは、いずれかの多くの写真のように。その戻り形状はであり、これは最後の状態を表します。となると、多対多です。その戻り形状はLSTMGRUSimpleRNNreturn_sequencesFalse(batch_size, hidden_unit_length)return_sequencesTrue(batch_size, time_step, hidden_unit_length)

features引数は関連性がありますか?feature引数は、「赤いボックスの大きさ」または各ステップの入力ディメンションを意味します。たとえば8種類の市場情報から予測したい場合は、を使用してデータを生成できますfeature==8

ステートフルソースコードを検索できます。状態を初期化するときに、の場合stateful==True、最後のトレーニングの状態が初期状態として使用されます。それ以外の場合は、新しい状態が生成されます。statefulまだ電源を入れていません。ただし、はbatch_size1の場合のみ可能であることに同意しませんstateful==True

現在、収集したデータを使用してデータを生成しています。株式情報がストリームとして送られてくるイメージを作成し、1日ごとにすべてのシーケンシャルが収集されるのを待つのではなく、ネットワークでトレーニング/予測しながらオンラインで入力データを生成したいとします。同じネットワークを共有する400銘柄がある場合は、を設定できますbatch_size==400


赤と緑のボックスを同じにする必要がある理由について少し混乱しました。私が行った編集(主に新しい写真)を見てコメントしてくれませんか?
sachinruk

1
確かに。ドキュメントを確認してください:stateful: Boolean (default False). If True, the last state for each sample at index i in a batch will be used as initial state for the sample of index i in the following batch.
Van

1
@Van多変量時系列がある場合でも、使用する必要がありますlookback = 1か?
innm 2017

1
出力空間のLSTM次元(32)がニューロン(LSTMセル)の数と異なるのはなぜですか?
スティッキー

1
追加stateful=True:バッチサイズは好きなようにすることができますが、それに固執する必要があります。バッチサイズが5のモデルを構築する場合、すべてのfit()predict()および関連するメソッドには5のバッチが必要になります。ただし、この状態はで保存されないためmodel.save()、望ましくないように見える場合があります。ただし、必要な場合は、手動で状態をhdf5ファイルに追加できます。ただし、これにより、モデルを保存して再ロードするだけでバッチサイズを変更できます。
jlh

192

受け入れられた回答を補足するものとして、この回答はケラスの振る舞いと各画像を達成する方法を示しています。

Kerasの一般的な動作

標準的なケラスの内部処理は、常に次の図のように多対多です(ここではfeatures=2、例として、圧力、温度を使用しています)。

ManyToMany

この画像では、他のディメンションとの混乱を避けるために、ステップ数を5に増やしています。

この例では:

  • N個のオイルタンクがあります
  • 1時間ごとに5時間かけて対策を講じました(タイムステップ)
  • 2つの特徴を測定しました。
    • 圧力P
    • 温度T

入力配列は、次のような形になります(N,5,2)

        [     Step1      Step2      Step3      Step4      Step5
Tank A:    [[Pa1,Ta1], [Pa2,Ta2], [Pa3,Ta3], [Pa4,Ta4], [Pa5,Ta5]],
Tank B:    [[Pb1,Tb1], [Pb2,Tb2], [Pb3,Tb3], [Pb4,Tb4], [Pb5,Tb5]],
  ....
Tank N:    [[Pn1,Tn1], [Pn2,Tn2], [Pn3,Tn3], [Pn4,Tn4], [Pn5,Tn5]],
        ]

スライディングウィンドウの入力

多くの場合、LSTMレイヤーはシーケンス全体を処理することになっています。ウィンドウを分割することは最善の方法ではありません。レイヤーには、シーケンスが前進するにつれてどのように進化するかについての内部状態があります。ウィンドウは、長いシーケンスを学習する可能性を排除し、すべてのシーケンスをウィンドウサイズに制限します。

ウィンドウでは、各ウィンドウは長い元のシーケンスの一部ですが、Kerasではそれらはそれぞれ独立したシーケンスと見なされます。

        [     Step1    Step2    Step3    Step4    Step5
Window  A:  [[P1,T1], [P2,T2], [P3,T3], [P4,T4], [P5,T5]],
Window  B:  [[P2,T2], [P3,T3], [P4,T4], [P5,T5], [P6,T6]],
Window  C:  [[P3,T3], [P4,T4], [P5,T5], [P6,T6], [P7,T7]],
  ....
        ]

この場合、最初はシーケンスが1つしかありませんが、ウィンドウを作成するためにそれを多くのシーケンスに分割していることに注意してください。

「シーケンスとは」の概念は抽象的です。重要な部分は次のとおりです。

  • 多くの個別のシーケンスを持つバッチを持つことができます
  • シーケンスをシーケンスにするのは、シーケンス(通常は時間ステップ)で進化するということです。

「単一層」で各ケースを達成する

多対多の標準を達成する:

StandardManyToMany

以下を使用して、単純なLSTMレイヤーで多対多を実現できますreturn_sequences=True

outputs = LSTM(units, return_sequences=True)(inputs)

#output_shape -> (batch_size, steps, units)

多対1の達成:

まったく同じレイヤーを使用すると、kerasはまったく同じ内部前処理を行いますが、使用するreturn_sequences=False(または単にこの引数を無視する)と、kerasは最後の前のステップを自動的に破棄します。

ManyToOne

outputs = LSTM(units)(inputs)

#output_shape -> (batch_size, units) --> steps were discarded, only the last was returned

1対多の達成

現在、これはkeras LSTMレイヤーだけではサポートされていません。ステップを増やすには、独自の戦略を作成する必要があります。2つの適切なアプローチがあります。

  • テンソルを繰り返すことで一定の多段階入力を作成する
  • a stateful=Trueを使用して、1つのステップの出力を繰り返し取得し、次のステップの入力として機能させます(必要output_features == input_features

1対多の繰り返しベクトル

ケラスの標準的な動作に合わせるために、段階的に入力が必要なので、必要な長さだけ入力を繰り返します。

OneToManyRepeat

outputs = RepeatVector(steps)(inputs) #where inputs is (batch,features)
outputs = LSTM(units,return_sequences=True)(outputs)

#output_shape -> (batch_size, steps, units)

ステートフルを理解する= True

の可能な使用法の1つになりました stateful=True(コンピューターのメモリに一度に収まらないデータの読み込みを回避する以外に)

ステートフルを使用すると、シーケンスの「部分」を段階的に入力できます。違いは次のとおりです。

  • ではstateful=False、2番目のバッチには、最初のバッチとは無関係に、まったく新しいシーケンスが含まれています。
  • ではstateful=True、2番目のバッチが最初のバッチを続行し、同じシーケンスを拡張します。

シーケンスをウィンドウで分割するようなものですが、次の2つの主な違いがあります。

  • これらのウィンドウは重なりません!!
  • stateful=True これらのウィンドウが単一の長いシーケンスとして接続されているのがわかります

ではstateful=True、新しいバッチはすべて、前のバッチを継続するものとして解釈されます(を呼び出すまでmodel.reset_states())。

  • バッチ2のシーケンス1は、バッチ1のシーケンス1を続行します。
  • バッチ2のシーケンス2は、バッチ1のシーケンス2を続行します。
  • バッチ2のシーケンスnは、バッチ1のシーケンスnを続行します。

入力の例、バッチ1にはステップ1と2が含まれ、バッチ2にはステップ3から5が含まれます。

                   BATCH 1                           BATCH 2
        [     Step1      Step2        |    [    Step3      Step4      Step5
Tank A:    [[Pa1,Ta1], [Pa2,Ta2],     |       [Pa3,Ta3], [Pa4,Ta4], [Pa5,Ta5]],
Tank B:    [[Pb1,Tb1], [Pb2,Tb2],     |       [Pb3,Tb3], [Pb4,Tb4], [Pb5,Tb5]],
  ....                                |
Tank N:    [[Pn1,Tn1], [Pn2,Tn2],     |       [Pn3,Tn3], [Pn4,Tn4], [Pn5,Tn5]],
        ]                                  ]

バッチ1とバッチ2のタンクの配置に注意してください!これが必要な理由ですshuffle=False(もちろん、シーケンスを1つだけ使用している場合を除きます)。

無制限に、任意の数のバッチを持つことができます。(各バッチで可変長にする場合は、を使用しますinput_shape=(None,features)

stateful = Trueの1対多

ここでは、1つの出力ステップを取得してそれを入力にしたいので、バッチごとに1つのステップのみを使用します。

画像の動作は「原因」ではないことに注意してくださいstateful=True。以下の手動ループでその動作を強制します。この例でstateful=Trueは、シーケンスを停止し、必要なものを操作し、停止したところから続行できるようにします。

OneToManyStateful

正直なところ、このケースではおそらく繰り返しアプローチがより良い選択です。しかし、を調べているstateful=Trueので、これは良い例です。これを使用する最良の方法は、次の「多対多」の場合です。

層:

outputs = LSTM(units=features, 
               stateful=True, 
               return_sequences=True, #just to keep a nice output shape even with length 1
               input_shape=(None,features))(inputs) 
    #units = features because we want to use the outputs as inputs
    #None because we want variable length

#output_shape -> (batch_size, steps, units) 

ここで、予測のための手動ループが必要になります。

input_data = someDataWithShape((batch, 1, features))

#important, we're starting new sequences, not continuing old ones:
model.reset_states()

output_sequence = []
last_step = input_data
for i in steps_to_predict:

    new_step = model.predict(last_step)
    output_sequence.append(new_step)
    last_step = new_step

 #end of the sequences
 model.reset_states()

stateful = Trueの多対多

ここで、非常に優れたアプリケーションを取得します。入力シーケンスを指定して、その将来の未知のステップを予測してみます。

上記の「1対多」と同じ方法を使用していますが、次の点が異なります。

  • シーケンス自体をターゲットデータとして使用します。
  • シーケンスの一部がわかっている(したがって、結果のこの部分は破棄する)。

ManyToManyStateful

レイヤー(上記と同じ):

outputs = LSTM(units=features, 
               stateful=True, 
               return_sequences=True, 
               input_shape=(None,features))(inputs) 
    #units = features because we want to use the outputs as inputs
    #None because we want variable length

#output_shape -> (batch_size, steps, units) 

トレーニング:

モデルをトレーニングして、シーケンスの次のステップを予測します。

totalSequences = someSequencesShaped((batch, steps, features))
    #batch size is usually 1 in these cases (often you have only one Tank in the example)

X = totalSequences[:,:-1] #the entire known sequence, except the last step
Y = totalSequences[:,1:] #one step ahead of X

#loop for resetting states at the start/end of the sequences:
for epoch in range(epochs):
    model.reset_states()
    model.train_on_batch(X,Y)

予測:

予測の最初の段階には、「国家の調整」が含まれます。これが、シーケンスのこの部分をすでに知っていても、シーケンス全体を再度予測する理由です。

model.reset_states() #starting a new sequence
predicted = model.predict(totalSequences)
firstNewStep = predicted[:,-1:] #the last step of the predictions is the first future step

次に、1対多の場合と同様にループに進みます。ただし、ここで状態をリセットしないでください。。モデルにシーケンスのどのステップにあるかを知らせたい(そして、先ほど行った予測のために、モデルが最初の新しいステップにあることを知っている)

output_sequence = [firstNewStep]
last_step = firstNewStep
for i in steps_to_predict:

    new_step = model.predict(last_step)
    output_sequence.append(new_step)
    last_step = new_step

 #end of the sequences
 model.reset_states()

このアプローチはこれらの回答とファイルで使用されました:

複雑な構成の実現

上記のすべての例で、「1つのレイヤー」の動作を示しました。

もちろん、必ずしも同じパターンに従っている必要はなく、多くのレイヤーを積み重ねて、独自のモデルを作成することもできます。

登場している興味深い例の1つは、「多対1のエンコーダ」とそれに続く「1対多」のデコーダを持つ「オートエンコーダ」です。

エンコーダ:

inputs = Input((steps,features))

#a few many to many layers:
outputs = LSTM(hidden1,return_sequences=True)(inputs)
outputs = LSTM(hidden2,return_sequences=True)(outputs)    

#many to one layer:
outputs = LSTM(hidden3)(outputs)

encoder = Model(inputs,outputs)

デコーダ:

「繰り返し」メソッドを使用します。

inputs = Input((hidden3,))

#repeat to make one to many:
outputs = RepeatVector(steps)(inputs)

#a few many to many layers:
outputs = LSTM(hidden4,return_sequences=True)(outputs)

#last layer
outputs = LSTM(features,return_sequences=True)(outputs)

decoder = Model(inputs,outputs)

オートエンコーダ:

inputs = Input((steps,features))
outputs = encoder(inputs)
outputs = decoder(outputs)

autoencoder = Model(inputs,outputs)

トレーニング fit(X,X)

追加説明

LSTMでのステップの計算方法の詳細、またはstateful=True上記のケースの詳細が必要な場合は、この回答で詳細を読むことができます。「Keras LSTMの理解」に関する疑問


1
入力として出力を使用するステートフルの非常に興味深い使用。追加の注記として、これを行う別の方法は、機能的なKeras APIを使用することです(ここで行ったように、シーケンシャルのものを使用できたと思いますが)、すべてのタイムステップで同じLSTMセルを再利用します、結果の状態とセルからの出力の両方をそれ自体に渡します。つまりmy_cell = LSTM(num_output_features_per_timestep, return_state=True)、ループが続くa, _, c = my_cell(output_of_previous_time_step, initial_states=[a, c])
Jacob R

1
セルと長さは完全に独立した値です。どの写真も「細胞」の数を表していない。それらはすべて「長さ」に対応しています。
DanielMöller18年

1
@DanielMöller私は少し遅れていることを知っていますが、あなたの答えは本当に私の注目を集めます。あなたのポイントの1つは、LSTMのバッチが何であるかについての私の理解に関するすべてを打ち砕きました。N個のタンク、5つのステップ、2つの機能の例を示します。バッチがたとえば2つの場合、2つのサンプル(5ステップ2機能のタンク)がネットワークに供給され、その後、重みが調整されると私は信じていました。しかし、私が正しく理解していれば、バッチ2は、サンプルのタイムステップが2に分割され、すべてのサンプルの前半がLSTM-> weight updateにフィードされ、2番目がフィードされることを意味すると述べています。
viceriel

1
はい。ステートフル= True、バッチ1 =サンプルのグループ、更新。次に、バッチ2 =同じサンプルグループのステップ数を増やし、更新します。
ダニエル・メーラー

2
これを100回賛成できればいいのに。超便利な答え。
adamconkey

4

RNNの最後のレイヤーにreturn_sequencesがある場合は、TimeDistributedを使用する代わりに単純な高密度レイヤーを使用できません。

これは他の人に役立つかもしれないコードの例です。

words = keras.layers.Input(batch_shape =(None、self.maxSequenceLength)、name = "input")

    # Build a matrix of size vocabularySize x EmbeddingDimension 
    # where each row corresponds to a "word embedding" vector.
    # This layer will convert replace each word-id with a word-vector of size Embedding Dimension.
    embeddings = keras.layers.embeddings.Embedding(self.vocabularySize, self.EmbeddingDimension,
        name = "embeddings")(words)
    # Pass the word-vectors to the LSTM layer.
    # We are setting the hidden-state size to 512.
    # The output will be batchSize x maxSequenceLength x hiddenStateSize
    hiddenStates = keras.layers.GRU(512, return_sequences = True, 
                                        input_shape=(self.maxSequenceLength,
                                        self.EmbeddingDimension),
                                        name = "rnn")(embeddings)
    hiddenStates2 = keras.layers.GRU(128, return_sequences = True, 
                                        input_shape=(self.maxSequenceLength, self.EmbeddingDimension),
                                        name = "rnn2")(hiddenStates)

    denseOutput = TimeDistributed(keras.layers.Dense(self.vocabularySize), 
        name = "linear")(hiddenStates2)
    predictions = TimeDistributed(keras.layers.Activation("softmax"), 
        name = "softmax")(denseOutput)  

    # Build the computational graph by specifying the input, and output of the network.
    model = keras.models.Model(input = words, output = predictions)
    # model.compile(loss='kullback_leibler_divergence', \
    model.compile(loss='sparse_categorical_crossentropy', \
        optimizer = keras.optimizers.Adam(lr=0.009, \
            beta_1=0.9,\
            beta_2=0.999, \
            epsilon=None, \
            decay=0.01, \
            amsgrad=False))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.