Объяснение TFRecords

Работа с TFRecords с введением в Protobufs

В этом посте я собираюсь обсудить Tensorflow Records. Tensorflow рекомендует хранить и читать данные в формате tfRecords. Он внутренне использует буферы протокола для сериализации / десериализации данных и хранения их в байтах, поскольку требуется меньше места для хранения большого количества данных, а также для их передачи.

Protobufs работают с предопределенными схемами, в отличие от JSON и XML. tfRecords имеет такие схемы, уже доступные в Protofiles, а также скомпилированный код для многих поддерживаемых языков. Я собираюсь импортировать такой скомпилированный код в Python и использовать его в своих данных.

Этот пост требует базового понимания Protobufs, поскольку они являются строительными блоками TFRecords. Я также писал ранее о работе с Protobufs. В этом посте я начну с рассмотрения предопределенных протоколов для tfRecords, используя их в Python с некоторыми рекомендациями и запретами. Затем у меня есть две демонстрации, в которых я буду создавать tfRecords из существующих наборов данных.

Предопределенные протоколы

Это фрагмент кода, взятый из feature.proto от Tensorflow. Полный файл можно найти здесь. Он содержит 3 типа прототипов сообщений: BytesList, FloatList и Int64List. Их можно использовать индивидуально для создания списка целых чисел, чисел с плавающей запятой или байтов, который можно сериализовать в двоичный файл. Я поговорил об этом подробнее в следующем разделе.

В следующем фрагменте перечислены еще 3 протокола из того же файла выше. Он имеет тип сообщения Feature, в котором используются BytesList, FloatList и Int64List как одно из полей. Поле Oneof используется, когда вы хотите установить только одно поле из всех полей за раз. Поля в oneof имеют общую память. Если вы установите более одного поля в oneof, тогда он автоматически очистит другие поля. Подробнее об этом можно прочитать здесь. Затем создается тип сообщения Функции, представляющий собой карту строки и созданного выше типа функции. Затем последним является Пример, в котором в качестве поля используются функции.

Скомпилированный код этих протоколов даст нам tf.train.Int64List, tf.train.FloatList, tf.train.BytesList, tf.train.Feature , tf.train.Features и tf.train.Example соответственно. Теперь давайте посмотрим на код Python, в котором я использую скомпилированные версии этих протоколов.

Использование скомпилированных Protos на Python

Этот код показывает использование основных протоколов, определенных выше. Я использовал их для создания экземпляров tf.train.Int64List, tf.train.FloatList и tf.train.BytesList путем передачи повторяющихся значений. Команда type показывает, что они импортированы из файла tensorflow.core.example.feature_pb2, который является файлом, созданным после компиляции протокола.

Здесь я создаю экземпляр tf.train.Feature, передавая все списки, созданные выше. Но когда мы его печатаем, он показывает только список с плавающей запятой, список int или список байтов не печатается. Причина в том, что tf.train.Feature содержит их как одно из полей. Таким образом, будет только тот список, который был добавлен последним, остальные будут удалены.

Это означает, что Tensorflow говорит, что мы не должны заполнять несколько полей при создании экземпляра tf.train.Feature. Использование oneof в протофиле в точности соответствует требованию. Но я заставил себя сделать это, даже зная все это. Тогда как мне на самом деле предотвратить повторение этого снова?

Ну вот как бы я это сделал. Я создаю оболочку для всех из них и вызываю внутри них tf.train.Feature, который заполняет только одно из этих полей. Мне нужно вызвать эти методы-оболочки для создания экземпляров tf.train.Feature. Это явно мешает мне создать tf.train.Feature, в котором все они будут заполнены сразу.

Но я столкнулся с другой проблемой: как создать tf.train.Feature со списком элементов (строка 18 кода). Передача списка элементов оболочке вызывает TypeError как tf.train.Feature принимает только список целых чисел, чисел с плавающей запятой или байтов, но не список списков. Если я передаю список самому методу-оболочке, он попытается создать элемент tf.train.Feature со списком, содержащим список целых чисел, чисел с плавающей запятой или байтов. Такова ошибка. Этого можно избежать, если у меня есть еще один метод, который будет принимать список вместо элемента.

Это снова оболочка, но она берет список и создает tf.Train.Feature со списком элементов. Это можно повторить со всеми предыдущими методами оболочки.

