Écriture de jeux 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 de jeux de données pour voir si le jeu de données que vous souhaitez est déjà présent.

TL; DR

Le moyen le plus simple d'écrire un nouveau jeu 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 nouveau jeu de données avec tfds.load('my_dataset') :

  • tfds.load détectera et chargera automatiquement le jeu 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. Un accès ultérieur lira directement ces fichiers prétraités.

La majeure partie du prétraitement est effectuée automatiquement. Chaque jeu 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 jeu de données

Modèle par défaut : tfds new

Utilisez l'interface de ligne de commande TFDS 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-le en conséquence.

Exemple de jeu de données

Tous les jeux 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:

  • Ensembles de données petits/moyens pouvant être générés sur une seule machine (ce tutoriel).
  • Ensembles de données très volumineux nécessitant une génération distribuée (en utilisant Apache Beam , consultez notre énorme guide d'ensemble de données )

Voici un exemple minimal d'un générateur de jeu 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 fournissons des générateurs d'ensembles de données prêts à l'emploi pour prendre en charge la plupart des traitements de données.

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

_info : métadonnées du jeu 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 : trouvez 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 n'y a qu'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 au jeu de données.
  • Les balises valides sont répertoriées dans tensorflow_datasets/core/valid_tags.txt .
  • Pour ajouter un tag à 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 lorsqu'ils sont stockés afin de rendre la distribution des classes plus uniforme dans l'ensemble de données, car souvent les enregistrements appartenant à la même classe sont contigus. Afin de spécifier que l'ensemble de données doit être trié par 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 partitions ne peuvent plus être lues en parallèle.

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

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

La plupart des ensembles de données doivent télécharger des données à partir du Web. Cela se fait à l'aide de 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 une structure imbriquée arbitraire ( 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 alors 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 une archive 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 fourni avec des divisions prédéfinies (par exemple, MNIST a des divisions train et test ), conservez-les. Sinon, ne spécifiez qu'un seul partage all . Les utilisateurs peuvent créer dynamiquement leurs propres sous-splits avec l' API de sous-split (par exemple, split='train[80%:]' ). Notez que n'importe quelle chaîne alphabétique peut être utilisée comme nom partagé, à 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 : Générateur d'exemples

_generate_examples génère les exemples pour chaque fractionnement à 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 de manière déterministe les exemples en utilisant hash(key) ou pour trier par clé lorsque le shuffling est désactivé (voir section Maintenir l'ordre des jeux de données ). Devrait être:
    • unique : Si deux exemples utilisent la même clé, une exception sera levée.
    • déterministe : ne doit pas dépendre de l'ordre download_dir , os.path.listdir ,... Générer les données deux fois devrait donner la même clé.
    • comparable : si le brassage est désactivé, la clé sera utilisée pour trier le jeu 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 des connecteurs de fonctions 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 au fichier 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 à Python.

Au lieu de cela, le 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, le jeu de données SVHN utilise scipy pour charger certaines données.

Si vous ajoutez un jeu de données dans le référentiel TFDS, veuillez utiliser tfds.core.lazy_imports pour garder le package tensorflow-datasets 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 à LazyImporter et à 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 JPEG invalides). Ces exemples doivent être ignorés, mais laissez une note dans la description du jeu de données indiquant combien d'exemples ont été supprimés et pourquoi.

Configuration/variantes du jeu de données (tfds.core.BuilderConfig)

Certains ensembles de données peuvent avoir plusieurs variantes ou options sur la façon 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 que l'ensemble de données expose.

    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 différentes valeurs 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 ).
  • S'il n'est pas spécifié, la première configuration dans BUILDER_CONFIGS sera utilisée (par exemple tfds.load('c4') par défaut sur 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 originale "externe" des données : 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 "interne" du code : les utilisateurs ne téléchargent que la version la plus récente. Toute mise à jour du code doit augmenter l'attribut de classe VERSION (par exemple de 1.0.0 à VERSION = tfds.core.Version('2.0.0') ) suite au versioning 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 de mise en œuvre courants

Veuillez vérifier les problèmes d'implémentation courants .

Testez votre jeu de données

Télécharger et préparer : 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 : passe en mode débogage si une exception est déclenchée.
  • --overwrite : supprime les fichiers existants si le jeu de données a déjà été généré.
  • --max_examples_per_split : ne génère que 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.

Voir la documentation CLI pour la liste complète des drapeaux.

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, 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 jeu de données

tfds.testing.DatasetBuilderTestCase est un TestCase de base pour exercer pleinement un jeu 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 qu'ils ont été 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 fractionnements de données de test, car le test échouera si les fractionnements de votre ensemble de données se chevauchent.
  • Les données de test ne doivent contenir aucun élément protégé par des droits d'auteur . En cas de doute, ne créez pas les données à l'aide du matériel 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 la première fois ?

Veuillez partager vos commentaires sur GitHub .