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

В соответствии с 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

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, то вы вообще не перетасовываете. Для очень больших наборов данных типичный подход "достаточно хорошо" заключается в случайном чередовании данных в несколько файлов один раз перед обучением, затем равномерно перемешивать имена файлов, а затем использовать меньший буфер перетасовки. Однако соответствующий выбор будет зависеть от точного характера вашей учебной работы.


Ответ 2

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

Я хотел бы продолжить предыдущий ответ @mrry, чтобы подчеркнуть важность buffer_size в tf.data.Dataset.shuffle().

Низкий buffer_size в некоторых случаях не просто даст вам buffer_size перетасовку: он может испортить всю вашу тренировку.


Практический пример: классификатор кошек

Предположим, например, что вы тренируете классификатор cat по изображениям, а ваши данные организованы следующим образом (по 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, или заранее buffer_size 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 в нашем примере).

Ответ 3

Как упоминалось выше, ответ @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]

Ответ 4

Я обнаружил, что @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)), где я - количество раз, которое вы запускали 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.

Ответ 5

Код

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]

Ответ 6

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