TFRecord и tf.Example

Смотрите на TensorFlow.org Запустите в Google Colab Изучайте код на GitHub Скачайте ноутбук

Чтобы эффективно читать данные будет полезно сериализовать ваши данные и держать их в наборе файлов (по 100-200MB каждый) каждый из которых может быть прочитан построчно. Это особенно верно если данные передаются по сети. Также это может быть полезно для кеширования и предобработки данных.

Формат TFRecord это простой формат для хранения последовательности двоичных записей.

Protocol buffers это кросс-платформенная, кросс-языковая библиотека для эффективной сериализации структурированных данных.

Сообщения протокола обычно определяются файлами .proto. Это часто простейший способ понять тип сообщения.

Сообщение tf.Example (или protobuf) гибкий тип сообщений, который преедставляет сопоставление {"string": value}. Он разработан для использования с TensorFlow и используется в высокоуровневых APIs таких как TFX.

Этот урок покажет как создавать, парсить и использовать сообщение tf.Example, а затем сериализовать читать и писать сообщения tf.Example в/из файлов .tfrecord.

Замечание: Хотя эти структуры полезны, они необязательны. Нет необходимости конвертировать существующий код для использования TFRecords если вы не используете tf.data и чтение данных все еще узкое место обучения. См. Производительность конвейера входных данных для советов по производительности датасета.

Setup

import tensorflow as tf

import numpy as np
import IPython.display as display

tf.Example

Типы данных для tf.Example

Фундаментально tf.Example это соответствие {"string": tf.train.Feature}.

Вид сообщений tf.train.Feature допускает один из следующих трех типов (См. файл .proto для справки). Большинство других общих типов может быть сведено к одному из этих трех:

  1. tf.train.BytesList (можно привести следующие типы)

    • string
    • byte
  2. tf.train.FloatList (можно привести следующие типы)

    • float (float32)
    • double (float64)
  3. tf.train.Int64List (можно привести следующие типы)

    • bool
    • enum
    • int32
    • uint32
    • int64
    • uint64

Чтобы преобразовать стандартный тип TensorFlow в tf.Example-совместимыйtf.train.Feature, вы можете использовать приведенные ниже функции. Обратите внимание, что каждая функция принимает на вход скалярное значение и возвращает tf.train.Feature содержащий один из трех вышеприведенных list типов:

# Следующая функция может быть использована чтобы преобразовать значение в тип совместимый с
# с tf.Example.

def _bytes_feature(value):
  """Преобразует string / byte в bytes_list."""
  if isinstance(value, type(tf.constant(0))):
    value = value.numpy() # BytesList не будет распаковывать строку из EagerTensor.
  return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))

def _float_feature(value):
  """Преобразует float / double в float_list."""
  return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))

def _int64_feature(value):
  """Преобразует bool / enum / int / uint в int64_list."""
  return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))

Замечание: Для простоты этот пример использует только скалярные входные данные. Простейший способ обработки нескалярных признаков - использование tf.serialize_tensor для конвертации тензоров в двоичнеые строки. Стоки являются скалярами в тензорфлоу. Используйте tf.parse_tensor для обратной конвертации двоичных сток в тензор.

Ниже приведены несколько примеров того как работают эти функции. Обратите внимание на различные типы ввода и стандартизированные типы вывода. Если входной тип функции не совпадает с одним из приводимых типов указанных выше, функция вызовет исключение (например _int64_feature(1.0) выдаст ошибку поскольку 1.0 это значение с плавающей точкой и должно быть использовано с функцией _float_feature):

print(_bytes_feature(b'test_string'))
print(_bytes_feature(u'test_bytes'.encode('utf-8')))

print(_float_feature(np.exp(1)))

print(_int64_feature(True))
print(_int64_feature(1))
bytes_list {
  value: "test_string"
}

bytes_list {
  value: "test_bytes"
}

float_list {
  value: 2.7182817459106445
}

int64_list {
  value: 1
}

int64_list {
  value: 1
}


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

feature = _float_feature(np.exp(1))

feature.SerializeToString()
b'\x12\x06\n\x04T\xf8-@'

Создание сообщения tf.Example

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

  1. В рамках каждого наблюдения каждое значение должно быть преобразовано в tf.train.Feature содержащее одно из 3 совместимых типов, с использованием одной из вышеприведенных функций.

  2. Вы создаете отображение (словарь) из строки названий признаков в закодированное значение признака выполненное на шаге #1.

  3. Отображение (map) созданное на шаге 2 конвертируется в Features message.

В этом уроке вы создадите датасет с использованием NumPy.

У этого датасета будет 4 признака:

  • булев признак, False или True с равной вероятностью
  • целочисленный признак - равномерно случайно выбранный из [0, 5]
  • строковый признак сгенерированный из табицы строк с использованием целочисленного признака в качестве индекса
  • признак с плавающей точкой из стандартного нормального распределения

Рассмотрим выборку состающую из 10 000 независимых, одинаково распределенных наблюдений из каждого вышеприведенного распределения:

# Число наблюдений в датасете.
n_observations = int(1e4)

# Булев признак, принимающий значения False или True.
feature0 = np.random.choice([False, True], n_observations)

# Целочисленный признак, случайное число от 0 до 4.
feature1 = np.random.randint(0, 5, n_observations)

# Строковый признак
strings = np.array([b'cat', b'dog', b'chicken', b'horse', b'goat'])
feature2 = strings[feature1]

# Признак с плавающей точкой, из стандартного нормального распределения
feature3 = np.random.randn(n_observations)

Каждый из этих признаков может быть приведен к tf.Example-совместимому типу с использованием одного из _bytes_feature, _float_feature, _int64_feature. Вы можете затем создать tf.Example-сообщение из этих закодированных признаков:

def serialize_example(feature0, feature1, feature2, feature3):
  """
  Создает tf.Example-сообщение готовое к записи в файл.
  """
  # Создает словарь отображение имен признаков в tf.Example-совместимые
  # типы данных.
  feature = {
      'feature0': _int64_feature(feature0),
      'feature1': _int64_feature(feature1),
      'feature2': _bytes_feature(feature2),
      'feature3': _float_feature(feature3),
  }

  # Создает Features message с использованием tf.train.Example.

  example_proto = tf.train.Example(features=tf.train.Features(feature=feature))
  return example_proto.SerializeToString()

Возьмем, например, одно наблюдение из датасета, [False, 4, bytes('goat'), 0.9876]. Вы можете создать и распечатать tf.Example-сообщение для этого наблюдения с использованием create_message(). Каждое наблюдение может быть записано в виде Features-сообщения как указано выше. Note that the tf.Example-сообщение это всего лишь обертка вокруг Features-сообщения:

# Это пример наблюдения из набора данных.

example_observation = []

serialized_example = serialize_example(False, 4, b'goat', 0.9876)
serialized_example
b'\nR\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x00\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x04\n\x14\n\x08feature2\x12\x08\n\x06\n\x04goat\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04[\xd3|?'

Для декодирования сообщения используйте метод tf.train.Example.FromString.

example_proto = tf.train.Example.FromString(serialized_example)
example_proto
features {
  feature {
    key: "feature0"
    value {
      int64_list {
        value: 0
      }
    }
  }
  feature {
    key: "feature1"
    value {
      int64_list {
        value: 4
      }
    }
  }
  feature {
    key: "feature2"
    value {
      bytes_list {
        value: "goat"
      }
    }
  }
  feature {
    key: "feature3"
    value {
      float_list {
        value: 0.9876000285148621
      }
    }
  }
}

Детали формата TFRecords

Файл TFRecord содержит последовательность записей. Файл может быть прочитан только линейно.

Каждая запись содержит строку байтов для данных плюс длину данных и CRC32C (32-bit CRC использующий полином Кастаньоли) хеши для проверки целостности.

Каждая запись хранится в следующих форматах:

uint64 length
uint32 masked_crc32_of_length
byte   data[length]
uint32 masked_crc32_of_data

Записи сцеплены друг с другом и организуют файл.. CRCs описаны тут, и маска CRC выглядит так:

masked_crc = ((crc >> 15) | (crc << 17)) + 0xa282ead8ul

Замечание: Не обязательно использовать tf.Example в файлах TFRecord. tf.Example это всего лишь метод сериализации словарей в байтовые строки. Строки текста, закодированные данные изображений, или сериализованные тензоры (с использованием tf.io.serialize_tensor, и tf.io.parse_tensor при загрузке). См. модуль tf.io для дополнительных возможностей.

