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()
Не забывайте перед использованием R-пакета 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)
})
Чтение файлов tfrecords также можно распараллелить при помощи параметра 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)
При использовании батчей по время обучения можно оптимизировать производительность c помощью функции 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
})
Заметим, что функции внутри предиката должны быть тензорными операциями (tfnot_equal, tfless и т.д.). Предоставляются соответствующие методы для стандартных операторов сравнения (<, >, <=) и логических операторов (!, &, |).Признаки и целевая переменная
Распространенным преобразованием является превращение таблицы, созданной при помощи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)
При использовании GPU данные могут быть предварительно загружены в память GPU с помощью 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()
Модели 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)
Комментариев нет:
Отправить комментарий