воскресенье, 30 сентября 2018 г.

TensorFlow: импорт данных - чтение данных

Чтение вводных данных

Получение NumPy массивов

Если все вводные данные помещаются в память, тогда простейший путь создать Dataset из данных - это конвертировать их в tf.Tensor объекты и использовать Dataset.from_tensor_slices().

# Загружаем тренировочные данные в два NumPy массива,
# используя np.load()
with np.load("/var/data/training_data.npy") as data:
  features = data["features"]
  labels = data["labels"]

# Предполагаем, что каждая строка массива features
# соответствует такой же строке массива labels
assert features.shape[0] == labels.shape[0]

dataset = tf.data.Dataset.from_tensor_slices((features, labels))

Следует отметить, что пример кода выше встроит массивы свойств и меток в TensorFlow граф как tf.constant() операции. Это работает хорошо на небольших датасетах, но тратит напрасно память, потому что содержимое массива будет скопировано много раз, и может достичь 2GB лимита для буфера tf.GraphDef протокола.

В качестве альтернативы, можно определить Dataset с точки зрения tf.placeholder() тензоров и передать NumPy массивы при инициализации Iterator для датасета.

# Загружаем тренировочные данные в два NumPy массива,
# используя `np.load()`.
with np.load("/var/data/training_data.npy") as data:
  features = data["features"]
  labels = data["labels"]

# Предполагаем, что каждая строка массива features
# соответствует такой же строке массива labels
assert features.shape[0] == labels.shape[0]

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))
# [Другие трансформации `dataset`...]
dataset = ...
iterator = dataset.make_initializable_iterator()

sess.run(iterator.initializer, feed_dict={features_placeholder: features,
                                          labels_placeholder: labels})

Получение TFRecord данных

tf.data API поддерживает различные форматы файлов, так можно обрабатывать большие наборы данных, которые не помещаются в память. Например, TFRecord формат - это простой ориентированный на записи бинарный формат, который многие TensorFlow приложения используют для тренировочных данных. tf.data.TFRecordDataset класс позволяет проходить по содержимому одного или нескольких TFRecord файлов как часть пайплайна ввода.

# Создаем датасет, который читает все примеры из двух файлов.
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
dataset = tf.data.TFRecordDataset(filenames)

filenames (имена файлов) аргумент для TFRecordDataset инициализатора может быть строкой, списком строк, или tf.Tensor строк. Таким образом можно иметь два набора файлов для целей тренировки и валидации, можно использовать tf.placeholder(tf.string) для представления filenames (имен файлов), и инициализировать итератор из подходящих имен файлов:

filenames = tf.placeholder(tf.string, shape=[None])
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...)  # Парсим запись в тензоры.
# Повторяем ввод неопределенное количество раз
dataset = dataset.repeat()  
dataset = dataset.batch(32)
iterator = dataset.make_initializable_iterator()

# Можно наполнить инициализатор подходящими именами файлов
# для текущей фазы исполнения (например, тренировки или валидации)

# Инициализируем `iterator` с тренировочными данными.
training_filenames = ["/var/data/file1.tfrecord", 
                      "/var/data/file2.tfrecord"]
sess.run(iterator.initializer, 
         feed_dict={filenames: training_filenames})

# Инициализируем `iterator` с валидационными данными.
validation_filenames = ["/var/data/validation1.tfrecord", ...]
sess.run(iterator.initializer, 
         feed_dict={filenames: validation_filenames})

Получение текстовых данных

Многие наборы данных поставляются как один или несколько текстовых файлов. tf.data.TextLineDataset предоставляет легкий путь извлечения строк из одного или нескольких файлов. Получив одно или несколько имен файлов, TextLineDataset производит один элемент строкового значения на каждую строку этих файлов. Как TFRecordDataset, TextLineDataset принимает имена файлов как tf.Tensor, таким образом можно параметризировать его передавая tf.placeholder(tf.string).

filenames = ["/var/data/file1.txt", "/var/data/file2.txt"]
dataset = tf.data.TextLineDataset(filenames)

По умолчанию, TextLineDataset выводит каждую строку каждого файла, что может быть нежелательным, например если файл начинается с линии заголовка или содержит комментарии. Эти строки могут быть удалены использованием Dataset.skip() и Dataset.filter() преобразований. Для применения преобразований к каждому файлу отдельно используем Dataset.flat_map() для создания вложенного Dataset для каждого файла.

filenames = ["/var/data/file1.txt", "/var/data/file2.txt"]

dataset = tf.data.Dataset.from_tensor_slices(filenames)

# Используем `Dataset.flat_map()`, чтобы преобразовать 
# каждый файл как отдельный вложенный датасет,
# и затем соединяем вместе их содержимое 
# последовательно в один "плоский" датасет.
# * Пропускаем первую строку (линия заголовка)
# * Фильтруем строки, начаниющиеся с "#" (комментарии).
dataset = dataset.flat_map(
    lambda filename: (
        tf.data.TextLineDataset(filename)
        .skip(1)
        .filter(lambda line: tf.not_equal(tf.substr(line, 0, 1), "#"))))

Получение CSV данных

CSV формат файлов - популярный формат для сохранения табличных данных в простой текст. tf.contrib.data.CsvDataset класс предоставляет путь извлечения записей из одного или нескольких CSV файлов, которые соотвествуют RFC 4180. Получив одно или несколько имен файлов и лист типа данных по умолчанию, CsvDataset производит tuple элементов, типы которых соответствуют типам, предоставленным по умолчанию, для каждой CSV записи. Как TFRecordDataset и TextLineDataset, CsvDataset принимает имена файлов как tf.Tensor, таким образом можно параметризировать его передавая tf.placeholder(tf.string).

# Создаем датасет, который читает все записи из двух CSV файлов
# каждый c 8 колонками нецельночисловых значений
filenames = ["/var/data/file1.csv", "/var/data/file2.csv"]
# 8 требуемых колонок нецельночисловых данных
record_defaults = [tf.float32] * 8   
dataset = tf.contrib.data.CsvDataset(filenames, record_defaults)

Если некоторые колонки пустые можно предоставить значения по умолчанию.

# Создаем датасет, который читает все записи из двух CSV файлов
# каждый c 4 колонками нецельночисловых значений,
# которые могут иметь пропущенные значения
record_defaults = [[0.0]] * 8
dataset = tf.contrib.data.CsvDataset(filenames, record_defaults)

По умолчанию CsvDataset выводит каждую колонку каждой строки файла, что может быть нежелательным, например если файл начинается с линии заголовка, которая должна быть пропущена, или некоторые колонки не требуются во вводе. Эти строки и поля могут быть удалены с header и select_cols аргументами соответственно.

# Создаем датасет, который читает все записи из двух CSV файлов
# с заголовками, извлевая нецельночисловые данные из 2 и 4 колонок.
# Предоставляем значения по умолчанию только для выбранных колонок.
record_defaults = [[0.0]] * 2  
dataset = tf.contrib.data.CsvDataset(filenames, record_defaults, 
                                     header=True, select_cols=[2,4])