120

根据 TensorFlow文档,类的prefetchmap方法tf.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上述代码段中的参数起什么作用?

4

6 回答 6

177

TL;DR尽管它们的名称相似,但这些论点的含义却大相径庭。buffer_sizein会影响数据集的随机性,Dataset.shuffle()从而影响生成元素的顺序。buffer_sizeinDataset.prefetch()仅影响生成下一个元素所需的时间。


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

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

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


于 2017-10-30T23:44:25.807 回答
149

buffer_size的重要性shuffle()

我想跟进@mrry 的先前回答,以强调in的重要性buffer_sizetf.data.Dataset.shuffle()

在某些情况下,低位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...

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

这里的解决方法是确保buffer_size大于20000,或者提前洗牌filenameslabels(显然具有相同的索引)。

由于将所有文件名和标签存储在内存中不是问题,因此我们实际上可以使用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...

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

于 2018-01-04T13:44:33.313 回答
7

代码

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]

于 2019-02-08T15:06:11.010 回答
2

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

您可以通过创建文件名和标签来验证它,因为他/她提到并打印随机值。

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

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))
于 2018-11-07T16:49:38.867 回答
2

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

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控制缓冲区大小。

于 2019-01-17T22:27:40.600 回答
0

以下代码片段演示了buffer_sizein的效果ds.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它是如何进入第一批的(数据的后半部分没有其他内容)。

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

于 2021-08-06T08:50:07.140 回答