Как использовать Keras для создания художественных произведений AI с «передачей стиля»

искусственный интеллект Keras алгоритм Нейронные сети
Как использовать Keras для создания художественных произведений AI с «передачей стиля»

За последние несколько лет сверточная нейронная сеть (CNN) стала передовым инструментом компьютерного зрения, широко используемым в промышленности и научных кругах. В дополнение к области распознавания лиц и вождения без водителя, CNN также был популярен в области искусства в последние годы.Репрезентативной технологией, полученной из него, является «передача стиля».На основе этой технологии родились многие приложения Meitu, например, пожар 2016 г. Приложение Prisma.

«Передача стиля» — интересный способ продемонстрировать мощь нейронных сетей. В 2015 году группа исследователей из Германии и США опубликовала статьюНейронный алгоритм художественного стиляПодробно обсуждается, как глубокие сверточные нейронные сети различают «содержание» и «стиль» на фотографиях. Авторы статьи показывают, как CNN может применять художественный стиль одной фотографии к другой, создавая новую привлекательную фотографию. И их подход не требует обучения новой нейронной сети, использование предварительно обученных весов из наборов данных, таких как ImageNet, работает хорошо.

В этой статье я (автор Валид Ахмад — прим. переводчика) покажу, как использовать популярную Python-библиотеку Keras для создания работ ИИ с «переносом стиля», общая идея та же, что и в методе из предыдущей статьи. Весь код в этой статьекликните сюдаПолучать.

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

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

Художественный стиль другого базового изображения, который мы хотим сохранить, назовем его a. Я выбрал известную картину в стиле барокко: "Скрипка на палитре".

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

##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
## Specify paths for 1) content image 2) style image and 3) generated image
##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

cImPath = './data/base_images/cat.jpg'
sImPath = './data/base_images/violin_and_palette.jpg'
genImOutputPath = './results/output.jpg'

##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
## 图像处理
##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
from keras import backend as K
from keras.applications.vgg16 import preprocess_input
from keras.preprocessing.image import load_img, img_to_array

targetHeight = 512
targetWidth = 512
targetSize = (targetHeight, targetWidth)

cImage = load_img(path=cImPath, target_size=targetSize)
cImArr = img_to_array(cImage)
cImArr = K.variable(preprocess_input(np.expand_dims(cImArr, axis=0)), dtype='float32')

sImage = load_img(path=sImPath, target_size=targetSize)
sImArr = img_to_array(sImage)
sImArr = K.variable(preprocess_input(np.expand_dims(sImArr, axis=0)), dtype='float32')

gIm0 = np.random.randint(256, size=(targetWidth, targetHeight, 3)).astype('float64')
gIm0 = preprocess_input(np.expand_dims(gIm0, axis=0))
gImPlaceholder = K.placeholder(shape=(1, targetWidth, targetHeight, 3))

Обратите внимание, что мы инициализируем glm0 значением float64 для последующей оптимизации. И чтобы избежать ошибок памяти для GPU, мы сохраняем cImArr и slmArr как float32.

Потеря контента

Цель потери контента состоит в том, чтобы гарантировать, что результирующая фотография x по-прежнему сохраняет «глобальный» стиль фотографии контента p. Например, в нашем случае мы хотим, чтобы результирующее изображение по-прежнему выглядело как кошка на фото p. Это означает, что лицо, уши, глаза и т. д. кошки узнаваемы. Для достижения этой цели функция потери контента определяется как среднеквадратическая ошибка между представлениями признаков p и x в заданном слое L соответственно. Функция потери контента:

это здесь,

  • F и P — две матрицы с N строками и M столбцами.

  • N — количество фильтров в данном слое L, M — количество пространственных элементов в карте объектов (высота умноженная на ширину) данного слоя I

  • F содержит представление объекта X в данном слое L

  • P содержит представление признаков p в данном слое L

def get_feature_reps(x, layer_names, model):
    """
    Get feature representations of input x for one or more layers in a given model.
    """
    featMatrices = []
    for ln in layer_names:
        selectedLayer = model.get_layer(ln)
        featRaw = selectedLayer.output
        featRawShape = K.shape(featRaw).eval(session=tf_session)
        N_l = featRawShape[-1]
        M_l = featRawShape[1]*featRawShape[2]
        featMatrix = K.reshape(featRaw, (M_l, N_l))
        featMatrix = K.transpose(featMatrix)
        featMatrices.append(featMatrix)
    return featMatrices

def get_content_loss(F, P):
    cLoss = 0.5*K.sum(K.square(F - P))
    return cLoss

потеря стиля

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

Матрица Грама — это квадратная матрица, содержащая скалярное произведение между каждым векторизованным фильтром уровня L. Поэтому матрицу можно рассматривать как нерегулярную матрицу фильтров уровня L.

def get_Gram_matrix(F):
    G = K.dot(F, K.transpose(F))
    return G

Затем мы можем определить функцию потери стиля в данном слое L как:

где A — матрица Грама фотографии стиля a, а G — матрица Грама сгенерированной фотографии x.

В большинстве сверточных нейронных сетей, таких как VGG, рецептивное поле восходящего слоя становится все больше и больше. По мере того, как рецептивное поле увеличивается, сохраняются более крупные черты входного изображения. Из-за этого мы должны выбрать несколько слоев для «передачи стиля», сочетая локальные и глобальные качества стиля. Чтобы сделать связь между этими слоями гладкой, мы можем присвоить вес w каждому слою и определить всю функцию потери стиля как:

def get_style_loss(ws, Gs, As):
    sLoss = K.variable(0.)
    for w, G, A in zip(ws, Gs, As):
        M_l = K.int_shape(G)[1]
        N_l = K.int_shape(G)[0]
        G_gram = get_Gram_matrix(G)
        A_gram = get_Gram_matrix(A)
        sLoss+= w*0.25*K.sum(K.square(G_gram - A_gram))/ (N_l**2 * M_l**2)
    return sLoss

Объединить две функции

Наконец, нам просто нужно присвоить весовые коэффициенты функции потери контента и функции потери стиля соответственно, и все готово!

Наконец-то у нас есть аккуратная и элегантная функциональная формула, которая позволяет нам использовать ⍺ и ß для настройки относительного влияния контента и стиля фотографий на результирующую фотографию. Следуя совету из этой статьи и моему собственному опыту, пусть ⍺ = 1, ß = 10 000 работает хорошо.

def get_total_loss(gImPlaceholder, alpha=1.0, beta=10000.0):
    F = get_feature_reps(gImPlaceholder, layer_names=[cLayerName], model=gModel)[0]
    Gs = get_feature_reps(gImPlaceholder, layer_names=sLayerNames, model=gModel)
    contentLoss = get_content_loss(F, P)
    styleLoss = get_style_loss(ws, Gs, As)
    totalLoss = alpha*contentLoss + beta*styleLoss
    return totalLoss

Сведения о приложении модели

Чтобы начать изменять наше сгенерированное изображение, чтобы минимизировать функцию потерь, мы должны определить еще две функции с помощью scipy и бэкэнда Keras. Во-первых, вычислите общие потери с помощью одной функции, а во-вторых, вычислите градиент с помощью другой функции. Результаты, полученные после двух расчетов, будут введены в функцию оптимизации Scipy в качестве целевой функции и функции градиента соответственно. Здесь мы используем алгоритм L-BFGS (BFGS с ограниченной памятью).

Для каждой фотографии контента и фотографии стиля мы извлекаем представления объектов, которые используются для построения P и A (для каждого выбранного слоя стиля), а затем присваиваем равные веса слоям стиля. На практике алгоритм L-BFGS обычно дает более надежные результаты после более чем 500 итераций.

def calculate_loss(gImArr):
  """
  Calculate total loss using K.function
  """
    if gImArr.shape != (1, targetWidth, targetWidth, 3):
        gImArr = gImArr.reshape((1, targetWidth, targetHeight, 3))
    loss_fcn = K.function([gModel.input], [get_total_loss(gModel.input)])
    return loss_fcn([gImArr])[0].astype('float64')

def get_grad(gImArr):
  """
  Calculate the gradient of the loss function with respect to the generated image
  """
    if gImArr.shape != (1, targetWidth, targetHeight, 3):
        gImArr = gImArr.reshape((1, targetWidth, targetHeight, 3))
    grad_fcn = K.function([gModel.input], 
                          K.gradients(get_total_loss(gModel.input), [gModel.input]))
    grad = grad_fcn([gImArr])[0].flatten().astype('float64')
    return grad

from keras.applications import VGG16
from scipy.optimize import fmin_l_bfgs_b

tf_session = K.get_session()
cModel = VGG16(include_top=False, weights='imagenet', input_tensor=cImArr)
sModel = VGG16(include_top=False, weights='imagenet', input_tensor=sImArr)
gModel = VGG16(include_top=False, weights='imagenet', input_tensor=gImPlaceholder)
cLayerName = 'block4_conv2'
sLayerNames = [
                'block1_conv1',
                'block2_conv1',
                'block3_conv1',
                'block4_conv1',
                ]

P = get_feature_reps(x=cImArr, layer_names=[cLayerName], model=cModel)[0]
As = get_feature_reps(x=sImArr, layer_names=sLayerNames, model=sModel)
ws = np.ones(len(sLayerNames))/float(len(sLayerNames))

iterations = 600
x_val = gIm0.flatten()
xopt, f_val, info= fmin_l_bfgs_b(calculate_loss, x_val, fprime=get_grad,
                            maxiter=iterations, disp=True)

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

Мы начинаем видеть надвигающуюся кубистическую версию котенка! После того, как алгоритм повторяется еще несколько раз:

Мы можем немного изменить фотографию в соответствии с размером исходного изображения кошки и поместить два изображения рядом. Легко заметить, что основные черты кошки, такие как глаза, нос и когти, остались в своем первоначальном состоянии. Однако, чтобы соответствовать стилю фотографии, они были сплющены и угловаты — а это именно то, что мы хотели!

Мы используем тот же метод, но пробуем другие фотографии. Например, я нашел в гугле архитектурный чертеж, а затем выбрал известную картину Ван Гога «Звездная ночь на Роне»:

Работает после переноса стиля:

Суммировать

В этой статье мы рассмотрели, как применить технику «переноса стиля» с помощью Keras, но есть еще много работы, которую можно проделать, чтобы создать что-то более привлекательное:

  • Поэкспериментируйте с разными весами: для разных сочетаний фотографий может потребоваться настройка веса потери стиля w или постоянная оптимизация значений ⍺ и ß. Например, в некоторых случаях соотношение ß/⍺, равное 10⁵, работает лучше.

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

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

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

Вот некоторые из материалов, на которые я ссылался, вы можете взглянуть:

Ссылка 1

Ссылка 2


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