Значение buffer_size в Dataset.map, Dataset.prefetch и Dataset.shuffle

Согласно документации TensorFlow, prefetch а также map методы tf.contrib.data.Dataset класс, оба имеют параметр с именем buffer_size,

За prefetch Метод, параметр известен как buffer_size и согласно документации:

buffer_size: скалярный tf.Tensor tf.int64, представляющий максимальное количество элементов, которые будут буферизироваться при предварительной выборке.

Для map Метод, параметр известен как output_buffer_size и согласно документации:

output_buffer_size: (Необязательно.) Скалярный tf.Tensor tf.int64, представляющий максимальное количество обработанных элементов, которые будут буферизованы.

Аналогично для shuffle Метод, такое же количество появляется и согласно документации:

buffer_size: скалярный tf.Tensor tf.int64, представляющий количество элементов из этого набора данных, из которого будет сэмплирован новый набор данных.

Какова связь между этими параметрами?

Предположим, я создаю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 параметры в приведенном фрагменте?

7 ответов

Решение

TL;DR Несмотря на схожие названия, эти аргументы имеют совершенно разные значения. buffer_size в Dataset.shuffle() может повлиять на случайность вашего набора данных и, следовательно, порядок, в котором создаются элементы. buffer_size в Dataset.prefetch() влияет только на время, необходимое для создания следующего элемента.


buffer_size аргумент в tf.data.Dataset.prefetch() и output_buffer_size аргумент в tf.contrib.data.Dataset.map() обеспечить способ настройки производительности вашего входного конвейера: оба аргумента говорят TensorFlow создать максимум буфер buffer_size элементы и фоновый поток, чтобы заполнить этот буфер в фоновом режиме. (Обратите внимание, что мы удалили output_buffer_size аргумент от Dataset.map() когда он переехал из tf.contrib.data в tf.data, Новый код должен использовать Dataset.prefetch() после map() чтобы получить такое же поведение.)

Добавление буфера предварительной выборки может повысить производительность, перекрывая предварительную обработку данных и последующие вычисления. Как правило, наиболее полезно добавить небольшой буфер предварительной выборки (возможно, с одним элементом) в самом конце конвейера, но более сложные конвейеры могут выиграть от дополнительной предварительной выборки, особенно когда время для создания одного элемента может изменяться.

В отличие от buffer_size аргумент tf.data.Dataset.shuffle() влияет на случайность преобразования. Мы разработали Dataset.shuffle() преобразование (например, tf.train.shuffle_batch() функция, которую он заменяет) для обработки наборов данных, которые слишком велики, чтобы поместиться в памяти. Вместо того, чтобы перетасовывать весь набор данных, он поддерживает буфер buffer_size элементы и случайным образом выбирает следующий элемент из этого буфера (заменяя его следующим входным элементом, если таковой имеется). Изменение значения buffer_size влияет на равномерность перетасовки: если buffer_size больше, чем количество элементов в наборе данных, вы получите равномерное перемешивание; если это 1 тогда вы не будете тасовать вообще. Для очень больших наборов данных типичный "достаточно хороший" подход состоит в том, чтобы случайным образом разбить данные на несколько файлов перед началом обучения, затем равномерно перемешать имена файлов и затем использовать меньший буфер перемешивания. Тем не менее, правильный выбор будет зависеть от точного характера вашей учебной работы.


Важность buffer_size в shuffle()

Я хотел бы продолжить предыдущий ответ от @mrry, чтобы подчеркнуть важность buffer_size в tf.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...

Большая проблема с кодом выше состоит в том, что набор данных на самом деле не будет перетасовываться правильно. Примерно в первой половине эпохи мы будем видеть только изображения кошек, а во второй половине только изображения не кошек. Это сильно повредит тренировкам.
В начале обучения набор данных будет 1000 имена файлов и поместите их в свой буфер, затем выберите один из них случайным образом. Так как все первые 1000 изображения являются изображениями кошки, мы только выберем изображения кошки в начале.

Исправление здесь, чтобы убедиться, что buffer_size больше чем 20000 или перемешать заранее filenames а также labels (с такими же показателями, очевидно).

Поскольку хранение всех имен файлов и меток в памяти не является проблемой, мы можем использовать 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...

Нужно всегда проверять, что будет делать тасование. Хорошим способом отловить эти ошибки может быть построение графика распределения пакетов по времени (убедитесь, что пакеты содержат примерно то же распределение, что и обучающий набор, наполовину cat и наполовину non cat в нашем примере).

Код

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]

Я обнаружил, что @ 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. Я думаю, что это работает следующим образом. Сначала выборки buffer_size выбираются по порядку из fake_data. Затем один за другим образцы 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). Это важно, когда ваши данные не помещаются в оперативную память. Я думаю, что лучший способ - это перемешать ваши data / file_names перед передачей их в tf.dataset, а затем контролировать размер буфера с помощью buffer_size.

На самом деле ответ @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))

Следующий фрагмент кода демонстрирует эффект buffer_size в 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 попал в первую партию (и ничего больше из второй половины данных).

Это поднимает все вопросы, затронутые в других ответах, например, достаточно ли у вас памяти для перетасовки всего набора данных в памяти, или вам лучше перемешать имена файлов или данные на диске, или и в памяти, и на диске.

Как упоминалось выше, ответ @olivier-moindrot не верен. Например.

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_size в памяти. Как только вы случайным образом получаете образец (пакет) из буфера, вы помещаете следующие элементы пакета в буфер и снова формируете новый буфер.

 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]
Другие вопросы по тегам