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

На этой странице описаны распространенные ошибки при реализации нового набора данных.

Следует избегать устаревшего SplitGenerator .

Старый API tfds.core.SplitGenerator устарел.

def _split_generator(...):
  return [
      tfds.core.SplitGenerator(name='train', gen_kwargs={'path': train_path}),
      tfds.core.SplitGenerator(name='test', gen_kwargs={'path': test_path}),
  ]

Следует заменить на:

def _split_generator(...):
  return {
      'train': self._generate_examples(path=train_path),
      'test': self._generate_examples(path=test_path),
  }

Обоснование : новый API менее подробный и более явный. Старый API будет удален в будущей версии.

Новые наборы данных должны храниться в отдельной папке.

При добавлении набора данных в репозиторий tensorflow_datasets/ обязательно соблюдайте структуру набора данных как папки (все контрольные суммы, фиктивные данные, код реализации находятся в отдельной папке).

  • Старые наборы данных (неправильные): <category>/<ds_name>.py
  • Новые наборы данных (хорошие): <category>/<ds_name>/<ds_name>.py

Используйте интерфейс командной строки TFDS ( tfds new или gtfds new для гуглеров), чтобы создать шаблон.

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

Списки описаний должны быть отформатированы как уценка.

str DatasetInfo.description имеет формат уценки. Списки Markdown требуют пустой строки перед первым элементом:

_DESCRIPTION = """
Some text.
                      # << Empty line here !!!
1. Item 1
2. Item 1
3. Item 1
                      # << Empty line here !!!
Some other text.
"""

Обоснование : неправильно отформатированное описание создает визуальные артефакты в документации нашего каталога. Без пустых строк приведенный выше текст будет отображаться как:

Какой-то текст. 1. Пункт 1 2. Пункт 1 3. Пункт 1 Другой текст

Забыли имена ClassLabel

При использовании tfds.features.ClassLabel попытайтесь предоставить удобочитаемые метки str с names= или names_file= (вместо num_classes=10 ).

features = {
    'label': tfds.features.ClassLabel(names=['dog', 'cat', ...]),
}

Обоснование : Читаемые человеком этикетки используются во многих местах:

  • Разрешить получение str непосредственно в _generate_examples : yield {'label': 'dog'}
  • Предоставляется пользователям, например, info.features['label'].names (метод преобразования .str2int('dog') ,... также доступен)
  • Используется в утилитах визуализации tfds.show_examples , tfds.as_dataframe

Забыл форму изображения

При использовании tfds.features.Image , tfds.features.Video , если изображения имеют статическую форму, их следует указать явно:

features = {
    'image': tfds.features.Image(shape=(256, 256, 3)),
}

Обоснование : он позволяет выводить статические формы (например ds.element_spec['image'].shape ), необходимые для пакетной обработки (пакетная обработка изображений неизвестной формы потребует предварительного изменения их размера).

Предпочитайте более конкретный тип вместо tfds.features.Tensor .

Когда это возможно, отдавайте предпочтение более конкретным типам tfds.features.ClassLabel , tfds.features.BBoxFeatures ,... вместо общего tfds.features.Tensor .

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

Ленивый импорт в глобальном пространстве

Ленивый импорт не должен вызываться из глобального пространства. Например, следующее неверно:

tfds.lazy_imports.apache_beam # << Error: Import beam in the global scope

def f() -> beam.Map:
  ...

Обоснование : использование отложенного импорта в глобальной области приведет к импорту модуля для всех пользователей tfds, что противоречит цели отложенного импорта.

Динамическое вычисление разделения поездов/тестов

Если набор данных не обеспечивает официального разделения, то и TFDS не должна этого делать. Следует избегать следующего:

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

Обоснование : TFDS пытается предоставить наборы данных, максимально приближенные к исходным. Вместо этого следует использовать API-интерфейс sub-split , чтобы позволить пользователям динамически создавать нужные им sub-split-ы:

ds_train, ds_test = tfds.load(..., split=['train[:80%]', 'train[80%:]'])

Руководство по стилю Python

Предпочитаю использовать API pathlib

Вместо API tf.io.gfile предпочтительнее использовать API pathlib . Все методы dl_manager возвращают объекты, подобные pathlib, совместимые с 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())

Обоснование : API pathlib — это современный объектно-ориентированный файловый API, в котором отсутствует шаблонный подход. Использование .read_text() / .read_bytes() также гарантирует правильное закрытие файлов.

Если метод не использует self , это должна быть функция

Если метод класса не использует self , это должна быть простая функция (определенная вне класса).

Обоснование : читателю становится ясно, что функция не имеет побочных эффектов или скрытого ввода/вывода:

x = f(y)  # Clear inputs/outputs

x = self.f(y)  # Does f depend on additional hidden variables ? Is it stateful ?

Ленивый импорт в Python

Мы лениво импортируем большие модули, такие как TensorFlow. Отложенный импорт откладывает фактический импорт модуля до первого его использования. Таким образом, пользователи, которым не нужен этот большой модуль, никогда не будут его импортировать. Мы используем 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

Под капотом класс LazyModule действует как фабрика, которая фактически импортирует модуль только при доступе к атрибуту ( __getattr__ ).

Вы также можете удобно использовать его с помощью контекстного менеджера:

from etils import epy

with epy.lazy_imports(error_callback=..., success_callback=...):
  import some_big_module