Реализация передачи нейронного стиля в TensorFlow и pyTorch

Эта статья следует из того, что мы обсуждали в первой статье. Пока мы говорили об интуиции и теории того, как работает Neural Style Transfer, мы перейдем к реализации исходной статьи. Если это первая статья из этой серии, которую вы прочитали, я умоляю вас прочитать предыдущую статью, приведенную ниже. Мы подробно объяснили, как работает нейронный стиль, этот пост в основном посвящен его реализации. Понимание теоретических нюансов также поможет вам понять реализацию.

Учебное пособие по передаче нейронного стиля - Часть 1: Теория передачи нейронного стиля

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

Коды реализации можно найти здесь. [Pytorch, Tensorflow]

Примечание: вы, должно быть, обратили внимание на это, вместо того, чтобы разделить код на набор файлов потерь, создания сети и обучающего файла. Мы только что создали два файла с именами train_Pytorch.py ​​и train_TensorFlow.py, мы можем изменить этот рабочий процесс в будущих кодах.

Давайте попробуем получить целостное представление о фреймворках Deep Learning, прежде чем мы углубимся в реализацию кода. Поскольку мы будем использовать Python, я буду придерживаться основных фреймворков, таких как Pytorch и Tensorflow. Это будет очень поверхностный обзор библиотек. Мы могли бы написать еще одну статью, посвященную различным доступным фреймворкам и их плюсам и минусам. Есть несколько других фреймворков, которые вы, возможно, захотите изучить, включая, помимо прочего, Dynet, Chainer и недавно выпущенный фреймворк языка Julia под названием flux. Еще одна вещь, на которую стоит обратить внимание: хотя это фреймворки для встраивания моделей, доступны еще более высокоуровневые абстракции, такие как fastai, spacy и keras.

Pytorch был создан Facebook, заимствованным у torch в Lua и построенным на caffe2, а Tensorflow был создан Google. Tensorflow остается текущим выбором фреймворка для большинства людей, но Pytorch быстро набирает скорость и, как говорят, в некоторых аспектах лучше. И Pytorch, и Tensorflow создают нечто, называемое вычислительными графами, и основное различие между ними заключается в том, как эти два создают эти вычислительные графы. Pytorch создает динамические графики, а Tensorflow создает статические графики. Это означает, что в Pytorch вы можете манипулировать своим графиком на ходу, а в Tensorflow вы должны полностью определить его заранее. Tensorflow2.0 пытается включить в язык динамические графы в форме активного выполнения. Pytorch более питоничен и, следовательно, интуитивно понятен для изучения, в то время как Tensorflow имеет много хороших функций, таких как Tensorboard (платформа визуализации для ваших моделей), а также его легче запустить в производство.

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

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

В pyTorch способ создания сети перегружает nn.Module. он всегда состоит из двух частей: сначала это метод __init__, который инициализирует все ваши слои, а затем есть метод forward, метод forward используется для фактической передачи вашего данные по сети и получить на выходе. В этом коде я решил написать свою собственную сеть VGG19, хотя есть способы, которыми вы можете просто импортировать встроенную модель и веса нагрузки. Некоторые вещи, которые я решил делать определенным образом, - это сохранение средних слоев пула, вы также можете использовать максимальные слои пула.

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

Одна вещь, на которую следует обратить внимание в рамках фреймворков и в целом при репликации документов или создании ваших моделей, - это то, что обычно у нас есть данные о процессоре, но обучение моделей NN для повышения эффективности обучается на графических процессорах, поэтому, как вы можете видеть, мы используем .cuda () для передачи таких объектов, как наш стиль и изображение содержимого, на графический процессор. Мы загружаем предварительно обученные веса VGG в модель, эти предварительно обученные веса взяты из VGG, обученного на Imagenet. Расширение файлов сохраненных весов - .pth, и одна вещь, на которую следует обратить внимание, - это когда вы пишете свою сеть, сохраняете веса и пытаетесь запустить ее на моей, это может не сработать, хотя мы оба обучаем vgg сети, такие детали, как разные имена слоев, или выбор слоев, таких как max вместо среднего пула, может вызвать ошибку при загрузке весов.

Переходя к функции потерь, сначала у нас есть класс матрицы грамма. Как объяснялось в предыдущей статье, мы используем матрицу Грама для извлечения векторных слоев, а .bmm () означает пакетное умножение матрицы. При пакетном умножении матриц последние 2 измерения используются для матричного умножения, а остальная часть измерения обрабатывается как пакеты. Я написал пример в комментариях после переменной, чтобы дать представление о размерах матрицы. Потеря стиля вычисляется, сначала передавая слой для получения его матричного представления грамма, а затем принимая среднеквадратичную ошибку между представлением и целью. Пройдя остальное шаг за шагом, мы отсоединяем слои от сети VGG и добавляем их к соответствующим целевым слоям. Затем мы переходим к определению потерь, целей и слоев потерь. Наконец, у нас есть вес стиля и вес контента, и вы можете поэкспериментировать с этим, как хотите. В документе предлагается, что 1000 и 5 - это хороший способ начать, и чем выше вес вашего контента, тем больше вы сохраняете исходное изображение и Чем выше вес вашего стиля, тем больше он становится заметным, а содержание отходит на второй план.

Наконец, цикл обучения, цикл обучения является важной частью построения всех видов моделей глубокого обучения. Чтобы обрисовать общую структуру, мы делаем три вещи в любом цикле обучения: мы выбираем оптимизатор, вычисляем потери и после этого обратное распространение. В приведенном выше случае мы используем оптимизатор LBFGS. Мы видим, что мы вызываем функцию .zero_grad (), потому что pytorch по умолчанию накапливает градиенты, поэтому нам нужно каждый раз устанавливать градиенты на ноль перед их вычислением. Затем общие потери накапливаются путем прохождения всех слоев стиля и содержимого, после чего функция .backward () используется для вычисления градиентов и .step () используется для обновления весов. Причина, по которой функция потерь передается в step (), заключается в том, что в случае LBFGS потери необходимо вычислять несколько раз.

Теперь, вместо того, чтобы полностью изучать код Tensorflow, я постараюсь указать на основное отличие, поскольку остальная часть кода очень похожа на код pyTorch. Одно из фундаментальных отличий заключается в том, что в run_style_transfer () мы можем наблюдать, что создается переменная сеанса, это подводит нас к тому факту, что, поскольку тензорный поток создает статические графики, нам нужно сначала определить сеанс, после которого модель обучается в рамках этого сеанса. Оптимизатор Adam используется для обучения модели вместо LBGFS. В цикле обучения сеанс запускается путем передачи ему оптимизатора. и убыток обновляется только в том случае, если текущий убыток лучше (т.е. меньше), чем предыдущий. Модель VGG с предварительно загруженными весами импортируется из keras, а не пишется с нуля. Во время обучения модели это может занять некоторое время, поскольку тензорный поток загрузит веса, если они недоступны локально.

Итак, у вас есть это, вы успешно ознакомились с кодом для передачи нейронного стиля. Следующее, что нужно сделать, - это запустить код и самому увидеть результаты. Мы продолжим серию с Fast Neural Style Transfer, чтобы дать краткое введение. Когда вы тренируете сеть, вы можете заметить, что для каждого стиля вам нужно повторно обучать всю сеть. Лучшим подходом к решению проблемы может быть изучение весов стилей, их сохранение, а затем загрузка любого изображения содержимого, которое вы хотите, когда захотите. Это может показаться запутанным, так что ждите дальнейшую статью.

Большое спасибо за ожидание этой статьи.

Спасибо Вамшик Шетти за соавторство со мной этой серии.

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