Написание пользовательских наборов данных

Следуйте этому руководству, чтобы создать новый набор данных (в TFDS или в вашем собственном репозитории).

Проверьте наш список наборов данных , чтобы узнать, существует ли уже нужный вам набор данных.

ТЛ;ДР

Самый простой способ написать новый набор данных — использовать 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/`

Чтобы использовать новый набор данных с tfds.load('my_dataset') :

  • tfds.load автоматически обнаружит и загрузит набор данных, сгенерированный в ~/tensorflow_datasets/my_dataset/ (например, с помощью tfds build ).
  • Альтернативно вы можете явно import my.project.datasets.my_dataset чтобы зарегистрировать свой набор данных:
import my.project.datasets.my_dataset  # Register `my_dataset`

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

Обзор

Наборы данных распространяются во всех форматах и ​​во всех местах, и они не всегда хранятся в формате, готовом к передаче в конвейер машинного обучения. Введите ТФДС.

TFDS обрабатывает эти наборы данных в стандартный формат (внешние данные -> сериализованные файлы), которые затем можно загрузить как конвейер машинного обучения (сериализованные файлы -> tf.data.Dataset ). Сериализация выполняется только один раз. Последующий доступ будет осуществляться непосредственно из этих предварительно обработанных файлов.

Большая часть предварительной обработки выполняется автоматически. Каждый набор данных реализует подкласс tfds.core.DatasetBuilder , который определяет:

  • Откуда поступают данные (т. е. их URL-адреса);
  • Как выглядит набор данных (т.е. его характеристики);
  • Как данные должны быть разделены (например, TRAIN и TEST );
  • и отдельные примеры в наборе данных.

Напишите свой набор данных

Шаблон по умолчанию: tfds new

Используйте TFDS CLI для создания необходимых файлов шаблонов Python.

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

Эта команда создаст новую папку my_dataset/ со следующей структурой:

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).

Найдите здесь TODO(my_dataset) и измените соответствующим образом.

Пример набора данных

Все наборы данных являются подклассами tfds.core.DatasetBuilder , который заботится о большей части шаблонов. Он поддерживает:

  • Маленькие/средние наборы данных, которые можно создать на одном компьютере (это руководство).
  • Очень большие наборы данных, требующие распределенной генерации (с использованием Apache Beam , см. наше руководство по огромным наборам данных ).

Вот минимальный пример построителя набора данных, основанного на 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',
      }

Обратите внимание, что для некоторых конкретных форматов данных мы предоставляем готовые к использованию конструкторы наборов данных , которые помогут выполнить большую часть обработки данных.

Давайте подробно рассмотрим три абстрактных метода перезаписи.

_info : метаданные набора данных

_info возвращает tfds.core.DatasetInfo , содержащий метаданные набора данных .

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,
  )

Большинство полей не требуют пояснений. Некоторые уточнения:

Написание файла BibText CITATIONS.bib :

  • Найдите на веб-сайте набора данных инструкции по цитированию (используйте их в формате BibTex).
  • Для статей arXiv : найдите статью и щелкните ссылку BibText справа.
  • Найдите статью в Google Scholar , щелкните двойную кавычку под заголовком и во всплывающем окне нажмите BibTeX .
  • Если связанного документа нет (например, есть только веб-сайт), вы можете использовать онлайн-редактор BibTeX для создания пользовательской записи BibTeX (в раскрывающемся меню есть тип записи Online ).

Обновление файла TAGS.txt :

  • Все разрешенные теги предварительно заполняются в сгенерированном файле.
  • Удалите все теги, которые не относятся к набору данных.
  • Допустимые теги перечислены в tensorflow_datasets/core/valid_tags.txt .
  • Чтобы добавить тег в этот список, отправьте PR.

Поддерживать порядок набора данных

По умолчанию записи наборов данных при хранении перемешиваются, чтобы сделать распределение классов более равномерным по набору данных, поскольку зачастую записи, принадлежащие одному классу, являются смежными. Чтобы указать, что набор данных должен быть отсортирован по ключу, сгенерированному _generate_examples , поле disable_shuffling должно быть установлено в True . По умолчанию установлено значение False .

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

Имейте в виду, что отключение перетасовки влияет на производительность, поскольку шарды больше не могут читаться параллельно.

_split_generators : загружает и разделяет данные.

Загрузка и извлечение исходных данных

Большинству наборов данных необходимо загружать данные из Интернета. Это делается с помощью входного аргумента tfds.download.DownloadManager _split_generators . dl_manager имеет следующие методы:

  • download : поддерживает http(s):// , ftp(s)://
  • extract : в настоящее время поддерживаются файлы .zip , .gz и .tar .
  • download_and_extract : То же, что и dl_manager.extract(dl_manager.download(urls))

Все эти методы возвращают tfds.core.Path (псевдонимы epath.Path ), которые являются объектами , подобными pathlib.Path .

Эти методы поддерживают произвольную вложенную структуру ( list , dict ), например:

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/'),
}

Ручная загрузка и извлечение

Некоторые данные не могут быть загружены автоматически (например, требуется вход в систему), в этом случае пользователь вручную загрузит исходные данные и поместит их в manual_dir/ (по умолчанию ~/tensorflow_datasets/downloads/manual/ ).

Доступ к файлам можно получить через 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)
    ...

Местоположение manual_dir можно настроить с помощью tfds build --manual_dir= или с помощью tfds.download.DownloadConfig .

Читать архив напрямую

dl_manager.iter_archive читает архивы последовательно, не распаковывая их. Это может сэкономить место для хранения и повысить производительность некоторых файловых систем.

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

fobj имеет те же методы, что и with open('rb') as fobj: (например, fobj.read() )

Указание разделения набора данных

Если набор данных поставляется с заранее определенными разбиениями (например, MNIST есть разделения train и test ), сохраните их. В противном случае укажите только одно разделение all . Пользователи могут динамически создавать свои собственные подразделения с помощью API-интерфейса subsplit (например, split='train[80%:]' ). Обратите внимание, что в качестве имени разделения можно использовать любую буквенную строку, кроме вышеупомянутой 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 : Пример генератора

_generate_examples генерирует примеры для каждого разделения из исходных данных.

Этот метод обычно считывает артефакты исходного набора данных (например, файл CSV) и выдает кортежи (key, feature_dict) :

  • key : Пример идентификатора. Используется для детерминированного перемешивания примеров с использованием hash(key) или для сортировки по ключу, когда перемешивание отключено (см. раздел «Поддержание порядка набора данных» ). Должно быть:
    • unique : если в двух примерах используется один и тот же ключ, будет возбуждено исключение.
    • детерминированный : не должен зависеть от порядка download_dir , os.path.listdir ... Двойная генерация данных должна дать один и тот же ключ.
    • сопоставимый : если перетасовка отключена, ключ будет использоваться для сортировки набора данных.
  • feature_dict : dict содержащий примеры значений.
    • Структура должна соответствовать структуре features= определенной в tfds.core.DatasetInfo .
    • Сложные типы данных (изображение, видео, аудио и т. д.) будут автоматически кодироваться.
    • Каждая функция часто принимает несколько типов ввода (например, видео принимает /path/to/vid.mp4 , np.array(shape=(l, h, w, c)) , List[paths] , List[np.array(shape=(h, w, c)] , List[img_bytes] ,...)
    • Дополнительную информацию см. в руководстве по функциональному разъему .
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'],
      }

Доступ к файлам и tf.io.gfile

Для поддержки облачных систем хранения избегайте использования встроенных операций ввода-вывода Python.

Вместо этого dl_manager возвращает объекты , подобные pathlib, напрямую совместимые с облачным хранилищем 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())

Альтернативно, используйте API tf.io.gfile вместо встроенного для файловых операций:

Pathlib следует отдавать предпочтение tf.io.gfile ( см .

Дополнительные зависимости

Для некоторых наборов данных дополнительные зависимости Python требуются только во время генерации. Например, набор данных SVHN использует scipy для загрузки некоторых данных.

Если вы добавляете набор данных в репозиторий TFDS, используйте tfds.core.lazy_imports чтобы сохранить небольшой размер пакета tensorflow-datasets . Пользователи будут устанавливать дополнительные зависимости только по мере необходимости.

Чтобы использовать lazy_imports :

  • Добавьте запись для вашего набора данных в DATASET_EXTRAS в setup.py . Благодаря этому пользователи могут, например, выполнить pip install 'tensorflow-datasets[svhn]' чтобы установить дополнительные зависимости.
  • Добавьте запись для импорта в LazyImporter и LazyImportsTest .
  • Используйте tfds.core.lazy_imports для доступа к зависимости (например, tfds.core.lazy_imports.scipy ) в вашем DatasetBuilder .

Поврежденные данные

Некоторые наборы данных не совсем чисты и содержат поврежденные данные (например, изображения находятся в файлах JPEG, но некоторые из них являются недействительными JPEG). Эти примеры следует пропустить, но оставьте в описании набора данных пометку, сколько примеров было исключено и почему.

Конфигурация/варианты набора данных (tfds.core.BuilderConfig)

Некоторые наборы данных могут иметь несколько вариантов или вариантов предварительной обработки и записи данных на диск. Например, Cycl_gan имеет одну конфигурацию для каждой пары объектов ( cycle_gan/horse2zebra , cycle_gan/monet2photo ,...).

Это делается через tfds.core.BuilderConfig :

  1. Определите свой объект конфигурации как подкласс tfds.core.BuilderConfig . Например, MyDatasetConfig .

    @dataclasses.dataclass
    class MyDatasetConfig(tfds.core.BuilderConfig):
      img_size: Tuple[int, int] = (0, 0)
    
  2. Определите член класса BUILDER_CONFIGS = [] в MyDataset , в котором перечислены MyDatasetConfig , предоставляемые набором данных.

    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. Используйте self.builder_config в MyDataset для настройки генерации данных (например, shape=self.builder_config.img_size ). Это может включать установку других значений в _info() или изменение доступа к данным загрузки.

Примечания:

  • Каждая конфигурация имеет уникальное имя. Полное имя конфигурации — dataset_name/config_name (например, coco/2017 ).
  • Если не указано, будет использоваться первая конфигурация в BUILDER_CONFIGS (например, tfds.load('c4') по умолчанию — c4/en )

См. anli для примера набора данных, который использует BuilderConfig s.

Версия

Версия может иметь два разных значения:

  • «Внешняя» исходная версия данных: например, COCO v2019, v2017,...
  • «Внутренняя» версия кода TFDS: например, переименуйте функцию в tfds.features.FeaturesDict , исправьте ошибку в _generate_examples

Чтобы обновить набор данных:

  • Для «внешнего» обновления данных: несколько пользователей могут одновременно захотеть получить доступ к определенному году/версии. Это делается с помощью одного tfds.core.BuilderConfig для каждой версии (например, coco/2017 , coco/2019 ) или одного класса для каждой версии (например, Voc2007 , Voc2012 ).
  • Для «внутреннего» обновления кода: пользователи загружают только самую последнюю версию. Любое обновление кода должно увеличивать атрибут класса VERSION (например, с 1.0.0 до VERSION = tfds.core.Version('2.0.0') ) после семантического управления версиями .

Добавить импорт для регистрации

Не забудьте импортировать модуль набора данных в свой проект __init__ , чтобы он автоматически регистрировался в tfds.load , tfds.builder .

import my_project.datasets.my_dataset  # Register MyDataset

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

Например, если вы участвуете в проекте tensorflow/datasets , добавьте импорт модуля в __init__.py его подкаталога (например, image/__init__.py .

Проверьте распространенные ошибки реализации

Пожалуйста, проверьте распространенные ошибки реализации .

Проверьте свой набор данных

Загрузите и подготовьте: tfds build

Чтобы сгенерировать набор данных, запустите tfds build из каталога my_dataset/ :

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

Некоторые полезные флаги для разработки:

  • --pdb : войти в режим отладки, если возникнет исключение.
  • --overwrite : удалить существующие файлы, если набор данных уже был создан.
  • --max_examples_per_split : генерировать только первые X примеров (по умолчанию 1), а не полный набор данных.
  • --register_checksums : записать контрольные суммы загруженных URL-адресов. Следует использовать только во время разработки.

Полный список флагов см. в документации CLI .

Контрольные суммы

Рекомендуется записывать контрольные суммы ваших наборов данных, чтобы гарантировать детерминированность, помогать с документацией... Это делается путем создания набора данных с помощью --register_checksums (см. предыдущий раздел).

Если вы выпускаете свои наборы данных через PyPI, не забудьте экспортировать файлы checksums.tsv (например, в package_data вашего setup.py ).

Проведите модульное тестирование вашего набора данных

tfds.testing.DatasetBuilderTestCase — это базовый TestCase для полной проверки набора данных. Он использует «фиктивные данные» в качестве тестовых данных, имитирующих структуру исходного набора данных.

  • Тестовые данные должны быть помещены в каталог my_dataset/dummy_data/ и должны имитировать артефакты исходного набора данных при загрузке и извлечении. Его можно создать вручную или автоматически с помощью скрипта ( пример скрипта ).
  • Обязательно используйте разные данные в разбиениях тестовых данных, так как тест завершится неудачей, если разбиения набора данных перекроются.
  • Данные испытаний не должны содержать материалов, защищенных авторским правом . В случае сомнений не создавайте данные, используя материал из исходного набора данных.
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()

Запустите следующую команду, чтобы проверить набор данных.

python my_dataset_test.py

Отправьте нам отзыв

Мы постоянно пытаемся улучшить рабочий процесс создания наборов данных, но сможем сделать это только в том случае, если будем знать о проблемах. С какими проблемами или ошибками вы столкнулись при создании набора данных? Была ли какая-то часть, которая сбивала с толку или не работала с первого раза?

Пожалуйста, поделитесь своим отзывом на GitHub .