Écriture d'ensembles de données personnalisés

Suivez ce guide pour créer un nouvel ensemble de données (soit dans TFDS, soit dans votre propre référentiel).

Consultez notre liste d'ensembles de données pour voir si l'ensemble de données que vous souhaitez est déjà présent.

TL;DR

Le moyen le plus simple d'écrire un nouvel ensemble de données consiste à utiliser la CLI 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/`

Pour utiliser le nouvel ensemble de données avec tfds.load('my_dataset') :

  • tfds.load détectera et chargera automatiquement l'ensemble de données généré dans ~/tensorflow_datasets/my_dataset/ (par exemple par tfds build ).
  • Vous pouvez également import my.project.datasets.my_dataset pour enregistrer votre ensemble de données :
import my.project.datasets.my_dataset  # Register `my_dataset`

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

Aperçu

Les ensembles de données sont distribués dans toutes sortes de formats et dans toutes sortes d'endroits, et ils ne sont pas toujours stockés dans un format prêt à alimenter un pipeline d'apprentissage automatique. Entrez TFDS.

TFDS traite ces ensembles de données dans un format standard (données externes -> fichiers sérialisés), qui peuvent ensuite être chargés en tant que pipeline d'apprentissage automatique (fichiers sérialisés -> tf.data.Dataset ). La sérialisation n'est effectuée qu'une seule fois. L'accès ultérieur lira directement ces fichiers prétraités.

La plupart du prétraitement est effectué automatiquement. Chaque ensemble de données implémente une sous-classe de tfds.core.DatasetBuilder , qui spécifie :

  • D'où proviennent les données (c'est-à-dire leurs URL) ;
  • À quoi ressemble l'ensemble de données (c'est-à-dire ses caractéristiques) ;
  • Comment les données doivent être divisées (par exemple TRAIN et TEST );
  • et les exemples individuels dans l'ensemble de données.

Écrivez votre ensemble de données

Modèle par défaut : tfds new

Utilisez TFDS CLI pour générer les fichiers Python de modèle requis.

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

Cette commande générera un nouveau dossier my_dataset/ avec la structure suivante :

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

Recherchez TODO(my_dataset) ici et modifiez en conséquence.

Exemple d'ensemble de données

Tous les ensembles de données sont des sous-classes implémentées de tfds.core.DatasetBuilder , qui prend en charge la plupart des passe-partout. Elle supporte:

Voici un exemple minimal de générateur d'ensemble de données basé sur 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',
      }

Notez que, pour certains formats de données spécifiques, nous proposons des générateurs de jeux de données prêts à l'emploi pour prendre en charge la plupart du traitement des données.

Voyons en détail les 3 méthodes abstraites pour écraser.

_info : métadonnées de l'ensemble de données

_info renvoie le tfds.core.DatasetInfo contenant les métadonnées de l'ensemble de données .

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 plupart des champs doivent être explicites. Quelques précisions :

Ecriture du fichier BibText CITATIONS.bib :

  • Recherchez sur le site Web de l'ensemble de données les instructions de citation (utilisez-les au format BibTex).
  • Pour les articles arXiv : recherchez l'article et cliquez sur le lien BibText sur le côté droit.
  • Recherchez l'article sur Google Scholar et cliquez sur le guillemet double sous le titre et dans la fenêtre contextuelle, cliquez sur BibTeX .
  • S'il n'y a pas d'article associé (par exemple, il y a juste un site Web), vous pouvez utiliser l' éditeur en ligne BibTeX pour créer une entrée BibTeX personnalisée (le menu déroulant a un type d'entrée Online ).

Mise à jour du fichier TAGS.txt :

  • Toutes les balises autorisées sont pré-remplies dans le fichier généré.
  • Supprimez toutes les balises qui ne s'appliquent pas à l'ensemble de données.
  • Les balises valides sont répertoriées dans tensorflow_datasets/core/valid_tags.txt .
  • Pour ajouter une balise à cette liste, veuillez envoyer un PR.

Maintenir l'ordre des ensembles de données

Par défaut, les enregistrements des ensembles de données sont mélangés lors du stockage afin de rendre la répartition des classes plus uniforme dans l'ensemble de données, car les enregistrements appartenant à la même classe sont souvent contigus. Afin de spécifier que l'ensemble de données doit être trié en fonction de la clé générée fournie par _generate_examples , le champ disable_shuffling doit être défini sur True . Par défaut, il est défini sur False .

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

Gardez à l’esprit que la désactivation de la lecture aléatoire a un impact sur les performances, car les fragments ne peuvent plus être lus en parallèle.

_split_generators : télécharge et divise les données

Téléchargement et extraction des données sources

La plupart des ensembles de données doivent télécharger des données à partir du Web. Cela se fait en utilisant l'argument d'entrée tfds.download.DownloadManager de _split_generators . dl_manager a les méthodes suivantes :

  • download : prend en charge http(s):// , ftp(s)://
  • extract : prend actuellement en charge les fichiers .zip , .gz et .tar .
  • download_and_extract : Identique à dl_manager.extract(dl_manager.download(urls))

Toutes ces méthodes renvoient tfds.core.Path (alias pour epath.Path ), qui sont des objets de type pathlib.Path .

Ces méthodes prennent en charge les structures imbriquées arbitraires ( list , dict ), comme :

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/'),
}

Téléchargement et extraction manuels

Certaines données ne peuvent pas être téléchargées automatiquement (par exemple, nécessitent une connexion), dans ce cas, l'utilisateur téléchargera manuellement les données sources et les placera dans manual_dir/ (par défaut ~/tensorflow_datasets/downloads/manual/ ).

Les fichiers sont ensuite accessibles via 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)
    ...

L'emplacement manual_dir peut être personnalisé avec tfds build --manual_dir= ou en utilisant tfds.download.DownloadConfig .

Lire l'archive directement

dl_manager.iter_archive lit les archives séquentiellement sans les extraire. Cela peut économiser de l'espace de stockage et améliorer les performances sur certains systèmes de fichiers.

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

fobj a les mêmes méthodes with open('rb') as fobj: (par exemple fobj.read() )

Spécification des fractionnements d'ensembles de données

Si l'ensemble de données est livré avec des répartitions prédéfinies (par exemple, MNIST a des répartitions train et test ), conservez-les. Sinon, spécifiez uniquement un seul partage all . Les utilisateurs peuvent créer dynamiquement leurs propres sous-splits avec l' API subsplit (par exemple split='train[80%:]' ). Notez que n'importe quelle chaîne alphabétique peut être utilisée comme nom de partage, à l'exception de 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 : Exemple de générateur

_generate_examples génère les exemples pour chaque division à partir des données source.

Cette méthode lira généralement les artefacts de l'ensemble de données source (par exemple, un fichier CSV) et produira des tuples (key, feature_dict) :

  • key : Exemple d'identifiant. Utilisé pour mélanger les exemples de manière déterministe à l'aide hash(key) ou pour trier par clé lorsque la lecture aléatoire est désactivée (voir la section Maintenir l'ordre des ensembles de données ). Devrait être:
    • unique : Si deux exemples utilisent la même clé, une exception sera levée.
    • déterministe : ne devrait pas dépendre de l'ordre download_dir , os.path.listdir ,... La génération des données deux fois devrait donner la même clé.
    • comparable : Si la lecture aléatoire est désactivée, la clé sera utilisée pour trier l'ensemble de données.
  • feature_dict : Un dict contenant les exemples de valeurs.
    • La structure doit correspondre à la structure features= définie dans tfds.core.DatasetInfo .
    • Les types de données complexes (image, vidéo, audio,...) seront automatiquement encodés.
    • Chaque fonctionnalité accepte souvent plusieurs types d'entrée (par exemple, video accept /path/to/vid.mp4 , np.array(shape=(l, h, w, c)) , List[paths] , List[np.array(shape=(h, w, c)] , List[img_bytes] ,...)
    • Consultez le guide du connecteur de fonctionnalités pour plus d’informations.
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'],
      }

Accès aux fichiers et tf.io.gfile

Afin de prendre en charge les systèmes de stockage Cloud, évitez d'utiliser les opérations d'E/S intégrées de Python.

Au lieu de cela, dl_manager renvoie des objets de type pathlib directement compatibles avec le stockage 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())

Vous pouvez également utiliser l'API tf.io.gfile au lieu de l'API intégrée pour les opérations sur les fichiers :

