Первые довольно много шагов в любом упражнении по машинному обучению связаны с наборами данных. Где и как их скачать? Напишите сценарии для организации и / или преобразования загруженных архивов, каталогов, баз данных и десятков других форматов, в которых они приходят к чему-то, что имеет значение для текущего проекта. И, наконец, как только данные будут подготовлены, разработайте конвейер для передачи данных (или, точнее, функций) в обучаемую нейронную сеть и / или алгоритм машинного обучения.

Что касается построения конвейера, то уже существует отличная абстракция, которую ядро ​​тензорного потока предоставляет с помощью модуля tf.data. Ядром этого модуля является tf.data.Dataset класс, который абстрагирует основной источник ваших входных функций. В зависимости от размера функций вы можете использовать простые массивы numpy или, если набор данных большой, вы можете использовать tfrecords в качестве базового хранилища на основе файлов. tfrecords в конечном итоге сохраняют образцы ваших функций в виде protobufs в соответствии с указанной вами схемой функций. Тем не менее, вам не нужно беспокоиться о том, как tfrecords хранятся данные внутри. Интересно то, что благодаря абстракции, предоставляемой tf.data.Dataset, вы можете применять тот же набор операций к своему входному конвейеру. Ниже приведен простой фрагмент кода, показывающий этот конвейер -

d = tf.data.TFRecordDataset(input_files)
d = d.shard(num_workers, worker_index)
d = d.repeat(num_epochs)
d = d.shuffle(shuffle_buffer_size)
d = d.map(parser_fn, num_parallel_calls=num_map_threads)
assert(isinstance(d, tf.data.Dataset))

Хотя tf.data.Dataset - хорошая абстракция, а tfrecords - хороший формат хранения, он все же не решает проблему согласованного подхода к загрузке, извлечению и подготовке данных. Часто сценарии / код, связанные с загрузкой, извлечением и преобразованием набора данных, отделены от основного конвейера для обучения и оценки и поэтому создают проблемы. Некоторые из них перечислены ниже -

  • Количество скриптов пропорционально количеству наборов данных некоторым фактором. В моем личном проекте у меня есть как минимум 4 сценария (в которых есть главная / точка входа) (помимо используемых ими модулей многократного использования) на набор данных только для подготовки обрабатываемых функций.
  • Разработчики должны беспокоиться о проверках корректности путей к файлам, правильном чтении изображений (в экосистеме Python есть несколько пакетов только для чтения изображений, и даже один проект в конечном итоге использует многие из них) и т. Д. Надеюсь, вы оцените это по мере роста вашего проекта. то же самое относится и к вкладам разработчиков с разной квалификацией, с их собственными предпочтениями в отношении библиотек и подходов.
  • Разъедините спецификацию функции, которую вы использовали при подготовке tfrecords, и спецификацию, которую вы использовали при написании своего parser_fn для сопоставления во время обучения и оценки.

