tf.data:组合多个from_generator()数据集以创建跨时间窗口填充的批次

时间:2018-11-09 20:52:33

标签: python tensorflow tensorflow-datasets

我正在处理一个时间序列问题,其中每个时间序列都相当长(10 ^ 3-10 ^ 4个时间步长,并且每个时间序列的长度都不同)。

对于每个序列,我可以定义一个Python生成器,一次生成一个值。我正在使用tf.data.Dataset.from_generator()构造函数将这些生成器包装到tf.data API中。该文档建议使用from_generator()tf.contrib.data.parallel_interleave()转换来并行化从我的Python生成器提取的内容。

我在这些数据的下游使用的是有状态的RNN(例如LSTM或GRU)。我想将时间序列分块到较小的窗口中(〜10 ^ 2),并使用每个块作为训练示例(即,截断的BPTT)。由于我的数据正在流式传输,我认为这意味着在将每个生成器通过管道传递之前,先保存window_size个时间步,再与其他生成器的数据进行批处理。我还想在这些块之间保存RNN状态,以便我仍然可以学习长期依赖关系。

我的问题是想要创建这些生成器的批处理输出的填充批处理。理想情况下,我想向神经网络展示生成器输出的窗口,当生成器的某些子集先于其他子集耗尽时,需要填充。我知道,如果我消耗了每个生成器的整个生成器输出,则可以使用Dataset.padded_batch()(然后可以根据需要在时间维度上将填充的批处理切成窗口大块)。但是,我想将每个窗口传递给它可用的神经网络。如果其中一个生成器先于其他生成器用尽自己的电量,那么我想用填充值填充它,直到所有其他生成器都具有填充值,以便我可以重置RNN状态并以空的初始RNN状态开始下一批生成器。我被困在这里是因为tf.contrib.data.parallel_interleave()转换产生的数据集在耗尽每个生成器时都会丢弃,并且时间序列在从其生成的样本之间不会保持一致的顺序。

这是一个小例子:

import tensorflow as tf

def stepwise_generator(length):
    for i in range(length):
        yield i

lengths = list(range(1,10,2)) # [1, 3, 5, 7, 9]

window_length = 4
batch_size = 3

dataset = tf.data.Dataset.from_tensor_slices(lengths)

gen = lambda length: tf.data.Dataset.from_generator(
    stepwise_generator, tf.float32, output_shapes=[], args=(length,)
).batch(window_length) # this batching saves window_length timesteps per generator

dataset = dataset.apply(
    tf.contrib.data.parallel_interleave(gen, cycle_length=batch_size)
)

dataset = dataset.padded_batch(batch_size, (-1,), np.inf)
# batching 3 generators at once, and padding exhausted ones with inf.
# using a batch_size value no more than cycle_length above means we
# shouldn't start a new generator mid-batch (i think)

iterator = dataset.make_one_shot_iterator()
tensor = iterator.get_next()

outs = []
with tf.Session() as sess:
    while True:
        try:
            out = sess.run(tensor)
            outs.append(out)
        except tf.errors.OutOfRangeError:
            break

print(np.asarray(outs))

输出:

[[[ 0. inf inf inf]   # batch 1
  [ 0.  1.  2. inf]
  [ 0.  1.  2.  3.]]

 [[ 4. inf inf inf]   # batch 2 - the generator in index -1 in the
  [ 0.  1.  2.  3.]   # previous batch gets cycled to index 0 and two
  [ 0.  1.  2.  3.]]  # new generators are initiated

 [[ 4.  5.  6. inf]   # batch 3 - more generator cycling, and the one in
  [ 4.  5.  6.  7.]   # index 1 also gets cycled to index 2 in the same
  [ 8. inf inf inf]]] # batch (because we have run out of generators in
                      # parallel_interleave)

我想要的输出将是

