Проект Google Deepmind.

Цель этой публикации - реализовать и понять статью Google Deepmind DRAW: рекуррентная нейронная сеть для генерации изображений. Код основан на работе Эрика Джанга, который в своем исходном коде смог добиться реализации всего в 158 строках кода Python.

Начнем с объяснения того, что означает DRAW…

Deep Recurrent Attentive Writer (DRAW) - это архитектура нейронной сети для генерации изображений. Сети DRAW сочетают в себе новый механизм пространственного внимания, имитирующий ямку человеческого глаза, с последовательной системой вариационного автокодирования, которая позволяет итеративно создавать сложные изображения.

Система существенно улучшает современные генеративные модели на основе MNIST, и при обучении на наборе данных Street View House Numbers она генерирует изображения, которые невозможно отличить от реальных данных невооруженным глазом.

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

DRAW архитектура

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

3 основных различия между DRAW и автокодировщиками

  1. И кодер, и декодер являются повторяющимися сетями в DRAW.
  2. Выходные данные декодера последовательно добавляются к распределению, чтобы генерировать данные, вместо того, чтобы генерировать это распределение за один шаг.
  3. Механизм динамически обновляемого внимания используется для ограничения как области ввода, наблюдаемой кодером, так и области вывода, измененной декодером. Проще говоря, сеть решает на каждом временном шаге, «где читать» и «куда писать», а также «что писать».

Слева: обычный вариационный автокодировщик.
Во время генерации образец z извлекается из предыдущего P (z) и передается через сеть декодера с прямой связью для вычисления вероятности входа P (x | z) с учетом выборки.

Во время вывода входной x передается в сеть кодировщика, производя приблизительный апостериорный Q (z | x) по скрытым переменным. Во время обучения z выбирается из Q (z | x), а затем используется для вычисления общей длины описания KL (Q (Z | x) ∣∣ P (Z) −log (P (x | z)), который минимизируется с помощью стохастического градиентного спуска.

Справа: сеть DRAW.
На каждом временном шаге образец z_t из предыдущего P (z_t) передается в повторяющийся сеть декодера, которая затем модифицирует часть матрицы холста. Последняя матрица холста cT используется для вычисления P (x | z_1: T).

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

Функция потерь

Окончательная матрица холста cT используется для параметризации модели D (X | cT) входных данных. Если вход двоичный, естественным выбором для D является распределение Бернулли со средними значениями, заданными как σ (cT). Потеря реконструкции Lx определяется как отрицательная логарифмическая вероятность x при D:

Скрытая потеря

для последовательности скрытых распределений

определяется как суммарное расхождение Кульбака-Лейблера некоторого скрытого априорного P (Z_t) от

Обратите внимание, что эта потеря зависит от скрытых выборок z_t, взятых из

которые, в свою очередь, зависят от входа x. Если скрытое распределение является диагональным гауссовским с μt, σt, где:

простой выбор для P (Z_t) - это стандартная гауссианская система с нулевым средним и единичным стандартным отклонением, и в этом случае уравнение принимает следующий вид:

Общие потери L для сети - это математическое ожидание суммы восстановления и скрытых потерь:

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

L ^ z можно интерпретировать как количество nats, необходимое для передачи скрытой последовательности выборок z_1: T в декодер из предыдущего, и (если x дискретно) L ^ x - количество натсов, необходимое декодеру для восстановления x с заданным z_1: T. Таким образом, общие потери эквивалентны ожидаемому сжатию данных декодером и предварительным сжатием.

Улучшение изображений

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

Улучшение изображения или прогрессивное уточнение - это просто разрушение нашего совместного распределения P (C) снова и снова, что приводит к цепочке скрытых переменных C1, C2,… CT − 1 к новому распределению наблюдаемых переменных P (CT).

Уловка состоит в том, чтобы несколько раз взять выборку из итеративного уточняющего распределения P (Ct | Ct-1), а не использовать прямую выборку из P (C).

В модели DRAW P (Ct | Ct − 1) является одинаковым распределением для всех t, поэтому мы можем компактно представить это как следующее рекуррентное соотношение (если нет, тогда у нас есть цепь Маркова вместо рекуррентной сети)

Применена модель DRAW

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

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

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

По сути, если мы думаем о скрытом коде в VAE (вариационном автокодировщике) как о векторе, представляющем все изображение, скрытые коды в DRAW можно рассматривать как векторы, которые представляют штрих пера. В конце концов, последовательность этих векторов создает воссоздание исходного изображения.

Хорошо, но как это на самом деле работает?

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

«Прочитанное» внимание состоит из двух частей:

  1. Выбор важной порции
  2. Обрезать изображение и забыть о других частях

Выбор важной части изображения

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

Обрезка изображения

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

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

Постойте ... это действительно сделано на практике?

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

В DRAW мы берем массив гауссовых фильтров, центры каждого из которых равномерно разнесены.

Покажи мне деньги… или код.

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

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

Давайте теперь соберем код воедино для завершения.

Полную записную книжку вы можете увидеть на моей странице на github.

Подпишитесь на бесплатную пробную версию сегодня, кредитная карта не требуется!

Об авторе: Самуэль Норьега - выпускник магистра наук о данных Барселонского университета. Он является руководителем отдела анализа маркетинговых данных в Shugert Analytics и соавтором 3Blades. Недавно стал соучредителем Roomies.es.