Автор: Кашиф Расул, научный сотрудник Zalando Research
Источник | Публичный аккаунт TensorFlow
Как и большинство отделов исследований ИИ, Zalando Research признает важность экспериментов с идеями и быстрого прототипирования. По мере роста наборов данных становится полезным знать, как эффективно и быстро обучать модели глубокого обучения с помощью имеющихся у нас общих ресурсов.
API оценки TensorFlow полезен для обучения моделей с использованием нескольких графических процессоров в распределенной среде. Эта статья будет посвящена этому рабочему процессу. Сначала мы обучаем пользовательский оценщик, написанный на tf.keras, с использованием небольшого набора данных Fashion-MNIST, а затем в конце статьи представляем более практический вариант использования.
ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ: команда TensorFlow работает над еще одной интересной новой функцией (которая все еще находится на стадии Master, когда я пишу это), с помощью которой вы можете тренироваться, используя всего несколько строк кода модели tf.keras без предварительного преобразования модели. к оценщику! Рабочий процесс тоже отличный. Ниже я сосредоточусь на Estimator API. Какой из них выбрать решать вам! Примечание: функциональная ссылкаGitHub.com/tensorflow/…
TL;DR: По сути, нам нужно помнить, что для модели tf.keras.мы просто преобразуем ее в объект tf.estimator.Estimator с помощью метода tf.keras.estimator.model_to_estimator, и мы можем использовать tf.estimator. API для обучения. После завершения преобразования мы можем обучить модель с различными аппаратными конфигурациями, используя механизм, предоставляемый оценщиком.
Вы можете загрузить код в этой статье из этой записной книжки и запустить его самостоятельно. Примечание: ссылка на блокнотGitHub.com/легкое Карла/тайфун-кан…
import os
import time
#!pip install -q -U tensorflow-gpu
import tensorflow as tf
import numpy as np
Импорт набора данных Fashion-MNIST
Давайте небрежно заменим MNIST набором данных Fashion-MNIST, который содержит тысячи изображений в градациях серого статей Zalando о моде. Получить обучающие и тестовые данные очень просто:
(train_images, train_labels), (test_images, test_labels) =
tf.keras.datasets.fashion_mnist.load_data()
Мы хотим преобразовать значение пикселя этих изображений из числа от 0 до 255 в число от 0 до 1 и преобразовать этот набор данных в формат [B, H, W, C], где B означает пакетное число обработанных изображений, H и W — высота и ширина соответственно, C — количество каналов нашего набора данных (оттенки серого равны 1):
TRAINING_SIZE = len(train_images)
TEST_SIZE = len(test_images)
train_images = np.asarray(train_images, dtype=np.float32) / 255
# Convert the train images and add channels
train_images = train_images.reshape((TRAINING_SIZE, 28, 28, 1))
test_images = np.asarray(test_images, dtype=np.float32) / 255
# Convert the test images and add channels
test_images = test_images.reshape((TEST_SIZE, 28, 28, 1))
Затем мы хотим преобразовать метки из целых чисел (например, 2 или пуловеры) в однократное кодирование (например, 0,0,1,0,0,0,0,0,0,0). Для этого воспользуемся функцией tf.keras.utils.to_categorical:
# How many categories we are predicting from (0-9)
LABEL_DIMENSIONS = 10
train_labels = tf.keras.utils.to_categorical(train_labels,
LABEL_DIMENSIONS)
test_labels = tf.keras.utils.to_categorical(test_labels,
LABEL_DIMENSIONS)
# Cast the labels to floats, needed later
train_labels = train_labels.astype(np.float32)
test_labels = test_labels.astype(np.float32)
Построить модель tf.keras
Мы будем использовать функциональный API Keras для создания нейронной сети. Keras — это высокоуровневый API для создания и обучения моделей глубокого обучения с модульной конструкцией, простотой использования и возможностью расширения. tf.keras — это реализация TensorFlow этого API, которая поддерживает Eager Execution, конвейеры tf.data, оценщики и многое другое.
Что касается архитектуры, мы будем использовать ConvNet. Очень общее утверждение состоит в том, что ConvNet представляет собой набор сверточных слоев (Conv2D) и слоев объединения (MaxPooling2D). Но самое главное, ConvNet обрабатывает каждый обучающий пример как тензор трехмерной формы (высота, ширина, каналы), для изображений в градациях серого тензор начинается с канала = 1 и возвращает трехмерный тензор.
Итак, после части ConvNet нам нужно сгладить тензор и добавить плотные слои, последний из которых возвращает вектор размера LABEL_DIMENSIONS с активациями tf.nn.softmax:
inputs = tf.keras.Input(shape=(28,28,1)) # Returns a placeholder
x = tf.keras.layers.Conv2D(filters=32,
kernel_size=(3, 3),
activation=tf.nn.relu)(inputs)
x = tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=2)(x)
x = tf.keras.layers.Conv2D(filters=64,
kernel_size=(3, 3),
activation=tf.nn.relu)(x)
x = tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=2)(x)
x = tf.keras.layers.Conv2D(filters=64,
kernel_size=(3, 3),
activation=tf.nn.relu)(x)
x = tf.keras.layers.Flatten()(x)
x = tf.keras.layers.Dense(64, activation=tf.nn.relu)(x)
predictions = tf.keras.layers.Dense(LABEL_DIMENSIONS,
activation=tf.nn.softmax)(x)
Теперь мы можем определить модель обучения, выбрать оптимизатор (мы выбираем один из TensorFlow вместо использования оптимизатора из tf.keras.optimizers) и скомпилировать:
model = tf.keras.Model(inputs=inputs, outputs=predictions)
optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
model.compile(loss='categorical_crossentropy',
optimizer=optimizer,
metrics=['accuracy'])
Создать оценщик
Создайте средство оценки, используя скомпилированную модель Keras, также известную как метод model_to_estimator. Обратите внимание, что начальное состояние модели Кераса сохраняется в созданном оценщике.
Так в чем же преимущества оценщиков? Перво-наперво:
Вы можете запускать модели на основе оценщика на локальном хосте или в распределенной среде с несколькими графическими процессорами, не изменяя свою модель; Оценщики могут упростить общие реализации между разработчиками моделей; Оценщики могут построить график для вас, так что это похоже на Eager Execution без явных сеансов.
Так как же обучить простую модель tf.keras использованию нескольких графических процессоров? Мы можем использовать парадигму tf.contrib.distribute.MirroredStrategy для репликации в графе с синхронным обучением. Чтобы узнать больше об этой стратегии, посмотрите доклад о распределенном обучении TensorFlow. Примечание. Распределенная ссылка TensorFlowWoohoo.YouTube.com/watch?V=BRM…
По сути, каждый рабочий GPU имеет копию сети и извлекает подмножество данных, вычисляет для него локальные градиенты и ждет, пока все рабочие процессы завершатся синхронно. Затем рабочие передают свои локальные градиенты друг другу с помощью операций Ring All-reduce, которые обычно оптимизируются для уменьшения пропускной способности сети и увеличения пропускной способности. После получения всех градиентов каждый рабочий процесс вычисляет свое среднее значение и обновляет параметры перед началом следующего шага. В идеале у вас есть несколько высокоскоростных взаимосвязанных графических процессоров на одном узле.
Чтобы использовать эту стратегию, мы сначала создаем оценщик с скомпилированной моделью tf.keras, а затем передаем ему конфигурацию MirroredStrategy через конфигурацию RunConfig. По умолчанию в этой конфигурации используются все графические процессоры, но вы также можете указать параметр num_gpus, чтобы использовать определенное количество графических процессоров:
NUM_GPUS = 2
strategy = tf.contrib.distribute.MirroredStrategy(num_gpus=NUM_GPUS)
config = tf.estimator.RunConfig(train_distribute=strategy)
estimator = tf.keras.estimator.model_to_estimator(model,
config=config)
Создайте входную функцию оценщика
Чтобы передать данные в оценщик, нам нужно определить функцию импорта данных, которая возвращает набор данных tf.data пакетных данных (изображения, метки). Функция ниже принимает массив numpy и возвращает набор данных через процесс ETL.
Обратите внимание, что в конце мы также вызываем метод предварительной выборки, который буферизует данные в GPU во время обучения, чтобы следующий пакет данных был готов и ожидал GPU, а не заставлял GPU ждать данных на каждой итерации. Графический процессор может по-прежнему использоваться недостаточно, чтобы исправить это, мы можем использовать объединенную версию операций преобразования (например, shuffle_and_repeat) вместо двух отдельных операций. Однако здесь я выбрал простой вариант использования.
def input_fn(images, labels, epochs, batch_size):
# Convert the inputs to a Dataset. (E)
ds = tf.data.Dataset.from_tensor_slices((images, labels))
# Shuffle, repeat, and batch the examples. (T)
SHUFFLE_SIZE = 5000
ds = ds.shuffle(SHUFFLE_SIZE).repeat(epochs).batch(batch_size)
ds = ds.prefetch(2)
# Return the dataset. (L)
return ds
обучить оценщика
Во-первых, мы определяем класс SessionRunHook, который записывает количество итераций стохастического градиентного спуска:
class TimeHistory(tf.train.SessionRunHook):
def begin(self):
self.times = []
def before_run(self, run_context):
self.iter_time_start = time.time()
def after_run(self, run_context, run_values):
self.times.append(time.time() - self.iter_time_start)
Изюминка здесь! Мы можем вызвать функцию обучения в оценщике и передать ей наш определенный input_fn (содержащий размер пакета и количество нужных нам эпох обучения) и экземпляр TimeHistory через параметр hooks:
time_hist = TimeHistory()
BATCH_SIZE = 512
EPOCHS = 5
estimator.train(lambda:input_fn(train_images,
train_labels,
epochs=EPOCHS,
batch_size=BATCH_SIZE),
hooks=[time_hist])
представление
Теперь мы можем использовать временной хук для расчета общего времени обучения и среднего количества изображений, обучаемых в секунду (средняя пропускная способность):
total_time = sum(time_hist.times)
print(f"total time with {NUM_GPUS} GPU(s): {total_time} seconds")
avg_time_per_batch = np.mean(time_hist.times)
print(f"{BATCH_SIZE*NUM_GPUS/avg_time_per_batch} images/second with
{NUM_GPUS} GPU(s)")
Пропускная способность и общее время обучения Fashion-MNIST при обучении с двумя графическими процессорами K80, с разными NUM_GPUS, демонстрирующими плохое масштабирование
Оценить оценщик
Чтобы проверить производительность модели, мы вызываем метод оценки в оценщике:
estimator.evaluate(lambda:input_fn(test_images,
test_labels,
epochs=1,
batch_size=BATCH_SIZE))
Пример изображения ОКТ сетчатки (оптическая когерентная томография)
Чтобы проверить производительность масштабирования модели на больших наборах данных, мы используем набор данных изображений OCT сетчатки, один из многих больших наборов данных на Kaggle. Набор данных состоит из рентгеновских изображений поперечного сечения сетчатки глаза живого человека, разделенных на четыре категории: НОРМАЛЬНАЯ, CNV, DME и ДРУЗЫ:
Репрезентативное изображение для оптической когерентной томографии из «Идентификация медицинских диагнозов и излечимых заболеваний с помощью глубокого обучения на основе изображений» Кермани и др.
Набор данных содержит в общей сложности 84 495 рентгеновских изображений в формате JPEG, в основном размером 512x496, которые можно загрузить через интерфейс командной строки Kaggle: Примечание: CLI-ссылкаGitHub.com/KA GG UP/Реформа карты…
#!pip install kaggle
#!kaggle datasets download -d paultimothymooney/kermany2018
После завершения загрузки классы обучающих и тестовых изображений находятся в соответствующих папках, поэтому мы можем определить шаблон как:
labels = ['CNV', 'DME', 'DRUSEN', 'NORMAL']
train_folder = os.path.join('OCT2017', 'train', '**', '*.jpeg')
test_folder = os.path.join('OCT2017', 'test', '**', '*.jpeg')
Затем мы хотим написать входную функцию оценщика, которая может извлекать любой режим файла и возвращать масштабированное изображение и метки с горячим кодированием в виде tf.data.Dataset. На этот раз мы следуем рекомендациям из руководства по производительности входного конвейера. В частности, обратите внимание, что если размер буфера предварительной выборки равен None, TensorFlow будет автоматически использовать оптимальный размер буфера предварительной выборки: Примечание. Введите ссылку «Руководство по производительности конвейера».woohoo.tensorflow.org/performance…
1 def input_fn(file_pattern, labels,
2 image_size=(224,224),
3 shuffle=False,
4 batch_size=64,
5 num_epochs=None,
6 buffer_size=4096,
7 prefetch_buffer_size=None):
8
9 table = tf.contrib.lookup.index_table_from_tensor(mapping=tf.constant(labels))
10 num_classes = len(labels)
11
12 def _map_func(filename):
13 label = tf.string_split([filename], delimiter=os.sep).values[-2]
14 image = tf.image.decode_jpeg(tf.read_file(filename), channels=3)
15 image = tf.image.convert_image_dtype(image, dtype=tf.float32)
16 image = tf.image.resize_images(image, size=image_size)
17 return (image, tf.one_hot(table.lookup(label), num_classes))
18
19 dataset = tf.data.Dataset.list_files(file_pattern, shuffle=shuffle)
20
21 if num_epochs is not None and shuffle:
22 dataset = dataset.apply(
23 tf.contrib.data.shuffle_and_repeat(buffer_size, num_epochs))
24 elif shuffle:
25 dataset = dataset.shuffle(buffer_size)
26 elif num_epochs is not None:
27 dataset = dataset.repeat(num_epochs)
28
29 dataset = dataset.apply(
30 tf.contrib.data.map_and_batch(map_func=_map_func,
31 batch_size=batch_size,
32 num_parallel_calls=os.cpu_count()))
33 dataset = dataset.prefetch(buffer_size=prefetch_buffer_size)
34
35 return dataset
На этот раз при обучении модели мы будем использовать предварительно обученный VGG16 и переобучим только его последние 5 слоев:
keras_vgg16 = tf.keras.applications.VGG16(input_shape=(224,224,3),
include_top=False)
output = keras_vgg16.output
output = tf.keras.layers.Flatten()(output)
prediction = tf.keras.layers.Dense(len(labels),
activation=tf.nn.softmax)(output)
model = tf.keras.Model(inputs=keras_vgg16.input,
outputs=prediction)
for layer in keras_vgg16.layers[:-4]:
layer.trainable = False
Теперь, когда у нас все готово, мы можем выполнить описанные выше шаги и обучить нашу модель за считанные минуты, используя графические процессоры NUM_GPUS:
model.compile(loss='categorical_crossentropy', optimizer=tf.train.AdamOptimizer(), metrics=['accuracy'])
NUM_GPUS = 2
strategy = tf.contrib.distribute.MirroredStrategy(num_gpus=NUM_GPUS)
config = tf.estimator.RunConfig(train_distribute=strategy)
estimator = tf.keras.estimator.model_to_estimator(model, config=config)
BATCH_SIZE = 64
EPOCHS = 1
estimator.train(input_fn=lambda:input_fn(train_folder, labels, shuffle=True, batch_size=BATCH_SIZE, buffer_size=2048, num_epochs=EPOCHS, prefetch_buffer_size=4), hooks=[time_hist])
После обучения мы можем оценить точность на тестовом наборе, которая должна быть около 95% (неплохо для начального базового уровня):
estimator.evaluate(input_fn=lambda:input_fn(test_folder,
labels,
shuffle=False,
batch_size=BATCH_SIZE,
buffer_size=1024,
num_epochs=1))
Пропускная способность и общее время обучения Fashion-MNIST при обучении с двумя графическими процессорами K80, с разными NUM_GPUS, с линейным масштабированием
Суммировать
Выше мы рассмотрели, как использовать Estimator API для простого обучения модели глубокого обучения Keras на нескольких графических процессорах, как написать передовой конвейер ввода, чтобы максимально использовать наши ресурсы (линейное масштабирование), и как подключить его для нас. тайминги пропускной способности обучения.
Важно отметить, что в конечном итоге нас больше всего интересуют ошибки набора тестов. Вы можете заметить, что точность набора тестов снижается по мере увеличения значения NUM_GPUS. Одной из причин может быть то, что MirroredStrategy эффективно обучает модель при использовании размера пакета BATCH_SIZE*NUM_GPUS, тогда как при увеличении количества графических процессоров может потребоваться корректировка BATCH_SIZE или скорости обучения. Все остальные гиперпараметры, кроме NUM_GPUS, оставлены в тексте постоянными для простоты построения графика, но на практике нам нужно настроить эти гиперпараметры.
Размер набора данных и модели также влияет на масштабируемость этих схем. Графические процессоры имеют низкую пропускную способность при чтении или записи небольших данных, особенно с более старыми графическими процессорами, такими как K80, и могут вызвать ситуацию, показанную на графике Fashion-MNIST выше.
Спасибо
Спасибо команде TensorFlow, особенно Джошу Гордону, и моим коллегам из Zalando Research, особенно Дункану Блайту, Гокхану Йилдириму и Себастьяну Хайнцу, за помощь в редактировании черновика.