Pisanie niestandardowych zestawów danych

Postępuj zgodnie z tym przewodnikiem, aby utworzyć nowy zestaw danych (w TFDS albo we własnym repozytorium).

Sprawdź naszą listę zestawów danych , aby upewnić się, czy interesujący Cię zestaw danych już się tam znajduje.

Krótko mówiąc

Najprostszym sposobem utworzenia nowego zestawu danych jest użycie interfejsu wiersza poleceń TFDS :

cd path/to/my/project/datasets/
tfds new my_dataset  # Create `my_dataset/my_dataset.py` template files
# [...] Manually modify `my_dataset/my_dataset_dataset_builder.py` to implement your dataset.
cd my_dataset/
tfds build  # Download and prepare the dataset to `~/tensorflow_datasets/`

Aby użyć nowego zestawu danych za pomocą tfds.load('my_dataset') :

  • tfds.load automatycznie wykryje i załaduje zestaw danych wygenerowany w ~/tensorflow_datasets/my_dataset/ (np. przez tfds build ).
  • Alternatywnie możesz jawnie import my.project.datasets.my_dataset , aby zarejestrować swój zestaw danych:
import my.project.datasets.my_dataset  # Register `my_dataset`

ds = tfds.load('my_dataset')  # `my_dataset` registered

Przegląd

Zbiory danych są dystrybuowane w najróżniejszych formatach i miejscach, a nie zawsze są przechowywane w formacie gotowym do wprowadzenia do procesu uczenia maszynowego. W tym momencie pojawia się TFDS.

TFDS przetwarza te zbiory danych do standardowego formatu (dane zewnętrzne -> pliki serializowane), który następnie można załadować jako potok uczenia maszynowego (pliki serializowane -> tf.data.Dataset ). Serializacja jest wykonywana tylko raz. Późniejszy dostęp będzie polegał na bezpośrednim odczycie tych wstępnie przetworzonych plików.

Większość wstępnego przetwarzania odbywa się automatycznie. Każdy zbiór danych implementuje podklasę tfds.core.DatasetBuilder , która określa:

  • Skąd pochodzą dane (czyli ich adresy URL);
  • Jak wygląda zbiór danych (tj. jakie są jego cechy);
  • Jak dane powinny być podzielone (np. TRAIN i TEST );
  • i poszczególne przykłady w zbiorze danych.

Napisz swój zestaw danych

Domyślny szablon: tfds new

Użyj interfejsu wiersza poleceń TFDS CLI , aby wygenerować wymagane pliki szablonów w języku Python.

cd path/to/project/datasets/  # Or use `--dir=path/to/project/datasets/` below
tfds new my_dataset

To polecenie wygeneruje nowy folder my_dataset/ o następującej strukturze:

my_dataset/
    __init__.py
    README.md # Markdown description of the dataset.
    CITATIONS.bib # Bibtex citation for the dataset.
    TAGS.txt # List of tags describing the dataset.
    my_dataset_dataset_builder.py # Dataset definition
    my_dataset_dataset_builder_test.py # Test
    dummy_data/ # (optional) Fake data (used for testing)
    checksum.tsv # (optional) URL checksums (see `checksums` section).

Wyszukaj tutaj TODO(my_dataset) i zmodyfikuj odpowiednio.

Przykład zestawu danych

Wszystkie zestawy danych są zaimplementowane jako podklasy klasy tfds.core.DatasetBuilder , która zajmuje się większością szablonów. Obsługuje ona:

  • Małe/średnie zbiory danych, które można wygenerować na jednym komputerze (ten samouczek).
  • Bardzo duże zbiory danych wymagające rozproszonego generowania (przy użyciu Apache Beam , zobacz nasz przewodnik po dużych zbiorach danych )

Oto minimalny przykład konstruktora zbiorów danych opartego na tfds.core.GeneratorBasedBuilder :

