Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/datasets/determinism.ipynb
25115 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

TFDS e determinismo

Este documento explica:

  • As garantias do TFDS sobre o determinismo

  • Em que ordem o TFDS lê os exemplos

  • Diversas ressalvas e problemas

Configuração

Datasets

É necessário algum contexto para entender como o TFDS lê os dados.

Durante a geração, o TFDS grava os dados originais em arquivos .tfrecord padronizados. Para grandes datasets, múltiplos arquivos .tfrecord são criados, cada um contendo múltiplos exemplos. Chamamos cada arquivo .tfrecord de fragmento (shard).

Este guia usa imagenet que possui 1024 fragmentos:

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)

Encontrando os IDs dos exemplos de datasets

Você pode pular para a seção seguinte se quiser apenas saber sobre determinismo.

Cada exemplo de dataset é identificado exclusivamente por um id (por exemplo 'imagenet2012-train.tfrecord-01023-of-01024__32'). Você pode recuperar esse id passando read_config.add_tfds_id = True que adicionará uma chave 'tfds_id' no dict do tf.data.Dataset.

Neste tutorial, definimos um pequeno utilitário que imprimirá os ids de exemplo do dataset (convertidos em inteiro para serem mais legíveis por humanos):

#@title def load_dataset(builder, **as_dataset_kwargs): """Load the dataset with the tfds_id.""" read_config = as_dataset_kwargs.pop('read_config', tfds.ReadConfig()) read_config.add_tfds_id = True # Set `True` to return the 'tfds_id' key return builder.as_dataset(read_config=read_config, **as_dataset_kwargs) def print_ex_ids( builder, *, take: int, skip: int = None, **as_dataset_kwargs, ) -> None: """Print the example ids from the given dataset split.""" ds = load_dataset(builder, **as_dataset_kwargs) if skip: ds = ds.skip(skip) ds = ds.take(take) exs = [ex['tfds_id'].numpy().decode('utf-8') for ex in ds] exs = [id_to_int(tfds_id, builder=builder) for tfds_id in exs] print(exs) def id_to_int(tfds_id: str, builder) -> str: """Format the tfds_id in a more human-readable.""" match = re.match(r'\w+-(\w+).\w+-(\d+)-of-\d+__(\d+)', tfds_id) split_name, shard_id, ex_id = match.groups() split_info = builder.info.splits[split_name] return sum(split_info.shard_lengths[:int(shard_id)]) + int(ex_id)

Determinismo ao ler

Esta seção explica a garantia determinística de tfds.load.

Com shuffle_files=False (padrão)

Por padrão, o TFDS entrega exemplos de forma determinística (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]

Para melhor desempenho, o TFDS lê vários fragmentos ao mesmo tempo usando tf.data.Dataset.interleave. Vemos neste exemplo que o TFDS muda para o fragmento 2 após ler 16 exemplos (..., 14, 15, 1251, 1252, ...). Mais sobre o interleave abaixo.

Da mesma forma, a API subsplit também é determinística:

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]

Se você estiver treinando para mais de uma época, a configuração acima não é recomendada, pois todas as épocas lerão os fragmentos na mesma ordem (portanto, a aleatoriedade será limitada ao tamanho do buffer ds = ds.shuffle(buffer)).

Com shuffle_files=True

Com shuffle_files=True, os fragmentos são embaralhados para cada época, portanto a leitura não é mais determinística.

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]

Observação: Definir shuffle_files=True também desativa deterministic em tf.data.Options para aumentar o desempenho. Portanto, mesmo pequenos datasets que possuem apenas um único fragmento (como mnist) tornam-se não determinísticos.

Veja a receita abaixo para obter embaralhamento determinístico de arquivos.

Ressalva ao determinismo: argumentos de intercalação

Alterar read_config.interleave_cycle_length, read_config.interleave_block_length irá mudar a ordem dos exemplos.

O TFDS depende de tf.data.Dataset.interleave para carregar apenas alguns fragmentos de cada vez, melhorando o desempenho e reduzindo o uso de memória.

A ordem dos exemplos só é garantida como sendo a mesma para um valor fixo de argumentos de intercalação. Veja o documento sobre intercalação (interleave) para também entender a que correspondem cycle_length e block_length.

  • cycle_length=16 , block_length=16 (padrão, igual ao acima):

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]

No segundo exemplo, vemos que o dataset lê 2 (block_length=2) exemplos num fragmento e depois muda para o próximo fragmento. A cada 2 * 3 (cycle_length=3) exemplos, ele volta para o primeiro shard0-ex0, shard0-ex1, shard1-ex0, shard1-ex1, shard2-ex0, shard2-ex1, shard0-ex2, shard0-ex3, shard1-ex2, shard1-ex3, shard2-ex2,...).

Subsplit e ordem dos exemplos

Cada exemplo possui um id 0, 1, ..., num_examples-1. A API subsplit seleciona uma fatia de exemplos (por exemplo, train[:x] select 0, 1, ..., x-1).

No entanto, dentro do subsplit, os exemplos não são lidos em ordem crescente de id (devido a fragmentos e intercalação).

Mais especificamente, ds.take(x) e split='train[:x]' não são equivalentes!

Isto pode ser visto facilmente no exemplo de intercalação acima, onde os exemplos vêm de fragmentos diferentes.

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]

Depois dos 16 exemplos (block_length), .take(25) muda para o fragmento seguinte enquanto train[:25] continua lendo exemplos do primeiro fragmento.

Receitas

Obtenha embaralhamento determinístico de arquivos

Há 2 maneiras de se obter embaralhamento determinístico:

  1. Configurando o shuffle_seed. Observação: Isto requer a alteração da semente em cada época, caso contrário, os fragmentos serão lidos na mesma ordem entre épocas.

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. Usando experimental_interleave_sort_fn: Isto garante controle total sobre quais fragmentos são lidos e em qual ordem, em vez de depender da ordem em 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]

Obtenha a pipeline preemptiva determinística

Este é mais complicado. Não existe uma solução fácil e satisfatória.

  1. Sem ds.shuffle e com embaralhamento determinístico, em teoria deveria ser possível contar os exemplos que foram lidos e deduzir quais exemplos foram lidos em cada fragmento (como função de cycle_length, block_length e ordem do fragmento). Então o skip, take para cada fragmento poderia ser injetado através de experimental_interleave_sort_fn.

  2. Com ds.shuffle, é provavelmente impossível sem repetir o pipeline de treinamento completo. Seria necessário salvar o estado do buffer ds.shuffle para deduzir quais exemplos foram lidos. Os exemplos podem ser não contínuos (por exemplo shard5_ex2, shard5_ex4 foram lidos, mas não shard5_ex3).

  3. Com ds.shuffle, uma solução seria salvar todos os shards_ids/example_ids lidos (deduzidos de tfds_id) e, em seguida, deduzir as instruções do arquivo a partir daí.

O caso mais simples para 1. é ter .skip(x).take(y) match train[x:x+y] match. Isto requer:

  • Definir o cycle_length=1 (para que os fragmentos sejam lidos sequencialmente)

  • Definir shuffle_files=False

  • Não usar ds.shuffle

Ele só deve ser usado em grandes datasets onde o treinamento dura apenas 1 época. Os exemplos seriam lidos na ordem aleatória padrão.

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]

Descubra quais fragmentos/exemplos são lidos para uma determinada subdivisão

Com o tfds.core.DatasetInfo, você tem acesso direto às instruções de leitura.

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)]