Pathlib doit être préféré à tf.io.gfile (voir rational .

Dépendances supplémentaires

Certains ensembles de données nécessitent des dépendances Python supplémentaires uniquement lors de la génération. Par exemple, l'ensemble de données SVHN utilise scipy pour charger certaines données.

Si vous ajoutez un ensemble de données dans le référentiel TFDS, veuillez utiliser tfds.core.lazy_imports pour que le package tensorflow-datasets reste petit. Les utilisateurs installeront des dépendances supplémentaires uniquement si nécessaire.

Pour utiliser lazy_imports :

  • Ajoutez une entrée pour votre ensemble de données dans DATASET_EXTRAS dans setup.py . Cela permet aux utilisateurs de faire, par exemple, pip install 'tensorflow-datasets[svhn]' pour installer les dépendances supplémentaires.
  • Ajoutez une entrée pour votre importation dans LazyImporter et dans LazyImportsTest .
  • Utilisez tfds.core.lazy_imports pour accéder à la dépendance (par exemple, tfds.core.lazy_imports.scipy ) dans votre DatasetBuilder .

Données corrompues

Certains ensembles de données ne sont pas parfaitement propres et contiennent des données corrompues (par exemple, les images sont dans des fichiers JPEG mais certaines sont des fichiers JPEG invalides). Ces exemples doivent être ignorés, mais laissez une note dans la description de l'ensemble de données combien d'exemples ont été supprimés et pourquoi.

Configuration/variantes de l'ensemble de données (tfds.core.BuilderConfig)

Certains ensembles de données peuvent avoir plusieurs variantes ou options sur la manière dont les données sont prétraitées et écrites sur le disque. Par exemple, cycle_gan a une configuration par paire d'objets ( cycle_gan/horse2zebra , cycle_gan/monet2photo ,...).

Cela se fait via tfds.core.BuilderConfig s :

  1. Définissez votre objet de configuration en tant que sous-classe de tfds.core.BuilderConfig . Par exemple, MyDatasetConfig .

    @dataclasses.dataclass
    class MyDatasetConfig(tfds.core.BuilderConfig):
      img_size: Tuple[int, int] = (0, 0)
    
  2. Définissez le membre de classe BUILDER_CONFIGS = [] dans MyDataset qui répertorie les MyDatasetConfig exposés par l'ensemble de données.

    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. Utilisez self.builder_config dans MyDataset pour configurer la génération de données (par exemple shape=self.builder_config.img_size ). Cela peut inclure la définition de valeurs différentes dans _info() ou la modification de l'accès aux données de téléchargement.

Remarques:

  • Chaque configuration a un nom unique. Le nom complet d'une configuration est dataset_name/config_name (par exemple coco/2017 ).
  • Si elle n'est pas spécifiée, la première configuration dans BUILDER_CONFIGS sera utilisée (par exemple tfds.load('c4') par défaut c4/en )

Voir anli pour un exemple d'ensemble de données qui utilise BuilderConfig s.

Version

La version peut faire référence à deux significations différentes :

  • La version des données originales "externes" : par exemple COCO v2019, v2017,...
  • La version "interne" du code TFDS : par exemple renommer une fonctionnalité dans tfds.features.FeaturesDict , corriger un bug dans _generate_examples

Pour mettre à jour un ensemble de données :

  • Pour la mise à jour des données « externes » : plusieurs utilisateurs peuvent souhaiter accéder simultanément à une année/version spécifique. Cela se fait en utilisant un tfds.core.BuilderConfig par version (par exemple coco/2017 , coco/2019 ) ou une classe par version (par exemple Voc2007 , Voc2012 ).
  • Pour la mise à jour du code « interne » : les utilisateurs téléchargent uniquement la version la plus récente. Toute mise à jour de code devrait augmenter l'attribut de classe VERSION (par exemple de 1.0.0 à VERSION = tfds.core.Version('2.0.0') ) après le versionnement sémantique .

Ajouter une importation pour l'enregistrement

N'oubliez pas d'importer le module dataset dans votre projet __init__ pour être automatiquement enregistré dans tfds.load , tfds.builder .

import my_project.datasets.my_dataset  # Register MyDataset

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

Par exemple, si vous contribuez à tensorflow/datasets , ajoutez le module import au __init__.py de son sous-répertoire (par exemple image/__init__.py .

Vérifiez les pièges courants de la mise en œuvre

Veuillez vérifier les pièges courants de la mise en œuvre .

Testez votre ensemble de données

Téléchargez et préparez : tfds build

Pour générer l'ensemble de données, exécutez tfds build à partir du répertoire my_dataset/ :

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

Quelques drapeaux utiles pour le développement :

  • --pdb : Entrez en mode débogage si une exception est levée.
  • --overwrite : Supprime les fichiers existants si l'ensemble de données a déjà été généré.
  • --max_examples_per_split : génère uniquement les X premiers exemples (par défaut : 1), plutôt que l'ensemble de données complet.
  • --register_checksums : enregistre les sommes de contrôle des URL téléchargées. Ne doit être utilisé qu’en cours de développement.

Consultez la documentation CLI pour la liste complète des indicateurs.

Sommes de contrôle

Il est recommandé d'enregistrer les sommes de contrôle de vos jeux de données pour garantir le déterminisme, l'aide à la documentation,... Cela se fait en générant le jeu de données avec le --register_checksums (voir section précédente).

Si vous publiez vos ensembles de données via PyPI, n'oubliez pas d'exporter les fichiers checksums.tsv (par exemple dans le package_data de votre setup.py ).

Testez unitairement votre ensemble de données

tfds.testing.DatasetBuilderTestCase est un TestCase de base pour exercer pleinement un ensemble de données. Il utilise des « données factices » comme données de test qui imitent la structure de l'ensemble de données source.

  • Les données de test doivent être placées dans le répertoire my_dataset/dummy_data/ et doivent imiter les artefacts de l'ensemble de données source tels que téléchargés et extraits. Il peut être créé manuellement ou automatiquement avec un script ( exemple de script ).
  • Assurez-vous d'utiliser des données différentes dans vos répartitions de données de test, car le test échouera si les répartitions de votre ensemble de données se chevauchent.
  • Les données de test ne doivent contenir aucun élément protégé par le droit d'auteur . En cas de doute, ne créez pas les données en utilisant des éléments de l'ensemble de données d'origine.
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()

Exécutez la commande suivante pour tester l'ensemble de données.

python my_dataset_test.py

Envoyez-nous vos commentaires

Nous essayons continuellement d'améliorer le flux de travail de création d'ensembles de données, mais nous ne pouvons le faire que si nous sommes conscients des problèmes. Quels problèmes ou erreurs avez-vous rencontrés lors de la création de l’ensemble de données ? Y avait-il une partie qui prêtait à confusion ou qui ne fonctionnait pas du premier coup ?

Veuillez partager vos commentaires sur GitHub .