class Builder(tfds.core.GeneratorBasedBuilder):
  """DatasetBuilder for my_dataset dataset."""

  VERSION = tfds.core.Version('1.0.0')
  RELEASE_NOTES = {
      '1.0.0': 'Initial release.',
  }

  def _info(self) -> tfds.core.DatasetInfo:
    """Dataset metadata (homepage, citation,...)."""
    return self.dataset_info_from_configs(
        features=tfds.features.FeaturesDict({
            'image': tfds.features.Image(shape=(256, 256, 3)),
            'label': tfds.features.ClassLabel(
                names=['no', 'yes'],
                doc='Whether this is a picture of a cat'),
        }),
    )

  def _split_generators(self, dl_manager: tfds.download.DownloadManager):
    """Download the data and define splits."""
    extracted_path = dl_manager.download_and_extract('http://data.org/data.zip')
    # dl_manager returns pathlib-like objects with `path.read_text()`,
    # `path.iterdir()`,...
    return {
        'train': self._generate_examples(path=extracted_path / 'train_images'),
        'test': self._generate_examples(path=extracted_path / 'test_images'),
    }

  def _generate_examples(self, path) -> Iterator[Tuple[Key, Example]]:
    """Generator of examples for each split."""
    for img_path in path.glob('*.jpeg'):
      # Yields (key, example)
      yield img_path.name, {
          'image': img_path,
          'label': 'yes' if img_path.name.startswith('yes_') else 'no',
      }

Należy pamiętać, że w przypadku niektórych konkretnych formatów danych udostępniamy gotowe do użycia narzędzia do tworzenia zbiorów danych , które wykonają większość zadań związanych z przetwarzaniem danych.

Przyjrzyjmy się szczegółowo trzem abstrakcyjnym metodom nadpisywania.

_info : metadane zbioru danych

_info zwraca tfds.core.DatasetInfo zawierający metadane zestawu danych .

def _info(self):
  # The `dataset_info_from_configs` base method will construct the
  # `tfds.core.DatasetInfo` object using the passed-in parameters and
  # adding: builder (self), description/citations/tags from the config
  # files located in the same package.
  return self.dataset_info_from_configs(
      homepage='https://dataset-homepage.org',
      features=tfds.features.FeaturesDict({
          'image_description': tfds.features.Text(),
          'image': tfds.features.Image(),
          # Here, 'label' can be 0-4.
          'label': tfds.features.ClassLabel(num_classes=5),
      }),
      # If there's a common `(input, target)` tuple from the features,
      # specify them here. They'll be used if as_supervised=True in
      # builder.as_dataset.
      supervised_keys=('image', 'label'),
      # Specify whether to disable shuffling on the examples. Set to False by default.
      disable_shuffling=False,
  )

Większość pól powinna być oczywista. Kilka uściśleń:

Zapisywanie pliku BibText CITATIONS.bib :

  • Przeszukaj witrynę zbioru danych pod kątem instrukcji cytowania (użyj ich w formacie BibTex).
  • W przypadku artykułów arXiv : znajdź artykuł i kliknij odnośnik BibText po prawej stronie.
  • Znajdź artykuł w serwisie Google Scholar i kliknij znak cudzysłowu pod tytułem. W wyskakującym okienku kliknij BibTeX .
  • Jeśli nie masz powiązanego dokumentu (na przykład istnieje tylko strona internetowa), możesz użyć edytora BibTeX Online , aby utworzyć własny wpis BibTeX (w menu rozwijanym znajduje się typ wpisu Online ).

Aktualizowanie pliku TAGS.txt :

  • Wszystkie dozwolone tagi są wstępnie wypełnione w wygenerowanym pliku.
  • Usuń wszystkie tagi, które nie mają zastosowania do zestawu danych.
  • Lista prawidłowych tagów znajduje się w pliku tensorflow_datasets/core/valid_tags.txt .
  • Aby dodać tag do tej listy, wyślij wiadomość PR.

Utrzymuj kolejność zbioru danych

