Dataset.map,Dataset.prefetch和Dataset.shuffle

时间:2017-09-27 09:18:36

标签: tensorflow tensorflow-gpu tensorflow-datasets

根据TensorFlow documentationprefetch类的maptf.contrib.data.Dataset方法,都有一个名为buffer_size的参数。

对于prefetch方法,该参数称为buffer_size并且根据文档:

  

buffer_size:一个tf.int64标量tf.Tensor,代表最大值   预取时将缓冲的数字元素。

对于map方法,该参数称为output_buffer_size并且根据文档:

  

output_buffer_size :(可选。)一个tf.int64标量tf.Tensor,   表示将处理的最大处理元素数   缓冲。

同样对于shuffle方法,会出现相同的数量并根据文档:

  

buffer_size:一个tf.int64标量tf.Tensor,表示数量   来自此数据集的元素,新数据集将从中进行采样。

这些参数之间有什么关系?

假设我按如下方式创建Dataset对象:

 tr_data = TFRecordDataset(trainfilenames)
    tr_data = tr_data.map(providefortraining, output_buffer_size=10 * trainbatchsize, num_parallel_calls\
=5)
    tr_data = tr_data.shuffle(buffer_size= 100 * trainbatchsize)
    tr_data = tr_data.prefetch(buffer_size = 10 * trainbatchsize)
    tr_data = tr_data.batch(trainbatchsize)

上述代码段中的buffer参数扮演了什么角色?

6 个答案:

答案 0 :(得分:95)

TL; DR 尽管名称相似,但这些论点的含义却截然不同。 buffer_size中的Dataset.shuffle()可能会影响数据集的随机性,从而影响元素的生成顺序。 buffer_size中的Dataset.prefetch()仅影响生成下一个元素所需的时间。

tf.data.Dataset.prefetch()中的buffer_size参数和tf.contrib.data.Dataset.map()中的output_buffer_size参数提供了一种调整输入管道性能的方法:参数告诉TensorFlow创建一个最多buffer_size个元素的缓冲区,以及一个后台线程来在后台填充该缓冲区。 (请注意,当output_buffer_sizeDataset.map()移至tf.contrib.data时,我们从tf.data移除了Dataset.prefetch()参数。新代码应在map()之后使用buffer_size得到相同的行为。)

添加预取缓冲区可以通过将数据预处理与下游计算重叠来提高性能。通常,在管道的最末端添加一个小的预取缓冲区(可能只有一个元素)是最有用的,但是更复杂的管道可以从额外的预取中受益,特别是当生成单个元素的时间可能不同时。 / p>

相比之下,tf.data.Dataset.shuffle()Dataset.shuffle()参数会影响转换的随机性。我们设计了buffer_size转换(就像它替换的tf.train.shuffle_batch()函数)来处理太大而无法放入内存的数据集。它不是对整个数据集进行混洗,而是维护buffer_size元素的缓冲区,并从该缓冲区中随机选择下一个元素(如果有的话,将其替换为下一个输入元素)。更改buffer_size的值会影响改组的统一程度:如果1大于数据集中的元素数,则会得到统一的随机播放;如果它是EdmGen.exe那么你根本就没有洗牌。对于非常大的数据集,典型的"足够好"方法是在训练之前将数据随机地分成多个文件,然后均匀地混洗文件名,然后使用较小的shuffle缓冲区。但是,适当的选择取决于培训工作的确切性质。

答案 1 :(得分:84)

buffer_size

shuffle()的重要性

我想跟进@mrry之前的回答,强调tf.data.Dataset.shuffle()buffer_size重要性

在某些情况下,拥有较低的buffer_size不仅会给你次要的洗牌:它会让你的整个训练陷入困境。

一个实际例子:猫分类器

假设您正在训练图像上的猫分类器,并且您的数据按以下方式组织(每个类别中包含10000个图像):

train/
    cat/
        filename_00001.jpg
        filename_00002.jpg
        ...
    not_cat/
        filename_10001.jpg
        filename_10002.jpg
        ...

使用tf.data输入数据的标准方法可以是拥有文件名列表和相应标签列表,并使用tf.data.Dataset.from_tensor_slices()创建数据集:

filenames = ["filename_00001.jpg", "filename_00002.jpg", ..., 
             "filename_10001.jpg", "filename_10002.jpg", ...]
labels = [1, 1, ..., 0, 0...]  # 1 for cat, 0 for not_cat

dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
dataset = dataset.shuffle(buffer_size=1000)  # 1000 should be enough right?
dataset = dataset.map(...)  # transform to images, preprocess, repeat, batch...

上面代码的大问题是数据集实际上不会以正确的方式进行洗牌。对于大约一个时代的前半部分,我们只会看到猫图像,而下半部分只会看到非猫图像。这会伤害很多训练。
在训练开始时,数据集将采用第一个1000文件名并将其放入缓冲区,然后在其中随机选择一个。由于所有第一张1000图像都是猫的图像,我们只会在开头选择猫图像。

此处的解决方法是确保buffer_size大于20000,或者提前洗牌filenameslabels(显然具有相同的索引)。< / p>

