对于一个我打算自发生成序列的模型来说,我发现逐样本训练并在样本之间保持状态是最自然的方式。在阅读了许多有用的资源后,我设法在Keras中构建了这个模型。(Stack Overflow:问题和两个出色的回答,Machine Learning Mastery 1, 2, 3)
首先构建一个序列(在我的案例中也是独热编码)。通过将Y向前移动一个时间步来从这个序列中生成X和Y。训练是以一个样本和一个时间步为单位的批次进行的。
在Keras中,这看起来像这样:
data = get_some_data() # Shape (samples, features)Y = data[1:, :] # Shape (samples-1, features)X = data[:-1, :].reshape((-1, 1, data.shape[-1])) # Shape (samples-1, 1, features)model = Sequential()model.add(LSTM(256, batch_input_shape=(1, 1, X.shape[-1]), stateful=True))model.add(Dense(Y.shape[-1], activation='softmax'))model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['categorical_accuracy'])for epoch in range(10): model.fit(X, Y, batch_size=1, shuffle=False) model.reset_states()
它确实有效。然而,在查看了我的任务管理器后,似乎它只使用了大约10%的GPU资源,而我的GPU资源本来就相当有限。我希望改进这一点以加速训练。增加批次大小将允许进行并行计算。
当前状态下的网络可能从训练序列的开始就“记住”了一些东西。为了在批次中训练,需要首先设置序列,然后预测一个值 – 并且对于多个值重复这个过程。要在完整序列上进行训练,需要生成形状为(samples-steps, steps, features)
的数据。我想象中,一个序列至少跨越几百个时间步并不罕见。因此,这将意味着数据量的巨大增加。
在稍微不同地定义问题和需要在内存中存储更多数据,以及仅使用少量处理资源之间,我必须问:
- 我对训练的自然方式和状态性的直觉是否正确?
- 这种每批次一个样本的训练还有其他缺点吗?
- 利用率问题是否有其他解决方法?
- 最后,是否有公认的方法来进行这种生成长序列的训练?
任何帮助都将不胜感激,我对LSTM还比较新手。
回答:
我不知道您的具体应用,但是仅发送一个时间步的数据肯定不是一个好主意。相反,您应该将整个序列的先前给定的独热向量(可能是单词)提供给LSTM
,如果需要的话,可以使用零进行pre-pad
,因为您似乎是在处理长度不同的序列。还请考虑在LSTM之前使用一个embedding
层,如果这些确实是单词的话。请仔细阅读文档。
您的GPU利用率低并不是问题。您只是没有足够的数据来在每个批次中充分利用所有资源。使用批次进行训练是一个顺序过程,至少没有一种初级且对您的目标有益的并行化方法。然而,如果您为LSTM提供更多每个时间步的数据,这肯定会提高您的利用率。
statefull
在LSTM中并不像您想的那样工作。LSTM总是记住它正在迭代的序列,因为它更新其内部隐藏状态,h
和c
。此外,这些构建内部状态的权重变换是在训练期间学习的。stateful
所做的只是保留来自上一批次索引的先前隐藏状态。这意味着,在批次中的第三个元素的最终隐藏状态将作为下一批次中第三个元素的初始隐藏状态发送,依此类推。我认为这对您的应用没有用处。
用每批次一个样本来训练LSTM是有缺点的。一般来说,使用小批次可以增加稳定性。然而,您似乎不是在用每批次一个样本进行训练,而是在用每个样本一个时间步进行训练。
编辑(来自评论)
如果您使用stateful并在与前一批次相同的位置发送序列的下一个’字符’,这将类似于发送完整的序列时间步每个样本。我仍然建议使用上述最初的方法,以提高应用程序的速度,并且更符合其他LSTM应用。我认为发送完整序列每个样本而不是在每个批次中进行的方法没有任何缺点。然而,速度的优势、能够每批次打乱输入数据以及更易读/一致性将是值得改变的理由,我认为。