TFDS и детерминизм

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть на GitHub Скачать блокнот

В этом документе объясняется:

  • TFDS гарантирует детерминизм
  • В каком порядке TFDS читает примеры
  • Различные предостережения и подводные камни

Настраивать

Наборы данных

Некоторый контекст необходим, чтобы понять, как TFDS считывает данные.

Во время генерации, TFDS записать исходные данные в стандартные .tfrecord файлы. Для больших наборов данных, несколько .tfrecord создаются файлы, каждый из которых содержит несколько примеров. Мы называем каждый .tfrecord файл осколок.

В этом руководстве используется imagenet с 1024 осколками:

import re
import tensorflow_datasets as tfds

imagenet = tfds.builder('imagenet2012')

num_shards = imagenet.info.splits['train'].num_shards
num_examples = imagenet.info.splits['train'].num_examples
print(f'imagenet has {num_shards} shards ({num_examples} examples)')
imagenet has 1024 shards (1281167 examples)

Поиск идентификаторов примеров наборов данных

Вы можете перейти к следующему разделу, если хотите узнать только о детерминизме.

Каждый пример набора данных однозначно идентифицируется id (например , 'imagenet2012-train.tfrecord-01023-of-01024__32' ). Вы можете восстановить этот id , передавая read_config.add_tfds_id = True , который добавит 'tfds_id' ключ в Словаре от tf.data.Dataset .

В этом руководстве мы определяем небольшую утилиту, которая будет печатать примеры идентификаторов набора данных (преобразованных в целое число, чтобы было удобнее читать):

Детерминизм при чтении

В этом разделе объясняется deterministim гарантии tfds.load .

С shuffle_files=False ( по умолчанию)

По умолчанию TFDS дают примеры детерминировано ( shuffle_files=False )

# Same as: imagenet.as_dataset(split='train').take(20)
print_ex_ids(imagenet, split='train', take=20)
print_ex_ids(imagenet, split='train', take=20)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1251, 1252, 1253, 1254]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1251, 1252, 1253, 1254]

Для повышения производительности, TFDS прочитал несколько черепков в то же время с помощью tf.data.Dataset.interleave . Мы видим , что в данном примере TFDS переключиться на осколок 2 после прочтения 16 примеров ( ..., 14, 15, 1251, 1252, ... ). Подробнее о чередовании ниже.

Аналогичным образом, API-интерфейс subplit также детерминирован:

print_ex_ids(imagenet, split='train[67%:84%]', take=20)
print_ex_ids(imagenet, split='train[67%:84%]', take=20)
[858382, 858383, 858384, 858385, 858386, 858387, 858388, 858389, 858390, 858391, 858392, 858393, 858394, 858395, 858396, 858397, 859533, 859534, 859535, 859536]
[858382, 858383, 858384, 858385, 858386, 858387, 858388, 858389, 858390, 858391, 858392, 858393, 858394, 858395, 858396, 858397, 859533, 859534, 859535, 859536]

Если вы тренируетесь более одной эпохи, выше настройка не рекомендуется , так как все эпохи прочтет черепки в том же порядке (так хаотичность ограничивается ds = ds.shuffle(buffer) размер буфера).

С shuffle_files=True

С shuffle_files=True , черепки перемешиваются для каждой эпохи, поэтому чтение не является детерминированным больше.

print_ex_ids(imagenet, split='train', shuffle_files=True, take=20)
print_ex_ids(imagenet, split='train', shuffle_files=True, take=20)
[568017, 329050, 329051, 329052, 329053, 329054, 329056, 329055, 568019, 568020, 568021, 568022, 568023, 568018, 568025, 568024, 568026, 568028, 568030, 568031]
[43790, 43791, 43792, 43793, 43796, 43794, 43797, 43798, 43795, 43799, 43800, 43801, 43802, 43803, 43804, 43805, 43806, 43807, 43809, 43810]

См. Рецепт ниже, чтобы получить детерминированное перемешивание файлов.

Предупреждение о детерминизме: чередование аргументов

Изменение read_config.interleave_cycle_length , read_config.interleave_block_length изменит порядок примеры.

TFDS полагается на tf.data.Dataset.interleave только загружать несколько черепков сразу, улучшая производительность и снижает потребление памяти.

Примерный порядок гарантированно будет одинаковым только для фиксированного значения аргументов чередования. См перемежения документ , чтобы понять , что cycle_length и block_length соответствуют тоже.

  • cycle_length=16 , block_length=16 ( по умолчанию, то же самое, что и выше):
print_ex_ids(imagenet, split='train', take=20)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1251, 1252, 1253, 1254]
  • cycle_length=3 , block_length=2 :
read_config = tfds.ReadConfig(
    interleave_cycle_length=3,
    interleave_block_length=2,
)
print_ex_ids(imagenet, split='train', read_config=read_config, take=20)
[0, 1, 1251, 1252, 2502, 2503, 2, 3, 1253, 1254, 2504, 2505, 4, 5, 1255, 1256, 2506, 2507, 6, 7]

Во втором примере, мы видим , что набор данных чтения 2 ( block_length=2 ) примеров в осколке, а затем перейти к следующему осколка. Каждые 2 * 3 ( cycle_length=3 ) примеры, она восходит к первому осколок ( shard0-ex0, shard0-ex1, shard1-ex0, shard1-ex1, shard2-ex0, shard2-ex1, shard0-ex2, shard0-ex3, shard1-ex2, shard1-ex3, shard2-ex2,... ).

Подразделение и пример заказа

Каждый пример имеет идентификатор 0, 1, ..., num_examples-1 . Subsplit API выбрать кусочек примеров (например , train[:x] выберите 0, 1, ..., x-1 ).

