На этой странице описаны распространенные ошибки при реализации нового набора данных.
Следует избегать устаревшего 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 Другой текст
Забыли имена меток классов
При использовании 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