Trucchi di implementazione comuni

Questa pagina descrive i trucchi di implementazione comuni quando si implementa un nuovo set di dati.

Il Legacy SplitGenerator dovrebbe essere evitato

La vecchia API tfds.core.SplitGenerator è deprecata.

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

Dovrebbe essere sostituito da:

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

Motivazione : la nuova API è meno dettagliata e più esplicita. La vecchia API verrà rimossa nella versione futura.

I nuovi set di dati dovrebbero essere autonomi in una cartella

Quando aggiungi un set di dati all'interno del repository tensorflow_datasets/ , assicurati di seguire la struttura del set di dati come cartella (tutti i checksum, i dati fittizi, il codice di implementazione contenuti in una cartella).

  • Vecchi set di dati (non validi): <category>/<ds_name>.py
  • Nuovi set di dati (buoni): <category>/<ds_name>/<ds_name>.py

Utilizza la CLI TFDS ( tfds new o gtfds new per i googler) per generare il modello.

Motivazione : la vecchia struttura richiedeva percorsi assoluti per checksum, dati falsi e distribuiva i file del set di dati in molti posti. Stava rendendo più difficile implementare i set di dati al di fuori del repository TFDS. Per coerenza, ora la nuova struttura dovrebbe essere utilizzata ovunque.

Gli elenchi di descrizioni devono essere formattati come markdown

La str DatasetInfo.description è formattata come markdown. Gli elenchi di markdown richiedono una riga vuota prima del primo elemento:

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

Motivazione : una descrizione formattata in modo errato crea artefatti visivi nella documentazione del nostro catalogo. Senza le righe vuote, il testo sopra verrebbe reso come:

Un po' di testo. 1. Punto 1 2. Punto 1 3. Punto 1 Qualche altro testo

Ho dimenticato i nomi ClassLabel

Quando si utilizza tfds.features.ClassLabel , provare a fornire le etichette leggibili dall'uomo str names= names_file= (invece di num_classes=10 ).

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

Motivazione : le etichette leggibili dall'uomo vengono utilizzate in molti luoghi:

Ho dimenticato la forma dell'immagine

Quando si utilizza tfds.features.Image , tfds.features.Video , se le immagini hanno forma statica, devono essere specificate esplicitamente:

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

Motivazione : consente l'inferenza della forma statica (ad esempio ds.element_spec['image'].shape ), necessaria per l'invio in batch (l'invio in batch di immagini di forma sconosciuta richiederebbe prima il loro ridimensionamento).

Preferisci un tipo più specifico anziché tfds.features.Tensor

Quando possibile, preferire i tipi più specifici tfds.features.ClassLabel , tfds.features.BBoxFeatures ,... invece del generico tfds.features.Tensor .

Motivazione : oltre ad essere semanticamente più corrette, le funzionalità specifiche forniscono metadati aggiuntivi agli utenti e vengono rilevate dagli strumenti.

Importazioni pigre nello spazio globale

Le importazioni pigre non dovrebbero essere richiamate dallo spazio globale. Ad esempio quanto segue è sbagliato:

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

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

Motivazione : l'utilizzo delle importazioni lente nell'ambito globale importerebbe il modulo per tutti gli utenti di tfds, vanificando lo scopo delle importazioni lente.

Calcolo dinamico delle suddivisioni treno/test

Se il set di dati non fornisce suddivisioni ufficiali, non dovrebbe farlo nemmeno TFDS. Dovrebbe essere evitato quanto segue:

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

Motivazione : TFDS tenta di fornire set di dati il ​​più vicini possibile ai dati originali. L' API sub-split dovrebbe invece essere utilizzata per consentire agli utenti di creare dinamicamente i sub-split che desiderano:

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

Guida allo stile Python

Preferisco utilizzare l'API pathlib

Invece dell'API tf.io.gfile , è preferibile utilizzare l' API pathlib . Tutti i metodi dl_manager restituiscono oggetti simili a pathlib compatibili con 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())

Motivazione : l'API pathlib è una moderna API di file orientata agli oggetti che rimuove il boilerplate. L'uso di .read_text() / .read_bytes() garantisce inoltre che i file vengano chiusi correttamente.

Se il metodo non utilizza self , dovrebbe essere una funzione

Se un metodo di classe non utilizza self , dovrebbe essere una funzione semplice (definita all'esterno della classe).

Motivazione : Rende esplicito al lettore che la funzione non ha effetti collaterali, né input/output nascosti:

x = f(y)  # Clear inputs/outputs

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

Importazioni pigre in Python

Importiamo pigramente grandi moduli come TensorFlow. Le importazioni lente rinviano l'importazione effettiva del modulo al primo utilizzo del modulo. Quindi gli utenti che non hanno bisogno di questo grande modulo non lo importeranno mai. Usiamo 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

Sotto il cofano, la classe LazyModule funge da factory, che importerà effettivamente il modulo solo quando si accede a un attributo ( __getattr__ ).

Puoi anche usarlo comodamente con un gestore di contesto:

from etils import epy

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