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:
- Consenti di produrre
str
direttamente in_generate_examples
:yield {'label': 'dog'}
- Esposto negli utenti come
info.features['label'].names
(disponibile anche il metodo di conversione.str2int('dog')
,...) - Utilizzato nelle utilità di visualizzazione
tfds.show_examples
,tfds.as_dataframe
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ù simili 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