В этом посте я расскажу о частичной повторной реализации недавней статьи о множественной регуляризации (Lecouat et al., 2018) для полууправляемого обучения с помощью Generative Adversarial Networks (Goodfellow et al., 2014). Я попытаюсь заново реализовать их основной вклад, вместо того, чтобы правильно понимать все детали гиперпараметров. Кроме того, для демонстрации, временных ограничений и простоты я буду рассматривать набор данных MNIST, а не наборы данных CIFAR10 или SVHN, как это сделано в документе. В конечном итоге этот пост направлен на преодоление разрыва между теорией и реализацией сетей GAN в условиях полууправляемого обучения. Код этого сообщения можно найти здесь.

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

Давайте быстро рассмотрим генеративные состязательные сети (GAN). Что касается текущего темпа в сообществе AI / ML, они существуют уже некоторое время (всего около 4 лет), так что вы, возможно, уже знакомы с ними. «Ванильная» процедура GAN состоит в том, чтобы обучить генератор генерировать реалистичные изображения, способные обмануть дискриминатор. Генератор генерирует изображения с помощью глубокой нейронной сети, которая принимает вектор шума z.

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

Полу-контролируемое обучение

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

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

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

Реализация

Давайте просто перейдем к реализации, поскольку это может быть лучшим способом понять, что происходит. Приведенный ниже фрагмент подготавливает данные. На самом деле в нем нет ничего сложного. По сути, мы берем 400 выборок для каждого класса и объединяем полученные массивы как наше фактическое контролируемое подмножество. Набор данных без меток состоит из всех данных поезда (он также включает данные с метками, поскольку мы можем их использовать в любом случае). Как это принято сейчас для обучения GAN, на выходе генератора используется функция гиперболического тангенса, то есть его выход находится между -1 и +1. Поэтому мы также масштабируем данные, чтобы они находились в этом диапазоне. Затем мы создаем TensorFlow итератора, чтобы мы могли эффективно обрабатывать данные позже, не сталкиваясь с запросами каналов.

Следующим шагом является определение дискриминаторной сети. Я немного отклонился от архитектуры в бумаге. Я собираюсь перестраховаться и просто использую слои Keras для построения модели. Фактически, это позволяет нам очень удобно повторно использовать все веса для разных входных тензоров, что окажется полезным позже. Короче говоря, архитектура дискриминатора использует 3 свертки с ядрами 5x5 и шагами 2x2, 2x2 и 1x1 соответственно. За каждой сверткой следует активация негерметичного ReLU и выпадающий слой со скоростью выпадения 0,3. Сглаженный вывод этого стека сверток будет использоваться как слой функций.

Слой объектов можно использовать для потери соответствия признаков (а не для сигмовидной перекрестной энтропии потери, как в ванильных GAN), что, как оказалось, обеспечивает более надежный процесс обучения. Часть сети до этого векторного слоя определена в _define_tail во фрагменте ниже. Метод _define_head определяет остальную часть сети. Голова сети представляет только один дополнительный полностью связанный слой с 10 выходами, которые соответствуют логитам меток классов. Помимо этого, есть несколько способов заставить интерфейс экземпляра Discriminator вести себя аналогично интерфейсу экземпляра tf.keras.models.Sequential.

В архитектуре генератора также используются 5x5 ядра. Многие реализации архитектур, подобных DCGAN, используют транспонированные свертки (иногда ошибочно называемые деконволюциями). Я решил попробовать альтернативу повышающей дискретизации-свертке. Это должно решить проблему шахматного узора, который иногда появляется на сгенерированных изображениях. Помимо этого, существуют нелинейности ReLU и первый уровень, который переходит от 100-мерного шума к пространственному представлению (довольно неудобной формы) 7x7x64.

Я попытался заставить эту модель работать с тем, что могут предложить слои Keras TensorFlow, чтобы код было легко переваривать (и, конечно же, реализовывать). Это также означает, что я отклонился от архитектур, описанных в документе (например, я не использую нормализацию веса). Благодаря этому экспериментальному подходу я также убедился, насколько чувствительна обучающая установка к небольшим изменениям сетевой архитектуры и параметров. Здесь перечислено множество изящных хаков GAN, которые я определенно нашел полезными.

Собираем все вместе

Давайте теперь проведем предварительные вычисления, чтобы увидеть, как все вышеперечисленное объединяется. Это состоит из настройки входного конвейера, вектора шума, генератора и дискриминатора. Все это делает приведенный ниже фрагмент. Обратите внимание, что когда define_generator возвращает экземпляр Sequential, мы можем просто использовать его как функтор, чтобы получить его выходные данные для тензора шума, заданного параметром z.

