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 :
- Autoriser à produire
str
directement dans_generate_examples
:yield {'label': 'dog'}
- Exposé chez les utilisateurs comme
info.features['label'].names
(méthode de conversion.str2int('dog')
,... également disponible) - Utilisé dans les utilitaires de visualisation
tfds.show_examples
,tfds.as_dataframe
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 : Il est explicite 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