[[[ 0. inf inf inf]   # batch 1
  [ 0.  1.  2. inf]
  [ 0.  1.  2.  3.]]

 [[inf]               # batch 2 - the leftover timestep from a padded 
  [inf]               # batch of the first 3 generators
  [4. ]]

 [[ 0.  1.  2.  3.]   # batch 3 - only two generators are left so this is 
  [ 0.  1.  2.  3.]]  # an end-of-epoch smaller batch

 [[ 4.  5.  6. inf]   # batch 4
  [ 4.  5.  6.  7.]]

 [[inf]               # batch 5
  [ 8.]]]

在这里,将在第2批和第5批之后重置RNN的内部状态。

同样,如果我消耗了每个生成器的全部输出,然后填充,批处理和切片,则可以轻松创建所需的输出,但是我想生成生成器的批处理,它们可能每个都在实际接收数据。从例如开始的时间单独的模拟,使其可用。

1 个答案:

答案 0 :(得分:0)

TensorFlow 中的有状态 RNN 需要固定批大小,因此您想要的输出不起作用:批大小从 3 变为 2。

所以你需要有这样的东西:

[[[ 0. inf inf inf]   # batch 1
  [ 0.  1.  2. inf]
  [ 0.  1.  2.  3.]]

 [[inf]               # batch 2 - the leftover timestep from a padded 
  [inf]               # batch of the first 3 generators
  [4. ]]

 [[ 0.  1.  2.  3.]   # batch 3 - only two generators are left
  [ 0.  1.  2.  3.]   # but we still need the same batch size
  [ inf inf inf inf]] # so this row of `inf` is needed

 [[ 4.  5.  6. inf]   # batch 4
  [ 4.  5.  6.  7.]
  [ inf inf inf inf]]

 [[inf]               # batch 5
  [ 8.]
  [inf]]]

我认为使用您的 interleave + padded_batch 方法无法做到这一点。

然而,一种有效的方法是将所有序列填充到相同的长度。这是一个使用 TensorFlow 2.4.1 的工作示例(它应该适用于其他 TF 2 版本):

import tensorflow as tf
import numpy as np

lengths = list(range(1,12,2)) # [1, 3, 5, 7, 9, 11]
max_length = max(lengths)

def stepwise_generator(length):
    for i in range(max_length):
        if i < length:
            yield float(i)
        else:
            yield np.inf

window_length = 4
batch_size = 3

dataset = tf.data.Dataset.from_tensor_slices(lengths)

gen = lambda length: tf.data.Dataset.from_generator(
    stepwise_generator, tf.float32, args=(length,)
).batch(window_length) # this batching saves window_length timesteps per generator

dataset = dataset.interleave(gen, cycle_length=batch_size)
dataset = dataset.batch(batch_size)

for batch in dataset:
    print(batch)

这给出了以下输出:

tf.Tensor(
[[ 0. inf inf inf]
 [ 0.  1.  2. inf]
 [ 0.  1.  2.  3.]], shape=(3, 4), dtype=float32)
tf.Tensor(
[[inf inf inf inf]
 [inf inf inf inf]
 [ 4. inf inf inf]], shape=(3, 4), dtype=float32)
tf.Tensor(
[[inf inf inf]
 [inf inf inf]
 [inf inf inf]], shape=(3, 3), dtype=float32)
tf.Tensor(
[[0. 1. 2. 3.]
 [0. 1. 2. 3.]
 [0. 1. 2. 3.]], shape=(3, 4), dtype=float32)
tf.Tensor(
[[ 4.  5.  6. inf]
 [ 4.  5.  6.  7.]
 [ 4.  5.  6.  7.]], shape=(3, 4), dtype=float32)
tf.Tensor(
[[inf inf inf]
 [ 8. inf inf]
 [ 8.  9. 10.]], shape=(3, 3), dtype=float32)

注意事项:

  • 生成器现在处理填充,因此我们使用 batch() 而不是 padded_batch()
  • 序列的数量必须是批量大小的倍数
  • tf.contrib 包已在 TF 2 中删除,但 tf.contrib.data.parallel_interleave() 已提升为核心 API:您现在可以使用 dataset.interleave()
  • 由于这是 TF 2,因此不需要 dataset.make_one_shot_iterator()iterator.get_next()tf.Session() 等。它要简单得多。