Typowe problemy z implementacją

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:

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