Генеративные состязательные сети или GAN - одна из самых активных областей исследований и разработок в области глубокого обучения из-за их невероятной способности генерировать синтетические результаты. В этом блоге мы построим базовую интуицию GAN на конкретном примере. Этот пост разбит следующим образом:

  • Основная идея и интуиция, лежащие в основе работы генеративных состязательных сетей
  • Реализация модели на основе GAN, которая генерирует данные из простого распределения.
  • Визуализация и анализ различных аспектов GAN, чтобы лучше понять, что происходит за кулисами.

Код этого блога можно найти здесь.

Генеративные состязательные сети

Основная идея GAN на самом деле очень проста. По своей сути GAN включает двух агентов с конкурирующими целями, которые работают над противоположными целями. Эта относительно простая установка приводит к тому, что оба агента придумывают все более сложные способы обмана друг друга. Такую ситуацию можно смоделировать в теории игр как минимаксную игру.

Давайте рассмотрим теоретический пример процесса подделки денег. В этом процессе мы можем представить себе агентов двух типов: преступник и полицейский. Давайте посмотрим на их конкурирующие цели:

  • Задача преступника. Основная задача преступника - придумать сложные способы подделки денег, чтобы полицейский не мог отличить поддельные деньги от настоящих.
  • Задача полицейского. Основная задача полицейского - найти сложные способы отличить фальшивые деньги от настоящих.

По мере продвижения этого процесса полицейский разрабатывает все более изощренные технологии для обнаружения подделки денег, а преступник разрабатывает все более изощренные технологии для подделки денег. Это основа так называемого состязательного процесса.

Генеративные состязательные сети используют состязательные процессы для обучения двух нейронных сетей, которые конкурируют друг с другом, пока не будет достигнуто желаемое равновесие. В этом случае у нас есть сеть генераторов G (Z), которая принимает входной случайный шум и пытается сгенерировать данные, очень близкие к тому набору данных, который у нас есть. Другая сеть называется сетью дискриминатора D (X), которая принимает сгенерированные входные данные и пытается различать сгенерированные данные и реальные данные. Эта сеть по своей сути реализует двоичную классификацию и выводит вероятность того, что входные данные действительно получены из реального набора данных (в отличие от синтетических или поддельных данных).

Формально целевую функцию всего этого процесса можно записать как:

Обычно желательная точка равновесия для указанных выше GAN состоит в том, что Генератор должен моделировать реальные данные, а Дискриминатор должен выдавать вероятность 0,5, поскольку сгенерированные данные совпадают с реальными данными, то есть не уверен, что поступают новые данные. от генератора с равной вероятностью может быть реальным или фальшивым.

Вам может быть интересно, зачем вообще нужен такой сложный процесс обучения? В чем преимущества изучения такой модели? Что ж, интуиция, стоящая за этим и всеми генеративными подходами, основана на известной цитате Ричарда Фейнмана:

What I cannot create, I do not understand.

Это актуально, потому что если мы можем сгенерировать реальное распределение данных из модели, это означает, что мы знаем все, что нужно знать об этой модели. Часто эти реальные распределения включают в себя миллионы изображений, и мы можем сгенерировать их, используя модель, которая имеет тысячи параметров, тогда эти параметры отражают суть данных изображений.

У GAN есть много других реальных краткосрочных приложений, которые мы обсудим в следующем разделе.

Внедрение GAN

В этом разделе мы сгенерируем очень простое распределение данных и попытаемся изучить функцию генератора, которая генерирует данные из этого распределения с использованием модели GAN, описанной выше. Этот раздел в общих чертах разделен на 3 части. Сначала мы напишем базовую функцию для генерации квадратичного распределения (распределения реальных данных). Во-вторых, мы пишем код для сетей Генератор и Дискриминатор. Затем мы будем использовать данные и сети, чтобы написать код для обучения этих двух сетей состязательным способом.

Цель этой реализации - изучить новую функцию, которая может генерировать данные из того же распределения, что и обучающие данные. Ожидается, что наша сеть Генератор начнет генерировать данные, соответствующие квадратичному распределению. Это объясняется и демонстрируется более подробно в следующем разделе. Хотя мы начинаем с очень простого распределения данных, этот подход можно легко расширить для генерации данных из гораздо более сложного набора данных. Несколько примеров GAN успешно генерируют изображения рукописных цифр, лиц знаменитостей, животных и т. Д.

Создание обучающих данных

Мы реализуем наш настоящий набор данных, генерируя случайные выборки с использованием библиотеки numpy, а затем генерируя вторую координату с помощью какой-то функции. В этой демонстрации мы сохранили функцию как квадратичную для простоты. Вы можете поиграть с этим кодом, чтобы сгенерировать набор данных с большим количеством измерений и / или более сложными отношениями между его функциями, такими как полином более высокой степени, синус, косинус и т. Д.

import numpy as np def get_y(x): return 10 + x*x def sample_data(n=10000, scale=100): data = [] x = scale*(np.random.random_sample((n,))-0.5) for i in range(n): yi = get_y(x[i]) data.append([x[i], yi]) return np.array(data)

Сгенерированные данные очень просты и могут быть нанесены на график, как показано здесь:

Реализация сетей генераторов и дискриминаторов

Теперь мы реализуем сети Генератор и Дискриминатор, используя слои тензорного потока. Мы реализуем сеть Генератора, используя следующую функцию:

def generator(Z,hsize=[16, 16],reuse=False): with tf.variable_scope("GAN/Generator",reuse=reuse): h1 = tf.layers.dense(Z,hsize[0],activation=tf.nn.leaky_relu) h2 = tf.layers.dense(h1,hsize[1],activation=tf.nn.leaky_relu) out = tf.layers.dense(h2,2) return out

Эта функция принимает placeholder для случайных выборок (Z), массив hsize для количества единиц в 2 скрытых слоях и переменную reuse, которая используется для повторного использования тех же слоев. Используя эти входные данные, он создает полностью связанную нейронную сеть из 2 скрытых слоев с заданным количеством узлов. Результатом этой функции является двумерный вектор, который соответствует размерам реального набора данных, который мы пытаемся изучить. Вышеупомянутую функцию можно легко изменить, включив в нее больше скрытых слоев, разные типы слоев, различную активацию и различные сопоставления вывода.

Мы реализуем сеть Дискриминатора, используя следующую функцию:

def discriminator(X,hsize=[16, 16],reuse=False): with tf.variable_scope("GAN/Discriminator",reuse=reuse): h1 = tf.layers.dense(X,hsize[0],activation=tf.nn.leaky_relu) h2 = tf.layers.dense(h1,hsize[1],activation=tf.nn.leaky_relu) h3 = tf.layers.dense(h2,2) out = tf.layers.dense(h3,1) return out, h3

Эта функция принимает входные данные placeholder для выборок из векторного пространства реального набора данных. Сэмплы могут быть как реальными, так и сгенерированными из сети генератора. Подобно сети генератора выше, она также принимает входные данные hsize и reuse. Мы используем 3 скрытых слоя для Дискриминатора, из которых размер первых двух слоев мы принимаем. Мы фиксируем размер третьего скрытого слоя на 2, чтобы мы могли визуализировать преобразованное пространство признаков в 2D-плоскости, как описано в следующем разделе. Результатом этой функции является прогноз logit для данного X и выход последнего слоя, который является преобразованием признаков, изученным Дискриминатором для X. Функция logit является обратной сигмоидной функции, которая используется для представления логарифма шансов (отношения вероятности того, что переменная равна 1, к вероятности того, что она равна 0).

Состязательная тренировка

В целях обучения мы определяем следующие заполнители X и Z для реальных выборок и выборок случайного шума соответственно:

X = tf.placeholder(tf.float32,[None,2]) Z = tf.placeholder(tf.float32,[None,2])

Нам также необходимо создать граф для генерации выборок из сети генератора и подачи реальных и сгенерированных выборок в сеть дискриминатора. Это делается с помощью функций и заполнителей, определенных выше:

G_sample = generator(Z) r_logits, r_rep = discriminator(X) f_logits, g_rep = discriminator(G_sample,reuse=True)

Используя логиты для сгенерированных данных и реальных данных, мы определяем функции потерь для сетей генератора и дискриминатора следующим образом:

disc_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=r_logits,labels=tf.ones_like(r_logits)) + tf.nn.sigmoid_cross_entropy_with_logits(logits=f_logits,labels=tf.zeros_like(f_logits))) gen_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=f_logits,labels=tf.ones_like(f_logits)))

Эти потери основаны на sigmoid cross entropy потерях с использованием уравнений, которые мы определили выше. Это часто используемая функция потерь для так называемой дискретной классификации. Он принимает на входе logit (который задается нашей discriminator сетью) и истинные метки для каждого образца. Затем он вычисляет ошибку для каждого образца. Мы используем оптимизированную версию, реализованную TensorFlow, которая более стабильна, чем прямое вычисление кросс-энтропии. Более подробную информацию вы можете найти в соответствующем API TensorFlow здесь.

Затем мы определяем оптимизаторы для двух сетей, используя функции потерь, определенные выше, и объем уровней, определенных в функциях generator и discriminator. Мы используем RMSProp Optimizer для обеих сетей со скоростью обучения 0.001. Используя область видимости, мы получаем веса / переменные только для данной сети.

gen_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,scope="GAN/Generator") disc_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,scope="GAN/Discriminator") gen_step = tf.train.RMSPropOptimizer(learning_rate=0.001).minimize(gen_loss,var_list = gen_vars) # G Train step disc_step = tf.train.RMSPropOptimizer(learning_rate=0.001).minimize(disc_loss,var_list = disc_vars) # D Train step

Затем мы обучаем обе сети поочередно для необходимого количества шагов:

for i in range(100001): X_batch = sample_data(n=batch_size) Z_batch = sample_Z(batch_size, 2) _, dloss = sess.run([disc_step, disc_loss], feed_dict={X: X_batch, Z: Z_batch}) _, gloss = sess.run([gen_step, gen_loss], feed_dict={Z: Z_batch}) print "Iterations: %d\t Discriminator loss: %.4f\t Generator loss: %.4f"%(i,dloss,gloss)

Приведенный выше код может быть изменен для включения более сложных процедур обучения, таких как выполнение нескольких шагов обновления дискриминатора и / или генератора, выборка характеристик реальных и сгенерированных образцов и построение сгенерированных выборок. Пожалуйста, обратитесь к репозиторию кода для таких изменений.

Анализ GAN

Визуализация потерь при обучении

Чтобы лучше понять, что происходит в этом процессе, мы можем построить график потерь на обучение после каждых 10 итераций. На графике ниже мы можем видеть, как изменения в потерях постепенно уменьшаются, и эти потери становятся почти постоянными к концу тренировки. Это незначительное изменение потери как Дискриминатора
, так и Генератора указывает на равновесие.

Визуализация образцов во время обучения

Мы также можем построить реальную и сгенерированную выборку после каждых 1000 итераций обучения. Эти графики визуализируют, как сеть Генератор начинается со случайного начального сопоставления между входным векторным пространством и векторным пространством набора данных, а затем постепенно развивается, чтобы напоминать образцы реального набора данных. Как видите, «фальшивая» выборка начинает все больше и больше походить на «реальное» распределение данных.

В этом разделе мы визуализируем эффект обновления весов сети Генератор в процессе обучения противников. Мы делаем это, отображая срабатывания последнего скрытого слоя сети Дискриминатор. Мы выбрали последний скрытый слой размером 2, чтобы его было легко построить, не требуя уменьшения размерности (т. Е. Преобразования входной выборки в другое векторное пространство). Мы заинтересованы в визуализации функции преобразования признаков, которую использует сеть Дискриминатор. Эта функция - это то, что наша сеть изучает, чтобы можно было разделить настоящие и поддельные данные.

Мы наносим на график преобразование признаков реальной и сгенерированной выборок, полученное на последнем слое сети Дискриминатор, до и после обновления весов сети Генератор. Мы также наносим на график центроиды точек, которые мы получаем после преобразования признаков входных выборок. Наконец, мы вычисляем центроиды точек отдельно для реальных и поддельных данных до и после обновления генератора. Из графиков можно сделать следующие выводы:

  • Как и ожидалось, преобразованные характеристики реальных выборок данных не изменились. По графикам видно, что они полностью совпадают.
  • По центроидам мы можем видеть, что центроид признаков сгенерированных выборок данных почти всегда перемещается в сторону центроида признаков реальных выборок данных.
  • Мы также можем видеть, что по мере увеличения количества итераций преобразованные характеристики реальных выборок все больше и больше смешиваются с преобразованными характеристиками сгенерированных выборок. Это также ожидается, потому что в конце обучения сеть Дискриминатор не сможет различать реальные и сгенерированные образцы. Следовательно, в конце обучения преобразованные признаки для обеих выборок должны совпадать.

Обсуждение и дальнейшая работа

Мы реализовали концептуальную модель GAN для генерации данных из очень простого распределения данных. В качестве упражнения для любознательного читателя мы рекомендуем изменить приведенный выше код, чтобы сделать следующее:

  • Визуализируйте до и после обновления Дискриминатора.
  • Измените функции активации слоев и посмотрите разницу в обучающей и сгенерированной выборках.
  • Добавьте больше слоев и различных типов слоев и посмотрите, как они влияют на время тренировки и ее стабильность.
  • Измените код для генерации данных, чтобы он включал данные из 2 разных кривых.
  • Измените приведенный выше код для работы с более сложными данными, такими как MNIST, CIFAR-10 и т. Д.

В будущей работе мы обсудим ограничения GAN и модификации, которые потребуются для их решения.

Первоначально опубликовано на blog.paperspace.com 8 мая 2018 г.