Pisanie niestandardowych zestawów danych

Postępuj zgodnie z tym przewodnikiem, aby utworzyć nowy zbiór danych (w TFDS lub we własnym repozytorium).

Sprawdź naszą listę zbiorów danych , aby sprawdzić, czy żądany zbiór danych już istnieje.

TL;DR

Najłatwiejszym sposobem napisania nowego zestawu danych jest użycie interfejsu CLI 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 zbiór 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 różnych formatach i w różnych miejscach i nie zawsze są przechowywane w formacie gotowym do wprowadzenia do potoku uczenia maszynowego. Wejdź do TFDS.

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

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

  • Skąd pochodzą dane (tj. ich adresy URL);
  • Jak wygląda zbiór danych (tj. jego cechy);
  • Jak należy podzielić dane (np. TRAIN i TEST );
  • oraz poszczególne przykłady w zbiorze danych.

Zapisz swój zbiór danych

Domyślny szablon: tfds new

Użyj interfejsu TFDS CLI , aby wygenerować wymagane pliki Pythona z szablonami.

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 odpowiednio zmodyfikuj.

Przykład zbioru danych

Wszystkie zestawy danych są zaimplementowanymi podklasami tfds.core.DatasetBuilder , który zajmuje się większością schematów. To wspiera:

Oto minimalny przykład kreatora 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 określonych formatów danych udostępniamy gotowe do użycia narzędzia do tworzenia zbiorów danych , które obsługują większość procesów przetwarzania danych.

Przyjrzyjmy się szczegółowo 3 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 doprecyzowań:

Zapisywanie pliku BibText CITATIONS.bib :

  • Wyszukaj w witrynie zbioru danych instrukcje dotyczące cytowań (użyj ich w formacie BibTex).
  • W przypadku artykułów arXiv : znajdź artykuł i kliknij łącze BibText po prawej stronie.
  • Znajdź artykuł w Google Scholar i kliknij cudzysłów pod tytułem, a następnie w wyskakującym okienku kliknij BibTeX .
  • Jeśli nie ma powiązanej publikacji (na przykład jest tylko witryna internetowa), możesz użyć Edytora BibTeX Online, aby utworzyć niestandardowy wpis BibTeX (w menu rozwijanym znajduje się typ wpisu Online ).

Aktualizacja pliku TAGS.txt :

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

Zachowaj porządek zbioru danych

Domyślnie rekordy zbiorów danych są tasowane podczas przechowywania, aby rozkład klas był bardziej równomierny w zbiorze danych, ponieważ często rekordy należące do tej samej klasy sąsiadują ze sobą. Aby określić, że zbiór danych powinien być sortowany według klucza wygenerowanego dostarczonego przez _generate_examples , pole disable_shuffling powinno być ustawione na True . Domyślnie jest ustawiona na False .

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

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

_split_generators : pobiera i dzieli dane

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

Większość zbiorów danych wymaga pobierania danych z Internetu. Odbywa się to za pomocą argumentu wejściowego tfds.download.DownloadManager _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 : To samo co 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ą strukturę zagnieżdżoną ( list , dict ), na przykład:

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 ręcznie pobierze dane źródłowe i umieści je w manual_dir/ (domyślnie jest to ~/tensorflow_datasets/downloads/manual/ ).

Dostęp do plików można następnie 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)
    ...

Położenie manual_dir można dostosować za pomocą tfds build --manual_dir= lub za pomocą tfds.download.DownloadConfig .

Przeczytaj archiwum bezpośrednio

