理解Keras中的LSTM

406
我正在尝试理解LSTMs的概念,并在Christopher Olah的这篇文章中找到了一些线索,该文章是用Keras实现的。我正在遵循Jason Brownlee撰写的Keras教程博客。我主要困惑的是:
  1. 将数据序列重新塑造为[samples, time steps, features]
  2. 有状态的LSTMs

让我们针对上述两个问题集中讨论以下代码:

# 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是一个三维数组,时间步和特征分别是最后两个维度(在此特定代码中为3和1)。关于下面的图片,这是否意味着我们考虑了“多对一”的情况,其中粉色框的数量为3?还是它确实意味着链长为3(即仅考虑3个绿色框)。 enter image description here

当我们考虑多变量序列时,features参数是否变得相关?例如同时建模两只金融股票?

有状态LSTMs

“stateful LSTMs”是指我们在批次运行之间保存单元格内存值吗?如果是这样,当 batch_size 为1时,内存在训练运行之间被重置,那么说它是有状态的有什么意义呢?我猜这可能与训练数据未被随机洗牌有关,但我不确定如何解释。
你有什么想法吗? 图像参考:http://karpathy.github.io/2015/05/21/rnn-effectiveness/

编辑1:

对于@van的评论中关于红色和绿色框相等的部分有些困惑。所以只是确认一下,以下API调用是否对应于展开的图表?特别是注意第二个图表(batch_size 是任意选择的): enter image description here enter image description here

编辑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

更新2:

我在这里总结了我对LSTM的大部分理解:https://www.youtube.com/watch?v=ywinX5wgdEU


10
第一张照片应该是(批大小,5,1);第二张照片应该是(批大小,4,3)(如果没有后续序列)。为什么输出仍然是“X”?它应该是“Y”吗? - Van
1
在这里,我假设X_1、X_2...X_6是一个单独的数字。三个数字(X_1、X_2、X_3)组成了形状为(3,)的向量。一个数字(X_1)组成了形状为(1,)的向量。 - Van
2
@Van,你的假设是正确的。很有趣,基本上模型不能学习超过时间步数的模式。因此,如果我的时间序列长度为1000,我可以在每隔100天看到一个模式,那么我应该将time_steps参数设置为至少100。这个观察结果正确吗? - sachinruk
4
好的,如果您每天可以收集三个相关特征,那么您可以像第二张照片中所做的那样将特征大小设置为3。在这种情况下,输入形状将是(batch_size,100,3)。 - Van
1
回答你的第一个问题,是因为我正在处理单个时间序列。例如股票价格,因此X和Y来自同一系列。 - sachinruk
显示剩余4条评论
4个回答

271
作为对已接受答案的补充,本答案展示了Keras的行为以及如何实现每个图片。

Keras的一般行为

标准的Keras内部处理始终是多对多,就像下面的图片一样(我使用features=2,压力和温度,仅作为示例):

ManyToMany

在这个图像中,我将步数增加到5,以避免与其他维度混淆。
对于这个例子:
- 我们有N个油罐 - 我们花了5个小时每小时进行测量(时间步长) - 我们测量了两个特征:
- 压力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]],
  ....
        ]

注意,在这种情况下,您最初只有一个序列,但是您将其分成多个序列以创建窗口。

“什么是序列”的概念是抽象的。重要的部分包括:

  • 您可以有许多单独的序列批次
  • 使序列成为序列的原因是它们以步长(通常是时间步长)演变

使用“单层”实现每种情况

实现标准的多对多:

StandardManyToMany

您可以通过简单的LSTM层,使用return_sequences=True实现多对多:

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

#output_shape -> (batch_size, steps, units)

实现多对一:

使用完全相同的层,Keras将执行完全相同的内部预处理,但当您使用return_sequences=False(或简单地忽略此参数)时,Keras会自动丢弃前面的步骤并保留最后一个步骤:

ManyToOne

outputs = LSTM(units)(inputs)

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

实现一对多