Однако внутри подразделения примеры не читаются в порядке возрастания идентификаторов (из-за осколков и чередования).

Более конкретно, ds.take(x) и split='train[:x]' не эквивалентны!

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

print_ex_ids(imagenet, split='train', take=25)  # tfds.load(..., split='train').take(25)
print_ex_ids(imagenet, split='train[:25]', take=-1)  # tfds.load(..., split='train[:25]')
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258, 1259]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]

После того, как 16 (block_length) примеров, .take(25) переключается на следующий осколок в то время как train[:25] продолжить чтение примеров в первом из осколка.

Рецепты

Получите детерминированное перемешивание файлов

Есть 2 способа детерминированного перемешивания:

  1. Установка shuffle_seed . Примечание. Для этого необходимо менять начальное число в каждую эпоху, иначе осколки будут считываться в том же порядке между эпохами.
read_config = tfds.ReadConfig(
    shuffle_seed=32,
)

# Deterministic order, different from the default shuffle_files=False above
print_ex_ids(imagenet, split='train', shuffle_files=True, read_config=read_config, take=22)
print_ex_ids(imagenet, split='train', shuffle_files=True, read_config=read_config, take=22)
[176411, 176412, 176413, 176414, 176415, 176416, 176417, 176418, 176419, 176420, 176421, 176422, 176423, 176424, 176425, 176426, 710647, 710648, 710649, 710650, 710651, 710652]
[176411, 176412, 176413, 176414, 176415, 176416, 176417, 176418, 176419, 176420, 176421, 176422, 176423, 176424, 176425, 176426, 710647, 710648, 710649, 710650, 710651, 710652]
  1. Использование experimental_interleave_sort_fn : Это дает полный контроль над которым черепки считываются и в каком порядке, а не полагаться на ds.shuffle порядке.
def _reverse_order(file_instructions):
  return list(reversed(file_instructions))

read_config = tfds.ReadConfig(
    experimental_interleave_sort_fn=_reverse_order,
)

# Last shard (01023-of-01024) is read first
print_ex_ids(imagenet, split='train', read_config=read_config, take=5)
[1279916, 1279917, 1279918, 1279919, 1279920]

Получите детерминированный вытесняемый конвейер

Этот более сложный. Нет простого удовлетворительного решения.

  1. Без ds.shuffle и с детерминированной перетасовкой, в теории это должно быть возможным подсчитать примеры , которые были прочитаны и выводят примеры , которые были прочитаны в каждом осколке (в зависимости от cycle_length , block_length и порядка шарда). Тогда skip , take для каждого осколка может быть введены через experimental_interleave_sort_fn .

  2. С ds.shuffle это, вероятно , невозможно без Воспроизводится полный трубопровод обучения. Это потребует сохранения ds.shuffle состояния буфера вывести примеры , которые были прочитаны. Примеры могут быть прерывистыми (например , shard5_ex2 , shard5_ex4 читать , но не shard5_ex3 ).

  3. С ds.shuffle , один из способов будет сохранить все shards_ids / example_ids читать (выведенные из tfds_id ), затем выводя инструкции файла из этого.

Простейший случай для 1. должен иметь .skip(x).take(y) матч train[x:x+y] совпадают. Это требует:

  • Набор cycle_length=1 (так Осколки считываются последовательно)
  • Набор shuffle_files=False
  • Не используйте ds.shuffle

Его следует использовать только на огромном наборе данных, где обучение длится всего 1 эпоха. Примеры будут читаться в порядке перемешивания по умолчанию.

read_config = tfds.ReadConfig(
    interleave_cycle_length=1,  # Read shards sequentially
)

print_ex_ids(imagenet, split='train', read_config=read_config, skip=40, take=22)
# If the job get pre-empted, using the subsplit API will skip at most `len(shard0)`
print_ex_ids(imagenet, split='train[40:]', read_config=read_config, take=22)
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61]
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61]

Найдите, какие осколки / примеры читаются для данного подразделения

С tfds.core.DatasetInfo , у вас есть прямой доступ к командам чтения.

imagenet.info.splits['train[44%:45%]'].file_instructions
[FileInstruction(filename='imagenet2012-train.tfrecord-00450-of-01024', skip=700, take=-1, num_examples=551),
 FileInstruction(filename='imagenet2012-train.tfrecord-00451-of-01024', skip=0, take=-1, num_examples=1251),
 FileInstruction(filename='imagenet2012-train.tfrecord-00452-of-01024', skip=0, take=-1, num_examples=1251),
 FileInstruction(filename='imagenet2012-train.tfrecord-00453-of-01024', skip=0, take=-1, num_examples=1251),
 FileInstruction(filename='imagenet2012-train.tfrecord-00454-of-01024', skip=0, take=-1, num_examples=1252),
 FileInstruction(filename='imagenet2012-train.tfrecord-00455-of-01024', skip=0, take=-1, num_examples=1251),
 FileInstruction(filename='imagenet2012-train.tfrecord-00456-of-01024', skip=0, take=-1, num_examples=1251),
 FileInstruction(filename='imagenet2012-train.tfrecord-00457-of-01024', skip=0, take=-1, num_examples=1251),
 FileInstruction(filename='imagenet2012-train.tfrecord-00458-of-01024', skip=0, take=-1, num_examples=1251),
 FileInstruction(filename='imagenet2012-train.tfrecord-00459-of-01024', skip=0, take=-1, num_examples=1251),
 FileInstruction(filename='imagenet2012-train.tfrecord-00460-of-01024', skip=0, take=1001, num_examples=1001)]