dl_manager.iter_archive czyta 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 ma wstępnie zdefiniowane podziały (np. MNIST ma podziały na train i test ), zachowaj je. W przeciwnym razie określ tylko pojedynczy podział all . Użytkownicy mogą dynamicznie tworzyć własne podpodziały za pomocą interfejsu API subsplit (np. split='train[80%:]' ). Należy pamiętać, że jako rozdzieloną nazwę można użyć dowolnego ciągu alfabetycznego, z wyjątkiem wyżej wymienionych 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 z danych źródłowych.

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

  • key : Przykładowy identyfikator. 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 porządku zbioru danych ). Powinien być:
    • unikalny : Jeśli dwa przykłady używają tego samego klucza, zostanie zgłoszony wyjątek.
    • deterministyczny : Nie powinien zależeć od download_dir , kolejności os.path.listdir ,... Dwukrotne wygenerowanie danych powinno dać ten sam klucz.
    • porównywalne : Jeśli mieszanie jest wyłączone, klucz zostanie użyty do sortowania zbioru 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 (obraz, wideo, audio,...) będą kodowane automatycznie.
    • Każda funkcja często akceptuje wiele typów danych wejściowych (np. wideo akceptowane /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łączu 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 plików i tf.io.gfile

Aby obsługiwać systemy przechowywania w chmurze, unikaj używania wbudowanych operacji we/wy Pythona.

Zamiast tego dl_manager zwraca obiekty podobne do pathlib bezpośrednio kompatybilne z pamięcią masową Google Cloud:

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:

Pathlib powinien być preferowany zamiast tf.io.gfile (patrz racjonalny plik .

Dodatkowe zależności

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

Jeśli dodajesz zestaw danych do repozytorium TFDS, użyj tfds.core.lazy_imports , aby pakiet tensorflow-datasets był mały. Użytkownicy będą instalować dodatkowe zależności tylko w razie potrzeby.

Aby użyć lazy_imports :

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

Uszkodzone dane

Niektóre zbiory danych nie są idealnie czyste i zawierają pewne uszkodzone dane (na przykład obrazy znajdują się w plikach JPEG, ale niektóre mają nieprawidłowy format JPEG). Te przykłady należy pominąć, ale w opisie zbioru danych zostaw notatkę, ile przykładów zostało pominiętych i dlaczego.

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

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

Odbywa się to poprzez tfds.core.BuilderConfig s:

  1. Zdefiniuj obiekt konfiguracyjny 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ę MyDatasetConfig s udostępnianych przez zestaw 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 różnych wartości w _info() lub zmianę dostępu do pobieranych danych.

Uwagi:

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

Zobacz anli , aby zapoznać się z przykładem zestawu danych używającego BuilderConfig s.

Wersja

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

  • „Zewnętrzna” oryginalna wersja danych: np. COCO v2019, v2017,...
  • „Wewnętrzna” wersja kodu TFDS: np. zmień nazwę funkcji w tfds.features.FeaturesDict , napraw błąd w _generate_examples

Aby zaktualizować zbiór danych:

  • W przypadku „zewnętrznej” aktualizacji danych: wielu użytkowników może chcieć jednocześnie uzyskać dostęp do określonego roku/wersji. Odbywa się to poprzez użycie jednego tfds.core.BuilderConfig na wersję (np. coco/2017 , coco/2019 ) lub jednej klasy na wersję (np. Voc2007 , Voc2012 ).
  • W przypadku „wewnętrznej” aktualizacji kodu: 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 wersjonowaniu semantycznym .

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 współtworzysz tensorflow/datasets , dodaj import modułu do __init__.py jego podkatalogu (np. image/__init__.py .

Sprawdź typowe problemy związane z implementacją

Sprawdź typowe problemy związane z implementacją .

Przetestuj swój zbiór danych

Pobierz i przygotuj: tfds build

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

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

Kilka przydatnych flag do programowania:

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

Pełną listę flag znajdziesz w dokumentacji CLI .

Sumy kontrolne

Zalecane jest zapisywanie sum kontrolnych zbiorów danych, aby zagwarantować determinizm, pomóc w dokumentacji,... Dokonuje się tego poprzez wygenerowanie zbioru danych z --register_checksums (patrz poprzednia sekcja).

Jeśli udostępniasz swoje zbiory danych poprzez PyPI, nie zapomnij wyeksportować plików checksums.tsv (np. w package_data pliku setup.py ).

Przetestuj jednostkowo swój zbiór danych

tfds.testing.DatasetBuilderTestCase to podstawowy TestCase umożliwiający pełne wykorzystanie zbioru danych. Wykorzystuje „dane fikcyjne” jako dane testowe, które naśladują strukturę źródłowego zbioru danych.

  • Dane testowe należy umieścić w katalogu my_dataset/dummy_data/ i powinny naśladować artefakty źródłowego zestawu danych po pobraniu i rozpakowaniu. Można go utworzyć ręcznie lub automatycznie za pomocą skryptu ( przykładowy skrypt ).
  • Upewnij się, że w podziałach danych testowych używasz różnych danych, ponieważ test zakończy się niepowodzeniem, jeśli podziały zestawu danych będą się nakładać.
  • Dane testowe nie powinny zawierać żadnych materiałów objętych prawami autorskimi . W razie wątpliwości nie twórz danych przy użyciu materiału z oryginalnego zbioru 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 opinie

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

Podziel się swoją opinią na GitHubie .