Escrevendo conjuntos de dados personalizados

Siga este guia para criar um novo conjunto de dados (seja no TFDS ou em seu próprio repositório).

Consulte nossa lista de conjuntos de dados para verificar se o conjunto de dados desejado já está presente.

Resumindo:

A maneira mais fácil de criar um novo conjunto de dados é usando a CLI do TFDS :

cd path/to/my/project/datasets/
tfds new my_dataset  # Create `my_dataset/my_dataset.py` template files
# [...] Manually modify `my_dataset/my_dataset_dataset_builder.py` to implement your dataset.
cd my_dataset/
tfds build  # Download and prepare the dataset to `~/tensorflow_datasets/`

Para usar o novo conjunto de dados com tfds.load('my_dataset') :

  • tfds.load detectará e carregará automaticamente o conjunto de dados gerado em ~/tensorflow_datasets/my_dataset/ (por exemplo, pelo tfds build ).
  • Alternativamente, você pode import my.project.datasets.my_dataset para registrar seu conjunto de dados:
import my.project.datasets.my_dataset  # Register `my_dataset`

ds = tfds.load('my_dataset')  # `my_dataset` registered

Visão geral

Os conjuntos de dados estão distribuídos em diversos formatos e locais, e nem sempre são armazenados em um formato pronto para ser utilizado em um pipeline de aprendizado de máquina. É aí que entra o TFDS.

O TFDS processa esses conjuntos de dados em um formato padrão (dados externos -> arquivos serializados), que podem então ser carregados como um pipeline de aprendizado de máquina (arquivos serializados -> tf.data.Dataset ). A serialização é feita apenas uma vez. Acessos subsequentes lerão diretamente desses arquivos pré-processados.

A maior parte do pré-processamento é feita automaticamente. Cada conjunto de dados implementa uma subclasse de tfds.core.DatasetBuilder , que especifica:

  • De onde vêm os dados (ou seja, seus URLs);
  • Qual é a aparência do conjunto de dados (ou seja, suas características);
  • Como os dados devem ser divididos (ex: TRAIN e TEST );
  • e os exemplos individuais no conjunto de dados.

Escreva seu conjunto de dados

Modelo padrão: tfds new

Utilize a CLI do TFDS para gerar os arquivos Python de modelo necessários.

cd path/to/project/datasets/  # Or use `--dir=path/to/project/datasets/` below
tfds new my_dataset

Este comando irá gerar uma nova pasta my_dataset/ com a seguinte estrutura:

my_dataset/
    __init__.py
    README.md # Markdown description of the dataset.
    CITATIONS.bib # Bibtex citation for the dataset.
    TAGS.txt # List of tags describing the dataset.
    my_dataset_dataset_builder.py # Dataset definition
    my_dataset_dataset_builder_test.py # Test
    dummy_data/ # (optional) Fake data (used for testing)
    checksum.tsv # (optional) URL checksums (see `checksums` section).

Procure por TODO(my_dataset) aqui e modifique conforme necessário.

Exemplo de conjunto de dados

Todos os conjuntos de dados são subclasses implementadas de tfds.core.DatasetBuilder , que cuida da maior parte do código repetitivo. Ele oferece suporte a:

  • Conjuntos de dados pequenos/médios que podem ser gerados em uma única máquina (este tutorial).
  • Conjuntos de dados muito grandes que exigem geração distribuída (usando Apache Beam , consulte nosso guia para conjuntos de dados enormes ).

Segue um exemplo mínimo de um construtor de conjunto de dados baseado em tfds.core.GeneratorBasedBuilder :

class Builder(tfds.core.GeneratorBasedBuilder):
  """DatasetBuilder for my_dataset dataset."""

  VERSION = tfds.core.Version('1.0.0')
  RELEASE_NOTES = {
      '1.0.0': 'Initial release.',
  }

  def _info(self) -> tfds.core.DatasetInfo:
    """Dataset metadata (homepage, citation,...)."""
    return self.dataset_info_from_configs(
        features=tfds.features.FeaturesDict({
            'image': tfds.features.Image(shape=(256, 256, 3)),
            'label': tfds.features.ClassLabel(
                names=['no', 'yes'],
                doc='Whether this is a picture of a cat'),
        }),
    )

  def _split_generators(self, dl_manager: tfds.download.DownloadManager):
    """Download the data and define splits."""
    extracted_path = dl_manager.download_and_extract('http://data.org/data.zip')
    # dl_manager returns pathlib-like objects with `path.read_text()`,
    # `path.iterdir()`,...
    return {
        'train': self._generate_examples(path=extracted_path / 'train_images'),
        'test': self._generate_examples(path=extracted_path / 'test_images'),
    }

  def _generate_examples(self, path) -> Iterator[Tuple[Key, Example]]:
    """Generator of examples for each split."""
    for img_path in path.glob('*.jpeg'):
      # Yields (key, example)
      yield img_path.name, {
          'image': img_path,
          'label': 'yes' if img_path.name.startswith('yes_') else 'no',
      }