Ключевым компонентом масштабируемого и обслуживаемого проекта разработки программного обеспечения является единообразие решения задач. Задача здесь - получить и подготовить наборы данных. Эта статья посвящена представлению пакета из tensorflow экосистемы под названием tensorflow_datasets( https://github.com/tensorflow/datasets), который предоставляет многообещающие решения перечисленных выше проблем.

tensorflow_datasets - это проект, инициированный разработчиками Google, однако он в значительной степени поддерживается сообществом и привлекает большой вклад сообщества машинного обучения.

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

Ниже приведен список, показывающий использование:

import tensorflow as tf
import tensorflow_datasets as tfds

# tfds works in both Eager and Graph modes
tf.enable_eager_execution()

# See available datasets
print(tfds.list_builders())

# Load a given dataset by name, along with the DatasetInfo
data, info = tfds.load("mnist", with_info=True)
train_data, test_data = data['train'], data['test']
assert isinstance(train_data, tf.data.Dataset)
assert info.features['label'].num_classes == 10
assert info.splits['train'].num_examples == 60000

# You can also access a builder directly
builder = tfds.builder("mnist")
assert builder.info.splits['train'].num_examples == 60000
builder.download_and_prepare()
datasets = builder.as_dataset()

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

Здесь следует особо отметить download_and_prepare. Этот метод, по сути, вызывает конкретную реализацию набора данных (в данном случае MNIST), где находится логика для загрузки, извлечения и подготовки tfrecords. Местоположение исходных, исходных и подготовленных данных также можно настроить, передав аргументы в download_and_prepare.

Также можно загрузить данные (поскольку иногда может потребоваться аутентификация и авторизация), а затем указать путь к ним. Другими словами, он достаточно гибкий, чтобы соответствовать большинству сценариев. Тяжелая задача загрузки и подготовки выполняется только один раз и при последующем использовании пропускает дорогостоящие операции.

Документация tensorflow_datasets действительно хороша. Они предоставляют достойное руководство не только по использованию, но и по тому, как кто-то может внести новый набор данных в свою коллекцию.

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

В этом примере я собираюсь использовать набор данных tiny-imagenet. Полный исходный код примера доступен по адресу https://github.com/ksachdeva/tiny-imagenet-tfds. Вы также можете установить этот пакет в свой собственный проект / блокнот / colab с помощью pip:

pip install git+https://github.com/ksachdeva/tiny-imagenet-tfds.git

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

Давайте сначала посмотрим на использование:

import os
import numpy as np
import tensorflow as tf

import tensorflow_datasets as tfds
from tiny_imagenet import TinyImagenetDataset

# optional
tf.compat.v1.enable_eager_execution()

tiny_imagenet_builder = TinyImagenetDataset()

tiny_imagenet_builder.download_and_prepare()

train_dataset = tiny_imagenet_builder.as_dataset(split="train")
validation_dataset = tiny_imagenet_builder.as_dataset(split="validation")

assert(isinstance(train_dataset, tf.data.Dataset))
assert(isinstance(validation_dataset, tf.data.Dataset))

for a_train_example in train_dataset.take(5):
    image, label, id = a_train_example["image"], a_train_example["label"], a_train_example["id"]
    print(f"Image Shape - {image.shape}")
    print(f"Label - {label.numpy()}")
    print(f"Id - {id.numpy()}")

# print info about the data
print(tiny_imagenet_builder.info)

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

Крошечный Imagenet

Tiny Imagenet - это уменьшенная версия набора данных ImageNet. Этот набор данных был создан ребятами из Стэнфорда для курса http://cs231n.stanford.edu/.

Должно быть ясно, что использование пакета tiny-imagenet-tfds не требует, чтобы вы знали, как загрузить и подготовить набор данных, но поскольку я собираюсь показать вам, как разработать реализацию, важно знать, как этот набор данных организовано.

Вот как выглядит набор данных при извлечении zip-файла.

Характерные черты

  • 200 классов изображений
  • Набор обучающих данных из 100000 изображений
  • Набор данных проверки из 10 000 изображений
  • Тестовый набор данных из 10 000 изображений.
  • Все изображения имеют размер 64 × 64.
  • wnids.txt содержит список имен классов (nXXXXXX)
  • words.txt содержит понятные имена, связанные с именами классов [мы не будем использовать в этом упражнении]
  • val_annotations.txt содержит список изображений. Для каждого изображения есть соответствующий столбец с соответствующей меткой (например, один из wnids.txt).
  • Папка train содержит папку для каждого класса, и в этой папке находится каталог с именем images, который содержит 500 файлов jpeg для этого класса. Каждый класс внутри поезда имеет nXX_boxes.txt, который содержит информацию об ограничивающем прямоугольнике изображений. Мы не собираемся обрабатывать информацию об ограничивающей рамке в этом упражнении.

tfds.core.GeneratorBasedBuilder

Основная рабочая лошадка, стоящая за реализацией различных наборов данных в tensorflow_datasets, - это этот класс. Как следует из названия, он ожидает, что вы сгенерируете примеры своих наборов данных, и он запишет их в tfrecords за вас.

tensorflow_datasets ожидаем, что вы реализуете класс, наследующий от tfds.core.GeneratorBasedBuilder, и реализуете три метода - _info, _split_generators и _generate_examples.

Метод _info - это то место, где вы собираетесь вернуть метаданные о своем наборе данных. Вот фрагмент, показывающий это для tiny-imagenet.

Самый важный элемент tfds.core.DatasetInfo, возвращенный из _info, - это спецификация функций. Если вы уже знакомы с tfrecords, вы заметите, что он отличается от того, что вы привыкли видеть. Фактически tensorflow_datasets предоставляет несколько эффективных оболочек для создания функций, которые значительно упрощают многие вещи. В этом примере я использую tfds.features.Image, тогда как если бы я вручную создавал tfrecords, мне пришлось бы использовать несколько полей, например, один для хранения необработанных данных, один для формы, еще один для формата изображения и т. д. Кроме того, мне пришлось бы читать изображение из моего скрипта. Как вы увидите позже при реализации функции _ generate_examples, нам не нужно ничего делать из этого.

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

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

Первая строка в этом методе, т.е. extracted_path = dl_manager.extract(dl_manager.download(_URL)), отвечает за его загрузку и извлечение. _URL в данном случае указывает на http://cs231n.stanford.edu/tiny-imagenet-200.zip. Возвращаемое значение, то есть extracted_path, содержит путь к месту, где был извлечен zip. Это было для меня первым источником замешательства и проблем. Когда я звонил download_and_prepare, он показывал мне полосу загрузки, но когда я смотрел на место, это выглядело как

Как показано на изображении выше, по умолчанию путь, по которому загружается и извлекается zip-архив, - ~/tensorflow_datasets/downloads/extracted. Теперь, когда каталог заканчивается расширением .zip, я подумал, что это файл, и извлечение не удается. В итоге потратил 15 минут, пытаясь понять, что не так, и наконец обратил внимание на то, что это каталог.

Но что, если набор данных был разделен между множеством файлов zip / tar / rar / gz?… Не беспокойтесь!, Вы защищены. dl_manager.download() может принимать словарь в качестве аргумента, а возвращаемое значение в этом случае будет словарем, содержащим соответствующие значения. В листинге ниже показан пример для этого варианта использования -

def _split_generators(self, dl_manager):
  # Equivalent to dl_manager.extract(dl_manager.download(urls))
  dl_paths = dl_manager.download_and_extract({
      'foo': 'https://example.com/foo.zip',
      'bar': 'https://example.com/bar.zip',
  })
  dl_paths['foo'], dl_paths['bar']

Я назвал этот метод рабочей лошадкой в ​​своей реализации, потому что именно здесь я читаю файлы метаданных (wnidx.txt, val_annotations.txt и т. Д.) И создаю словари, содержащие список изображений для различных классов, и назначаю классам числовые метки. .

Обратите внимание, что организация данных обучения и проверки в tiny-imagenet отличается, и поэтому у меня есть два разных метода обработки информации, но я объединяю их в одном и том же формате словаря. Вы можете увидеть детали реализации по адресу - https://github.com/ksachdeva/tiny-imagenet-tfds/blob/master/tiny_imagenet/_imagenet.py

Интересный для вас кодовый оператор в _split_generators - это возвращаемое значение, в которое я возвращаю различные генераторы разделения. Код должен быть достаточно ясным, чтобы говорить сам за себя. Единственная деталь, на которую следует обратить внимание, - это клавиша gen_kwargs в tfds.core.SplitGenerator. По сути, все, что вы передадите в качестве значения этого поля, будет передано в качестве аргумента для _generate_examples.

Теперь вы реализуете эту большую часть класса набора данных, и при выдаче download_and_prepare вы получите уведомление об ошибке, в котором говорится, что он не может найти файл checksum. Это был второй шаг, который заставил меня немного поработать, и в основном я тратил больше времени на поиски этой проблемы, чем на реализацию всего пакета.

Вот что происходит. Для каждого загружаемого вами удаленного архивного файла (в данном случае http://cs231n.stanford.edu/tiny-imagenet-200.zip) вы должны предоставить информацию о контрольной сумме. Информация о контрольной сумме должна храниться в файле с расширением .txt. Поскольку мы разрабатываем наш собственный пакет python (т.е. не внутри tensorflow_datasets), вам также необходимо указать tensorflow_datasets, где находится каталог, в котором у вас будет файл, содержащий контрольную сумму. Вот что вам нужно добавить в свой модуль для этого:

checksum_dir = os.path.join(os.path.dirname(__file__),'url_checksums/')
checksum_dir = os.path.normpath(checksum_dir)
tfds.download.add_checksums_dir(checksum_dir)

Другой аспект - каким должно быть имя файла контрольной суммы? После небольшого количества проб и ошибок я решил, что это должно быть то же имя, что и имя вашего класса, но со змеиным корпусом, т.е. для TinyImagenetDataset вы бы назвали свой файл какtiny_imagenet_dataset.txt Эта часть не очень ясна из их руководства, по крайней мере, я не смог ее найти где-то указывал и пришлось разбираться самому.

Тем не менее работа еще не сделана; откуда бы вы взяли саму контрольную сумму? Вычислить вручную? К счастью, здесь поможет фреймворк. Все, что вам нужно сделать, это то, что в первый раз, когда вы вызовете download_and_prepare из теста / примера, который использует ваш класс набора данных, вы должны передать следующий аргумент -

dl_config = tfds.download.DownloadConfig(register_checksums=True)
download_and_prepare(download_config=dl_config))

Теперь вы увидите, что загрузка будет успешной без каких-либо ошибок контрольной суммы, и ваш файл контрольной суммы (в данном случае tiny_imagenet_dataset.txt) будет содержать запись. С этого момента вы можете удалить параметр download_config.

Пора взглянуть на _generate_examples.

Как видно из кода, это функция генератора yielding двух возвращаемых значений. Первый - это ключ, который представляет собой уникальный идентификатор для образца, который будет записан в tfrecords, а второй - это словарь, соответствующий схеме, указанной вами в методе _info. Как я упоминал ранее, вам больше не нужно читать файлы изображений, вы просто указываете путь к файлу изображения, и tensorflow_datasets обязательно прочитает его и вставит в tfrecords

Упаковка вашей реализации (необязательно)

Если вы создаете частный набор данных, скорее всего, это будет просто пакет python в вашем репозитории. Однако, если вы делитесь им в своей организации и у вас есть какой-то репозиторий пакетов Python, вы можете создать файл wheel.

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

Вы можете посмотреть https://github.com/ksachdeva/tiny-imagenet-tfds/blob/master/setup.py в качестве примера того, как это сделать.

Заключительные замечания

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

Надеюсь, вы найдете полезность tensorflow_datasets в своем проекте, и эта статья может помочь вам в его реализации.