R interface to TensorFlow Dataset API
Перевод https://tensorflow.rstudio.com/tools/tfdatasets/articles/introduction.html
API TensorFlow Dataset обеспечивает возможности создания масштабируемых пайплайнов для передачи данных в модели TensorFlow, в том числе:- чтение данных из различных форматов, включая CSV и TFRecords (стандартный бинарный формат входных данных для TensorFlow);
- преобразования наборов данных, включая применение к ним произвольных функций;
- перемешивание наборов данных, разбивка на батчи и повторение набора по числу эпох;
- интерфейс потоковой передачи для чтения сколь угодно больших наборов данных;
- чтение и преобразование данных являются операциями в составе вычислительного графа TensorFlow, поэтому выполняются кодом на С++ параллельно с обучением модели.
Исходник этого документа и файлы примеров можно найти в репозитории. 
Сперва установите
Установка
Для использованияtfdatasets нужно установить библиотеку TensorFlow и соответствующий R-пакет.Сперва установите
tfdatasets с GitHub:# devtools::install_github("rstudio/tfdatasets")
remotes::install_github("rstudio/tfdatasets")install_tensorflow() для установки TensorFlow:library(tfdatasets)
install_tensorflow()tensorflow указать, куда установлена Python-овская библиотека, например, reticulate::use_condaenv("r-reticulate") - прим. пер.Создание набора данных
Наборы данных создаются из текстовых файлов, файлов в формате tfrecords или из данных в ОЗУ при помощи соответствующих функций.Текстовые файлы
Например, для создания набора данных из текстового файла сперва нужно создать спецификацию того, как записи будут декодироваться при чтении из файла, а затем вызватьtext_line_dataset() с именем файла и спецификацией:library(tfdatasets)
# создание спецификации для парсинга файла
iris_spec <- csv_record_spec("data/iris.csv")
# чтение набора данных
dataset <- text_line_dataset("data/iris.csv", record_spec = iris_spec) 
# структура полученного набора данных
str(dataset)## <MapDataset shapes: {Sepal.Length: (), Sepal.Width: (), Petal.Length: (), Petal.Width: (), Species: ()}, types: {Sepal.Length: tf.float32, Sepal.Width: tf.float32, Petal.Length: tf.float32, Petal.Width: tf.float32, Species: tf.string}>csv_record_spec() обрабатывает файл с образцом данных, который используется для автоматического определения имен и типов столбцов (для этого читается до 1000 первый строк в файле). Вы также можете явно задать имена и/или типы данных для столбцов при помощи параметров names и types (обратите внимание, что файл-образец при этом не нужен):# задаем имена и типы данных
iris_spec <- csv_record_spec(
  names = c("SepalLength", "SepalWidth", "PetalLength", "PetalWidth", 
            "Species"),
  types = c("double", "double", "double", "double", 
            "character"), 
  skip = 1
)
# чтение набора данных
dataset <- text_line_dataset("data/iris.csv", record_spec = iris_spec)skip = 1, чтобы пропустить первую строку в файле, содержащую имена столбцов.Поддерживаемые типы: целое число (integer), число с плавающей точкой (double) и строка (character). Вы также можете указывать типы в более компактной форме посредством односимвольных аббревиатур (например,
types = "dddi"):mtcars_spec <- csv_record_spec("data/mtcars.csv", types = "dididddiiii")Параллельный парсинг
Парсинг текстовых строк в требуемый формат может быть вычислительно затратным. Вы можете выполнять эти вычисления в параллельной режиме при помощи параметраparallel_record. Например:dataset <- text_line_dataset("data/iris.csv", 
                             record_spec = iris_spec, 
                             parallel_records = 4)dataset_prefetch():dataset <- text_line_dataset("data/iris.csv", 
                             record_spec = iris_spec,
                             parallel_records = 4) %>% 
  dataset_batch(128) %>% 
  dataset_prefetch(1)При наличии нескольких входных файлов вы можете распараллелить их чтение по нескольким ПК (шардирование) и/или по нескольким потокам на одном ПК (параллельное чтение с чередованием). Подробнее об этом см. в разделе “Чтение множественных файлов”.