Файлы TFRecord с использованием tf.data

Модуль tf.data также предоставляет инструменты для чтения и записи данных в TensorFlow.

Запись файла TFRecord

Простейший способ помещения данных в датасет это использование метода from_tensor_slices.

Примененный к массиву он возвращает датасет скаляров:

tf.data.Dataset.from_tensor_slices(feature1)
<TensorSliceDataset shapes: (), types: tf.int64>

Примененный к кортежу массивов он возвращает датасет кортежей:

features_dataset = tf.data.Dataset.from_tensor_slices((feature0, feature1, feature2, feature3))
features_dataset
<TensorSliceDataset shapes: ((), (), (), ()), types: (tf.bool, tf.int64, tf.string, tf.float64)>
# Используйте `take(1)` чтобы взять только один пример из датасета.
for f0,f1,f2,f3 in features_dataset.take(1):
  print(f0)
  print(f1)
  print(f2)
  print(f3)
tf.Tensor(True, shape=(), dtype=bool)
tf.Tensor(3, shape=(), dtype=int64)
tf.Tensor(b'horse', shape=(), dtype=string)
tf.Tensor(-0.6202455251907255, shape=(), dtype=float64)

Используйте метод tf.data.Dataset.map чтобы применить функцию к каждому элементу Dataset.

«Функция отображения должна работать в графовом режиме TensorFlow - она должна принимать и возвращатьtf.Tensors. Не тензорная функция, такая как create_example, может быть заключена вtf.py_function, для совместимости.

Использование tf.py_function требует указания размерности и информации о типе, которая в противном случае недоступна:

def tf_serialize_example(f0,f1,f2,f3):
  tf_string = tf.py_function(
    serialize_example,
    (f0,f1,f2,f3),  # передайте эти аргументы в верхнюю функцию.
    tf.string)      # возвращаемый тип `tf.string`.
  return tf.reshape(tf_string, ()) # Результатом является скаляр
tf_serialize_example(f0,f1,f2,f3)
<tf.Tensor: shape=(), dtype=string, numpy=b'\nS\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x03\n\x15\n\x08feature2\x12\t\n\x07\n\x05horse\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04i\xc8\x1e\xbf'>

Примените эту функцию к каждому элементу датасета:

serialized_features_dataset = features_dataset.map(tf_serialize_example)
serialized_features_dataset
<MapDataset shapes: (), types: tf.string>
def generator():
  for features in features_dataset:
    yield serialize_example(*features)
serialized_features_dataset = tf.data.Dataset.from_generator(
    generator, output_types=tf.string, output_shapes=())
serialized_features_dataset
<FlatMapDataset shapes: (), types: tf.string>

И запишите их в файл TFRecord:

filename = 'test.tfrecord'
writer = tf.data.experimental.TFRecordWriter(filename)
writer.write(serialized_features_dataset)

Чтение TFRecord файла

Вы можете также прочитать TFRecord файл используя класс tf.data.TFRecordDataset.

Больше информации об использовании TFRecord файлов с использованием tf.data может быть найдено тут..

Использование TFRecordDataset-ов может быть полезно для стандартизации входных данных и оптимизации производительности.

filenames = [filename]
raw_dataset = tf.data.TFRecordDataset(filenames)
raw_dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>

На этом этапе датасет содержит сериализованные сообщения tf.train.Example. При их итерации возвращаются скалярные строки тензоров.

Используйте метод .take чтобы показать только первые 10 записей.

Замечание: итерация по tf.data.Dataset работает только при включенном eager execution.

for raw_record in raw_dataset.take(10):
  print(repr(raw_record))
