Новый метод чтения данных TensorFlow: руководство по Dataset API

искусственный интеллект TensorFlow API Element

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 вы можете обратиться к следующим материалам: