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

from __future__ import absolute_import, division, print_function, unicode_literals

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(False, shape=(), dtype=bool)
tf.Tensor(2, shape=(), dtype=int64)
tf.Tensor(b'chicken', shape=(), dtype=string)
tf.Tensor(0.32220384656954204, 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: id=30, shape=(), dtype=string, numpy=b'\nU\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x00\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x02\n\x17\n\x08feature2\x12\x0b\n\t\n\x07chicken\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04\xe7\xf7\xa4>'>

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

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
<DatasetV1Adapter 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: id=50092, shape=(), dtype=string, numpy=b'\nU\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x00\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x02\n\x17\n\x08feature2\x12\x0b\n\t\n\x07chicken\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04\xe7\xf7\xa4>'>
<tf.Tensor: id=50093, shape=(), dtype=string, numpy=b'\nQ\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x00\n\x13\n\x08feature2\x12\x07\n\x05\n\x03cat\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04\xd1\x04l\xbf'>
<tf.Tensor: id=50094, shape=(), dtype=string, numpy=b'\nQ\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x00\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x01\n\x13\n\x08feature2\x12\x07\n\x05\n\x03dog\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04^P\xea='>
<tf.Tensor: id=50095, shape=(), dtype=string, numpy=b'\nU\n\x17\n\x08feature2\x12\x0b\n\t\n\x07chicken\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04\x99_.>\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x02'>
<tf.Tensor: id=50096, shape=(), dtype=string, numpy=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\xa8y\xdb>'>
<tf.Tensor: id=50097, shape=(), dtype=string, numpy=b'\nQ\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x00\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x01\n\x13\n\x08feature2\x12\x07\n\x05\n\x03dog\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04\x00\t\x9a\xbf'>
<tf.Tensor: id=50098, shape=(), dtype=string, numpy=b'\nQ\n\x13\n\x08feature2\x12\x07\n\x05\n\x03dog\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04L7j\xbe\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x01'>
<tf.Tensor: id=50099, shape=(), dtype=string, numpy=b'\nQ\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x01\n\x13\n\x08feature2\x12\x07\n\x05\n\x03dog\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04Dj\x89\xbf'>
<tf.Tensor: id=50100, shape=(), dtype=string, numpy=b'\nQ\n\x13\n\x08feature2\x12\x07\n\x05\n\x03cat\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04R\x93 ?\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x00'>
<tf.Tensor: id=50101, shape=(), dtype=string, numpy=b'\nQ\n\x13\n\x08feature2\x12\x07\n\x05\n\x03cat\n\x14\n\x08feature3\x12\x08\x12\x06\n\x04Q\x16\x15\xc0\n\x11\n\x08feature0\x12\x05\x1a\x03\n\x01\x01\n\x11\n\x08feature1\x12\x05\x1a\x03\n\x01\x00'>

Эти тензоры может распарсить используя нижеприведенную функцию. Заметьте что 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: id=50133, shape=(), dtype=int64, numpy=0>, 'feature1': <tf.Tensor: id=50134, shape=(), dtype=int64, numpy=2>, 'feature2': <tf.Tensor: id=50135, shape=(), dtype=string, numpy=b'chicken'>, 'feature3': <tf.Tensor: id=50136, shape=(), dtype=float32, numpy=0.32220384>}
{'feature0': <tf.Tensor: id=50137, shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: id=50138, shape=(), dtype=int64, numpy=0>, 'feature2': <tf.Tensor: id=50139, shape=(), dtype=string, numpy=b'cat'>, 'feature3': <tf.Tensor: id=50140, shape=(), dtype=float32, numpy=-0.9219485>}
{'feature0': <tf.Tensor: id=50141, shape=(), dtype=int64, numpy=0>, 'feature1': <tf.Tensor: id=50142, shape=(), dtype=int64, numpy=1>, 'feature2': <tf.Tensor: id=50143, shape=(), dtype=string, numpy=b'dog'>, 'feature3': <tf.Tensor: id=50144, shape=(), dtype=float32, numpy=0.1144111>}
{'feature0': <tf.Tensor: id=50145, shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: id=50146, shape=(), dtype=int64, numpy=2>, 'feature2': <tf.Tensor: id=50147, shape=(), dtype=string, numpy=b'chicken'>, 'feature3': <tf.Tensor: id=50148, shape=(), dtype=float32, numpy=0.17028655>}
{'feature0': <tf.Tensor: id=50149, shape=(), dtype=int64, numpy=0>, 'feature1': <tf.Tensor: id=50150, shape=(), dtype=int64, numpy=4>, 'feature2': <tf.Tensor: id=50151, shape=(), dtype=string, numpy=b'goat'>, 'feature3': <tf.Tensor: id=50152, shape=(), dtype=float32, numpy=0.42866254>}
{'feature0': <tf.Tensor: id=50153, shape=(), dtype=int64, numpy=0>, 'feature1': <tf.Tensor: id=50154, shape=(), dtype=int64, numpy=1>, 'feature2': <tf.Tensor: id=50155, shape=(), dtype=string, numpy=b'dog'>, 'feature3': <tf.Tensor: id=50156, shape=(), dtype=float32, numpy=-1.2033997>}
{'feature0': <tf.Tensor: id=50157, shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: id=50158, shape=(), dtype=int64, numpy=1>, 'feature2': <tf.Tensor: id=50159, shape=(), dtype=string, numpy=b'dog'>, 'feature3': <tf.Tensor: id=50160, shape=(), dtype=float32, numpy=-0.22872657>}
{'feature0': <tf.Tensor: id=50161, shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: id=50162, shape=(), dtype=int64, numpy=1>, 'feature2': <tf.Tensor: id=50163, shape=(), dtype=string, numpy=b'dog'>, 'feature3': <tf.Tensor: id=50164, shape=(), dtype=float32, numpy=-1.0735555>}
{'feature0': <tf.Tensor: id=50165, shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: id=50166, shape=(), dtype=int64, numpy=0>, 'feature2': <tf.Tensor: id=50167, shape=(), dtype=string, numpy=b'cat'>, 'feature3': <tf.Tensor: id=50168, shape=(), dtype=float32, numpy=0.6272479>}
{'feature0': <tf.Tensor: id=50169, shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: id=50170, shape=(), dtype=int64, numpy=0>, 'feature2': <tf.Tensor: id=50171, shape=(), dtype=string, numpy=b'cat'>, 'feature3': <tf.Tensor: id=50172, shape=(), dtype=float32, numpy=-2.329487>}

Здесь функция 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: 0
      }
    }
  }
  feature {
    key: "feature1"
    value {
      int64_list {
        value: 2
      }
    }
  }
  feature {
    key: "feature2"
    value {
      bytes_list {
        value: "chicken"
      }
    }
  }
  feature {
    key: "feature3"
    value {
      float_list {
        value: 0.3222038447856903
      }
    }
  }
}

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

Это пример того как читать и писать данные изображений используя 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