Разогреть

Допустим, в вашем районе проходит очень крутая вечеринка, на которую вы действительно хотите пойти. Но есть проблема. Чтобы попасть на вечеринку, нужен специальный билет - он давно раскуплен.

Подожди! Разве это не статья о генерирующих состязательных сетях? Да, это так. Но пока потерпите, оно того стоит.

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

Поскольку у вас нет талантов в боевых искусствах, единственный способ справиться с ними - это обмануть их очень убедительным поддельным билетом.

Однако с этим планом есть большая проблема - вы никогда не видели, как выглядит билет.

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

Чтобы помочь решить проблему, вы решаете позвонить своему другу Бобу, который сделает всю работу за вас.

Миссия Боба очень проста. Он попытается попасть на вечеринку с вашим фальшивым пропуском. Если ему откажут, он вернется к вам с полезными советами, как должен выглядеть билет.

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

Если не брать в расчет «маленькие дыры» в этом анекдоте, можно сказать, что именно так работают генеративные состязательные сети (GAN).

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

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

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

GAN - это генеративные модели, разработанные Goodfellow et al. в 2014 году. В конфигурации GAN две дифференцируемые функции, представленные нейронными сетями, заблокированы в игре. Два игрока (генератор и дискриминатор) играют разные роли в этой структуре.

Генератор пытается получить данные, основанные на некотором распределении вероятностей. Это было бы вы пытаетесь воспроизвести билеты на вечеринку.

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

Таким образом, игра следует с:

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

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

В статье DCGAN авторы описывают комбинацию некоторых методов глубокого обучения как ключевую для обучения GAN. Эти методы включают: (i) всю сверточную сеть и (ii) пакетную нормализацию (BN).

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

Без лишних слов, давайте углубимся в детали реализации и поговорим о GAN по ходу дела. Мы представляем реализацию Deep Convolutional Generative Adversarial Network (DCGAN). Наша реализация использует Tensorflow и следует некоторым практикам, описанным в документе DCGAN.

Генератор

Сеть имеет 4 сверточных слоя, за которыми следуют активации BN (за исключением выходного слоя) и Rectified Linear unit (ReLU).

На входе он принимает случайный вектор z (взятый из нормального распределения). После изменения формы z для получения четырехмерной формы мы передаем ее генератору, который запускает серию слоев с повышающей дискретизацией.

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

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

Шаг операции транспонированной свертки определяет размер выходного слоя. При «одинаковом» заполнении и шаге 2 размер выходных объектов будет вдвое больше, чем у входного слоя.

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

Короче говоря, генератор начинается с этого очень глубокого, но узкого входного вектора. После каждой свертки транспонирования z становится шире и мельче. Все свертки транспонирования используют размер ядра 5x5 с уменьшением глубины от 512 до 3, что соответствует цветному изображению RGB.


def transpose_conv2d(x, output_space):
    return tf.layers.conv2d_transpose(x, output_space, 
      kernel_size=5, strides=2, padding='same',
      kernel_initializer=tf.random_normal_initializer(mean=0.0,
                                                      stddev=0.02))

Последний слой выводит тензор 32x32x3, сжатый между значениями -1 и 1 с помощью функции Гиперболический тангенс (tanh).

Эта окончательная форма вывода определяется размером обучающих изображений. В этом случае при обучении SVHN генератор создает изображения размером 32x32x3. Однако при обучении для MNIST будет создано изображение в оттенках серого 28x28.

Наконец, обратите внимание, что перед подачей входного вектора z в генератор нам необходимо масштабировать его до интервала от -1 до 1. То есть, чтобы следовать выбору использования tanh функция.

def generator(z, output_dim, reuse=False, alpha=0.2, training=True):
    """
    Defines the generator network
    :param z: input random vector z
    :param output_dim: output dimension of the network
    :param reuse: Indicates whether or not the existing model variables should be used or recreated
    :param alpha: scalar for lrelu activation function
    :param training: Boolean for controlling the batch normalization statistics
    :return: model's output
    """
    with tf.variable_scope('generator', reuse=reuse):
        fc1 = dense(z, 4*4*512)

        # Reshape it to start the convolutional stack
        fc1 = tf.reshape(fc1, (-1, 4, 4, 512))
        fc1 = batch_norm(fc1, training=training)
        fc1 = tf.nn.relu(fc1)

        t_conv1 = transpose_conv2d(fc1, 256)
        t_conv1 = batch_norm(t_conv1, training=training)
        t_conv1 = tf.nn.relu(t_conv1)

        t_conv2 = transpose_conv2d(t_conv1, 128)
        t_conv2 = batch_norm(t_conv2, training=training)
        t_conv2 = tf.nn.relu(t_conv2)

        logits = transpose_conv2d(t_conv2, output_dim)

        out = tf.tanh(logits)
        return out

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