<tf.Tensor: shape=(), dtype=string, numpy=b'\nS\n\x15\n\x08feature2\x12\t\n\x07\n\x05horse\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x03\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04i\xc8\x1e\xbf'>
<tf.Tensor: shape=(), dtype=string, numpy=b'\nR\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04\xeb\xcc\x0c?\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x04\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x00\n\x14\n\x08feature2\x12\x08\n\x06\n\x04goat'>
<tf.Tensor: shape=(), dtype=string, numpy=b'\nQ\n\x13\n\x08feature2\x12\x07\n\x05\n\x03cat\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x00\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04[\xd2\xd8\xbd'>
<tf.Tensor: shape=(), dtype=string, numpy=b'\nR\n\x14\n\x08feature2\x12\x08\n\x06\n\x04goat\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x04\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x00\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04\xce5\x80='>
<tf.Tensor: shape=(), dtype=string, numpy=b'\nQ\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04=`&?\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x00\n\x13\n\x08feature2\x12\x07\n\x05\n\x03cat\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01'>
<tf.Tensor: shape=(), dtype=string, numpy=b'\nQ\n\x13\n\x08feature2\x12\x07\n\x05\n\x03dog\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x01\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04Wwz\xbd'>
<tf.Tensor: shape=(), dtype=string, numpy=b'\nQ\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04\xbau`\xbf\n\x13\n\x08feature2\x12\x07\n\x05\n\x03dog\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x00\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x01'>
<tf.Tensor: shape=(), dtype=string, numpy=b'\nR\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x00\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04\xa0\x95{\xbf\n\x14\n\x08feature2\x12\x08\n\x06\n\x04goat\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x04'>
<tf.Tensor: shape=(), dtype=string, numpy=b'\nU\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04B\xf2\x80\xbe\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x02\n\x17\n\x08feature2\x12\x0b\n\t\n\x07chicken'>
<tf.Tensor: shape=(), dtype=string, numpy=b'\nU\n\x17\n\x08feature2\x12\x0b\n\t\n\x07chicken\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04J\xfc\xe6\xbe\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x02'>

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

# Создайте описание этих признаков
feature_description = {
    'feature0': tf.io.FixedLenFeature([], tf.int64, default_value=0),
    'feature1': tf.io.FixedLenFeature([], tf.int64, default_value=0),
    'feature2': tf.io.FixedLenFeature([], tf.string, default_value=''),
    'feature3': tf.io.FixedLenFeature([], tf.float32, default_value=0.0),
}

def _parse_function(example_proto):
  # Разберите `tf.Example` proto используя вышеприведенный словарь.
  return tf.io.parse_single_example(example_proto, feature_description)

Альтернативно, используйте tf.parse example чтобы распарсить весь пакет за раз. Примените эту функцию к кажому элементу датасета используя метод tf.data.Dataset.map:

parsed_dataset = raw_dataset.map(_parse_function)
parsed_dataset
<MapDataset shapes: {feature0: (), feature1: (), feature2: (), feature3: ()}, types: {feature0: tf.int64, feature1: tf.int64, feature2: tf.string, feature3: tf.float32}>

Используйте eager execution чтобы показывать наблюдения в датасете. В этом наборе данных 10,000 наблюдений, но вы выведете только первые 10. Данные показываются как словарь признаков. Каждое наблюдение это tf.Tensor, и элемент numpyэтого тензора показывает значение признака:

for parsed_record in parsed_dataset.take(10):
  print(repr(parsed_record))
{'feature0': <tf.Tensor: shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: shape=(), dtype=int64, numpy=3>, 'feature2': <tf.Tensor: shape=(), dtype=string, numpy=b'horse'>, 'feature3': <tf.Tensor: shape=(), dtype=float32, numpy=-0.6202455>}
{'feature0': <tf.Tensor: shape=(), dtype=int64, numpy=0>, 'feature1': <tf.Tensor: shape=(), dtype=int64, numpy=4>, 'feature2': <tf.Tensor: shape=(), dtype=string, numpy=b'goat'>, 'feature3': <tf.Tensor: shape=(), dtype=float32, numpy=0.5500018>}
{'feature0': <tf.Tensor: shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: shape=(), dtype=int64, numpy=0>, 'feature2': <tf.Tensor: shape=(), dtype=string, numpy=b'cat'>, 'feature3': <tf.Tensor: shape=(), dtype=float32, numpy=-0.10586997>}
{'feature0': <tf.Tensor: shape=(), dtype=int64, numpy=0>, 'feature1': <tf.Tensor: shape=(), dtype=int64, numpy=4>, 'feature2': <tf.Tensor: shape=(), dtype=string, numpy=b'goat'>, 'feature3': <tf.Tensor: shape=(), dtype=float32, numpy=0.062602624>}
{'feature0': <tf.Tensor: shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: shape=(), dtype=int64, numpy=0>, 'feature2': <tf.Tensor: shape=(), dtype=string, numpy=b'cat'>, 'feature3': <tf.Tensor: shape=(), dtype=float32, numpy=0.649906>}
{'feature0': <tf.Tensor: shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: shape=(), dtype=int64, numpy=1>, 'feature2': <tf.Tensor: shape=(), dtype=string, numpy=b'dog'>, 'feature3': <tf.Tensor: shape=(), dtype=float32, numpy=-0.061148968>}
{'feature0': <tf.Tensor: shape=(), dtype=int64, numpy=0>, 'feature1': <tf.Tensor: shape=(), dtype=int64, numpy=1>, 'feature2': <tf.Tensor: shape=(), dtype=string, numpy=b'dog'>, 'feature3': <tf.Tensor: shape=(), dtype=float32, numpy=-0.87679636>}
{'feature0': <tf.Tensor: shape=(), dtype=int64, numpy=0>, 'feature1': <tf.Tensor: shape=(), dtype=int64, numpy=4>, 'feature2': <tf.Tensor: shape=(), dtype=string, numpy=b'goat'>, 'feature3': <tf.Tensor: shape=(), dtype=float32, numpy=-0.98275185>}
{'feature0': <tf.Tensor: shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: shape=(), dtype=int64, numpy=2>, 'feature2': <tf.Tensor: shape=(), dtype=string, numpy=b'chicken'>, 'feature3': <tf.Tensor: shape=(), dtype=float32, numpy=-0.25184828>}
{'feature0': <tf.Tensor: shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: shape=(), dtype=int64, numpy=2>, 'feature2': <tf.Tensor: shape=(), dtype=string, numpy=b'chicken'>, 'feature3': <tf.Tensor: shape=(), dtype=float32, numpy=-0.45114356>}

Здесь функция tf.parse_example распаковывает поля tf.Example в стандартные тензоры.

TFRecord файлы в Python

Модуль tf.io также содержит чисто Python функции для чтения и записи файлов TFRecord.

Запись TFRecord файла

Далее запишем эти 10 000 наблюдений в файл test.tfrecord. Каждое наблюдения конвертируется в tf.Example-сообщение и затем пишется в файл. Вы можете после проверить, что файл test.tfrecord был создан:

# Запишем наблюдения `tf.Example` в файл.
with tf.io.TFRecordWriter(filename) as writer:
  for i in range(n_observations):
    example = serialize_example(feature0[i], feature1[i], feature2[i], feature3[i])
    writer.write(example)
du -sh {filename}
984K    test.tfrecord

Чтение TFRecord файла

Эти сериализованные тензоры могут быть легко распарсены с использование tf.train.Example.ParseFromString:

filenames = [filename]
raw_dataset = tf.data.TFRecordDataset(filenames)
raw_dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>
for raw_record in raw_dataset.take(1):
  example = tf.train.Example()
  example.ParseFromString(raw_record.numpy())
  print(example)
features {
  feature {
    key: "feature0"
    value {
      int64_list {
        value: 1
      }
    }
  }
  feature {
    key: "feature1"
    value {
      int64_list {
        value: 3
      }
    }
  }
  feature {
    key: "feature2"
    value {
      bytes_list {
        value: "horse"
      }
    }
  }
  feature {
    key: "feature3"
    value {
      float_list {
        value: -0.6202455163002014
      }
    }
  }
}


Упражнение: Чтение и запись данных изображений

Это пример того как читать и писать данные изображений используя TFRecords. Цель этого показать как, от начала до конца, ввести данные (в этом случае изображение) и записать данные в TFRecord файл, затем прочитать файл и показать изображение.

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

Сперва давайте скачаем это изображение кота и покажем это фото строительства моста Williamsburg, NYC.

Получите изображения

cat_in_snow  = tf.keras.utils.get_file('320px-Felis_catus-cat_on_snow.jpg', 'https://storage.googleapis.com/download.tensorflow.org/example_images/320px-Felis_catus-cat_on_snow.jpg')
williamsburg_bridge = tf.keras.utils.get_file('194px-New_East_River_Bridge_from_Brooklyn_det.4a09796u.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/194px-New_East_River_Bridge_from_Brooklyn_det.4a09796u.jpg')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/320px-Felis_catus-cat_on_snow.jpg
24576/17858 [=========================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/194px-New_East_River_Bridge_from_Brooklyn_det.4a09796u.jpg
16384/15477 [===============================] - 0s 0us/step

display.display(display.Image(filename=cat_in_snow))
display.display(display.HTML('Image cc-by: <a "href=https://commons.wikimedia.org/wiki/File:Felis_catus-cat_on_snow.jpg">Von.grzanka</a>'))

jpeg

display.display(display.Image(filename=williamsburg_bridge))
display.display(display.HTML('<a "href=https://commons.wikimedia.org/wiki/File:New_East_River_Bridge_from_Brooklyn_det.4a09796u.jpg">From Wikimedia</a>'))

jpeg

Write the TFRecord file

Как и ранее закодируйте признаки как типы совместимые с tf.Example. Здесь хранится необработанные данные изображения в формате string, так же как и высота, ширина, глубина и произвольный признак label. Последнее используется когда вы пишете файл чтобы различать изображение кота и моста. Используйте 0 изображения кота, и 1 для моста:

image_labels = {
    cat_in_snow : 0,
    williamsburg_bridge : 1,
}
# Это пример использования только изображения кота.
image_string = open(cat_in_snow, 'rb').read()

label = image_labels[cat_in_snow]

# Создайте библиотеку с признаками которые могут быть релевантны.
def image_example(image_string, label):
  image_shape = tf.image.decode_jpeg(image_string).shape

  feature = {
      'height': _int64_feature(image_shape[0]),
      'width': _int64_feature(image_shape[1]),
      'depth': _int64_feature(image_shape[2]),
      'label': _int64_feature(label),
      'image_raw': _bytes_feature(image_string),
  }

  return tf.train.Example(features=tf.train.Features(feature=feature))

for line in str(image_example(image_string, label)).split('\n')[:15]:
  print(line)
print('...')
features {
  feature {
    key: "depth"
    value {
      int64_list {
        value: 3
      }
    }
  }
  feature {
    key: "height"
    value {
      int64_list {
        value: 213
      }
...

Заметьте что все признаки сейчас содержатся в tf.Example-сообщении. Далее функционализируйте вышеприведенный код и запишите пример сообщений в файл с именем images.tfrecords:

# Запишем файлы изображений в `images.tfrecords`.
# Сперва, преобразуем два изображения в `tf.Example`-сообщения.
# Затем запишем их в `.tfrecords` файл.
record_file = 'images.tfrecords'
with tf.io.TFRecordWriter(record_file) as writer:
  for filename, label in image_labels.items():
    image_string = open(filename, 'rb').read()
    tf_example = image_example(image_string, label)
    writer.write(tf_example.SerializeToString())
du -sh {record_file}
36K images.tfrecords

Чтение TFRecord файла

У вас сейчас есть файл images.tfrecords и вы можете проитерировать записи в нем чтобы прочитать то что вы в него записали. Поскольку этот пример содержит только изображение единственное свойство которое вам нужно это необработанная строка изображения. Извлеките ее используя геттеры описанные выше, а именно example.features.feature['image_raw'].bytes_list.value[0]. Вы можете также использовать метки чтобы определить, которая запись является котом, и которая мостом:

raw_image_dataset = tf.data.TFRecordDataset('images.tfrecords')

# Создадим словарь описывающий свойства.
image_feature_description = {
    'height': tf.io.FixedLenFeature([], tf.int64),
    'width': tf.io.FixedLenFeature([], tf.int64),
    'depth': tf.io.FixedLenFeature([], tf.int64),
    'label': tf.io.FixedLenFeature([], tf.int64),
    'image_raw': tf.io.FixedLenFeature([], tf.string),
}

def _parse_image_function(example_proto):
  # Распарсим входной tf.Example proto используя вышесозданный словарь.
  return tf.io.parse_single_example(example_proto, image_feature_description)

parsed_image_dataset = raw_image_dataset.map(_parse_image_function)
parsed_image_dataset
<MapDataset shapes: {depth: (), height: (), image_raw: (), label: (), width: ()}, types: {depth: tf.int64, height: tf.int64, image_raw: tf.string, label: tf.int64, width: tf.int64}>

Восстановим изображение из TFRecord файла:

for image_features in parsed_image_dataset:
  image_raw = image_features['image_raw'].numpy()
  display.display(display.Image(data=image_raw))

jpeg

jpeg