现在,这不仅仅是由keras LSTM层支持的。您将不得不创建自己的策略来复制步骤。有两种好方法:

  • 通过重复张量创建一个恒定的多步输入
  • 使用stateful=True,以循环地获取一步的输出,并将其作为下一步的输入提供(需要output_features == input_features

使用重复向量的一对多

为了适应keras的标准行为,我们需要按步骤输入,因此,我们只需重复要求长度的输入即可:

OneToManyRepeat

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

#output_shape -> (batch_size, steps, units)

了解stateful = True

现在是stateful=True的可能用法之一(除了避免一次性加载无法适应计算机内存的数据)

Stateful允许我们分阶段输入序列的“部分”。不同之处在于:

  • stateful=False中,第二个批次包含全新的序列,与第一个批次独立。
  • stateful=True中,第二个批次继续第一个批次,扩展相同的序列。

这就像将序列分成窗口一样,有两个主要区别:

  • 这些窗口不重叠!
  • 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(当然,除非我们只使用一个序列)。

您可以拥有任意数量的批次,无限制。(对于每个批次具有可变长度的情况,请使用input_shape=(None,features)

一对多,stateful=True

对于我们这里的情况,我们将每个批次仅使用1个步骤,因为我们想获取一个输出步骤并使其成为输入。

请注意,图片中的行为不是由stateful=True引起的。我们将在下面的手动循环中强制执行该行为。在本例中,stateful=True是允许我们停止序列,操纵我们想要的内容,并从我们停止的地方继续的“允许”。

OneToManyStateful

说实话,对于这种情况,重复的方法可能是更好的选择。但由于我们正在研究 "stateful=True",这是一个很好的例子。使用它的最佳方式是下一个 "many to many" 情况。
层:
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的多对多

现在,我们有一个非常好的应用程序:给定一个输入序列,尝试预测其未来的未知步骤。

我们使用与上面“一对多”相同的方法,不同之处在于:

  • 我们将使用序列本身作为目标数据,提前一步
  • 我们知道部分序列(因此我们会丢弃这部分结果)。

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

现在我们像处理一对多的情况那样进入循环。但是不要在这里重置状态!我们希望模型知道序列中的哪个步骤(并且它知道它在第一个新步骤,因为我们刚刚做出了预测)。
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()

这种方法被用于以下答案和文件:

实现复杂配置

在以上所有示例中,我展示了“一层”的行为。

当然,您可以将许多层堆叠在一起,不一定都遵循相同的模式,并创建自己的模型。

一个有趣的例子是“自编码器”,它具有“多对一编码器”后跟“一对多解码器”:

编码器:

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 LSTMs的疑问


1
非常有趣的使用状态机,将输出作为输入。另外一种方法是使用功能性Keras API(就像你在这里所做的那样,尽管我相信你可以使用顺序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
2
单元格和长度是完全独立的值。这些图片都不代表“单元格”的数量,它们都是关于“长度”的。 - Daniel Möller
1
@DanielMöller 我知道有点晚了,但是你的回答真的引起了我的注意。你提供了一个关于LSTM批处理的例子,其中包括N个水箱、五个步骤和两个特征。我曾经认为,如果批处理的大小为2,那么就意味着会将两个样本(具有5个步骤和2个特征的水箱)馈送到网络中,然后进行权重调整。但如果我正确理解的话,你的意思是批处理大小为2意味着样本的时间步长将被分成2部分,首先将馈送所有样本的前一半到LSTM->权重更新,然后再馈送后一半。 - viceriel
2
是的。在状态为True时,批处理1表示一组样本,进行更新。然后批处理2表示同一组样本的更多步骤,进行更新。 - Daniel Möller
@DanielMöller,非常好的回答,我有一个疑问,在一对多的情况下,第一层的输出(具有2个单元,而不是隐藏状态)是如何传递到LSTM层(具有5个单元)的? - undefined
显示剩余22条评论

212

首先,您选择了出色的教程(12)作为起点。

时间步长含义:在描述数据形状时,X.shape中Time-steps==3表示有三个粉色方框。由于在Keras中每个步骤都需要输入,因此绿色和红色方框的数量通常应该相等,除非您修改了结构。

many to many vs. many to one:在Keras中,当您初始化LSTM、GRU或SimpleRNN时,存在一个return_sequences参数。当return_sequencesFalse(默认值)时,如图所示它是many to one。其返回形状为(batch_size, hidden_unit_length),表示最后状态。当return_sequencesTrue时,其是many to many。其返回形状为(batch_size, time_step, hidden_unit_length)

features参数是否相关:特征参数意味着“您的红框有多大”或每个步骤的输入维度是多少。如果您想从8种市场信息中进行预测,那么可以使用feature==8生成数据。

有状态的: 您可以查阅源代码。如果初始化状态时,stateful==True,那么上一次训练的状态将被用作初始状态,否则它将生成一个新的状态。我还没有打开stateful选项。然而,我不同意当stateful==Truebatch_size只能为1的说法。

当前,您使用收集到的数据生成数据。假设您的股票信息是以流的形式提供,而不是等待一天来收集所有连续数据,您希望在训练/预测网络时在线生成输入数据。如果您有400个股票共享同一个网络,那么您可以将batch_size==400


有点困惑为什么红色和绿色的框必须相同。您能否看一下我所做的修改(主要是新图片)并进行评论? - sachinruk
2
确实。请查看文档:stateful: 布尔值(默认为False)。如果为True,则在批次中索引i处的每个样本的最后状态将用作以下批次中索引i处样本的初始状态。 - Van
1
@Van 如果我有一个多元时间序列,我是否仍应该使用 lookback = 1 - i.n.n.m
1
为什么LSTM输出空间的维度(32)与神经元数量(LSTM单元)不同? - Sticky
2
添加到 stateful=True:批大小可以任意设置,但必须保持一致。如果您使用批大小为 5 构建模型,则所有的 fit()predict() 和相关方法都需要一批大小为 5 的数据。注意,然而这个状态不会随着 model.save() 被保存,这似乎是不可取的。不过,如果您需要,可以将状态手动添加到 hdf5 文件中。但实际上,这使您可以通过保存和重新加载模型来更改批大小。 - jlh
显示剩余5条评论

11
当你在RNN的最后一层使用return_sequences时,你不能使用简单的Dense层,而是要使用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))

4

更多细节请参考动画展示RNN、LSTM和GRU

下面的图像可以更好地展示LSTM。这是一个LSTM单元。 This figure

正如您所看到的,X有三个特征(绿色圆圈),因此该单元的输入是一个维数为3的向量,而隐藏状态有2个单元(红色圆圈),因此该单元的输出(以及细胞状态)是一个维度为2的向量。

下面的图像显示了具有3个时间步骤(3个LSTM单元)的一层LSTM的示例:

This image

** 一个模型可以有多个LSTM层。

现在我再次使用Daniel Möller的示例来更好地理解: 我们有10个油罐。对于每个油罐,我们每隔1小时测量2个特征:温度、压力,每个特征测量5次。 现在参数如下:

  • batch_size = 一次正向/反向传递使用的样本数(默认=32)-->例如,如果您有1000个样本,并将batch_size设置为100,则模型将需要10次迭代才能将所有样本一次通过网络传递(1个epoch)。批大小越大,所需的内存空间就越多。因为此示例中的样本数量很少,我们将批大小视为等于所有样本=10。
  • timesteps = 5
  • features = 2
  • units = 它是一个正整数,确定隐藏状态和细胞状态的维度,或者换句话说,传递给下一个LSTM单元的参数数量。可以根据特征和时间步长任意选择或经验性地选择。使用更多的单元会导致更高的精度以及更多的计算时间。但可能会导致过拟合。
  • input_shape = (batch_size,timesteps,features) = (10,5,2)
  • output_shape:
    • 如果return_sequences=True,则为(batch_size,timesteps,units)
    • 如果return_sequences=False,则为(batch_size,units)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接