对可变长度序列进行训练和预测

时间:2020-12-21 15:07:50

标签: python tensorflow keras lstm

分散在我网站上的传感器(相同类型)正在不定期地手动向我的后端报告。在报告之间,传感器聚合事件并将它们作为批处理报告。

以下数据集是序列事件数据的集合,批量收集。例如传感器 1 报告了 2 次。第一批 2 个事件和第二批 3 个事件,而传感器 2 报告 1 次,有 3 个事件。

我想将此数据用作我的火车数据X

<头>
sensor_id batch_id 时间戳 功能_1 feature_n
1 1 2020-12-21T00:00:00+00:00 0.54 0.33
1 1 2020-12-21T01:00:00+00:00 0.23 0.14
1 2 2020-12-21T03:00:00+00:00 0.51 0.13
1 2 2020-12-21T04:00:00+00:00 0.23 0.24
1 2 2020-12-21T05:00:00+00:00 0.33 0.44
2 1 2020-12-21T00:00:00+00:00 0.54 0.33
2 1 2020-12-21T01:00:00+00:00 0.23 0.14
2 1 2020-12-21T03:00:00+00:00 0.51 0.13

我的目标 y,是根据传感器收集的所有事件计算得出的分数:
socre_sensor_1 = f([[batch1...],[batch2...]])

<头>
sensor_id final_score
1 0.8
2 0.6

我想在每次收集批次时预测 y,即具有 2 个报告的传感器的 2 个预测。


LSTM 模型:
我从 LSTM 模型开始,因为我试图预测事件的时间序列。 我的第一个想法是选择一个固定大小的输入,并在收集到的事件数量小于输入大小时对输入进行零填充。然后屏蔽填充值:

model.add(Masking(mask_value=0., input_shape=(num_samples, num_features)))

例如:

<头>
sensor_id batch_id 时间戳 功能_1 feature_n
1 1 2020-12-21T00:00:00+00:00 0.54 0.33
1 1 2020-12-21T01:00:00+00:00 0.23 0.14

如果选择的长度为 5,将产生以下输入:

[
 [0.54, 0.33],
 [0.23, 0.14],
 [0,0],
 [0,0],
 [0,0]
]

但是,我的训练数据中每个传感器报告的事件数量差异很大,一个报告可以收集 1000 个事件,而另一个可以收集 10 个。因此,如果我选择平均大小(假设为 200),一些输入会有很多填充,而另一些则会被截断,数据会丢失。

我听说过 ragged tensors,但我不确定它是否适合我的用例。如何解决这样的问题?

4 个答案:

答案 0 :(得分:1)

我没有您的模型的具体信息,但 LSTM 的 TF 实现通常期望 (batch, seq, features) 作为输入。

现在不要假设这是您的 batch_id 之一:

data = np.zeros((15,5))

array([[0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.]])

您可以使用 (1, 15, 5) 对其进行整形并将其提供给模型,但是只要您的 batch_id 长度发生变化,您的序列长度也会发生变化,您的模型需要一个固定序列。

相反,您可以在训练前重塑数据,以便将 batch_id 长度作为批量大小传递:

data = data[:,np.newaxis,:] 

array([[[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]],

       [[0., 0., 0., 0., 0.]]])

相同的数据,形状为 (15, 1, 5),但您的模型现在将查看固定长度 1,并且样本数量会有所不同。

确保也重塑您的 label

据我所知,RNN 和 LSTM 被应用于每个时间步和状态在 bacthe 之间重置,这不会影响模型行为。

答案 1 :(得分:1)

粗糙的张量是要走的路:

<块引用>

不规则张量是嵌套可变长度列表的 TensorFlow 等价物。它们可以轻松存储和处理具有非均匀形状的数据

您可以通过多种方式创建参差不齐的张量,一种来自嵌套列表。

import tensorflow as tf
# your first sensor data from your example above
data = [[[0.54 , 0.33],[0.23 , 0.14]],[[0.51,0.13],[0.23,0.24],[0.33, 0.44]]]
X = tf.ragged.constant(data)

<tf.RaggedTensor [[[0.5400000214576721, 0.33000001311302185],   [0.23000000417232513, 0.14000000059604645]], [[0.5099999904632568, 0.12999999523162842], [0.23000000417232513, 0.23999999463558197], [0.33000001311302185, 0.4399999976158142]]]>

然后在您的模型中,您的第一层应该是带有 ragged=True 且形状为 [None , number_of_features] 的输入:

model = Sequential()
model.add(Input(shape=[None,2], dtype=tf.float32, ragged=True))
model.add(LSTM(16, activation='tanh'))
...

答案 2 :(得分:0)

使用可变大小的输入序列非常简单。虽然每个批次中具有相同大小的序列有限制,但存在 NO RESTRICTION of having variable-sized sequences between the batches。充分利用这一点,您只需将 LSTM 的输入序列设置为 (None, features) 并将 batch_size 设为 1。

让我们创建一个生成器,生成 2 个特征的可变长度序列和一个随机浮点分数,您根据这些序列寻找该分数,类似于传感器的输入数据。

#Infinitely creates batches of dummy data
def generator():
    while True:
        length = np.random.randint(2, 10) #Variable length sequences
        x_train = np.random.random((1, length, 2)) #batch, seq, features
        y_train = np.random.random((1,1)) #batch, score
        yield x_train, y_train

