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

101

Согласно TensorFlow документации , то prefetchи mapметоды 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параметры в приведенном выше фрагменте?

Удджвал
источник
1
404 ссылка на "документацию" не найдена.
Прадип Сингх,

Ответы:

151

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аргумент to tf.data.Dataset.shuffle()влияет на случайность преобразования. Мы разработали Dataset.shuffle()преобразование (как и tf.train.shuffle_batch()функцию, которую оно заменяет) для обработки наборов данных, которые слишком велики для размещения в памяти. Вместо перетасовки всего набора данных он поддерживает буфер buffer_sizeэлементов и случайным образом выбирает следующий элемент из этого буфера (заменяя его следующим входным элементом, если он доступен). Изменение значения buffer_sizeвлияет на равномерность перемешивания: если buffer_sizeоно больше, чем количество элементов в наборе данных, вы получите равномерное перемешивание; если это1тогда у вас вообще не будет перетасовки. Для очень больших наборов данных типичным «достаточно хорошим» подходом является случайное разделение данных на несколько файлов один раз перед обучением, затем равномерное перемешивание имен файлов и использование меньшего буфера перемешивания. Однако правильный выбор будет зависеть от конкретного характера вашей учебной работы.


мрри
источник
По поводу этого объяснения у меня все еще есть некоторые замешательства tf.data.Dataset.shuffle(). Я хотел бы знать точный процесс перетасовки. Скажем, первые batch_sizeобразцы выбираются случайным образом из первых buffer_sizeэлементов и так далее.
BS He
1
@mrry IIUC перестановка имен файлов важна, потому что в противном случае каждая эпоха будет видеть один и тот же элемент в пакетах 0 ... 999; и партиями 1000.1999 г .; и т.д., где я предполагаю, что 1 файл = 1000 пакетов. Даже при перетасовке имени файла все еще есть некоторая неслучайность: это потому, что все примеры из файла #k близки друг к другу в каждую эпоху. Это может быть не так уж плохо, поскольку сам файл #k случайный; тем не менее, в некоторых случаях даже это могло испортить обучение. Единственный способ добиться идеального перемешивания - установить buffer_sizeравный размеру файла (и, конечно же, перемешать файлы).
max
Tensorflow RC 15.0. С dataset.shuffle(buffer_size=1)перетасовкой все равно происходит. есть идеи?
Сергей Бушманов
@SergeyBushmanov, это может зависеть от преобразования перед вашим перемешиванием, например list_files (), который по умолчанию перемешивает имена файлов в начале каждой эпохи.
Сяолун
130

Важность 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...

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

Оливье Муиндро
источник
1
Следующая выборка всегда выбирается из буфера (здесь размером 1000). Итак, первый образец взят из первых 1000 имен файлов. Размер буфера уменьшается до 999, поэтому он берет следующий input ( filename_01001) и добавляет его. Вторая выборка выбирается случайным образом из этих 1000 имен файлов (1001 первое имя файла минус первая выборка).
Olivier Moindrot
1
Проблема с этим маленьким размером буфера в том, что у вас будут кошки только в ваших первых партиях. Так что модель тривиально научится предсказывать только «кошку». Лучший способ обучить сеть - иметь пакеты с одинаковым количеством «кошек» и «не кошек».
Olivier Moindrot
1
Вы можете использовать tf.summary.histogramдля построения графика распределения меток во времени.
Olivier Moindrot
3
Это не опечатка :) В наборе данных по 10к изображений каждого класса, поэтому общий размер буфера должен быть больше 20к. Но в приведенном выше примере я взял размер буфера 1 КБ, что слишком мало.
Olivier Moindrot
1
Да, установка размера буфера на размер набора данных, как правило, подходит. Все, что превышает размер набора данных, в любом случае будет бесполезным (и если вы не повторите набор данных перед перетасовкой, буфер не может быть больше, чем набор данных).
Olivier Moindrot
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]

Владимир
источник
2
Это указывает на то, что для каждого элемента, выданного итератором, буфер заполняется соответствующим следующим элементом набора данных, которого раньше не было в буфере.
Alex
2

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

Рамиро RC
источник