В 2015 году Леон Гэтис и др. предложили метод передачи нейронного стиля в своей статье «Нейронный алгоритм художественного стиля» (arXiv: 1508.06576v2). Меня всегда восхищал тот факт, что модели нейронных сетей способны передавать что-то вроде передачи стилей, и в то время результаты казались мне волшебством. В этой короткой статье я хочу остановиться на реализации исходного алгоритма Neural Style Transfer с использованием PyTorch.

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

Во-первых, я хочу рассмотреть основы подхода NST - извлечение признаков. Сверточные нейронные сети обладают тем, что не могут быть полностью связаны или рекуррентными нейронными сетями, - интерпретируемыми извлеченными функциями. Если вы знакомы с тем, как сверточные слои распространяют сигнал, вы, скорее всего, видели карты активации и визуализации фильтров. Если нет, то хорошим ориентиром является раздел 5.4 книги Франсуа Шоле «Глубокое обучение с помощью Python». Кроме того, на рис. 1.2 в «Глубинном обучении» Яна Гудфеллоу и др. отличный справочник.

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

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

Я надеюсь, что вы не слишком заблудились (если вообще потерялись) на этом этапе, поскольку методология переноса нейронного стиля сначала немного сбивает с толку, но, в конечном итоге, очень прямолинейна.

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

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

Вот несколько иллюстраций, которые я использовал для модели передачи нейронного стиля:

Реализация

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

Gatys et al. Предлагаю извлечь 2 набора функций, используя модель VGG19. Первый набор функций, функции содержимого, извлекается из изображения содержимого с помощью слоя модели «block4_conv2». Второй набор функций, особенности стиля, извлекается из изображения стиля с использованием слоев «block1_conv1», «block2_conv1», «block3_conv1», «block4_conv1», «block5_conv1».

Почему мы извлекаем стиль из 5 слоев, а не из 1? На рисунке 1 в документе представлено отличное сравнение реконструкций стиля и содержания с использованием различных слоев модели VGG19. Итак, ответ будет: эмпирически стиль выглядит более привлекательным, если мы используем эти 5 слоев. Эксперименты с количеством слоев и другими факторами дают конкретные результаты, и мы эмпирически выбираем лучшие из них, как и в любой другой модели нейронного машинного обучения. Обратите внимание, что если мы будем использовать другую модель, а не VGG19, почти наверняка нам придется выбрать некоторые другие наборы слоев как для извлечения содержимого, так и для извлечения стилей.

Следовательно, я определяю настраиваемую производную VGG, как показано ниже:

Теперь, когда у нас определена сеть экстракторов функций VGG, мы можем продолжить.

Gatys et al. упомянуть, что для лучших результатов они заменяют максимальные слои объединения на средние слои объединения:

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

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

Мы можем извлечь функции контента, передав изображение контента через нашу сеть VGG и собирая выходные данные слоя «block4_conv2». Теперь результирующие активации имеют размеры CxHxW. Гэтис предлагает развернуть карты активации в двумерные тензоры CxH * W, так что мы тоже это сделаем:

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

Теперь нам нужно извлечь стиль из изображения стиля. Мы делаем это, передавая образ стиля через нашу сеть VGG и собирая выходные данные слоев «block1_conv1», «block2_conv1», «block3_conv1», «block4_conv1», «block5_conv1». Затем мы собираемся «развернуть» их, как мы это делали с активациями контента.

Есть дополнительный шаг, связанный с извлечением стиля из изображения стиля. Сами по себе черты недостаточны для извлечения стиля изображения. Однако Gatys et al. предлагаем вычислить пространственные корреляции в каждой активации слоя. Мы можем сделать это, вычислив матрицы Грамиана для каждого слоя. Матрица грамиана - это скалярное произведение столбцов входных тензоров и транспонирования этих столбцов. В матрице Грамиана мы можем видеть, какие пространственные особенности доминируют над другими:

Объединение извлечения стиля дает:

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

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

При этом потери определены в уравнениях (1) и (4–5) в исходной статье. Я использую оптимизатор Adam для поиска в пространстве изображений. Мы можем определить наш шумовой образ и оптимизатор следующим образом:

Обратите внимание, что многие реализации предлагают использовать алгоритм оптимизации L-BFGS. Однако, чтобы упростить понимание и показать, что на самом деле не имеет значения, какой оптимизатор мы используем, я использую оптимизатор Adam.

Теперь мы начинаем наш цикл оптимизации. Процесс можно резюмировать следующим образом:

  1. Получите контентные активации шумового изображения из VGG
  2. «Разверните» контентные активации шумового изображения
  3. Вычислите потерю контента: SSE между активациями контента изображения контента и активациями контента шумового изображения
  4. Получите активацию стиля шумового изображения из VGG
  5. «Разверните» активацию стиля шумового изображения
  6. Рассчитайте матрицы Грама для каждого слоя активации стиля шумового изображения.
  7. Сложите взвешенные потери стиля для каждого слоя вместе
  8. Сложите взвешенный стиль и потери контента вместе
  9. Обратное распространение

10. Обновите пиксели шумового изображения.

11. Повторите 1–10.

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

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

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

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

Результаты

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

Извлечение стилей

Передача стиля

У исходного метода есть один серьезный недостаток: мы должны запускать алгоритм оптимизации каждый раз, когда мы хотим выполнить перенос стиля. Кроме того, выполнение передачи стиля с использованием исходного метода действительно затратно с вычислительной точки зрения (по крайней мере, с оптимизатором Adam), поэтому он не очень хорошо подходит для онлайн-использования или производства.

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

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

Спасибо за чтение!

РЕДАКТИРОВАТЬ. Вот несколько интересных GIF-изображений обретающего форму шума, которые я записал во время тренировки: