【问题标题】:Train and predict on variable length sequences对可变长度序列进行训练和预测
【发布时间】:2021-03-31 08:37:01
【问题描述】:

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

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

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

sensor_id batch_id timestamp feature_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 个报告的传感器的 I.E 2 预测。


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

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

例如:

sensor_id batch_id timestamp feature_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,但我不确定它是否适合我的用例。如何解决这样的问题?

【问题讨论】:

  • 您可以重新构建您的问题以具有固定的序列长度。您可以尝试(batch, 1, features),而不是尝试将您的网络工作适应(batch, seq (between 10 and 1000), features)。您的可变数量的事件将在批处理维度中传递,并且在训练期间将不再影响您的模型。
  • @YoanB.M.Sc 感谢您的回复,能否请您详细说明或添加代码?
  • 见下面的答案。不要忘记重塑你的标签,以确保每个时间步都有一个匹配的标签。
  • 可变大小的输入序列非常常见,可以通过将 LSTM 的输入形状指定为none 来解决。您只需要确保在给定批次中传递相同长度的序列即可;这就是诀窍。因此,如果您在这样的网络中传递批量大小为 1 的每个事件集,则可以处理可变大小的序列,而无需填充/截断。查看我的答案了解更多详情。
  • 另外,只是一个简单的问题,你的输出也是可变长度的吗?还是它的固定长度,无论输入序列的长度如何?我也可以针对这种情况修改我的代码示例,但是 IIUC 你有固定的形状输出和可变长度的输入。

标签: python tensorflow keras lstm


【解决方案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 被应用于每个时间步长,并且状态在两次之间重置,这不会影响模型行为。

【讨论】:

  • 谢谢,这是我可以采取的一个方向。我暂时保留这个问题,看看有什么替代方法。
  • @Yoan,有趣的方法。仅此行中的一个更正 - but anytime your batch_id length vary your sequence length will vary too and your model expect a fix sequence. 该模型不期望一个固定的序列长度,只是一批期望它。这是因为梯度更新需要每批次进行一次。如果您每批次有一个样本,则模型 (LSTM) 不会期望序列有任何长度,除非您明确将其指定为输入形状。
【解决方案2】:

使用可变大小的输入序列非常简单。虽然每个批次中有相同大小的序列存在限制,但有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)

以 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 并为每个序列提供 1 的 batch_size。就个人而言,我更喜欢使用生成器来训练数据,因为它还可以为您提供更大的预处理灵活性。

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

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

希望这能解决您的问题。

【讨论】:

    【解决方案3】:

    无需将所有内容都提供给同一个 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)
    

    【讨论】:

      【解决方案4】:

      不规则张量是要走的路:

      不规则张量是嵌套可变长度列表的 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'))
      ...
      

      【讨论】:

        猜你喜欢
        • 2011-06-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-06
        • 1970-01-01
        • 2020-11-04
        • 2020-07-17
        • 2020-09-14
        相关资源
        最近更新 更多