buffer_size的含义——Dataset.map , Dataset.prefetch and Dataset.shuffle

本文翻译自stackoverflow网友的回答,某些地方可能有些生硬甚至错误,翻译成中文(为尽可能保留原义有的词没翻译)也是便于自己日后再次查看,仅供参考。(读完其实也还没弄太明白,了解的大神或者有相同困惑的小伙伴可以一起讨论下)

源地址https://stackoverflow.com/questions/46444018/meaning-of-buffer-size-in-dataset-map-dataset-prefetch-and-dataset-shuffle

提问者:

根据tensorflow文档,tf.contrib.data.Dataset类都有prefetch和map方法(后面版本tensorflow的data从contrib中移除,成为核心API中的一员),他们都有一个参数——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的元素的数量,从中新的dataset将被sample。

这些参数间的关系是什么呢?

假设我创建了以下一个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参数扮演什么角色?

回答者mrry:

尽管名字相似,这些参数的意义差别很大。Dataset.shuffle()中的buffer_size会影响你的dataset的随机性,即元素生成的顺序。Dataset.prefetch()中的buffer_size仅仅影响生成下一个元素的时间。

tf.data.Dataset.prefetch()中的buffer_size参数与tf.contrib.data.Dataset.map()中的参数提供了一种方法调整你的输入管道的性能:两个参数都告诉tensorflow创建一个容纳至少buffer_size个元素的buffer,和一个后台线程在后台填充那个buffer。(注意我们从Dataset.map()中移除了output_buffer_size参数,当它从tf.contrib.data移动到tf.data后。新的代码在map()后应该调用Dataset.prefetch()以得到相同的表现。)

增加一个prefetch buffer能够提高性能,通过将数据预处理与下游计算重叠。典型地,在管道末尾增加一个prefetch buffer(也许仅仅是单个样本),但更复杂的管道能够从额外的prefetching获益,尤其是当生成单个元素的时间变化时。

相比之下,tf.data.Dataset.shuffle()中的buffe_size参数影响转换的随机性。我们设计Dataset.shuffle()转换(比如它代替的tf.train.shuffle_batch函数)来处理太大而不能 fit 进内存中的数据集。不是shuffle整个数据集,而是维护buffe_size个元素的buffer,从那个buffer中随机选取下一个元素(用下一个输入元素替换它,如果有的话)。改变buffer_size的值影响shuffling的均匀性:如果buffer_size比数据集中元素数大的话,你会得到一个均匀的shuffle,如果是 1 那就根没有shuffle。对于非常大的数据集,一个典型地“好的足够的”方法是在训练之前将数据随机地分到多个文件中,然后均匀地shuffle filenames,然后用一个更小的shuffle buffer。然而,最恰当的选择依赖于你的训练工作的性质。

回答者Olivier Monidrot:

shuffle()中buffer_size的重要性:

我想接着上面的回答者mrry强调下tf.data.Dataset.shuffle()中的buffle的重要性。

有一个很低的buffer_size在某些情况下不仅会给你很差的shuffle:它会搅乱你的整个训练。

一个实际的例子:猫分类

假设一个情况:你正在训练一个基于图片的猫分类,你的数据集以下面的方式组织(每个类有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...

上面代码最大的问题是数据集事实上不会以正确的方式被shuffle。对于一个epoch前半部分,我们只会看见猫的图片,后半部分只能看见非猫的图片。这对于训练非常不利。

在训练开始时,数据集将取出前1000个文件名把它们放进它的buffer,然后从中随机pick一个。因为第一个1000张图片都是猫图,开始我们将只能pick出猫图。

这里的关键是确保 buffer_size 比20000大,或者提前shuffle文件名和标签(显然序数对应)。

因为将所有文件名和标签存入内存不是一个问题,我们实际可以用 buffer_size = len(filenames)来确保每个样本都将一起被shuffle。确保在应用heavy转换(比如读图像、预处理、batching)之前调用 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...

The takeaway is to always double check what the shuffling will do. A good way to catch these errors might be to plot the distribution of batches over time (make sure that batches contain about the same distribution as the training set, half cat and half non cat in our example).

回答者 lsaac Cheng:

事实上Olivier Monidrot的回答是不正确的。

你可以创建文件名和标签来验证他/她提出的,和打印出shuffle的值。

你将会看到每个shuffle程序将会从dataset中随机生成大小等于buffer size的样本。

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))

回答者Houtarou Oreki:

正如上面提出的,Olivier Monidrot的回答是不正确的。比如:

import tensorflow as  tf
dataset = tf.data.Dataset.from_tensor_slices([0,1,2,3,4,5,6,7,8,9])
dataset=dataset.shuffle(buffer_size=2)
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(10):
        print(sess.run(next_element))

我得到了以下输出:

[1]
[0]
[3]
[2]
[4]
[5]
[7]
[8]
[9]
[6]

buffer背后的关键idea是,在memory中总是keep着buffer_size个元素。一旦你从buffer中随机地得到了一个 sample(batch),你会把下一个batch的元素放进buffer,再次从新buffer中sample。

 buffer:0,1, get a sample  [1]
 buffer:0,2, get a sample  [0]
 buffer:2,3, get a sample  [3]
 buffer:2,4, get a sample  [2]
 buffer:4,5, get a sample  [4]
 buffer:5,6, get a sample  [5]
 buffer:6,7, get a sample  [7]
 buffer:6,8, get a sample  [8]
 buffer:6,9, get a sample  [9]
 buffer:6    get a sample  [6]

猜你喜欢

转载自blog.csdn.net/Eartha1995/article/details/84930492