next(generator())
#x.shape = (1,4,2), y.shape = (1,1)
(array([[[0.63841991, 0.91141833],
         [0.73131801, 0.92771373],
         [0.61298585, 0.6455549 ],
         [0.25893925, 0.40202978]]]),
 array([[0.05934613]]))

上面是一个由生成器创建的长度为 4 的序列的示例,而下一个是长度为 9 的序列。

next(generator())
#x.shape = (1,9,2), y.shape = (1,1)
(array([[[0.76006158, 0.27457503],
         [0.57739596, 0.75416962],
         [0.03029365, 0.29339812],
         [0.93866829, 0.79137367],
         [0.52739961, 0.11475738],
         [0.85832651, 0.19247399],
         [0.37098216, 0.48703114],
         [0.95846681, 0.15507787],
         [0.86945015, 0.70949593]]]),
 array([[0.02560889]]))

现在,让我们创建一个基于 LSTM 的神经网络,它可以为每个批次处理这些可变大小的序列。

from tensorflow.keras import layers, Model, utils

inp = layers.Input((None, 2))
x = layers.LSTM(10, return_sequences=True)(inp)
x = layers.LSTM(10)(x)
out = layers.Dense(1)(x)

model = Model(inp, out)
utils.plot_model(model, show_layer_names=False, show_shapes=True)

enter image description here

以 1 的批量大小训练这些 -

model.compile(loss='binary_crossentropy', optimizer='adam')
model.fit(generator(), steps_per_epoch=100, epochs=10, batch_size=1)
#Steps_per_epoch is to stop the generator from generating infinite batches of data per epoch.
Epoch 1/10
100/100 [==============================] - 1s 5ms/step - loss: 1.5145
Epoch 2/10
100/100 [==============================] - 0s 5ms/step - loss: 0.7435
Epoch 3/10
100/100 [==============================] - 0s 4ms/step - loss: 0.7885
Epoch 4/10
100/100 [==============================] - 0s 4ms/step - loss: 0.7384
Epoch 5/10
100/100 [==============================] - 0s 4ms/step - loss: 0.7139
Epoch 6/10
100/100 [==============================] - 0s 5ms/step - loss: 0.7462
Epoch 7/10
100/100 [==============================] - 0s 4ms/step - loss: 0.7173
Epoch 8/10
100/100 [==============================] - 0s 4ms/step - loss: 0.7116
Epoch 9/10
100/100 [==============================] - 0s 4ms/step - loss: 0.6875
Epoch 10/10
100/100 [==============================] - 0s 4ms/step - loss: 0.7153

这是将可变大小序列作为输入的方式。只有属于同一批次的序列才需要填充/屏蔽。

现在,您可以为您的输入数据创建一个生成器,该生成器一次生成一个事件序列作为模型的输入,在这种情况下,您显式 do not need to specify batch_size,因为您正在生成一个已经一次排序。

<块引用>

如果您的数据采用数据集、生成器或 keras.utils.Sequence 实例的形式(因为它们会生成批次),请不要指定 batch_size。

或者您可以使用您提到的 ragged tensors 并为每个序列提供一个 batch_size 为 1。就我个人而言,我更喜欢使用生成器来训练数据,因为它也为您提供了更大的预处理灵活性。

有趣的是,您可以进一步 optimize 此代码,但将相同长度序列的批次捆绑在一个批次中,然后传递一个 variable batch size。如果您有大量数据并且无法为每次梯度更新运行 batch_size 为 1,这将有所帮助!

另外一个警告!如果您的序列非常长,那么我建议您使用 Truncated Backpropagation through time (TBPTT)(查找详细信息 here)。

希望这能解决您的问题。

答案 3 :(得分:0)

无需将所有内容都提供给同一个 LSTM,并且当“我的训练数据中每个传感器报告的事件数量差异很大”时,子网方法应该更有效。

如果您有 N 个采样间隔变化较大的传感器,则使 N Inputs 和 LSTMs(并行),然后在稍后阶段连接特征。为了避免制作许多 LSTM,请按预期的样本长度分组传感器,例如100-150、900-1100 等,以及每组内的 pad 以达到各自的最大长度。

避免填充过多,因为(典型的右填充)具有缩小“学习信号”的严重缺点(BPTT展开right-to-left,因此如果“正确”大部分为零,则大多数“学习”忽略它们而不是特征提取)。您的 batch_size 与对上述差异进行分组的能力一样宽松;因此,这是为您的数据找到合适的平衡点(只是不要使用 batch_size<32 进行批量规范,而是更喜欢批量重新归一化或其他小批量替代)。

最后,对于如此稀疏的数据,我会推荐一种注意力机制(可用的各种实现)。


多分支 LSTM 示例

from tensorflow.keras.layers import Input, LSTM, Dense, concatenate
from tensorflow.keras.models import Model

ipt1 = Input(shape=(None, 2))  # 2 sensors grouped, variable input length
x1   = LSTM(4)(ipt1)
ipt2 = Input(shape=(None, 3))  # 3 sens, var
x2   = LSTM(6)(ipt2)

xc   = concatenate([x1, x2])
out  = Dense(1, activation='sigmoid')(xc)

model = Model([ipt1, ipt2], out)
model.compile('adam', 'binary_crossentropy')

x1 = np.random.randn(2, 4, 2)
x2 = np.random.randn(2, 5, 3)
y  = np.random.randint(0, 2, 2)

model.fit([x1, x2], y)
相关问题