In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
|
|
|
Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует официальной документации на английском языке. Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в tensorflow/docs репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на docs-ru@tensorflow.org list.
Чтобы эффективно читать данные будет полезно сериализовать ваши данные и держать их в наборе файлов (по 100-200MB каждый) каждый из которых может быть прочитан построчно. Это особенно верно если данные передаются по сети. Также это может быть полезно для кеширования и предобработки данных.
Формат TFRecord это простой формат для хранения последовательности двоичных записей.
Protocol buffers это кросс-платформенная, кросс-языковая библиотека для эффективной сериализации структурированных данных.
Сообщения протокола обычно определяются файлами .proto
. Это часто простейший способ понять тип сообщения.
Сообщение tf.Example
(или protobuf) гибкий тип сообщений, который преедставляет сопоставление {"string": value}
. Он разработан для использования с TensorFlow и используется в высокоуровневых APIs таких как TFX.
Этот урок покажет как создавать, парсить и использовать сообщение tf.Example
, а затем сериализовать читать и писать сообщения tf.Example
в/из файлов .tfrecord
.
Замечание: Хотя эти структуры полезны, они необязательны. Нет необходимости конвертировать существующий код для использования TFRecords если вы не используете tf.data
и чтение данных все еще узкое место обучения. См. Производительность конвейера входных данных для советов по производительности датасета.
In [ ]:
import tensorflow as tf
import numpy as np
import IPython.display as display
Фундаментально tf.Example
это соответствие {"string": tf.train.Feature}
.
Вид сообщений tf.train.Feature
допускает один из следующих трех типов (См. файл .proto
для справки). Большинство других общих типов может быть сведено к одному из этих трех:
tf.train.BytesList
(можно привести следующие типы)
string
byte
tf.train.FloatList
(можно привести следующие типы)
float
(float32
)double
(float64
)tf.train.Int64List
(можно привести следующие типы)
bool
enum
int32
uint32
int64
uint64
Чтобы преобразовать стандартный тип TensorFlow в tf.Example
-совместимыйtf.train.Feature
, вы можете использовать приведенные ниже функции. Обратите внимание, что каждая функция принимает на вход скалярное значение и возвращает tf.train.Feature
содержащий один из трех вышеприведенных list
типов:
In [ ]:
# Следующая функция может быть использована чтобы преобразовать значение в тип совместимый с
# с 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
):
In [ ]:
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))
Все proto сообщения могут быть сериализованы в двоичную строку с использованием метода .SerializeToString
:
In [ ]:
feature = _float_feature(np.exp(1))
feature.SerializeToString()
Допустим вы хотите создать сообщение tf.Example
из существующих данных. На практике данные могут прийти откуда угодно, но процедура создания сообщения tf.Example
из одного наблюдения будет той же:
В рамках каждого наблюдения каждое значение должно быть преобразовано в tf.train.Feature
содержащее одно из 3 совместимых типов, с использованием одной из вышеприведенных функций.
Вы создаете отображение (словарь) из строки названий признаков в закодированное значение признака выполненное на шаге #1.
Отображение (map) созданное на шаге 2 конвертируется в Features
message.
В этом уроке вы создадите датасет с использованием NumPy.
У этого датасета будет 4 признака:
False
или True
с равной вероятностью[0, 5]
Рассмотрим выборку состающую из 10 000 независимых, одинаково распределенных наблюдений из каждого вышеприведенного распределения:
In [ ]:
# Число наблюдений в датасете.
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
-сообщение из этих закодированных признаков:
In [ ]:
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
-сообщения:
In [ ]:
# Это пример наблюдения из набора данных.
example_observation = []
serialized_example = serialize_example(False, 4, b'goat', 0.9876)
serialized_example
Для декодирования сообщения используйте метод tf.train.Example.FromString
.
In [ ]:
example_proto = tf.train.Example.FromString(serialized_example)
example_proto
Файл 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
для дополнительных возможностей.
Модуль tf.data
также предоставляет инструменты для чтения и записи данных в TensorFlow.
In [ ]:
tf.data.Dataset.from_tensor_slices(feature1)
Примененный к кортежу массивов он возвращает датасет кортежей:
In [ ]:
features_dataset = tf.data.Dataset.from_tensor_slices((feature0, feature1, feature2, feature3))
features_dataset
In [ ]:
# Используйте `take(1)` чтобы взять только один пример из датасета.
for f0,f1,f2,f3 in features_dataset.take(1):
print(f0)
print(f1)
print(f2)
print(f3)
Используйте метод tf.data.Dataset.map
чтобы применить функцию к каждому элементу Dataset
.
«Функция отображения должна работать в графовом режиме TensorFlow - она должна принимать и возвращатьtf.Tensors
. Не тензорная функция, такая как create_example
, может быть заключена вtf.py_function
, для совместимости.
Использование tf.py_function
требует указания размерности и информации о типе, которая в противном случае недоступна:
In [ ]:
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, ()) # Результатом является скаляр
In [ ]:
tf_serialize_example(f0,f1,f2,f3)
Примените эту функцию к каждому элементу датасета:
In [ ]:
serialized_features_dataset = features_dataset.map(tf_serialize_example)
serialized_features_dataset
In [ ]:
def generator():
for features in features_dataset:
yield serialize_example(*features)
In [ ]:
serialized_features_dataset = tf.data.Dataset.from_generator(
generator, output_types=tf.string, output_shapes=())
In [ ]:
serialized_features_dataset
И запишите их в файл TFRecord:
In [ ]:
filename = 'test.tfrecord'
writer = tf.data.experimental.TFRecordWriter(filename)
writer.write(serialized_features_dataset)
Вы можете также прочитать TFRecord файл используя класс tf.data.TFRecordDataset
.
Больше информации об использовании TFRecord файлов с использованием tf.data
может быть найдено тут..
Использование TFRecordDataset
-ов может быть полезно для стандартизации входных данных и оптимизации производительности.
In [ ]:
filenames = [filename]
raw_dataset = tf.data.TFRecordDataset(filenames)
raw_dataset
На этом этапе датасет содержит сериализованные сообщения tf.train.Example
. При их итерации возвращаются скалярные строки тензоров.
Используйте метод .take
чтобы показать только первые 10 записей.
Замечание: итерация по tf.data.Dataset
работает только при включенном eager execution.
In [ ]:
for raw_record in raw_dataset.take(10):
print(repr(raw_record))
Эти тензоры может распарсить используя нижеприведенную функцию. Заметьте что feature_description
обязателен тут поскольку датасеты используют графовое исполнение и нуждаются в этом описании для построения своей размерностной и типовой сигнатуры:
In [ ]:
# Создайте описание этих признаков
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
:
In [ ]:
parsed_dataset = raw_dataset.map(_parse_function)
parsed_dataset
Используйте eager execution чтобы показывать наблюдения в датасете. В этом наборе данных 10,000 наблюдений, но вы выведете только первые 10. Данные показываются как словарь признаков. Каждое наблюдение это tf.Tensor
, и элемент numpy
этого тензора показывает значение признака:
In [ ]:
for parsed_record in parsed_dataset.take(10):
print(repr(parsed_record))
Здесь функция tf.parse_example
распаковывает поля tf.Example
в стандартные тензоры.
Модуль tf.io
также содержит чисто Python функции для чтения и записи файлов TFRecord.
Далее запишем эти 10 000 наблюдений в файл test.tfrecord
. Каждое наблюдения конвертируется в tf.Example
-сообщение и затем пишется в файл. Вы можете после проверить, что файл test.tfrecord
был создан:
In [ ]:
# Запишем наблюдения `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)
In [ ]:
!du -sh {filename}
In [ ]:
filenames = [filename]
raw_dataset = tf.data.TFRecordDataset(filenames)
raw_dataset
In [ ]:
for raw_record in raw_dataset.take(1):
example = tf.train.Example()
example.ParseFromString(raw_record.numpy())
print(example)
Это пример того как читать и писать данные изображений используя TFRecords. Цель этого показать как, от начала до конца, ввести данные (в этом случае изображение) и записать данные в TFRecord файл, затем прочитать файл и показать изображение.
Это будет полезно если, например, вы хотите использовать несколько моделей на одних и тех же входных данных. Вместо хранения сырых данных изображений, они могут быть предобработанны в формат TFRecords, и затем могут быть использованы во всех дальнейших обработках и моделированиях.
Сперва давайте скачаем это изображение кота и покажем это фото строительства моста Williamsburg, NYC.
In [ ]:
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')
In [ ]:
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>'))
In [ ]:
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>'))
Как и ранее закодируйте признаки как типы совместимые с tf.Example
. Здесь хранится необработанные данные изображения в формате string, так же как и высота, ширина, глубина и произвольный признак label
. Последнее используется когда вы пишете файл чтобы различать изображение кота и моста. Используйте 0
изображения кота, и 1
для моста:
In [ ]:
image_labels = {
cat_in_snow : 0,
williamsburg_bridge : 1,
}
In [ ]:
# Это пример использования только изображения кота.
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('...')
Заметьте что все признаки сейчас содержатся в tf.Example
-сообщении. Далее функционализируйте вышеприведенный код и запишите пример сообщений в файл с именем images.tfrecords
:
In [ ]:
# Запишем файлы изображений в `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())
In [ ]:
!du -sh {record_file}
У вас сейчас есть файл images.tfrecords
и вы можете проитерировать записи в нем чтобы прочитать то что вы в него записали. Поскольку этот пример содержит только изображение единственное свойство которое вам нужно это необработанная строка изображения. Извлеките ее используя геттеры описанные выше, а именно example.features.feature['image_raw'].bytes_list.value[0]
. Вы можете также использовать метки чтобы определить, которая запись является котом, и которая мостом:
In [ ]:
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
Восстановим изображение из TFRecord файла:
In [ ]:
for image_features in parsed_image_dataset:
image_raw = image_features['image_raw'].numpy()
display.display(display.Image(data=image_raw))