Note que, para alguns formatos de dados específicos, fornecemos construtores de conjuntos de dados prontos para uso que cuidam da maior parte do processamento de dados.

Vamos analisar em detalhes os 3 métodos abstratos para sobrescrever.

_info : metadados do conjunto de dados

_info retorna o tfds.core.DatasetInfo contendo os metadados do conjunto de dados .

def _info(self):
  # The `dataset_info_from_configs` base method will construct the
  # `tfds.core.DatasetInfo` object using the passed-in parameters and
  # adding: builder (self), description/citations/tags from the config
  # files located in the same package.
  return self.dataset_info_from_configs(
      homepage='https://dataset-homepage.org',
      features=tfds.features.FeaturesDict({
          'image_description': tfds.features.Text(),
          'image': tfds.features.Image(),
          # Here, 'label' can be 0-4.
          'label': tfds.features.ClassLabel(num_classes=5),
      }),
      # If there's a common `(input, target)` tuple from the features,
      # specify them here. They'll be used if as_supervised=True in
      # builder.as_dataset.
      supervised_keys=('image', 'label'),
      # Specify whether to disable shuffling on the examples. Set to False by default.
      disable_shuffling=False,
  )

A maioria dos campos deve ser autoexplicativa. Algumas especificações:

Escrevendo o arquivo BibText CITATIONS.bib :

  • Procure instruções de citação no site do conjunto de dados (use o formato BibTeX).
  • Para artigos do arXiv : encontre o artigo e clique no link BibText no lado direito.
  • Encontre o artigo no Google Acadêmico , clique nas aspas duplas abaixo do título e, na janela pop-up, clique em BibTeX .
  • Caso não haja um artigo associado (por exemplo, apenas um site), você pode usar o Editor Online do BibTeX para criar uma entrada BibTeX personalizada (o menu suspenso possui um tipo de entrada Online ).

Atualizando o arquivo TAGS.txt :

  • Todas as tags permitidas já estão preenchidas no arquivo gerado.
  • Remova todas as tags que não se aplicam ao conjunto de dados.
  • As tags válidas estão listadas em tensorflow_datasets/core/valid_tags.txt .
  • Para adicionar uma etiqueta a essa lista, envie um PR (Pull Request).

Manter a ordem do conjunto de dados

Por padrão, os registros dos conjuntos de dados são embaralhados ao serem armazenados para tornar a distribuição das classes mais uniforme em todo o conjunto de dados, visto que, frequentemente, registros pertencentes à mesma classe são contíguos. Para especificar que o conjunto de dados deve ser classificado pela chave gerada por _generate_examples , o campo disable_shuffling deve ser definido como True . Por padrão, ele está definido como False .

def _info(self):
  return self.dataset_info_from_configs(
    # [...]
    disable_shuffling=True,
    # [...]
  )

Lembre-se de que desativar o embaralhamento tem um impacto no desempenho, pois os fragmentos não podem mais ser lidos em paralelo.

_split_generators : baixa e divide dados

Download e extração de dados de origem

A maioria dos conjuntos de dados precisa baixar dados da web. Isso é feito usando o argumento de entrada tfds.download.DownloadManager de _split_generators . dl_manager possui os seguintes métodos:

  • download : suporta http(s):// , ftp(s)://
  • extract : atualmente suporta arquivos .zip , .gz e .tar .
  • download_and_extract : O mesmo que dl_manager.extract(dl_manager.download(urls))

Todos esses métodos retornam tfds.core.Path (aliases para epath.Path ), que são objetos semelhantes a pathlib.Path .

Esses métodos suportam estruturas aninhadas arbitrárias ( list , dict ), como:

extracted_paths = dl_manager.download_and_extract({
    'foo': 'https://example.com/foo.zip',
    'bar': 'https://example.com/bar.zip',
})
# This returns:
assert extracted_paths == {
    'foo': Path('/path/to/extracted_foo/'),
    'bar': Path('/path/extracted_bar/'),
}

Download e extração manual

Alguns dados não podem ser baixados automaticamente (por exemplo, exigem login); nesse caso, o usuário deverá baixar manualmente os dados de origem e colocá-los em manual_dir/ (o padrão é ~/tensorflow_datasets/downloads/manual/ ).

Os arquivos podem então ser acessados ​​através de dl_manager.manual_dir :

class MyDataset(tfds.core.GeneratorBasedBuilder):

  MANUAL_DOWNLOAD_INSTRUCTIONS = """
  Register into https://example.org/login to get the data. Place the `data.zip`
  file in the `manual_dir/`.
  """

  def _split_generators(self, dl_manager):
    # data_path is a pathlib-like `Path('<manual_dir>/data.zip')`
    archive_path = dl_manager.manual_dir / 'data.zip'
    # Extract the manually downloaded `data.zip`
    extracted_path = dl_manager.extract(archive_path)
    ...

O local manual_dir pode ser personalizado com tfds build --manual_dir= ou usando tfds.download.DownloadConfig .

Leia o arquivo diretamente

A dl_manager.iter_archive lê arquivos sequencialmente sem extraí-los. Isso pode economizar espaço de armazenamento e melhorar o desempenho em alguns sistemas de arquivos.

for filename, fobj in dl_manager.iter_archive('path/to/archive.zip'):
  ...

fobj tem os mesmos métodos que with open('rb') as fobj: (ex: fobj.read() )

Especificando divisões do conjunto de dados

Se o conjunto de dados vier com divisões predefinidas (por exemplo, MNIST tem divisões train e test ), mantenha-as. Caso contrário, especifique apenas uma única divisão all . Os usuários podem criar subdivisões dinamicamente com a API subsplit (por exemplo split='train[80%:]' ). Observe que qualquer string alfabética pode ser usada como nome da divisão, exceto o já mencionado all .

def _split_generators(self, dl_manager):
  # Download source data
  extracted_path = dl_manager.download_and_extract(...)

  # Specify the splits
  return {
      'train': self._generate_examples(
          images_path=extracted_path / 'train_imgs',
          label_path=extracted_path / 'train_labels.csv',
      ),
      'test': self._generate_examples(
          images_path=extracted_path / 'test_imgs',
          label_path=extracted_path / 'test_labels.csv',
      ),
  }

_generate_examples : Gerador de exemplos

_generate_examples gera os exemplos para cada divisão a partir dos dados de origem.

