Глубокая сверточная ГАН, обученная рисовать каракули летучих мышей

Введение

С появлением суперкомпьютеров мы находимся на этапе, когда мы не только анализируем большие объемы данных с помощью машинного обучения и других статистических методов, но и учимся генерировать важные и важные (синтетические) данные для выполнения повседневных задач. Генеративное моделирование, подотрасль машинного обучения, включает в себя обучение созданию новых данных, аналогичных заданному набору обучающих данных. Он включает в себя создание модели, которая может изучать основные шаблоны и структуру данных, а затем использовать эту модель для создания новых данных, аналогичных обучающим данным. GAN или генеративно-состязательные сети — это базовые модели глубокого обучения, которые используются для создания этих новых синтетических данных.

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

Генеративно-состязательные сети (GAN) — это подход к генеративному моделированию, в котором проблема остроумно ставится как проблема контролируемого обучения с двумя подмоделями. Две подмодели

  1. генератор — эта модель играет роль ученика, пытающегося освоить навык.
  2. Дискриминатор – эта модель играет роль эксперта в навыках, которыми ученик пытается овладеть.

Задача дискриминатора состоит в том, чтобы различать реальные изображения из набора данных и изображения, нарисованные (созданные) генератором. Задача генератора состоит в том, чтобы генерировать изображения таким образом, чтобы дискриминатор не мог легко отличить, было ли изображение создано генератором или фактически было частью исходного набора данных изображений.

Таким образом, генератор и дискриминатор играют в игру с нулевой суммой, в которой дискриминатор пытается лучше обнаруживать подделки (сгенерированные изображения). С другой стороны, генератор пытается лучше создавать изображения, которые дискриминатор не может воспринимать как подделку. Здесь, с технической точки зрения, дискриминатор, обучающийся дифференцированию, подразумевает минимизацию потерь классификации дискриминатора, а генератор, обучающийся рисованию (производству изображений), подразумевает максимизацию потерь классификации дискриминатора. Мы рассмотрим, как этого добиться в следующих разделах.

В этом сообщении блога мы расскажем, как создавать дудлы, представляющие супергероя века — Бэтмена!

Набор данных

Наш проект вдохновлен быстрым набором данных Google. Quick-draw от Google — это игра, похожая на skribbl, за одним исключением: противники в игре — не ваши друзья, а нейросеть, пытающаяся стать лучше в игре. Таким образом, эта игра привела к сбору крупнейшего в мире набора данных дудлов с открытым исходным кодом.

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

  1. Наброски речи — проект, превращающий речевую подсказку в набросок
  2. То, как ты рисуешь круги, многое говорит о тебе
  3. Visual Averages — твит-шторм Кайла Макдональда о культурных ценностях путем визуализации средних рисунков по странам.

Выполнение

Теперь давайте посмотрим, как мы можем создавать рисунки летучих мышей, используя Deep Convolutional GAN ​​с кодом, написанным с использованием TensorFlow, начиная с сетевых архитектур.

Дискриминатор

Модель дискриминатора – это сверточная нейронная сеть, которая принимает на вход изображение и выдает прогноз (классификацию) относительно того, является ли изображение настоящим (изображения из набора данных) или подделка(изображения, сгенерированные генератором). Код для создания модели выглядит следующим образом:

def make_discriminator_model():
    model = tf.keras.Sequential([
        layers.Conv2D(32, (5, 5), strides=(2, 2), padding='same', input_shape=[compress_size, compress_size, 1], activation="LeakyReLU"),
        layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same', activation="LeakyReLU"),
        layers.Dropout(0.1),
        
        layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same', activation="LeakyReLU"),
        layers.Dropout(0.1),
        
        layers.Conv2D(256, (5, 5), strides=(2, 2), padding='same', activation="LeakyReLU"),
        layers.Dropout(0.1),
        
        layers.Flatten(),
        layers.Dense(128, activation="relu"),
        layers.Dense(64, activation="relu"),
        layers.Dense(16, activation="relu"),
        layers.Dense(8, activation="relu"),
        layers.Dense(1, activation="linear"),  # Sigmoid calculated during loss calculation
    ], name="discriminator")
    assert model.input_shape == (None, base_size*4, base_size*4, 1)

    return model

Чтобы обучить модель дискриминатора, нам нужно определить стоимость сети, которую необходимо минимизировать. Мы определяем эту стоимость как BinaryCrossEntropy потерю, поскольку мы классифицируем бинарные метки — настоящие и поддельные изображения.

cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)

def discriminator_loss(real_output, fake_output):
    # y*log(y_hat)
    real_loss = cross_entropy(tf.ones_like(real_output), real_output)

    # (1 - y)*log(1 - y_hat)
    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
    
    # Log-loss for classification
    total_loss = real_loss + fake_loss
    return total_loss

Генератор

Генераторная модель — это глубокая сверточная сеть с несколькими этапами свертки, за каждым из которых следует пакетная нормализация и нелинейная (ReLU) активация. Код для создания модели выглядит следующим образом:

def make_generator_model():
    model = tf.keras.Sequential([
        layers.Dense(base_size*base_size*256, input_shape=(noise_dim,), use_bias=False),
        layers.BatchNormalization(),
        layers.LeakyReLU(),
    
        layers.Reshape((base_size, base_size, 256)),
    
        layers.Conv2DTranspose(210, (5, 5), strides=(1, 1), padding='same', use_bias=False),
        layers.BatchNormalization(),
        layers.LeakyReLU(),
    
        layers.Conv2DTranspose(175, (5, 5), strides=(1, 1), padding='same', use_bias=False),
        layers.BatchNormalization(),
        layers.LeakyReLU(),
    
        layers.Conv2DTranspose(150, (5, 5), strides=(1, 1), padding='same', use_bias=False),
        layers.BatchNormalization(),
        layers.LeakyReLU(),
    
        layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False),
        layers.BatchNormalization(),
        layers.LeakyReLU(),
    
        layers.Conv2DTranspose(80, (5, 5), strides=(1, 1), padding='same', use_bias=False),
        layers.BatchNormalization(),
        layers.LeakyReLU(),
    
        layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False),
        layers.BatchNormalization(),
        layers.LeakyReLU(),
    
        layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', activation='tanh', use_bias=False),
    ], name="generator")
    assert model.output_shape == (None, base_size*4, base_size*4, 1)

    return model

Чтобы обучить модель генератора, нам нужно определить стоимость сети, которую необходимо минимизировать. Но теперь вычисление функции стоимости для генератора — задача, дающая пищу для размышлений. Как мы можем определить обучение для генератора? Что значит сказать, что сгенерированное изображение хорошее или плохое? Если подумать, награда для ученика — это обман мастера, а наказание для ученика — это мастер, поймавший ученика на ошибке. Таким образом, теперь мы можем разумно определить выходные данные сети дискриминатора как стоимость генератора, но только тогда, когда дискриминатор улавливает поддельные изображения. Поэтому мы определяем стоимость, как показано ниже.

cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)

def generator_loss(fake_output):
    # - (1 - y)*log(y_hat)
    return cross_entropy(tf.ones_like(fake_output), fake_output)

Обучение

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

  1. Создайте такое же количество поддельных изображений, сколько есть реальных изображений на этапе обучения.
  2. Запишите (запишите) шаги прямого распространения для расчета потерь
  • Создание изображений из шума
  • Генерация прогнозов дискриминатора из оригинальных и поддельных изображений
  • Рассчитайте потери генератора и дискриминатора из предопределенных функций

3. Получите градиенты на основе потерь для всех обучаемых параметров.

4. Примените один шаг градиентного спуска с помощью оптимизатора Адама для всех обучаемых параметров.

Код, реализующий вышеуказанные шаги, выглядит следующим образом:

@tf.function
def train_step(images):
    # Step-1
    noise = tf.random.uniform([BATCH_SIZE, noise_dim], minval=-1)

    # Step-2
    with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
        # Step-2.1
        generated_images = generator(noise, training=True)
        
        # Step-2.2
        real_output = discriminator(images, training=True)
        fake_output = discriminator(generated_images, training=True)
  
        # Step-2.3
        gen_loss = generator_loss(fake_output)
        disc_loss = discriminator_loss(real_output, fake_output)

    # Step-3
    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
    gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)

    # Step-4
    generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))
    discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))

Параметры, которые мы используем для обучения, следующие:

Вот некоторые соображения, которые нам пришлось рассмотреть перед выбором параметров.

  1. Количество изображений. Мы рассмотрели все изображения в наборе данных, поскольку каждый дудл чрезвычайно зашумлен, учитывая, что это всего лишь грубый набросок, сделанный менее чем за 20 секунд.
  2. Размер изображения — размер каждого изображения из исходного набора данных составлял 255*255 пикселей, состоящих из 3 каналов. Мы выполнили предварительную обработку этих изображений, чтобы удалить каналы RGB, поскольку все эскизы были только черно-белыми. Мы также уменьшили размер до 72*72 из-за ограничений в вычислениях.
  3. Эпохи. Это критический параметр, поскольку обучение модели на меньшем количестве эпох просто создает изображения со случайным шумом, поскольку генератор не изучил бы особенности набора данных. С другой стороны, обучение модели со слишком большим количеством эпох также неблагоприятно, потому что, когда дискриминатор устанавливает минимумы, генератор начинает генерировать тот же случайный шум, что и его изображение, которое дискриминатор не может классифицировать как поддельное изображение.

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

for epoch in range(epochs):
    start = time()
    for image_batch in dataset:
        train_step(image_batch)
    print (f'Time for epoch {epoch + 1} is {time()-start)} sec')

Полученные результаты

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

Посмотрим, что генерирует генератор после каждой эпохи обучения для одних и тех же входных векторов (25 векторов)

Давайте посмотрим, как изменение входного вектора (случайные значения) и перемещение входного вектора во входном пространстве влияет на то, какие изображения он создает.

На эти выходные данные действительно интересно смотреть, и мы можем создать множество таких вариаций, еще больше перемещаясь по нашему входному вектору. Это подводит нас к концу этого очень интересного способа взаимодействия с системами машинного обучения. Генеративное моделирование с течением времени имело множество применений, и достижения в этой области только добавляют к этому. Успешное генеративное моделирование продолжает предоставлять альтернативные и потенциально более предметно-ориентированные решения для множества вариантов использования в мире ИИ и машинного обучения. Наш проект был скромной попыткой понять основы последних инноваций. Всем спасибо за чтение!

Ссылки

  1. Полный код этого проекта можно найти здесь, на GitHub.
  2. Дополненное YouTube видео с кратким обзором нашего проекта
  3. Блог Google AI о наборе данных QuickDraw
  4. Нежное введение в генеративно-состязательные сети (GAN)
    Джейсона Браунли
  5. Генеративно-состязательные сети — Ян Дж. Гудфеллоу и др. др.

Авторы

  • Чьяван Майсур Чандрашекар (CM65624)
  • Рочан Нехете (RRN479)
  • Ручи Шарма (RS58898)
  • Шрикар-Ланка (SL54387)
  • Ришаб Тивари (RT27739)