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

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

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

Вкратце:

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

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 , который берет на себя большую часть шаблонного кода. Он поддерживает:

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

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

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

_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 подразделений (например, 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) или для сортировки по ключу, когда перемешивание отключено (см. раздел «Поддержание порядка в наборе данных »). Должно быть:
    • уникальный : Если два примера используют один и тот же ключ, будет сгенерировано исключение.
    • Детерминированный : не должен зависеть от download_dir , порядка os.path.listdir и т.д. Генерация данных дважды должна дать один и тот же ключ.
    • Compared : Если перемешивание отключено, для сортировки набора данных будет использоваться ключ.
  • feature_dict : dict , содержащий примеры значений.
    • Структура должна соответствовать структуре features= определенной в tfds.core.DatasetInfo .
    • Сложные типы данных (изображения, видео, аудио и т. д.) будут кодироваться автоматически.
    • Каждая функция часто принимает несколько типов входных данных (например, для видео: accept /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 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())

В качестве альтернативы для операций с файлами можно использовать 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)

Некоторые наборы данных могут иметь несколько вариантов или параметров предварительной обработки данных и записи на диск. Например, для cycle_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 ).

Пример набора данных, использующего BuilderConfig , можно найти в anli .

Версия

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

  • Внешняя версия исходных данных: например, 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-адресов. Использовать только в процессе разработки.

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

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

Рекомендуется записывать контрольные суммы ваших наборов данных для обеспечения детерминированности, упрощения документирования и т. д. Это делается путем генерации набора данных с параметром --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 .