Дискриминатор также представляет собой 4-х слойную CNN с BN (кроме его входного слоя) и утечкой активации ReLU. Многие функции активации будут нормально работать с этой базовой архитектурой GAN. Однако дырявые ReLU очень популярны, потому что они помогают градиентам легче проходить через архитектуру.

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

def lrelu(x, alpha=0.2):
     # non-linear activation function
    return tf.maximum(alpha * x, x)

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

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

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

Наконец, дискриминатор должен выводить вероятности. Для этого мы используем функцию активации Logistic Sigmoid в финальных логитах.

def discriminator(x, reuse=False, alpha=0.2, training=True):
    """
    Defines the discriminator network
    :param x: input for network
    :param reuse: Indicates whether or not the existing model variables should be used or recreated
    :param alpha: scalar for lrelu activation function
    :param training: Boolean for controlling the batch normalization statistics
    :return: A tuple of (sigmoid probabilities, logits)
    """
    with tf.variable_scope('discriminator', reuse=reuse):
        # Input layer is 32x32x?
        conv1 = conv2d(x, 64)
        conv1 = lrelu(conv1, alpha)

        conv2 = conv2d(conv1, 128)
        conv2 = batch_norm(conv2, training=training)
        conv2 = lrelu(conv2, alpha)

        conv3 = conv2d(conv2, 256)
        conv3 = batch_norm(conv3, training=training)
        conv3 = lrelu(conv3, alpha)

        # Flatten it
        flat = tf.reshape(conv3, (-1, 4*4*256))
        logits = dense(flat, 1)

        out = tf.sigmoid(logits)
        return out, logits

Обратите внимание, что в этой структуре дискриминатор действует как обычный двоичный классификатор. Половину времени он получает изображения от обучающей выборки, а другую половину - от генератора.

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

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

Вот насколько важен дискриминатор. Фактически, генератор будет производить данные так же хорошо, как их распознает дискриминатор.

Убытки

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

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

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

def model_loss(input_real, input_z, output_dim, alpha=0.2, smooth=0.1):
    """
    Get the loss for the discriminator and generator
    :param input_real: Images from the real dataset
    :param input_z: random vector z
    :param out_channel_dim: The number of channels in the output image
    :param smooth: label smothing scalar
    :return: A tuple of (discriminator loss, generator loss)
    """
    g_model = generator(input_z, output_dim, alpha=alpha)
    d_model_real, d_logits_real = discriminator(input_real, alpha=alpha)

    d_model_fake, d_logits_fake = discriminator(g_model, reuse=True, alpha=alpha)

    # for the real images, we want them to be classified as positives,  
    # so we want their labels to be all ones.
    # notice here we use label smoothing for helping the discriminator to generalize better.
    # Label smoothing works by avoiding the classifier to make extreme predictions when extrapolating.
    d_loss_real = tf.reduce_mean(
        tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_real, labels=tf.ones_like(d_logits_real) * (1 - smooth)))

    # for the fake images produced by the generator, we want the discriminator to clissify them as false images,
    # so we set their labels to be all zeros.
    d_loss_fake = tf.reduce_mean(
        tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, labels=tf.zeros_like(d_model_fake)))

    # since the generator wants the discriminator to output 1s for its images, it uses the discriminator logits for the
    # fake images and assign labels of 1s to them.
    g_loss = tf.reduce_mean(
        tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, labels=tf.ones_like(d_model_fake)))

    d_loss = d_loss_real + d_loss_fake

    return d_loss, g_loss

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

Мы хотим, чтобы дискриминатор выдавал вероятности, близкие к 1 для реальных изображений и близкие к 0 для поддельных изображений. Для этого дискриминатору нужны две потери. Следовательно, общие потери дискриминатора складываются из этих двух частичных потерь. Один для максимизации вероятностей реальных изображений, а другой - для минимизации вероятности поддельных изображений.

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

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

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

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

Заключение

В настоящее время GAN - одна из самых актуальных тем в машинном обучении. Эти модели могут открыть для себя методы обучения без учителя, которые откроют для машинного обучения новые горизонты.

С момента его создания исследователи разрабатывают множество методов обучения GAN. В книге «Улучшенные методы обучения GAN» авторы описывают современные методы как для генерации изображений, так и для полууправляемого обучения.

Если вам интересно разобраться в этих предметах, я рекомендую прочитать Генеративные модели.

Также обратите внимание на:





А если вам нужно больше, это мой блог глубокого обучения.

Наслаждайтесь! Спасибо за чтение!

Благодарим Сэма Уильямса за эту потрясающую гифку с хлопком в ладоши! Смотрите в его посте.