Normalmente, esse método lê artefatos do conjunto de dados de origem (por exemplo, um arquivo CSV) e produz tuplas (key, feature_dict) :

  • key : Identificador do exemplo. Usado para embaralhar os exemplos deterministicamente usando hash(key) ou para classificar por chave quando o embaralhamento estiver desativado (consulte a seção Manter a ordem do conjunto de dados ). Deve ser:
    • exclusivo : Se dois exemplos usarem a mesma chave, uma exceção será lançada.
    • Determinístico : Não deve depender de download_dir , os.path.listdir , etc. Gerar os dados duas vezes deve resultar na mesma chave.
    • Comparável : Se o embaralhamento estiver desativado, a chave será usada para classificar o conjunto de dados.
  • feature_dict : Um dict contendo os valores de exemplo.
    • A estrutura deve corresponder à estrutura features= definida em tfds.core.DatasetInfo .
    • Tipos de dados complexos (imagem, vídeo, áudio, etc.) serão codificados automaticamente.
    • Cada recurso geralmente aceita vários tipos de entrada (por exemplo, vídeo aceita /path/to/vid.mp4 , np.array(shape=(l, h, w, c)) , List[paths] , List[np.array(shape=(h, w, c)] , List[img_bytes] ,...)
    • Consulte o guia do conector de recursos para obter mais informações.
def _generate_examples(self, images_path, label_path):
  # Read the input data out of the source files
  with label_path.open() as f:
    for row in csv.DictReader(f):
      image_id = row['image_id']
      # And yield (key, feature_dict)
      yield image_id, {
          'image_description': row['description'],
          'image': images_path / f'{image_id}.jpeg',
          'label': row['label'],
      }

Acesso a arquivos e tf.io.gfile

Para dar suporte a sistemas de armazenamento em nuvem, evite usar as operações de E/S integradas do Python.

Em vez disso, o dl_manager retorna objetos semelhantes ao pathlib, diretamente compatíveis com o armazenamento do Google Cloud:

path = dl_manager.download_and_extract('http://some-website/my_data.zip')

json_path = path / 'data/file.json'

json.loads(json_path.read_text())

Como alternativa, utilize a API tf.io.gfile em vez da API integrada para operações com arquivos:

Pathlib deve ser preferido a tf.io.gfile (ver justificativa) .

Dependências adicionais

Alguns conjuntos de dados requerem dependências adicionais do Python apenas durante a geração. Por exemplo, o conjunto de dados SVHN usa scipy para carregar alguns dados.

Se você estiver adicionando um conjunto de dados ao repositório TFDS, use tfds.core.lazy_imports para manter o pacote tensorflow-datasets pequeno. Os usuários instalarão dependências adicionais somente quando necessário.

Para usar lazy_imports :

  • Adicione uma entrada para seu conjunto de dados em DATASET_EXTRAS no setup.py . Isso permite que os usuários façam, por exemplo, pip install 'tensorflow-datasets[svhn]' para instalar as dependências extras.
  • Adicione uma entrada para sua importação ao LazyImporter e ao LazyImportsTest .
  • Use tfds.core.lazy_imports para acessar a dependência (por exemplo, tfds.core.lazy_imports.scipy ) em seu DatasetBuilder .

Dados corrompidos

Alguns conjuntos de dados não são perfeitamente limpos e contêm dados corrompidos (por exemplo, as imagens estão em arquivos JPEG, mas alguns são JPEG inválidos). Esses exemplos devem ser ignorados, mas inclua uma nota na descrição do conjunto de dados informando quantos exemplos foram descartados e o motivo.

Configuração/variantes do conjunto de dados (tfds.core.BuilderConfig)

Alguns conjuntos de dados podem ter múltiplas variantes ou opções de como os dados são pré-processados ​​e gravados em disco. Por exemplo, o cycle_gan tem uma configuração por par de objetos ( cycle_gan/horse2zebra , cycle_gan/monet2photo , ...).

Isso é feito através de tfds.core.BuilderConfig :

  1. Defina seu objeto de configuração como uma subclasse de tfds.core.BuilderConfig . Por exemplo, MyDatasetConfig .

    @dataclasses.dataclass
    class MyDatasetConfig(tfds.core.BuilderConfig):
      img_size: Tuple[int, int] = (0, 0)
    
  2. Defina o membro de classe BUILDER_CONFIGS = [] em MyDataset que lista MyDatasetConfig que o conjunto de dados expõe.

    class MyDataset(tfds.core.GeneratorBasedBuilder):
      VERSION = tfds.core.Version('1.0.0')
      # pytype: disable=wrong-keyword-args
      BUILDER_CONFIGS = [
          # `name` (and optionally `description`) are required for each config
          MyDatasetConfig(name='small', description='Small ...', img_size=(8, 8)),
          MyDatasetConfig(name='big', description='Big ...', img_size=(32, 32)),
      ]
      # pytype: enable=wrong-keyword-args
    
  3. Use self.builder_config em MyDataset para configurar a geração de dados (por exemplo, shape=self.builder_config.img_size ). Isso pode incluir definir valores diferentes em _info() ou alterar o acesso aos dados de download.

Notas:

  • Cada configuração tem um nome único. O nome totalmente qualificado de uma configuração é dataset_name/config_name (ex: coco/2017 ).
  • Se não for especificado, a primeira configuração em BUILDER_CONFIGS será usada (por exemplo, tfds.load('c4') usa c4/en como padrão).

Consulte anli para obter um exemplo de conjunto de dados que usa BuilderConfig .

Versão

A versão pode ter dois significados diferentes:

  • A versão original dos dados "externos": por exemplo, COCO v2019, v2017,...
  • A versão "interna" do código TFDS: por exemplo, renomear um recurso em tfds.features.FeaturesDict , corrigir um bug em _generate_examples

Para atualizar um conjunto de dados:

  • Para atualização de dados "externos": Vários usuários podem querer acessar um ano/versão específico simultaneamente. Isso é feito usando um tfds.core.BuilderConfig por versão (por exemplo, coco/2017 , coco/2019 ) ou uma classe por versão (por exemplo, Voc2007 , Voc2012 ).
  • Para atualização de código "interna": Os usuários baixam apenas a versão mais recente. Qualquer atualização de código deve aumentar o atributo de classe VERSION (por exemplo, de 1.0.0 para VERSION = tfds.core.Version('2.0.0') ), seguindo a versão semântica .

Adicionar uma importação para registro

Não se esqueça de importar o módulo de conjunto de dados para o __init__ do seu projeto para que ele seja registrado automaticamente em tfds.load e tfds.builder .

import my_project.datasets.my_dataset  # Register MyDataset

ds = tfds.load('my_dataset')  # MyDataset available

Por exemplo, se você estiver contribuindo para tensorflow/datasets , adicione a importação do módulo ao arquivo __init__.py do subdiretório (por exemplo, image/__init__.py ).

Verifique possíveis problemas comuns de implementação.

Por favor, verifique as armadilhas comuns de implementação .

Teste seu conjunto de dados

Baixe e prepare: tfds build

Para gerar o conjunto de dados, execute o tfds build a partir do diretório my_dataset/ :

cd path/to/datasets/my_dataset/
tfds build --register_checksums

Algumas opções úteis para desenvolvimento:

  • --pdb : Entra no modo de depuração se uma exceção for lançada.
  • --overwrite : Exclui os arquivos existentes se o conjunto de dados já tiver sido gerado.
  • --max_examples_per_split : Gera apenas os primeiros X exemplos (o padrão é 1), em vez do conjunto de dados completo.
  • --register_checksums : Registra os checksums dos URLs baixados. Deve ser usado apenas durante o desenvolvimento.

Consulte a documentação da CLI para obter a lista completa de opções.

Somas de verificação

É recomendável registrar os checksums dos seus conjuntos de dados para garantir o determinismo, auxiliar na documentação, etc. Isso é feito gerando o conjunto de dados com a --register_checksums (veja a seção anterior).

Se você estiver disponibilizando seus conjuntos de dados através do PyPI, não se esqueça de exportar os arquivos checksums.tsv (por exemplo, no package_data do seu setup.py ).

Faça testes unitários no seu conjunto de dados.

tfds.testing.DatasetBuilderTestCase é um TestCase básico para exercitar completamente um conjunto de dados. Ele usa "dados fictícios" como dados de teste que imitam a estrutura do conjunto de dados de origem.

  • Os dados de teste devem ser colocados no diretório my_dataset/dummy_data/ e devem reproduzir os artefatos do conjunto de dados de origem, conforme baixados e extraídos. Eles podem ser criados manualmente ou automaticamente com um script ( exemplo de script ).
  • Certifique-se de usar dados diferentes nas divisões dos seus dados de teste, pois o teste falhará se as divisões do seu conjunto de dados se sobrepuserem.
  • Os dados de teste não devem conter nenhum material protegido por direitos autorais . Em caso de dúvida, não crie os dados usando material do conjunto de dados original.
import tensorflow_datasets as tfds
from . import my_dataset_dataset_builder


class MyDatasetTest(tfds.testing.DatasetBuilderTestCase):
  """Tests for my_dataset dataset."""
  DATASET_CLASS = my_dataset_dataset_builder.Builder
  SPLITS = {
      'train': 3,  # Number of fake train example
      'test': 1,  # Number of fake test example
  }

  # If you are calling `download/download_and_extract` with a dict, like:
  #   dl_manager.download({'some_key': 'http://a.org/out.txt', ...})
  # then the tests needs to provide the fake output paths relative to the
  # fake data directory
  DL_EXTRACT_RESULT = {
      'name1': 'path/to/file1',  # Relative to my_dataset/dummy_data dir.
      'name2': 'file2',
  }


if __name__ == '__main__':
  tfds.testing.test_main()

Execute o seguinte comando para testar o conjunto de dados.

python my_dataset_test.py

Envie-nos seu feedback

Estamos constantemente aprimorando o fluxo de trabalho de criação de conjuntos de dados, mas só conseguimos fazer isso se estivermos cientes dos problemas. Quais problemas ou erros você encontrou ao criar o conjunto de dados? Houve alguma parte que foi confusa ou que não funcionou na primeira tentativa?

Por favor, compartilhe seu feedback no GitHub .