Problèmes courants de mise en œuvre

Cette page décrit le problème d'implémentation courant lors de l'implémentation d'un nouvel ensemble de données.

L'ancien SplitGenerator doit être évité

L'ancienne API tfds.core.SplitGenerator est obsolète.

def _split_generator(...):
  return [
      tfds.core.SplitGenerator(name='train', gen_kwargs={'path': train_path}),
      tfds.core.SplitGenerator(name='test', gen_kwargs={'path': test_path}),
  ]

Doit être remplacé par :

def _split_generator(...):
  return {
      'train': self._generate_examples(path=train_path),
      'test': self._generate_examples(path=test_path),
  }

Justification : La nouvelle API est moins verbeuse et plus explicite. L'ancienne API sera supprimée dans la future version.

Les nouveaux ensembles de données doivent être autonomes dans un dossier

Lors de l'ajout d'un ensemble de données dans le référentiel tensorflow_datasets/ , assurez-vous de suivre la structure de l'ensemble de données en tant que dossier (toutes les sommes de contrôle, les données factices, le code d'implémentation autonome dans un dossier).

  • Anciens ensembles de données (mauvais) : <category>/<ds_name>.py
  • Nouveaux ensembles de données (bons) : <category>/<ds_name>/<ds_name>.py

Utilisez la CLI TFDS ( tfds new ou gtfds new pour les googleurs) pour générer le modèle.

Justification : l'ancienne structure nécessitait des chemins absolus pour les sommes de contrôle, les fausses données et distribuait les fichiers de l'ensemble de données à de nombreux endroits. Cela rendait plus difficile la mise en œuvre d’ensembles de données en dehors du référentiel TFDS. Par souci de cohérence, la nouvelle structure devrait désormais être utilisée partout.

Les listes de descriptions doivent être formatées en markdown

La str DatasetInfo.description est formatée en markdown. Les listes Markdown nécessitent une ligne vide avant le premier élément :

_DESCRIPTION = """
Some text.
                      # << Empty line here !!!
1. Item 1
2. Item 1
3. Item 1
                      # << Empty line here !!!
Some other text.
"""

Justification : Une description mal formatée crée des artefacts visuels dans la documentation de notre catalogue. Sans les lignes vides, le texte ci-dessus serait rendu comme suit :

Un peu de texte. 1. Point 1 2. Point 1 3. Point 1 Un autre texte

Noms ClassLabel oubliés

Lorsque vous utilisez tfds.features.ClassLabel , essayez de fournir les étiquettes lisibles par l'homme str avec names= ou names_file= (au lieu de num_classes=10 ).

features = {
    'label': tfds.features.ClassLabel(names=['dog', 'cat', ...]),
}

Justification : Les étiquettes lisibles par l'homme sont utilisées à de nombreux endroits :

Forme de l'image oubliée

Lors de l'utilisation de tfds.features.Image , tfds.features.Video , si les images ont une forme statique, elles doivent être explicitement spécifiées :

features = {
    'image': tfds.features.Image(shape=(256, 256, 3)),
}

Justification : il permet l'inférence de forme statique (par exemple ds.element_spec['image'].shape ), ce qui est requis pour le traitement par lots (le traitement par lots d'images de forme inconnue nécessiterait d'abord de les redimensionner).

Préférez un type plus spécifique au lieu de tfds.features.Tensor

Lorsque cela est possible, préférez les types plus spécifiques tfds.features.ClassLabel , tfds.features.BBoxFeatures ,... au lieu du générique tfds.features.Tensor .

Justification : En plus d'être plus correctes sémantiquement, les fonctionnalités spécifiques fournissent des métadonnées supplémentaires aux utilisateurs et sont détectées par les outils.

Importations paresseuses dans l’espace mondial

Les importations paresseuses ne doivent pas être appelées depuis l’espace global. Par exemple, ce qui suit est faux :

tfds.lazy_imports.apache_beam # << Error: Import beam in the global scope

def f() -> beam.Map:
  ...

Justification : l'utilisation d'importations paresseuses dans la portée globale importerait le module pour tous les utilisateurs de tfds, ce qui irait à l'encontre de l'objectif des importations paresseuses.

Calcul dynamique des répartitions train/test

Si l’ensemble de données ne fournit pas de répartitions officielles, TFDS ne le devrait pas non plus. Les éléments suivants doivent être évités :

_TRAIN_TEST_RATIO = 0.7

def _split_generator():
  ids = list(range(num_examples))
  np.random.RandomState(seed).shuffle(ids)

  # Split train/test
  train_ids = ids[_TRAIN_TEST_RATIO * num_examples:]
  test_ids = ids[:_TRAIN_TEST_RATIO * num_examples]
  return {
      'train': self._generate_examples(train_ids),
      'test': self._generate_examples(test_ids),
  }

Justification : TFDS essaie de fournir des ensembles de données aussi proches que les données originales. L' API de sous-split doit être utilisée à la place pour permettre aux utilisateurs de créer dynamiquement les sous-splits qu'ils souhaitent :

ds_train, ds_test = tfds.load(..., split=['train[:80%]', 'train[80%:]'])

Guide de style Python

Je préfère utiliser l'API pathlib

Au lieu de l'API tf.io.gfile , il est préférable d'utiliser l' API pathlib . Toutes les méthodes dl_manager renvoient des objets de type pathlib compatibles avec GCS, S3,...

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

json_path = path / 'data/file.json'

json.loads(json_path.read_text())

Justification : l'API pathlib est une API de fichier orientée objet moderne qui supprime le passe-partout. L'utilisation de .read_text() / .read_bytes() garantit également que les fichiers sont correctement fermés.

Si la méthode n'utilise pas self , cela devrait être une fonction

Si une méthode de classe n'utilise pas self , il doit s'agir d'une fonction simple (définie en dehors de la classe).

Justification : Cela indique explicitement au lecteur que la fonction n'a pas d'effets secondaires, ni d'entrée/sortie cachée :

x = f(y)  # Clear inputs/outputs

x = self.f(y)  # Does f depend on additional hidden variables ? Is it stateful ?

Importations paresseuses en Python

Nous importons paresseusement de gros modules comme TensorFlow. Les importations paresseuses reportent l'importation réelle du module à la première utilisation du module. Ainsi, les utilisateurs qui n'ont pas besoin de ce gros module ne l'importeront jamais. Nous utilisons etils.epy.lazy_imports .

from tensorflow_datasets.core.utils.lazy_imports_utils import tensorflow as tf
# After this statement, TensorFlow is not imported yet

...

features = tfds.features.Image(dtype=tf.uint8)
# After using it (`tf.uint8`), TensorFlow is now imported

Sous le capot, la classe LazyModule agit comme une usine, qui n'importera réellement le module que lors de l'accès à un attribut ( __getattr__ ).

Vous pouvez également l'utiliser facilement avec un gestionnaire de contexte :

from etils import epy

with epy.lazy_imports(error_callback=..., success_callback=...):
  import some_big_module