Файлы tfrecords
Вы можете загружать данные из файлов в формате tfrecords при помощи функцииtfrecord_dataset().Часто бывает нужно отобразить записи из набора данных в последовательность именованных столбцов. Это можно сделать при помощи вызова
dataset_map() в сочетании с функцией tf$parse_single_example():# Создание набора данных, который читает наблюдения из двух файлов и 
# извлекает изображение с меткой класса
filenames <- c("/var/data/file1.tfrecord", 
               "/var/data/file2.tfrecord")
dataset <- tfrecord_dataset(filenames) %>%
  dataset_map(function(example_proto) {
    features <- list(
      image = tf$FixedLenFeature(shape(), tf$string),
      label = tf$FixedLenFeature(shape(), tf$int32)
    )
    tf$parse_single_example(example_proto, features)
  })num_parallel_reads:filenames <- c("/var/data/file1.tfrecord", 
               "/var/data/file2.tfrecord")
dataset <- tfrecord_dataset(filenames, num_parallel_reads = 4)Базы данных SQLite
Чтение наборов данных из БД SQLite осуществляется при помощи функцииsqlite_dataset(). Для этого нужно указать имя файла с базой данный, SQL-запрос и спецификацию sql_record_spec(), которая описывает имена и типы данных для столбцов, участвующих в запросе. Например:record_spec <- sql_record_spec(
  names = c("disp", "drat", "vs", "gear", "mpg", 
            "qsec", "hp", "am", "wt",  "carb", "cyl"),
  types = c(tf$float64, tf$int32, tf$float64, tf$int32, tf$float64, 
            tf$float64, tf$float64, tf$int32, tf$int32, tf$int32, tf$int32)
)
dataset <- sqlite_dataset(
  "data/mtcars.sqlite3",
  "select * from mtcars",
  record_spec
)
dataset## <MapDataset shapes: {disp: (), drat: (), vs: (), gear: (), mpg: (), qsec: (), hp: (), am: (), wt: (), carb: (), cyl: ()}, types: {disp: tf.float64, drat: tf.int32, vs: tf.float64, gear: tf.int32, mpg: tf.float64, qsec: tf.float64, hp: tf.float64, am: tf.int32, wt: tf.int32, carb: tf.int32, cyl: tf.int32}>tf$float64, поскольку tf$float32не поддерживаются при чтении из БД SQLite.Преобразования
Применение преобразований
Вы можете применить произвольные функции для преобразования данных к записях в наборе данных при помощиdataset_map(). Например, преобразовать столбец “Species” при помощи прямого кодирования (one-hot encoding) можно следующим образом:dataset <- text_line_dataset("data/iris.csv", 
                             record_spec = iris_spec,
                             parallel_records = 4)
dataset <- dataset %>% 
  dataset_map(function(record) {
    record$Species <- tf$one_hot(
      tf$strings$to_number(record$Species, tf$int32), 
      3L)
    record
  })
dataset## <MapDataset shapes: {SepalLength: (), SepalWidth: (), PetalLength: (), PetalWidth: (), Species: (3,)}, types: {SepalLength: tf.float32, SepalWidth: tf.float32, PetalLength: tf.float32, PetalWidth: tf.float32, Species: tf.float32}>dataset_map() реализована как функция на языке R, но некоторые специальные ограничения, налагаемые на нее, позволяют ее выполнять не в интерпретаторе R, а как часть вычислительного графа TensorFlow.Для набора данных, созданного при помощи функции
text_line_dataset(), передаваемая запись будет представлять собой именованный список тензоров (один тензор для каждого столбца). Возвращаемое значение должно быть другим набором тензоров, которые создаются при помощи функций TensorFlow (например, tf$one_hot()). Вызов функции dataset_map() конвертируется в операции вычислительного графа TensorFlow, которые выполняют пребразования с использованием нативного кода.Преобразования в параллельной режиме
Вычислительно затратные преобразования могут выполняться в несколько потоков при помощи параметраnum_parallel_calls:dataset <- text_line_dataset("data/iris.csv", 
                             record_spec = iris_spec,
                             parallel_records = 4)
dataset <- dataset %>% 
  dataset_map(num_parallel_calls = 4, function(record) {
    record$Species <- tf$one_hot(
      tf$strings$to_number(record$Species, tf$int32), 
      3L)
    record
  })dataset_prefetch():dataset <- text_line_dataset("data/iris.csv", 
                             record_spec = iris_spec,
                             parallel_records = 4)
dataset <- dataset %>% 
  dataset_map(num_parallel_calls = 4, function(record) {
    record$Species <- tf$one_hot(
      tf$strings$to_number(record$Species, tf$int32), 
      3L)
    record
  }) %>% 
  dataset_prefetch(1)dataset_map_and_batch(), объединяющей операции преобразования данных и создания батча:dataset <- text_line_dataset("data/iris.csv", 
                             record_spec = iris_spec,
                             parallel_records = 4)
dataset <- dataset %>% 
  dataset_map_and_batch(batch_size = 128, function(record) {
    record$Species <- tf$one_hot(
      tf$strings$to_number(record$Species, tf$int32), 
      3L)
    record
  }) %>% 
  dataset_prefetch(1)Выбор элементов
Вы можете отбирать элементы в наборе данных по условия при помощи функцииdataset_filter(), которая принимает на вход предикат и возвращает булев тензор для записей, которые должны быть включены в выдачу:mtcars_spec <- csv_record_spec("data/mtcars.csv", types = "dididddiiii")
dataset <- text_line_dataset("data/mtcars.csv",
                             record_spec = mtcars_spec) %>%
  dataset_filter(function(record) {
    record$mpg >= 20
})
dataset <- text_line_dataset("data/mtcars.csv",
                             record_spec = mtcars_spec) %>%
  dataset_filter(function(record) {
    record$mpg >= 20 & record$cyl >= 6L
  })Признаки и целевая переменная
Распространенным преобразованием является превращение таблицы, созданной при помощиtext_line_dataset() или tfrecord_dataset(), в список из двух элементов: “x” (признаки) и “y” (целевая переменная). Для этих целей служит функция dataset_prepare():mtcars_dataset <- text_line_dataset("data/mtcars.csv", 
                                    record_spec = mtcars_spec) %>% 
  dataset_prepare(x = c(mpg, disp), y = cyl)
iris_dataset <- text_line_dataset("data/iris.csv", 
                                  record_spec = iris_spec) %>% 
  dataset_prepare(x = -Species, y = Species)dataset_prepare() также также работает со стандартным формульным интерфейсом R:mtcars_dataset <- text_line_dataset("data/mtcars.csv", 
                                    record_spec = mtcars_spec) %>% 
  dataset_prepare(cyl ~ mpg + disp)batch_size можно объединить этапы dataset_prepare() и dataset_batch(), что, как правило, ускоряет работу:mtcars_dataset <- text_line_dataset("data/mtcars.csv", 
                                    record_spec = mtcars_spec) %>% 
  dataset_prepare(cyl ~ mpg + disp, batch_size = 16)Перемешивание данных и формирование батчей
Существует несколько функций, управляющих формированием батчей. Например, следующий код задает формирование батчей по 128 элементов из наблюдений, перемешиваемых внутри скользящего окна размером 1000; набор данных повторяется 10 раз для обучения в течение 10 эпох:dataset <- dataset %>% 
  dataset_shuffle(1000) %>%
  dataset_repeat(10) %>% 
  dataset_batch(128)dataset_shuffle_and_repeat(), но теперь так делать не рекомендуется:dataset <- dataset %>% 
  dataset_shuffle_and_repeat(buffer_size = 1000, count = 10) %>%
  dataset_batch(128)
# shuffle_and_repeat (from tensorflow.python.data.experimental.ops.shuffle_ops) 
# is deprecated and will be removed in a future version.
# Instructions for updating:
# Use `tf.data.Dataset.shuffle(buffer_size, seed)` followed by 
# `tf.data.Dataset.repeat(count)`. Static tf.data optimizations will 
# take care of using the fused implementation.Предварительная загрузка
Ранее мы упоминали функциюdataset_prefetch(), позволяющую предварительно загружать указанное количество элементов (или батчей, если таковые используются). Например:dataset <- text_line_dataset("data/iris.csv", 
                             record_spec = iris_spec,
                             parallel_records = 4)
