За последние несколько лет сверточная нейронная сеть (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 или комбинировать разные архитектуры нейронных сетей.
-
Попробуйте несколько фотографий контента и фотографий стиля: вы можете добавить несколько фотографий стиля к функции потери, смешать несколько фотографий или несколько художественных стилей. Добавление фотографий контента может привести к более интересным художественным эффектам.
-
Добавлен метод полного вариационного шумоподавления: если вы внимательно посмотрите на фотографию, которую я получил выше, вы увидите некоторые зернистые узоры — маленькие завихрения цвета. Обработка фотографий с помощью нейронных сетей обычно имеет эту проблему, и одна из причин заключается в том, что сжатие фотографий с потерями вносится в карту признаков. Добавление полного вариационного шумоподавления может эффективно решить эту проблему, щелкните, чтобы просмотреть этот шаг.код.
Вот некоторые из материалов, на которые я ссылался, вы можете взглянуть:
Добро пожаловать, чтобы следовать за нами, учебные ресурсы, учебные пособия по искусственному интеллекту, интерпретация документов, интересная популярная наука, все, что вы хотите увидеть, здесь!