Domyślnie rekordy zestawów danych są tasowane podczas przechowywania, aby zapewnić bardziej równomierny rozkład klas w całym zestawie danych, ponieważ często rekordy należące do tej samej klasy są ciągłe. Aby określić, że zestaw danych powinien być sortowany według klucza wygenerowanego przez _generate_examples , pole disable_shuffling powinno być ustawione na True . Domyślnie jest ono ustawione na False .

def _info(self):
  return self.dataset_info_from_configs(
    # [...]
    disable_shuffling=True,
    # [...]
  )

Należy pamiętać, że wyłączenie funkcji tasowania ma wpływ na wydajność, ponieważ nie można już odczytywać fragmentów równolegle.

_split_generators : pobiera i dzieli dane

Pobieranie i wyodrębnianie danych źródłowych

Większość zestawów danych wymaga pobrania danych z internetu. Odbywa się to za pomocą argumentu wejściowego tfds.download.DownloadManager funkcji _split_generators . dl_manager ma następujące metody:

  • download : obsługuje http(s):// , ftp(s)://
  • extract : obecnie obsługuje pliki .zip , .gz i .tar .
  • download_and_extract : Tak samo jak dl_manager.extract(dl_manager.download(urls))

Wszystkie te metody zwracają tfds.core.Path (aliasy dla epath.Path ), które są obiektami podobnymi do pathlib.Path .

Metody te obsługują dowolną zagnieżdżoną strukturę ( list , dict ), taką jak:

extracted_paths = dl_manager.download_and_extract({
    'foo': 'https://example.com/foo.zip',
    'bar': 'https://example.com/bar.zip',
})
# This returns:
assert extracted_paths == {
    'foo': Path('/path/to/extracted_foo/'),
    'bar': Path('/path/extracted_bar/'),
}

Ręczne pobieranie i wyodrębnianie

Niektórych danych nie można pobrać automatycznie (np. wymagają logowania). W takim przypadku użytkownik musi ręcznie pobrać dane źródłowe i umieścić je w manual_dir/ (domyślnie ~/tensorflow_datasets/downloads/manual/ ).

Dostęp do plików można uzyskać poprzez dl_manager.manual_dir :

class MyDataset(tfds.core.GeneratorBasedBuilder):

  MANUAL_DOWNLOAD_INSTRUCTIONS = """
  Register into https://example.org/login to get the data. Place the `data.zip`
  file in the `manual_dir/`.
  """

  def _split_generators(self, dl_manager):
    # data_path is a pathlib-like `Path('<manual_dir>/data.zip')`
    archive_path = dl_manager.manual_dir / 'data.zip'
    # Extract the manually downloaded `data.zip`
    extracted_path = dl_manager.extract(archive_path)
    ...

Lokalizację manual_dir można dostosować za pomocą tfds build --manual_dir= lub korzystając z tfds.download.DownloadConfig .

Przeczytaj archiwum bezpośrednio

dl_manager.iter_archive odczytuje archiwa sekwencyjnie, bez ich rozpakowywania. Może to zaoszczędzić miejsce na dysku i poprawić wydajność w niektórych systemach plików.

for filename, fobj in dl_manager.iter_archive('path/to/archive.zip'):
  ...

fobj ma te same metody co with open('rb') as fobj: (np. fobj.read() )

Określanie podziałów zbioru danych

Jeśli zbiór danych zawiera predefiniowane podziały (np. MNIST ma podziały train i test ), zachowaj je. W przeciwnym razie określ tylko jeden podział all . Użytkownicy mogą dynamicznie tworzyć własne podpodziały za pomocą interfejsu API podpodziałów (np. split='train[80%:]' ). Należy pamiętać, że jako nazwę podziału można użyć dowolnego ciągu alfabetycznego, z wyjątkiem wspomnianego wcześniej all .

def _split_generators(self, dl_manager):
  # Download source data
  extracted_path = dl_manager.download_and_extract(...)

  # Specify the splits
  return {
      'train': self._generate_examples(
          images_path=extracted_path / 'train_imgs',
          label_path=extracted_path / 'train_labels.csv',
      ),
      'test': self._generate_examples(
          images_path=extracted_path / 'test_imgs',
          label_path=extracted_path / 'test_labels.csv',
      ),
  }

_generate_examples : Generator przykładów

_generate_examples generuje przykłady dla każdego podziału na podstawie danych źródłowych.

Ta metoda zazwyczaj odczytuje artefakty źródłowego zbioru danych (np. plik CSV) i zwraca krotki (key, feature_dict) :

  • key : Identyfikator przykładu. Służy do deterministycznego tasowania przykładów za pomocą hash(key) lub do sortowania według klucza, gdy tasowanie jest wyłączone (patrz sekcja „Utrzymywanie kolejności zbioru danych ”). Powinno być:
    • unique : Jeśli dwa przykłady używają tego samego klucza, zostanie zgłoszony wyjątek.
    • deterministyczny : Nie powinien zależeć od download_dir , os.path.listdir , kolejności,... Dwukrotne wygenerowanie danych powinno dać ten sam klucz.
    • porównywalne : Jeśli tasowanie jest wyłączone, klucz zostanie użyty do posortowania zestawu danych.
  • feature_dict : dict zawierający przykładowe wartości.
    • Struktura powinna być zgodna ze strukturą features= zdefiniowaną w tfds.core.DatasetInfo .
    • Złożone typy danych (obrazy, wideo, audio,...) zostaną zakodowane automatycznie.
    • Każda funkcja często akceptuje wiele typów danych wejściowych (np. akceptacja wideo /path/to/vid.mp4 , np.array(shape=(l, h, w, c)) , List[paths] , List[np.array(shape=(h, w, c)] , List[img_bytes] ,...)
    • Więcej informacji można znaleźć w przewodniku po złączach funkcji .
def _generate_examples(self, images_path, label_path):
  # Read the input data out of the source files
  with label_path.open() as f:
    for row in csv.DictReader(f):
      image_id = row['image_id']
      # And yield (key, feature_dict)
      yield image_id, {
          'image_description': row['description'],
          'image': images_path / f'{image_id}.jpeg',
          'label': row['label'],
      }

Dostęp do pliku i tf.io.gfile

Aby obsługiwać systemy pamięci masowej w chmurze, należy unikać korzystania z wbudowanych operacji wejścia/wyjścia języka Python.

Zamiast tego dl_manager zwraca obiekty typu pathlib, które są bezpośrednio kompatybilne z usługą przechowywania danych w chmurze Google:

path = dl_manager.download_and_extract('http://some-website/my_data.zip')

json_path = path / 'data/file.json'

json.loads(json_path.read_text())

Alternatywnie, użyj interfejsu API tf.io.gfile zamiast wbudowanego do operacji na plikach:

Należy preferować Pathlib zamiast tf.io.gfile (patrz rational .

Dodatkowe zależności

Niektóre zestawy danych wymagają dodatkowych zależności Pythona tylko podczas generowania. Na przykład zestaw danych SVHN używa scipy do załadowania niektórych danych.

Jeśli dodajesz zbiór danych do repozytorium TFDS, użyj tfds.core.lazy_imports , aby zachować niewielki rozmiar pakietu tensorflow-datasets . Użytkownicy będą instalować dodatkowe zależności tylko w razie potrzeby.

Aby użyć lazy_imports :

  • Dodaj wpis dla swojego zestawu danych w DATASET_EXTRAS w setup.py . Dzięki temu użytkownicy będą mogli na przykład wykonać pip install 'tensorflow-datasets[svhn]' aby zainstalować dodatkowe zależności.
  • Dodaj wpis dotyczący importu do LazyImporter i LazyImportsTest .
  • Użyj tfds.core.lazy_imports , aby uzyskać dostęp do zależności (na przykład tfds.core.lazy_imports.scipy ) w DatasetBuilder .

Uszkodzone dane

Niektóre zbiory danych nie są idealnie czyste i zawierają uszkodzone dane (na przykład obrazy są w plikach JPEG, ale niektóre są w nieprawidłowym formacie JPEG). Te przykłady należy pominąć, ale w opisie zbioru danych należy umieścić informację o liczbie pominiętych przykładów i przyczynach ich usunięcia.

Konfiguracja/warianty zestawu danych (tfds.core.BuilderConfig)

Niektóre zestawy danych mogą mieć wiele wariantów lub opcji dotyczących sposobu wstępnego przetwarzania i zapisywania danych na dysku. Na przykład cycle_gan ma jedną konfigurację na parę obiektów ( cycle_gan/horse2zebra , cycle_gan/monet2photo ,...).

Można to zrobić za pomocą pliku tfds.core.BuilderConfig :

  1. Zdefiniuj obiekt konfiguracji jako podklasę tfds.core.BuilderConfig . Na przykład MyDatasetConfig .

    @dataclasses.dataclass
    class MyDatasetConfig(tfds.core.BuilderConfig):
      img_size: Tuple[int, int] = (0, 0)
    
  2. Zdefiniuj element klasy BUILDER_CONFIGS = [] w MyDataset , który zawiera listę konfiguracji MyDatasetConfig udostępnianych przez zbiór danych.

    class MyDataset(tfds.core.GeneratorBasedBuilder):
      VERSION = tfds.core.Version('1.0.0')
      # pytype: disable=wrong-keyword-args
      BUILDER_CONFIGS = [
          # `name` (and optionally `description`) are required for each config
          MyDatasetConfig(name='small', description='Small ...', img_size=(8, 8)),
          MyDatasetConfig(name='big', description='Big ...', img_size=(32, 32)),
      ]
      # pytype: enable=wrong-keyword-args
    
  3. Użyj self.builder_config w MyDataset , aby skonfigurować generowanie danych (np. shape=self.builder_config.img_size ). Może to obejmować ustawienie innych wartości w _info() lub zmianę dostępu do pobierania danych.

Uwagi:

  • Każda konfiguracja ma unikalną nazwę. Pełna nazwa konfiguracji to dataset_name/config_name (np. coco/2017 ).
  • Jeżeli nie określono inaczej, zostanie użyta pierwsza konfiguracja w BUILDER_CONFIGS (np. tfds.load('c4') domyślnie c4/en ).

Przykład zestawu danych wykorzystującego BuilderConfig można znaleźć w anli .

Wersja

Wersja może odnosić się do dwóch różnych znaczeń:

  • Wersja danych oryginalnych „zewnętrznych”, np. COCO v2019, v2017,...
  • Wersja kodu „wewnętrznego” TFDS: np. zmiana nazwy funkcji w tfds.features.FeaturesDict , naprawa błędu w _generate_examples

Aby zaktualizować zestaw danych:

  • W przypadku aktualizacji danych „zewnętrznych”: wielu użytkowników może chcieć uzyskać dostęp do określonego roku/wersji jednocześnie. Można to zrobić, używając jednego pliku tfds.core.BuilderConfig na wersję (np. coco/2017 , coco/2019 ) lub jednej klasy na wersję (np. Voc2007 , Voc2012 ).
  • W przypadku aktualizacji kodu „wewnętrznego”: Użytkownicy pobierają tylko najnowszą wersję. Każda aktualizacja kodu powinna zwiększyć atrybut klasy VERSION (np. z 1.0.0 do VERSION = tfds.core.Version('2.0.0') ) po przeprowadzeniu semantycznej kontroli wersji .

Dodaj import do rejestracji

Nie zapomnij zaimportować modułu zestawu danych do swojego projektu __init__ , aby został on automatycznie zarejestrowany w tfds.load i tfds.builder .

import my_project.datasets.my_dataset  # Register MyDataset

ds = tfds.load('my_dataset')  # MyDataset available

Na przykład, jeśli dodajesz coś do tensorflow/datasets , dodaj moduł import do jego podkatalogu __init__.py (np. image/__init__.py .

Sprawdź typowe problemy z implementacją

Sprawdź typowe problemy związane z implementacją .

Przetestuj swój zestaw danych

Pobierz i przygotuj: tfds build

Aby wygenerować zbiór danych, uruchom polecenie tfds build z katalogu my_dataset/ :

cd path/to/datasets/my_dataset/
tfds build --register_checksums

Kilka przydatnych flag dla rozwoju:

  • --pdb : Przejdź do trybu debugowania, jeśli zostanie zgłoszony wyjątek.
  • --overwrite : Usuń istniejące pliki, jeśli zestaw danych został już wygenerowany.
  • --max_examples_per_split : Generuje tylko pierwsze X przykładów (domyślnie 1), a nie cały zestaw danych.
  • --register_checksums : Rejestruje sumy kontrolne pobranych adresów URL. Należy z niego korzystać wyłącznie w fazie rozwoju.

Pełną listę flag można znaleźć w dokumentacji CLI .

Sumy kontrolne

Zaleca się rejestrowanie sum kontrolnych zestawów danych w celu zagwarantowania determinizmu, ułatwienia dokumentacji itd. Można to zrobić, generując zestaw danych za pomocą opcji --register_checksums (patrz poprzednia sekcja).

Jeśli udostępniasz zestawy danych za pośrednictwem PyPI, nie zapomnij wyeksportować plików checksums.tsv (np. w package_data w pliku setup.py ).

Przeprowadź test jednostkowy swojego zestawu danych

tfds.testing.DatasetBuilderTestCase to podstawowy TestCase do pełnego testowania zbioru danych. Wykorzystuje on „dane pozorne” jako dane testowe, które naśladują strukturę zbioru źródłowego.

  • Dane testowe powinny zostać umieszczone w katalogu my_dataset/dummy_data/ i powinny odzwierciedlać artefakty źródłowego zbioru danych pobrane i wyodrębnione. Można je utworzyć ręcznie lub automatycznie za pomocą skryptu ( przykładowy skrypt ).
  • Pamiętaj o użyciu różnych danych w podziale danych testowych, ponieważ test zakończy się niepowodzeniem, jeśli podziały zbioru danych będą się na siebie nakładać.
  • Dane testowe nie powinny zawierać żadnych materiałów chronionych prawem autorskim . W razie wątpliwości nie należy tworzyć danych, korzystając z materiałów z oryginalnego zestawu danych.
import tensorflow_datasets as tfds
from . import my_dataset_dataset_builder


class MyDatasetTest(tfds.testing.DatasetBuilderTestCase):
  """Tests for my_dataset dataset."""
  DATASET_CLASS = my_dataset_dataset_builder.Builder
  SPLITS = {
      'train': 3,  # Number of fake train example
      'test': 1,  # Number of fake test example
  }

  # If you are calling `download/download_and_extract` with a dict, like:
  #   dl_manager.download({'some_key': 'http://a.org/out.txt', ...})
  # then the tests needs to provide the fake output paths relative to the
  # fake data directory
  DL_EXTRACT_RESULT = {
      'name1': 'path/to/file1',  # Relative to my_dataset/dummy_data dir.
      'name2': 'file2',
  }


if __name__ == '__main__':
  tfds.testing.test_main()

Uruchom następujące polecenie, aby przetestować zestaw danych.

python my_dataset_test.py

Wyślij nam opinię

Nieustannie staramy się usprawniać proces tworzenia zbioru danych, ale możemy to robić tylko wtedy, gdy jesteśmy świadomi problemów. Jakie problemy lub błędy napotkałeś podczas tworzenia zbioru danych? Czy jakaś część była myląca lub nie działała za pierwszym razem?

Podziel się swoją opinią na GitHub .