dataset <- dataset %>% 
  dataset_map_and_batch(batch_size = 128, function(record) {
    record$Species <- tf$one_hot(
      tf$strings$to_number(record$Species, tf$int32), 
      3L)
    record
  }) %>% 
  dataset_prefetch(1)dataset_prefetch_to_device():dataset <- text_line_dataset("data/iris.csv", 
                             record_spec = iris_spec,
                             parallel_records = 4)
dataset <- dataset %>% 
  dataset_map_and_batch(batch_size = 128, function(record) {
    record$Species <- tf$one_hot(
      tf$strings$to_number(record$Species, tf$int32), 
      3L)
    record
  }) %>% 
  dataset_prefetch_to_device("/gpu:0")buffer_size).Полный пример
Ниже приводится полный пример совместного использования различных преобразований: фильтрация наблюдений, разбивка на “x” и “y”, перемешивание и деление на батчи.dataset <- text_line_dataset("data/mtcars.csv", 
                             record_spec = mtcars_spec) %>%
  dataset_filter(function(record) {
    record$mpg >= 20 & record$cyl >= 6L
  }) %>% 
  dataset_shuffle_and_repeat(buffer_size = 1000, count = 10) %>% 
  dataset_prepare(cyl ~ mpg + disp, batch_size = 128) %>% 
  dataset_prefetch(1)Чтение наборов данных
Методы чтения данных различаются в зависимости от API, используемых при построении моделей. Наборы данных tfdatasets могут быть использованы совместно с Keras примерно так же, как хранящиеся в ОЗУ матрицы и массивы; при использовании низкоуровневого интерфейса TensorFlow потребуется явно вызывать функцию-итератор.
В этом разделе приводятся примеры для обоих вариантов.
В этом разделе приводятся примеры для обоих вариантов.
Пакет keras
ВАЖНОЕ ПРИМЕЧАНИЕ: для этих примеров требуются актуальные версии Keras (>=2.2) и TensorFlow (>=1.9). Установить их можно стандартным способом:library(keras)
install_keras()fit():model %>% fit(
  x_train, y_train, 
  epochs = 30, 
  batch_size = 128
)train_on_batch() для передачи данных в виде батчей, но и при этом все преобразования выполняются на стороне R, а не в нативном коде.Альтернативой является передача в функции
fit() и evaluate() наборов данных, созданных при помощи tfdatasets.Ниже рассмотрен пример для классической задачи классификации MNIST.
Создать файлы в формате tfrecords можно при помощи данного скрипта, заменив в нем
tf.python_io.TFRecordWriter на tf.io.TFRecordWriter и указав numFiles = 1 - прим. пер.library(keras)
library(tfdatasets)
batch_size = 128
steps_per_epoch = 500
# Функция для чтения и обработки набора данных MNIST
mnist_dataset <- function(filename) {
  dataset <- tfrecord_dataset(filename) %>%
    dataset_map(function(example_proto) {
      # Парсинг 
      features <- tf$io$parse_single_example(
        example_proto,
        features = list(
          height = tf$io$FixedLenFeature(shape(), tf$int64),
          width = tf$io$FixedLenFeature(shape(), tf$int64),
          img_string = tf$io$VarLenFeature(tf$float32),
          label = tf$io$FixedLenFeature(shape(), tf$int64)
        )
      )
      # Обработка изображения
      image <- tf$divide(features$img_string, 255)
      image <- tf$sparse$to_dense(image)
      # image <- tf$reshape(image, c(features$height, features$width))
      # image <- tf$expand_dims(image, -1L)
      
      # one-hot кодирование метки класса
      label <- tf$one_hot(tf$cast(features$label, dtype = tf$int32), 10L)
      list(image, label)
    }) %>%
    dataset_repeat() %>%
    dataset_shuffle(1000) %>%
    dataset_batch(batch_size, drop_remainder = TRUE) %>%
    dataset_prefetch(1)
}
# Проверка
# iter <- dataset %>% make_iterator_one_shot()
# iterator_get_next(iter)
  
model <- keras_model_sequential() %>%
  layer_dense(units = 256, activation = "relu", input_shape = c(784)) %>%
  layer_dropout(rate = 0.4) %>%
  layer_dense(units = 128, activation = "relu") %>%
  layer_dropout(rate = 0.3) %>%
  layer_dense(units = 10, activation = "softmax")
model %>% compile(
  loss = "categorical_crossentropy",
  optimizer = optimizer_rmsprop(),
  metrics = c('accuracy')
)
history <- model %>% fit(
  mnist_dataset("data/MNIST_train_data_strings_0.tfrecord"),
  steps_per_epoch = steps_per_epoch,
  epochs = 20,
  validation_data = mnist_dataset("data/MNIST_train_data_strings_0.tfrecord"),
  validation_steps = steps_per_epoch
)
score <- model %>% evaluate(
  mnist_dataset("mnist/test.tfrecords"),
  steps = steps_per_epoch
)
print(score)dataset_map(). Мы указываем drop_remainder = TRUE, чтобы все батчи получались одинакового размера, как того требует Keras.Пакет tensorflow
Для получения батчей в виде тензоров служат функцииmake_iterator_one_shot() и iterator_get_next():dataset <- text_line_dataset("data/mtcars.csv", 
                             record_spec = mtcars_spec) %>% 
  dataset_prepare(cyl ~ mpg + disp) %>% 
  dataset_shuffle(20) %>% 
  dataset_batch(5)
iter <- make_iterator_one_shot(dataset)
next_batch <- iterator_get_next(iter)
next_batch## $x
## tf.Tensor(
## [[ 33.9  71.1]
##  [ 19.2 167.6]
##  [ 32.4  78.7]
##  [ 15.2 275.8]
##  [ 15.2 304. ]], shape=(5, 2), dtype=float32)
## 
## $y
## tf.Tensor([4 6 4 8 8], shape=(5,), dtype=int32)dataset_repeat() с указанием требуемого количества итераций (steps <- 200):mtcars_spec <- csv_record_spec("data/mtcars.csv")
dataset <- text_line_dataset("data/mtcars.csv", 
                             record_spec = mtcars_spec) %>% 
  dataset_shuffle(5000) %>% 
  dataset_repeat() %>% # повторять бесконечно
  dataset_prepare(x = c(mpg, disp), y = cyl) %>% 
  dataset_batch(128)  
iter <- make_iterator_one_shot(dataset)
steps <- 200
for (i in 1:steps) {
  # обучение модели
}Другой подход заключается в определении момента, когда все батчи были получены из набора данных. После исчерпания батчей возникает исключение out of range, которое может быть перехвачено при помощи
out_of_range_handler и функции tryCatch():tryCatch({
  while(TRUE) {
    batch <- iterator_get_next(iter)
    str(batch)
  }
}, error = out_of_range_handler)until_out_of_range():until_out_of_range({
  batch <- iterator_get_next(iter)
  str(batch)
})Чтение множественных файлов
Множественные файлы могут обрабатываться параллельно на одном или нескольких ПК. Возможности для этого предоставляет функцияread_files().Пример с чтением всех CSV-файлов в папке посредством функции
text_line_dataset():dataset <- read_files("data/*.csv", 
                      text_line_dataset, 
                      record_spec = mtcars_spec,
                      parallel_files = 4, 
                      parallel_interleave = 16) %>% 
  dataset_prefetch(5000) %>% 
  dataset_shuffle_and_repeat(buffer_size = 1000, count = 3) %>% 
  dataset_batch(128)parallel_files = 4 задает параллельное чтение 4 файлов, а parallel_interleave = 16 обеспечивает наличие в итоговом наборе данных блоков из 16 последовательных записей из каждого входного файла.Использование нескольких ПК
При обучении на нескольких ПК можно использовать параллельную загрузку данных на каждом из них (шардирование):# Флаги скрипта для обучения (информация о шардировании предоставляется 
# управляющим кодом, который запускает обучение)
FLAGS <- flags(
  flag_integer("num_shards", 1),
  flag_integer("shard_index", 1)
)
dataset <- read_files("data/*.csv", 
                      text_line_dataset, 
                      record_spec = mtcars_spec,
                      parallel_files = 4, 
                      parallel_interleave = 16,
                      num_shards = FLAGS$num_shards, 
                      shard_index = FLAGS$shard_index) %>% 
  dataset_shuffle_and_repeat(buffer_size = 1000, count = 3) %>% 
  dataset_batch(128) %>% 
  dataset_prefetch(1) 
Комментариев нет:
Отправить комментарий