Scrittura di set di dati personalizzati

Segui questa guida per creare un nuovo set di dati (in TFDS o nel tuo repository personale).

Consulta il nostro elenco di dataset per verificare se il dataset che desideri è già presente.

In breve

Il modo più semplice per scrivere un nuovo dataset è utilizzare la CLI di 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/`

Per utilizzare il nuovo dataset con tfds.load('my_dataset') :

  • tfds.load rileverà e caricherà automaticamente il dataset generato in ~/tensorflow_datasets/my_dataset/ (ad esempio da tfds build ).
  • In alternativa, puoi import my.project.datasets.my_dataset per registrare il tuo dataset:
import my.project.datasets.my_dataset  # Register `my_dataset`

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

Panoramica

I dataset sono distribuiti in svariati formati e in svariate posizioni, e non sempre vengono archiviati in un formato pronto per essere utilizzato in una pipeline di machine learning. Ecco che entra in gioco TFDS.

TFDS elabora questi set di dati convertendoli in un formato standard (dati esterni -> file serializzati), che possono poi essere caricati come pipeline di machine learning (file serializzati -> tf.data.Dataset ). La serializzazione viene eseguita una sola volta. Gli accessi successivi leggeranno direttamente da questi file pre-elaborati.

La maggior parte della preelaborazione viene eseguita automaticamente. Ogni dataset implementa una sottoclasse di tfds.core.DatasetBuilder , che specifica:

  • Da dove provengono i dati (ovvero i relativi URL);
  • Che aspetto ha il dataset (ovvero le sue caratteristiche);
  • Come suddividere i dati (ad esempio, TRAIN e TEST );
  • e i singoli esempi presenti nel dataset.

Scrivi il tuo set di dati

Modello predefinito: tfds new

Utilizza TFDS CLI per generare i file Python modello necessari.

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

Questo comando genererà una nuova cartella my_dataset/ con la seguente struttura:

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

Cerca TODO(my_dataset) qui e modifica di conseguenza.

Esempio di set di dati

Tutti i dataset sono sottoclassi implementate di tfds.core.DatasetBuilder , che si occupa della maggior parte del codice ripetitivo. Supporta:

  • Set di dati di piccole/medie dimensioni che possono essere generati su una singola macchina (questo tutorial).
  • Set di dati molto grandi che richiedono la generazione distribuita (utilizzando Apache Beam , vedi la nostra guida sui set di dati di grandi dimensioni ).

Ecco un esempio minimo di generatore di dataset basato su 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',
      }

Si noti che, per alcuni formati di dati specifici, forniamo strumenti di creazione di dataset pronti all'uso per gestire la maggior parte dell'elaborazione dei dati.

Vediamo nel dettaglio i 3 metodi astratti per sovrascrivere.

_info : metadati del dataset

_info restituisce tfds.core.DatasetInfo contenente i metadati del dataset .

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

La maggior parte dei campi dovrebbe essere autoesplicativa. Alcune precisazioni:

Scrittura del file BibText CITATIONS.bib :

  • Consulta il sito web del dataset per le istruzioni di citazione (utilizza il formato BibTex).
  • Per gli articoli su arXiv : individua l'articolo e clicca sul link BibText a destra.
  • Trova l'articolo su Google Scholar , fai clic sulle virgolette doppie sotto il titolo e nella finestra a comparsa fai clic su BibTeX .
  • Se non è presente un documento cartaceo associato (ad esempio, esiste solo un sito web), è possibile utilizzare l' editor online di BibTeX per creare una voce BibTeX personalizzata (il menu a tendina include la tipologia di voce Online ).

Aggiornamento del file TAGS.txt :

  • Tutti i tag consentiti sono precompilati nel file generato.
  • Rimuovi tutti i tag che non si applicano al dataset.
  • I tag validi sono elencati nel file tensorflow_datasets/core/valid_tags.txt .
  • Per aggiungere un tag a quell'elenco, invia una pull request.

Mantenere l'ordine dei dataset

Per impostazione predefinita, i record dei dataset vengono mescolati durante il salvataggio per rendere più uniforme la distribuzione delle classi all'interno del dataset, poiché spesso i record appartenenti alla stessa classe sono contigui. Per specificare che il dataset deve essere ordinato in base alla chiave generata da _generate_examples , il campo disable_shuffling deve essere impostato su True . Per impostazione predefinita è impostato su False .

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

Tieni presente che la disattivazione dello shuffling ha un impatto sulle prestazioni, poiché i frammenti non possono più essere letti in parallelo.

_split_generators : scarica e divide i dati

Download ed estrazione dei dati sorgente

La maggior parte dei dataset richiede il download dei dati dal web. Questo viene fatto utilizzando l'argomento di input tfds.download.DownloadManager di _split_generators . dl_manager ha i seguenti metodi:

  • download : supporta http(s):// , ftp(s)://
  • extract : attualmente supporta file .zip , .gz e .tar .
  • download_and_extract : Equivalente a dl_manager.extract(dl_manager.download(urls))

Tutti questi metodi restituiscono tfds.core.Path (alias per epath.Path ), che sono oggetti simili a pathlib.Path .

Questi metodi supportano strutture annidate arbitrarie ( list , dict ), come ad esempio:

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 ed estrazione manuali

Alcuni dati non possono essere scaricati automaticamente (ad esempio, richiedono l'accesso tramite login); in questo caso, l'utente dovrà scaricare manualmente i dati sorgente e posizionarli nella manual_dir/ (il percorso predefinito è ~/tensorflow_datasets/downloads/manual/ ).

È quindi possibile accedere ai file tramite 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)
    ...

La posizione manual_dir può essere personalizzata con tfds build --manual_dir= oppure utilizzando tfds.download.DownloadConfig .

Leggi l'archivio direttamente

dl_manager.iter_archive legge gli archivi in ​​sequenza senza estrarli. Questo può consentire di risparmiare spazio di archiviazione e migliorare le prestazioni su alcuni file system.

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

fobj ha gli stessi metodi di with open('rb') as fobj: (ad esempio fobj.read() )

Specificare le suddivisioni del set di dati

Se il dataset include suddivisioni predefinite (ad esempio, MNIST ha suddivisioni train e test ), mantenetele. Altrimenti, specificate solo una singola suddivisione " all ". Gli utenti possono creare dinamicamente le proprie sotto-suddivisioni con l' API subsplit (ad esempio split='train[80%:]' ). Si noti che è possibile utilizzare qualsiasi stringa alfabetica come nome della suddivisione, ad eccezione del già citato 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 : Generatore di esempi

_generate_examples genera gli esempi per ogni suddivisione a partire dai dati di origine.

Questo metodo in genere legge gli artefatti del dataset di origine (ad esempio un file CSV) e restituisce tuple (key, feature_dict) :

  • key : identificatore dell'esempio. Utilizzato per mescolare in modo deterministico gli esempi tramite hash(key) o per ordinare per chiave quando la mescolatura è disabilitata (vedere la sezione Mantenere l'ordine del dataset ). Dovrebbe essere:
    • unico : se due esempi utilizzano la stessa chiave, verrà generata un'eccezione.
    • deterministico : non dovrebbe dipendere dall'ordine di download_dir , os.path.listdir ,... La generazione dei dati due volte dovrebbe produrre la stessa chiave.
    • comparabile : se la randomizzazione è disabilitata, la chiave verrà utilizzata per ordinare il dataset.
  • feature_dict : Un dict contenente i valori di esempio.
    • La struttura deve corrispondere alla struttura features= definita in tfds.core.DatasetInfo .
    • I tipi di dati complessi (immagini, video, audio, ecc.) verranno codificati automaticamente.
    • Ogni funzionalità spesso accetta più tipi di input (ad esempio, i video accettano /path/to/vid.mp4 , np.array(shape=(l, h, w, c)) , List[paths] , List[np.array(shape=(h, w, c)] , List[img_bytes] , ...)
    • Consulta la guida ai connettori delle funzionalità per ulteriori informazioni.
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'],
      }

Accesso ai file e tf.io.gfile

Per supportare i sistemi di archiviazione cloud, evitare l'utilizzo delle operazioni di I/O integrate di Python.

Invece, dl_manager restituisce oggetti simili a pathlib direttamente compatibili con l'archiviazione di 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())

In alternativa, per le operazioni sui file, utilizzare l'API tf.io.gfile anziché quella integrata:

Pathlib dovrebbe essere preferito a tf.io.gfile (vedi rational .

Dipendenze aggiuntive

Alcuni dataset richiedono dipendenze Python aggiuntive solo durante la fase di generazione. Ad esempio, il dataset SVHN utilizza scipy per caricare alcuni dati.

Se stai aggiungendo un dataset al repository TFDS, utilizza tfds.core.lazy_imports per mantenere il pacchetto tensorflow-datasets di dimensioni ridotte. Gli utenti installeranno le dipendenze aggiuntive solo se necessario.

Per utilizzare lazy_imports :

  • Aggiungi una voce per il tuo dataset in DATASET_EXTRAS nel setup.py . In questo modo, gli utenti potranno, ad esempio, eseguire pip install 'tensorflow-datasets[svhn]' per installare le dipendenze aggiuntive.
  • Aggiungi una voce per la tua importazione a LazyImporter e a LazyImportsTest .
  • Utilizza tfds.core.lazy_imports per accedere alla dipendenza (ad esempio, tfds.core.lazy_imports.scipy ) nel tuo DatasetBuilder .

Dati corrotti

Alcuni dataset non sono perfettamente puliti e contengono dati corrotti (ad esempio, le immagini sono in file JPEG ma alcune non sono JPEG validi). Questi esempi dovrebbero essere ignorati, ma è necessario aggiungere una nota nella descrizione del dataset indicando quanti esempi sono stati scartati e perché.

Configurazione/varianti del dataset (tfds.core.BuilderConfig)

Alcuni dataset possono avere più varianti, ovvero diverse opzioni per la preelaborazione e la scrittura dei dati su disco. Ad esempio, cycle_gan ha una configurazione per ogni coppia di oggetti ( cycle_gan/horse2zebra , cycle_gan/monet2photo , ...).

Questo viene fatto tramite tfds.core.BuilderConfig s:

  1. Definisci il tuo oggetto di configurazione come una sottoclasse di tfds.core.BuilderConfig . Ad esempio, MyDatasetConfig .

    @dataclasses.dataclass
    class MyDatasetConfig(tfds.core.BuilderConfig):
      img_size: Tuple[int, int] = (0, 0)
    
  2. Definisci il membro di classe BUILDER_CONFIGS = [] in MyDataset che elenca le MyDatasetConfig esposte dal dataset.

    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. Utilizza self.builder_config in MyDataset per configurare la generazione dei dati (ad esempio, shape=self.builder_config.img_size ). Ciò potrebbe includere l'impostazione di valori diversi in _info() o la modifica dell'accesso ai dati scaricati.

Note:

  • Ogni configurazione ha un nome univoco. Il nome completo di una configurazione è dataset_name/config_name (ad esempio coco/2017 ).
  • Se non specificato, verrà utilizzata la prima configurazione in BUILDER_CONFIGS (ad esempio tfds.load('c4') predefinito su c4/en ).

Vedi anli per un esempio di dataset che utilizza BuilderConfig .

Versione

La versione può riferirsi a due significati diversi:

  • La versione originale dei dati "esterna": ad esempio COCO v2019, v2017,...
  • La versione "interna" del codice TFDS: ad esempio, rinominare una funzionalità in tfds.features.FeaturesDict , correggere un bug in _generate_examples

Per aggiornare un set di dati:

  • Per l'aggiornamento dei dati "esterni": più utenti potrebbero voler accedere contemporaneamente a un anno/versione specifici. Ciò si ottiene utilizzando un tfds.core.BuilderConfig per ogni versione (ad esempio coco/2017 , coco/2019 ) o una classe per ogni versione (ad esempio Voc2007 , Voc2012 ).
  • Per gli aggiornamenti del codice "interni": gli utenti scaricano solo la versione più recente. Qualsiasi aggiornamento del codice dovrebbe incrementare l'attributo di classe VERSION (ad esempio da 1.0.0 a VERSION = tfds.core.Version('2.0.0') ) seguendo la versione semantica .

Aggiungi l'importazione per la registrazione

Non dimenticare di importare il modulo dataset nel tuo progetto __init__ per essere registrato automaticamente in tfds.load e tfds.builder .

import my_project.datasets.my_dataset  # Register MyDataset

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

Ad esempio, se stai contribuendo a tensorflow/datasets , aggiungi l'importazione del modulo al file __init__.py della sua sottocartella (ad esempio image/__init__.py ).

Verifica la presenza di problemi comuni durante l'implementazione.

Si prega di verificare la presenza di problemi comuni durante l'implementazione .

Verifica il tuo set di dati

Scarica e prepara: tfds build

Per generare il dataset, esegui tfds build dalla directory my_dataset/ :

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

Alcuni flag utili per lo sviluppo:

  • --pdb : Attiva la modalità di debug se viene generata un'eccezione.
  • --overwrite : Elimina i file esistenti se il dataset è già stato generato.
  • --max_examples_per_split : Genera solo i primi X esempi (valore predefinito: 1), anziché l'intero set di dati.
  • --register_checksums : Registra i checksum degli URL scaricati. Da utilizzare solo in fase di sviluppo.

Consultare la documentazione della CLI per l'elenco completo dei flag.

Codici di controllo

Si consiglia di registrare i checksum dei propri dataset per garantire il determinismo, facilitare la documentazione, ecc. Questo si fa generando il dataset con l'opzione --register_checksums (vedere la sezione precedente).

Se pubblichi i tuoi dataset tramite PyPI, non dimenticare di esportare i file checksums.tsv (ad esempio nella package_data del tuo setup.py ).

Esegui un test unitario sul tuo set di dati.

tfds.testing.DatasetBuilderTestCase è un TestCase di base per testare a fondo un dataset. Utilizza "dati fittizi" come dati di test che replicano la struttura del dataset di origine.

  • I dati di test devono essere inseriti nella directory my_dataset/dummy_data/ e devono replicare gli artefatti del dataset di origine così come sono stati scaricati ed estratti. Possono essere creati manualmente o automaticamente tramite uno script ( esempio di script ).
  • Assicurati di utilizzare dati diversi nelle suddivisioni del set di dati di test, poiché il test fallirà se le suddivisioni del set di dati si sovrappongono.
  • I dati di test non devono contenere materiale protetto da copyright . In caso di dubbio, non creare i dati utilizzando materiale proveniente dal dataset originale.
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()

Eseguire il seguente comando per testare il set di dati.

python my_dataset_test.py

inviaci un feedback

Ci impegniamo costantemente a migliorare il flusso di lavoro per la creazione dei dataset, ma possiamo farlo solo se siamo a conoscenza dei problemi. Quali problemi o errori hai riscontrato durante la creazione del dataset? C'è stata qualche fase che ti ha creato confusione o che non ha funzionato al primo tentativo?

Vi preghiamo di condividere il vostro feedback su GitHub .