fbpx
  • Туториал: перенос стиля изображений с 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)