Затем я помещаю созданные выше экземпляры tf.train.Feature на карту tf.train.Features и создаю экземпляр tf.train.Example из карты. Этот экземпляр tf.train.Example я буду сериализовать в двоичный файл.

tfRecord из tf.train.Example

Итак, все основы готовы. Теперь мы знаем, как создать tf.train.Example из группы элементов типа integer, float или byte. Пора сделать TFRecord. TFRecord - это когда последовательность таких записей сериализуется в двоичную форму. Двоичный формат требует меньше памяти для хранения по сравнению с любыми другими форматами данных.

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

  • Для каждого значения в записи создайте экземпляр tf.train.Feature.
  • Добавьте каждый экземпляр записи tf.train.Feature на карту в качестве значения с именем атрибута (имя столбца) в качестве ключа. Будет создана карта tf.train.Features для записи набора данных.
  • Создайте экземпляр tf.train.Example, используя созданную выше карту tf.train.Features. В этом процессе количество созданных экземпляров tf.train.Example будет таким же, как количество записей в наборе данных.

У меня есть две демонстрации, чтобы показать этот подход. Один из них будет в файле csv, а другой - в наборе данных изображения. Но прежде чем углубиться в это, нужно прояснить еще несколько вещей.

  1. Мне нужен метод для создания соответствующего tf.train.Feature в зависимости от типа значения в записи. Итак, здесь я написал один метод, который можно использовать в обоих примерах.
  • Здесь метод берет строки из набора данных, а также его типы столбцов и имена столбцов в виде списков.
  • Затем он выполняет итерацию по значениям строки, проверяет тип и затем создает соответствующий элемент tf.train.Feature.
  • Затем, как объяснено выше, добавьте экземпляр tf.train.Feature в карту tf.train.Features в качестве значения против имени столбца в качестве ключа. Затем карта используется для создания экземпляра tf.train.Example.

2. Мне нужен другой метод для анализа tfrecords при чтении сериализованных форматов. У каждого столбца есть собственный механизм синтаксического анализа в зависимости от его типа.

  • Это показывает механизм анализа каждого атрибута при чтении из tfrecord.
  • Он принимает карту имен столбцов и типов столбцов в виде пар ключ-значение.
  • Затем он использует конфигурацию tf.io.FixedLenFeature для анализа входных функций фиксированной длины с соответствующими типами значений. Это заполняет другую карту с именами столбцов в качестве ключей и синтаксических анализаторов, созданных в качестве значений.

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

Пример 1. Набор данных CSV в tfrecords

Выбранный набор данных - это csv-файл с автомобилями, доступный здесь. Он содержит 8 столбцов, столбец 1 содержит строки, столбцы 3 и 8 содержат целые числа, а другие содержат числа с плавающей запятой.

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

Затем я перебираю строки набора данных, чтобы получить экземпляры tf.train.Example для каждого из них, вызывая первый метод, описанный выше. Затем я сериализую их и записываю в файл tfrecords. Давайте посмотрим на результат ниже. Выходные данные до сериализации - это просто карта пар ключ и значение. После сериализации все данные в двоичном формате преобразуются в байты, и, как я вижу, сериализованный вывод является компактным и экономит много места по сравнению с другими форматами.

Когда мне нужны данные обратно, мне нужно снова прочитать файл tfrecords, используя второй метод, определенный выше.

Здесь я читаю файл tfrecords, сопоставляя его с методом parse_example, где он фактически анализирует каждую запись из файла tfrecords, используя описание функции, определенное во втором методе.

Пример 2: изображения в TFRecords для обнаружения объектов

Для обнаружения объектов у меня есть набор изображений и их соответствующие ограничивающие рамки в файлах xml, хранящихся на диске.

Вот как я разбираю XML-файл. Python имеет библиотеку xml, ElementTree которой можно использовать здесь для извлечения имени файла, xmin, xmax, ymin и ymax ограничивающей рамки и меток (Healthy или Unhealthy).

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

Я открываю изображение, декодирую его с помощью tf.image API и получаю сведения о его ограничивающей рамке из xml. Затем я создаю ряд всех входных данных, необходимых для обнаружения объекта, и создаю tf.train.Example, отправляя их первому методу. Затем экземпляр Example сериализуется и записывается в файл tfrecords.

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

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

  1. Чтобы узнать больше о TFRecords, посетите TFRecords.
  2. Чтобы узнать больше о буферах протокола, посетите Protobufs.