Na tej stronie opisano typowe problemy związane z implementacją podczas implementowania nowego zestawu danych.
Należy unikać starszego SplitGenerator
Stary interfejs API tfds.core.SplitGenerator
jest przestarzały.
def _split_generator(...):
return [
tfds.core.SplitGenerator(name='train', gen_kwargs={'path': train_path}),
tfds.core.SplitGenerator(name='test', gen_kwargs={'path': test_path}),
]
Należy zastąpić:
def _split_generator(...):
return {
'train': self._generate_examples(path=train_path),
'test': self._generate_examples(path=test_path),
}
Uzasadnienie : nowy interfejs API jest mniej gadatliwy i bardziej przejrzysty. Stary interfejs API zostanie usunięty w przyszłej wersji.
Nowe zestawy danych powinny znajdować się w osobnym folderze
Dodając zestaw danych do repozytorium tensorflow_datasets/
, pamiętaj o przestrzeganiu struktury zestawu danych jako folderu (wszystkie sumy kontrolne, fikcyjne dane, kod implementacji zawarty w folderze).
- Stare zbiory danych (złe):
<category>/<ds_name>.py
- Nowe zbiory danych (dobre):
<category>/<ds_name>/<ds_name>.py
Użyj interfejsu CLI TFDS ( tfds new
lub gtfds new
dla użytkowników Google), aby wygenerować szablon.
Uzasadnienie : Stara struktura wymagała bezwzględnych ścieżek dla sum kontrolnych, fałszywych danych i rozpowszechniała pliki zestawu danych w wielu miejscach. Utrudniało to implementację zbiorów danych poza repozytorium TFDS. Dla zachowania spójności nowa struktura powinna być teraz stosowana wszędzie.
Listy opisowe powinny być sformatowane jako przeceny
str
DatasetInfo.description
jest sformatowany jako przecena. Listy przecen wymagają pustej linii przed pierwszym elementem:
_DESCRIPTION = """
Some text.
# << Empty line here !!!
1. Item 1
2. Item 1
3. Item 1
# << Empty line here !!!
Some other text.
"""
Uzasadnienie : Źle sformatowany opis powoduje powstawanie artefaktów wizualnych w dokumentacji naszego katalogu. Bez pustych linii powyższy tekst byłby renderowany jako:
Jakiś tekst. 1. Pozycja 1 2. Pozycja 1 3. Pozycja 1 Inny tekst
Zapomniałem nazw ClassLabel
Używając tfds.features.ClassLabel
, spróbuj podać czytelne dla człowieka etykiety str
z names=
lub names_file=
(zamiast num_classes=10
).
features = {
'label': tfds.features.ClassLabel(names=['dog', 'cat', ...]),
}
Uzasadnienie : Etykiety czytelne dla człowieka są używane w wielu miejscach:
- Pozwól uzyskać
str
bezpośrednio w_generate_examples
:yield {'label': 'dog'}
- Ujawniane użytkownikom, takie jak
info.features['label'].names
(metoda konwersji.str2int('dog')
,... dostępna również) - Używane w narzędziach wizualizacyjnych
tfds.show_examples
,tfds.as_dataframe
Zapomniałem kształtu obrazu
W przypadku korzystania z tfds.features.Image
, tfds.features.Video
, jeśli obrazy mają statyczny kształt, należy je wyraźnie określić:
features = {
'image': tfds.features.Image(shape=(256, 256, 3)),
}
Uzasadnienie : Umożliwia statyczne wnioskowanie o kształcie (np. ds.element_spec['image'].shape
), które jest wymagane do przetwarzania wsadowego (wsadowe przesyłanie obrazów o nieznanym kształcie wymagałoby najpierw zmiany ich rozmiaru).
Preferuj bardziej konkretny typ zamiast tfds.features.Tensor
Jeśli to możliwe, preferuj bardziej szczegółowe typy tfds.features.ClassLabel
, tfds.features.BBoxFeatures
,... zamiast ogólnego tfds.features.Tensor
.
Uzasadnienie : Oprócz tego, że są bardziej poprawne semantycznie, określone funkcje udostępniają użytkownikom dodatkowe metadane i są wykrywane przez narzędzia.
Leniwy import w przestrzeni globalnej
Nie należy wywoływać leniwego importu z przestrzeni globalnej. Na przykład poniższe jest błędne:
tfds.lazy_imports.apache_beam # << Error: Import beam in the global scope
def f() -> beam.Map:
...
Uzasadnienie : Użycie leniwego importu w zakresie globalnym spowodowałoby zaimportowanie modułu dla wszystkich użytkowników tfds, co byłoby sprzeczne z celem leniwego importu.
Dynamiczne obliczanie podziału pociągu/testu
Jeśli zbiór danych nie zapewnia oficjalnych podziałów, TFDS też nie powinien. Należy unikać:
_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),
}
Uzasadnienie : TFDS stara się dostarczać zbiory danych tak zbliżone do danych oryginalnych. Zamiast tego należy użyć interfejsu API podziału części, aby umożliwić użytkownikom dynamiczne tworzenie wybranych podziałów:
ds_train, ds_test = tfds.load(..., split=['train[:80%]', 'train[80%:]'])
Przewodnik po stylu Pythona
Wolę używać API pathlib
Zamiast interfejsu API tf.io.gfile
lepiej jest używać interfejsu API pathlib . Wszystkie metody dl_manager
zwracają obiekty podobne do pathlib kompatybilne z 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())
Uzasadnienie : pathlib API to nowoczesny obiektowy interfejs API plików, który usuwa szablony. Użycie .read_text()
/ .read_bytes()
gwarantuje również prawidłowe zamknięcie plików.
Jeśli metoda nie używa self
, powinna to być funkcja
Jeśli metoda klasowa nie używa self
, powinna to być prosta funkcja (zdefiniowana poza klasą).
Uzasadnienie : wyraźnie daje czytelnikowi do zrozumienia, że funkcja nie ma skutków ubocznych ani ukrytych danych wejściowych/wyjściowych:
x = f(y) # Clear inputs/outputs
x = self.f(y) # Does f depend on additional hidden variables ? Is it stateful ?
Leniwy import w Pythonie
Leniwie importujemy duże moduły, takie jak TensorFlow. Leniwy import opóźnia faktyczny import modułu do pierwszego użycia modułu. Dlatego użytkownicy, którzy nie potrzebują tego dużego modułu, nigdy go nie zaimportują. Używamy 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
Pod maską klasa LazyModule
działa jak fabryka, która faktycznie importuje moduł tylko wtedy, gdy uzyskany zostanie dostęp do atrybutu ( __getattr__
).
Można go także wygodnie używać z menedżerem kontekstu:
from etils import epy
with epy.lazy_imports(error_callback=..., success_callback=...):
import some_big_module