Туториал: перенос стиля изображений с TensorFlow

style transfer tutorial tensorflow

Перенос стиля (style transfer)  — одно из наиболее креативных приложений сверточных нейронных сетей. Взяв контент с одного изображения и стиль от второго, нейронная сеть объединяет их в одно художественное произведение.

Перевод статьи Introduction to Neural Style Transfer with TensorFlow, автор — Marco Peixeiro, ссылка на оригинал — в подвале статьи.

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

Пример сгенерированных изображений с использованием Prisma
Пример сгенерированных изображений с использованием Prisma

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

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

Не стесняйтесь сверяться с тетрадкой с реализацией, если вы застрянете на каком-нибудь шаге.

Что ж, давайте сделаем это!

Шаг 1. Загрузка VGG-19

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

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

Процесс использования уже обученной ранее нейронной сети для других целей называется transfer learning.

Давайте загрузим модель:

model = load_vgg_model(«pretrained-model / imagenet-vgg-verydeep-19.mat»)

Какая же она большая! Теперь я буду использовать изображение Лувра в качестве изображения контента. Вы можете загрузить любое изображение, которое хотите, но убедитесь, что оно не слишком большое:

content_image = scipy.misc.imread («images / louvre.jpg»)
imshow (content_image)

В моем случае это выглядит так:

Лувр

Шаг 2. Определение функции потерь для контента

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

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

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

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

Из приведенного выше уравнения n_H и n_W — это высота и ширина изображения соответственно, где n_c — количество каналов в скрытом слое.

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

Развертывание сверточного слоя для расчета loss
Развертывание сверточного слоя для расчета значения функции потерь

Теперь мы кодируем эту логику:

Шаг 3. Определение функции потерь для стиля

Теперь нам нужен стиль изображения. В моем случае я буду использовать работу импрессиониста Клода Моне, но не стесняйтесь использовать любой другой вид искусства:

Клод Моне Маковое поле
Кто-нибудь может сказать мне название этой картины?

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

Шаг 3.1. Определить матрицу Грама

Матрица стилей также называется матрицей Грама и представляет собой скалярное произведение точек набора векторов.

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

Матрица Грама
Матрица Грама

Вот так матрица Грама может эффективно измерять стиль изображения.

Реализуем это в коде:

Шаг 3.2. Определение функции потерь для стиля

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

Другими словами, мы хотим минимизировать расстояние между двумя матрицами Грама. Это выражается как:

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

Кодирование логики:

Шаг 3.3. Назначение весов разным слоям стиля

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

Тогда математически функция потерь стиля становится:

Функция потерь для комбинированного стиля всех слоев
Функция потерь для комбинированного стиля всех слоев

где λ — вес для каждого слоя.

Таким образом, мы добавляем эту ячейку кода:

Шаг 3.4. Объединение всего

Теперь мы просто объединяем все в единую функцию потерь для стиля:

Шаг 4. Определение общей функции потерь

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

Общая функция потерь
Общая функция потерь

где α и β — произвольные веса.

Шаг 5. Решение задачи оптимизации и создание изображения

А теперь лучшая часть! Со всем на месте, мы можем решить задачу оптимизации и создать художественный образ!

# Reset the graph
tf.reset_default_graph()
# Start interactive session
sess = tf.InteractiveSession()
content_image = scipy.misc.imread("images/louvre_small.jpg")
content_image = reshape_and_normalize_image(content_image)
style_image = scipy.misc.imread("images/monet.jpg")
style_image = reshape_and_normalize_image(style_image)
model = load_vgg_model("weights/imagenet-vgg-verydeep-19.mat")
# Assign the content image to be the input of the VGG model.
sess.run(model['input'].assign(content_image))
# Select the output tensor of layer conv4_2
out = model['conv4_2']
# Set a_C to be the hidden layer activation from the layer we have selected
a_C = sess.run(out)
# Set a_G to be the hidden layer activation from same layer. Here, a_G references model['conv4_2']
# and isn't evaluated yet. Later in the code, we'll assign the image G as the model input, so that
# when we run the session, this will be the activations drawn from the appropriate layer, with G as input.
a_G = out
# Compute the content cost
J_content = compute_content_cost(a_C, a_G)
# Assign the input of the model to be the "style" image
sess.run(model['input'].assign(style_image))
# Compute the style cost
J_style = compute_style_cost(model, STYLE_LAYERS)
J = total_cost(J_content,J_style,alpha=10,beta=40)
# define optimizer
optimizer = tf.train.AdamOptimizer(2.0)
# define train_step
train_step = optimizer.minimize(J)
def model_nn(sess, input_image, num_iterations = 200):
  # Initialize global variables (you need to run the session on the initializer)
  sess.run(tf.global_variables_initializer())
  # Run the noisy input image (initial generated image) through the model. Use assign().
  sess.run(model["input"].assign(input_image))
  for i in range(num_iterations):
    # Run the session on the train_step to minimize the total cost
    sess.run(train_step)
    # Compute the generated image by running the session on the current model['input']
    generated_image = sess.run(model['input'])
    # Print every 20 iteration.
    if i % 20 == 0:
      Jt, Jc, Js = sess.run([J, J_content, J_style])
      print("Iteration " + str(i) + " :")
      print("total cost = " + str(Jt))
      print("content cost = " + str(Jc))
      print("style cost = " + str(Js))
      # save current generated image in the "/output" directory
      save_image("output/" + str(i) + ".png", generated_image)
      # save last generated image
  save_image('output/generated_image.jpg', generated_image)
  return generated_image
model_nn(sess, generated_image)

В моем случае я получаю следующий результат:

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

Поздравляю! Вы только что выполнили перенос стиля изображений в TensorFlow! Смело меняйте изображения контента и стиля, а также поиграйтесь с количеством эпох и скоростью обучения (learning rate)

Подписаться
Уведомить о
guest

0 Comments
Межтекстовые Отзывы
Посмотреть все комментарии

gogpt