Dataset API — это новый модуль, представленный в TensorFlow 1.3. Он в основном служит для чтения данных и построения конвейера входных данных.
Раньше в TensorFlow обычно было два способа чтения данных:
- Используйте заполнитель для чтения данных в памяти
- Используйте очередь для чтения данных с жесткого диска (для этого метода вы можете обратиться к моей предыдущей статье:Десять диаграмм подробно объясняют механизм чтения данных тензорного потока)
API набора данных поддерживает одновременное чтение из памяти и жесткого диска, что синтаксически сравнивается с двумя предыдущими методами.Более лаконично и понятно. Кроме того, если вы хотите использовать новый режим Eager TensorFlow, вы должны использовать API набора данных для чтения данных.
В этой статье будет подробно рассказано об использовании API набора данных (в том числе в режиме без ожидания и в режиме с нетерпением).
Импорт API набора данных
В TensorFlow 1.3 API набора данных находится в пакете contrib:
tf.contrib.data.Dataset
В TensorFlow 1.4 Dataset API был удален из пакета contrib и стал частью основного API:
tf.data.Dataset
В следующем примере кода в качестве примера используется версия TensorFlow 1.4.Если вы используете TensorFlow 1.3, вам необходимо внести простые изменения (то есть добавить вклад).
Основные понятия: набор данных и итератор
Давайте разберемся с API набора данных из базовых классов. Обратитесь к диаграмме классов в API набора данных, официально предоставленном Google:
Вначале нам нужно сосредоточиться только на двух наиболее важных базовых классах:Наборы данных и итераторы.
Набор данных можно рассматривать как упорядоченный список «элементов» одного типа.. В реальном использовании один «элемент» может быть вектором, строкой, изображением или даже кортежем или словарем.
Начни с самого простого,Каждый элемент набора данных представляет собой число, например:
import tensorflow as tf
import numpy as np
dataset = tf.data.Dataset.from_tensor_slices(np.array([1.0, 2.0, 3.0, 4.0, 5.0]))
Таким образом, мы создаем набор данных с 5 элементами: 1.0, 2.0, 3.0, 4.0, 5.0.
Как вынуть элементы в этом наборе данных?Способ сделать это — создать экземпляр Iterator из набора данных, а затем выполнить итерацию по Iterator.
В неактивном режиме, метод чтения элементов в приведенном выше наборе данных:
iterator = dataset.make_one_shot_iterator()
one_element = iterator.get_next()
with tf.Session() as sess:
for i in range(5):
print(sess.run(one_element))
Соответствующий вывод должен быть от 1.0 до 5.0. Оператор iterator = dataset.make_one_shot_iterator() создает экземпляр Iterator из набора данных, который является «однократным итератором», то есть его можно прочитать с начала до конца только один раз. one_element = iterator.get_next() означает взять элемент из итератора.Поскольку это не режим Eager, one_element — это просто тензор, а не фактическое значение. Только после вызова sess.run(one_element) можно извлечь значение.
Если элемент в наборе данных был прочитан, а затем попробовать sess.run(one_element), будет выдано исключение tf.errors.OutOfRangeError,Это поведение согласуется с поведением использования очереди для чтения данных.В реальной программе это исключение может быть перехвачено во внешнем мире, чтобы определить, были ли прочитаны данные, обратитесь к следующему коду:
dataset = tf.data.Dataset.from_tensor_slices(np.array([1.0, 2.0, 3.0, 4.0, 5.0]))
iterator = dataset.make_one_shot_iterator()
one_element = iterator.get_next()
with tf.Session() as sess:
try:
while True:
print(sess.run(one_element))
except tf.errors.OutOfRangeError:
print("end!")
В шаблоне Eager итераторы создаются по-другому.Это создать итератор и выполнить итерацию непосредственно в форме tfe.Iterator (набор данных). Значение можно получить непосредственно при итерации, нет необходимости использовать sess.run():
import tensorflow.contrib.eager as tfe
tfe.enable_eager_execution()
dataset = tf.data.Dataset.from_tensor_slices(np.array([1.0, 2.0, 3.0, 4.0, 5.0]))
for one_element in tfe.Iterator(dataset):
print(one_element)
Создание более сложных наборов данных из памяти
Ранее мы создали простейший набор данных с помощью tf.data.Dataset.from_tensor_slices:
dataset = tf.data.Dataset.from_tensor_slices(np.array([1.0, 2.0, 3.0, 4.0, 5.0]))
На самом деле, функция tf.data.Dataset.from_tensor_slices больше, чем это, его реальная функцияРазделите первое измерение входящего тензора, чтобы создать соответствующий набор данных.
Например:
dataset = tf.data.Dataset.from_tensor_slices(np.random.uniform(size=(5, 2)))
Входящее значение представляет собой матрицу формы (5, 2), tf.data.Dataset.from_tensor_slices будет нарезать первое измерение своей формы, а один из результирующих наборов данных содержит 5 элементов, каждый Форма каждого элемента (2 , ),т. е. каждый элемент является строкой матрицы.
В реальном использовании мы также можем захотеть, чтобы каждый элемент в наборе данных имел более сложную форму, например, чтобы каждый элемент был кортежем в Python или словарем в Python.Например, в задаче распознавания изображений элемент может иметь форму {"image": image_tensor, "label": label_tensor}, что более удобно для обработки.
tf.data.Dataset.from_tensor_slices также поддерживает создание такого набора данных, например, мы можем сделать каждый элемент словарем:
dataset = tf.data.Dataset.from_tensor_slices(
{
"a": np.array([1.0, 2.0, 3.0, 4.0, 5.0]),
"b": np.random.uniform(size=(5, 2))
}
)
В это время функция разделит значение в «a» и значение в «b» соответственно, и один элемент в конечном наборе данных будет иметь вид {«a»: 1,0, «b»: [0,9, 0,1 ]}.
Также можно использовать tf.data.Dataset.from_tensor_slices для создания набора данных, где каждый элемент является кортежем:
dataset = tf.data.Dataset.from_tensor_slices(
(np.array([1.0, 2.0, 3.0, 4.0, 5.0]), np.random.uniform(size=(5, 2)))
)
Преобразование элементов в наборе данных: Преобразование
Набор данных поддерживает особый вид операций: преобразование. Набор данных становится новым набором данных посредством преобразования.Обычно мы можем выполнить ряд операций, таких как преобразование данных, скремблирование, формирование пакетов и генерация эпох с помощью преобразования.
Обычно используемые трансформации:
- map
- batch
- shuffle
- repeat
Ниже они будут представлены отдельно.
(1) карта
карта получитьфункция, каждый элемент в наборе данных будет использоваться в качестве входных данных этой функции, а возвращаемое значение функции будет использоваться в качестве нового набора данных, Например, мы можем добавить 1 к значению каждого элемента в наборе данных:
dataset = tf.data.Dataset.from_tensor_slices(np.array([1.0, 2.0, 3.0, 4.0, 5.0]))
dataset = dataset.map(lambda x: x + 1) # 2.0, 3.0, 4.0, 5.0, 6.0
(2) партия
Пакет — это объединение нескольких элементов в пакет.Следующая программа объединяет каждый элемент в наборе данных в пакет размером 32:
dataset = dataset.batch(32)
(3) перемешать
Функция shuffle заключается в перемешивании элементов в наборе данных, у него есть параметр buffersize, который указывает размер буфера, используемого при перемешивании:
dataset = dataset.shuffle(buffer_size=10000)
(4) повторить
Функция повтора состоит в многократном повторении всей последовательности. В основном она используется для обработки эпох в машинном обучении. Предполагая, что исходные данные представляют собой одну эпоху, можно использовать повтор (5), чтобы превратить ее в эпохи 5:
dataset = dataset.repeat(5)
Если Repeat() вызывается напрямую, сгенерированная последовательность будет бесконечно повторяться без завершения, поэтому tf.errors.OutOfRangeError не будет сгенерирован:
dataset = dataset.repeat()
Пример: чтение образа диска и соответствующей метки
На этом этапе мы можем рассмотреть простой, но также очень распространенный пример.: Считайте изображение на диске и соответствующую метку изображения и зашифруйте их, чтобы сформировать обучающую выборку размером batch_size=32. Повторяйте в течение 10 эпох во время тренировки.
Соответствующая программа (измененная из официального образца программы):
# 函数的功能时将filename对应的图片文件读进来,并缩放到统一的大小
def _parse_function(filename, label):
image_string = tf.read_file(filename)
image_decoded = tf.image.decode_image(image_string)
image_resized = tf.image.resize_images(image_decoded, [28, 28])
return image_resized, label
# 图片文件的列表
filenames = tf.constant(["/var/data/image1.jpg", "/var/data/image2.jpg", ...])
# label[i]就是图片filenames[i]的label
labels = tf.constant([0, 37, ...])
# 此时dataset中的一个元素是(filename, label)
dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
# 此时dataset中的一个元素是(image_resized, label)
dataset = dataset.map(_parse_function)
# 此时dataset中的一个元素是(image_resized_batch, label_batch)
dataset = dataset.shuffle(buffersize=1000).batch(32).repeat(10)
В ходе этого процесса набор данных претерпевает три преобразования:
- После запуска dataset = tf.data.Dataset.from_tensor_slices((имена файлов, метки)), одним элементом набора данных будет (имя файла, метка). имя_файла — это имя файла изображения, а метка — это метка, соответствующая изображению.
- После этого через карту считывается изображение, соответствующее имени файла, и масштабируется до размера 28x28. На данный момент элемент в наборе данных (image_resized, label)
- Наконец, функция dataset.shuffle(buffersize=1000).batch(32).repeat(10) состоит в том, чтобы перемешать изображения в пакеты размером 32 в каждую эпоху и повторить 10 раз.Наконец, один элемент в наборе данных (image_resized_batch, label_batch), форма image_resized_batch (32, 28, 28, 3) и форма label_batch (32,), тогда мы можем использовать эти два тензора для построения модели. .
Другие методы создания набора данных....
В дополнение к tf.data.Dataset.from_tensor_slices API набора данных в настоящее время предоставляет три других способа создания набора данных:
- tf.data.TextLineDataset(): ввод этой функции — список файлов, а вывод — набор данных. Каждый элемент в наборе данных соответствует строке в файле.Вы можете использовать эту функцию для чтения файла CSV.
- tf.data.FixedLengthRecordDataset(): ввод этой функции представляет собой список файлов и байты записи, после чего каждый элемент набора данных является содержимым фиксированного количества байтов байтов записи в файле.Обычно используется для чтения файлов, сохраненных в двоичной форме, таких как набор данных CIFAR10.
- tf.data.TFRecordDataset(): как следует из названия, эта функция используется для чтения файлов TFRecord, и каждый элемент в наборе данных является TFExample.
Подробное описание их использования см. в документации:Module: tf.data
Другие типы итераторов....
В режиме без Eager самый простой способ создать итератор — это создать одноразовый итератор с помощью dataset.make_one_shot_iterator(). В дополнение к этому одноразовому итератору есть еще три сложных итератора, а именно:
- initializable iterator
- reinitializable iterator
- feedable iterator
инициализируемый итератор должен быть инициализирован с помощью sess.run() перед использованием. Используя инициализируемый итератор, заполнитель можно заменить на итератор, что позволяет нам быстро определить новый итератор с помощью параметров.Простой пример использования инициализируемого итератора:
limit = tf.placeholder(dtype=tf.int32, shape=[])
dataset = tf.data.Dataset.from_tensor_slices(tf.range(start=0, limit=limit))
iterator = dataset.make_initializable_iterator()
next_element = iterator.get_next()
with tf.Session() as sess:
sess.run(iterator.initializer, feed_dict={limit: 10})
for i in range(10):
value = sess.run(next_element)
assert i == value
Предел в настоящее время эквивалентен «параметру», который указывает «верхний предел» числа в наборе данных.
Инициализируемый итератор имеет еще одну функцию: чтение больших массивов.
При использовании tf.data.Dataset.from_tensor_slices(array) на самом деле происходит то, что массив сохраняется в графе вычислений как tf.constants. При очень большом массиве граф вычислений станет очень большим, что принесет неудобства при передаче и хранении. В настоящее время мы можем заменить массив здесь заполнителем и использовать инициализируемый итератор для передачи массива только при необходимости, чтобы избежать сохранения большого массива в графе.Пример кода (из официальной процедуры) :
# 从硬盘中读入两个Numpy数组
with np.load("/var/data/training_data.npy") as data:
features = data["features"]
labels = data["labels"]
features_placeholder = tf.placeholder(features.dtype, features.shape)
labels_placeholder = tf.placeholder(labels.dtype, labels.shape)
dataset = tf.data.Dataset.from_tensor_slices((features_placeholder, labels_placeholder))
iterator = dataset.make_initializable_iterator()
sess.run(iterator.initializer, feed_dict={features_placeholder: features,
labels_placeholder: labels})
Повторно инициализируемый итератор и подаваемый итератор более сложны и используются реже, чем инициализируемый итератор.Если вы хотите понять их функции, вы можете обратиться кОфициальное введение, и здесь повторяться не будем.
Суммировать
В этой статье в основном представлена базовая архитектура API набора данных: класс набора данных и класс Iterator, а также их основные методы использования.
В режиме без стремления элемент, прочитанный в наборе данных, обычно соответствует пакетному тензору, и мы можем использовать этот тензор для построения модели в графе расчета.
В режиме Eager набор данных устанавливает итератор по-другому.В это время считываемые данные представляют собой тензор со значением, что удобно для отладки.
Поскольку API набора данных совместим с обоими режимами, он должен стать основным способом чтения данных TensorFlow в будущем. Для дальнейшего ознакомления с Dataset API вы можете обратиться к следующим материалам:
- Importing Data: Официальное руководство
- Module: tf.data: Документация по API
- Introduction to TensorFlow Datasets and Estimators: Как объединить набор данных и оценщик