Дискриминатор сделает гораздо больше. Потребуется (i) «поддельные» изображения, поступающие от генератора, (ii) пакет немаркированных изображений и, наконец, (iii) пакет помеченных изображений (как с, так и без dropout, чтобы также сообщить о точности поезда). Мы можем просто многократно вызывать экземпляр Discriminator, чтобы построить график для каждого из этих выходов. Керас позаботится о том, чтобы переменные использовались повторно во всех случаях. Чтобы отключить прерывание для помеченных данных обучения, мы должны явно передать training=False.

Потеря дискриминатора

Напомним, что дискриминатор будет делать больше, чем просто отделять подделку от реальной. Он также классифицирует помеченные данные. Для этого мы определяем контролируемые потери, которые принимают выходной сигнал softmax. С точки зрения реализации это означает, что мы скармливаем ненормализованные логиты tf.nn.sparse_cross_entropy_with_logits.

Определение потерь для неконтролируемой части - вот где все становится немного сложнее. Поскольку распределение softmax чрезмерно параметризовано, мы можем исправить ненормализованный логит на 0, чтобы изображение было фальшивым (то есть исходящим от генератора). Если мы это сделаем, вероятность того, что это реально, просто превратится в:

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

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

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

Мы еще не там. Генеративное состязательное обучение требует от нас подняться по градиенту:

Поэтому всякий раз, когда мы вызываем tf.train.AdamOptimizer.minimize, мы должны спуститься:

Первый член в правой части уравнения можно записать:

Второй член правой части можно записать как:

Итак, в итоге мы приходим к следующей потере:

Оптимизация дискриминатора

Настроим операции по актуальному обновлению параметров дискриминатора. Мы просто будем использовать оптимизатор Adam. Настраивая параметры до того, как я написал этот пост, я подумал, что могу замедлить дискриминатор, установив его скорость обучения в 0,1 раза выше скорости генератора. После этого мои результаты стали намного лучше, поэтому я решил пока оставить это там. Также обратите внимание, что мы можем очень легко выбрать подмножество переменных, соответствующее дискриминатору, используя инкапсуляцию, предлагаемую Keras.

Добавление некоторого потока управления к графу

После того, как у нас есть новые веса для дискриминатора, мы хотим, чтобы обновление генератора знало об обновленных весах. TensorFlow не гарантирует, что обновленные веса действительно будут использоваться, даже если мы повторно объявим прямое вычисление после определения операций минимизации для дискриминатора. Мы все еще можем принудительно использовать tf.control_dependencies. Любая операция, определенная в области действия этого диспетчера контекста, будет зависеть от оценки тех, которые передаются диспетчеру контекста при создании экземпляра. Другими словами, обновление нашего генератора, которое мы определим позже, будет гарантированно вычислять градиенты с использованием обновленных весов дискриминатора.

Потеря и обновления генератора

В этой реализации генератор пытается минимизировать расстояние L2 между средними характеристиками сгенерированных изображений и средними характеристиками реальных изображений. . Эта потеря соответствия характеристик (Salimans et al., 2016) оказалась более стабильной для обучения GAN, чем прямая попытка оптимизировать вероятность дискриминатора для наблюдения за реальными данными. Это просто реализовать. Пока мы занимаемся этим, давайте также определим операции обновления для генератора. Обратите внимание, что скорость обучения этого оптимизатора в 10 раз выше, чем у дискриминатора.

Добавление регуляризации многообразия

Lecouat et. al (2018) предлагают добавить множественную регуляризацию к процедуре обучения GAN по сопоставлению характеристик, предложенной Salimans et al. (2016) . Регуляризация заставляет дискриминатор выдавать аналогичные логиты (ненормализованные логарифмические вероятности) для ближайших точек в скрытом пространстве, в котором находится z. Это может быть реализовано путем генерации второй возмущенной версии z и повторного вычисления выходных сигналов генератора и дискриминатора с этим слегка измененным вектором.

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

Потери дискриминатора будут обновлены следующим образом (обратите внимание на 3 дополнительные строки внизу):

Классификация

Так как же это работает на самом деле? Ниже я привел несколько сюжетов. Есть много вещей, которые я мог бы попытаться выжать дополнительную производительность (например, просто тренировка на более длительный срок, использование графика скорости обучения, реализация нормализации веса), но основной целью написания этого поста было познакомиться с относительно простым, но мощным полу-контролируемый подход к обучению. После 100 эпох обучения средняя точность теста приближается к 98,9 процента.

Полный сценарий можно найти здесь. Спасибо за прочтение!