由于将所有文件名和标签存储在内存中不是问题,我们实际上可以使用buffer_size = len(filenames)来确保所有内容都将被混合在一起。确保在应用重度转换之前调用tf.data.Dataset.shuffle()(例如读取图像,处理图像,批处理......)。

dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
dataset = dataset.shuffle(buffer_size=len(filenames)) 
dataset = dataset.map(...)  # transform to images, preprocess, repeat, batch...

外卖是要经常仔细检查洗牌会做什么。捕获这些错误的一个好方法可能是绘制批次随时间的分布(确保批次包含与训练集大致相同的分布,在我们的示例中包含半猫和一半非猫)。

答案 2 :(得分:2)

我发现@ olivier-moindrot确实是正确的,我使用@max指向的修改尝试了@Houtarou Oreki提供的代码。我使用的代码如下:

fake_data = np.concatenate((np.arange(1,500,1),np.zeros(500)))

dataset = tf.data.Dataset.from_tensor_slices(fake_data)
dataset=dataset.shuffle(buffer_size=100)
dataset = dataset.batch(batch_size=10)
iterator = dataset.make_initializable_iterator()
next_element=iterator.get_next()

init_op = iterator.initializer

with tf.Session() as sess:
    sess.run(init_op)
    for i in range(50):
        print(i)
        salida = np.array(sess.run(next_element))
        print(salida)
        print(salida.max())

代码输出确实是从1到(buffer_size +(i * batch_size))的数字,其中 i 是您运行 next_element 的次数。 我认为其工作方式如下。首先,从 fake_data 中依次提取 buffer_size 个样本。然后从缓冲区中选取一个 batch_size 样本。每次从缓冲区中提取一批样品时,它都会被替换为一个新的样品,从 fake_data 中按顺序进行。我使用以下代码测试了这最后一件事:

aux = 0
for j in range (10000):
    with tf.Session() as sess:
        sess.run(init_op)
        salida = np.array(sess.run(next_element))
        if salida.max() > aux:
            aux = salida.max()

print(aux)

该代码产生的最大值为109。因此,您需要确保您的 batch_size 中的样本均衡,以确保训练期间进行均匀的采样。

我还测试了@mrry关于性能的内容,发现 batch_size 会将预取的样本量预取到内存中。我使用以下代码对此进行了测试:

dataset = dataset.shuffle(buffer_size=20)
dataset = dataset.prefetch(10)
dataset = dataset.batch(batch_size=5)

更改 dataset.prefetch(10)的数量不会导致所使用的内存(RAM)发生变化。当您的数据不适合RAM时,这一点很重要。我认为最好的方法是在将数据/文件名输入tf.dataset之前先对其进行混洗,然后使用 buffer_size 控制缓冲区的大小。

答案 3 :(得分:2)

代码

import tensorflow as tf
def shuffle():
    ds = list(range(0,1000))
    dataset = tf.data.Dataset.from_tensor_slices(ds)
    dataset=dataset.shuffle(buffer_size=500)
    dataset = dataset.batch(batch_size=1)
    iterator = dataset.make_initializable_iterator()
    next_element=iterator.get_next()
    init_op = iterator.initializer
    with tf.Session() as sess:
        sess.run(init_op)
        for i in range(100):
            print(sess.run(next_element), end='')

shuffle()

输出

[298] [326] [2] [351] [92] [398] [72] [134] [404] [378] [238] [131] [369] [324] [35] [182 ] [441] [370] [372] [144] [77] [11] [199] [65] [346] [418] [493] [343] [444] [470] [222] [83] [ 61] [81] [366] [49] [295] [399] [177] [507] [288] [524] [401] [386] [89] [371] [181 ] [489] [172] [159] [195] [232] [160] [352] [495] [241] [435] [127] [268] [429] [382] [479] [ 519] [116] [395] [165] [233] [37] [486] [553] [111] [525] [170] [571] [215] [530] [47] [291] [558] [21] [245] [514 ] [103] [45] [545] [219] [468] [338] [392] [54] [139] [339] [448] [471] [589] [321] [223] [311] [234] [314]

答案 4 :(得分:1)

实际上@ olivier-moindrot的答案不正确。

您可以通过在提及时创建文件名和标签并打印随机值来对其进行验证。

您将看到每个随机播放过程将随机生成样本,其大小等于数据集中的缓冲区大小。

dataset = dataset.shuffle(buffer_size=1000)
iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()
with tf.Session() as sess:
    for i in range(1000):
        print(sess.run(next_element))

答案 5 :(得分:0)

以下代码片段演示了 buffer_sizeds.shuffle 的效果:

t = tf.range(10)
ds = tf.data.Dataset.from_tensor_slices(t)
for batch in ds.shuffle(buffer_size=2, seed=42).batch(5):
  print(batch)

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

Shuffle 是一个“动作”(对于熟悉 Spark 的人来说),它将 buffer_size 的数据读入内存并在内存中混洗。之后,根据批量大小将打乱后的数据分成批次。请注意,5 是如何将其放入第一批的(而后半部分没有其他数据)。

这带来了其他答案中涉及的所有问题,例如您是否有足够的内存来在内存中混洗整个数据集,或者您最好混洗文件名,或混洗磁盘上的数据,或同时在内存中和在磁盘。