"Это 4-й день моего участия в ноябрьском испытании обновлений, ознакомьтесь с подробностями события:Вызов последнего обновления 2021 г."
предисловие
Перенос нейронного стиля вызвал большой интерес в отрасли, как только он был предложен, и некоторые веб-сайты позволяют пользователям загружать фотографии для переноса стиля, а некоторые даже используют его для мерчендайзинга (например, «цифровые картины маслом своими руками» и т. д. ).
передача нейронного стиля
Изображение можно разложить на содержание и стиль.Содержимое описывает композицию изображения, например цветы и деревья на изображении, а стиль относится к деталям изображения, таким как текстура озера и цвет. деревьев. Фотографии одного и того же здания в разное время суток, с разными тонами и яркостью можно рассматривать как имеющие одинаковое содержание, но разные стили.
В статье Gatys et al. CNN используется для переноса художественного стиля одного изображения на другое:
В отличие от большинства моделей глубокого обучения, для которых требуется много обучающих данных, для переноса нейронного стиля требуется только два изображения — изображение содержимого и изображение стиля. Стиль можно перенести из изображений стиля в изображения содержимого с помощью обученной CNN (например, VGG).
Как вы можете видеть на изображении выше, (A) — это изображение содержимого, (B)–(D) показывают изображение стиля и стилизованное изображение содержимого, и результаты потрясающие! Некоторые даже используют алгоритм для создания и продажи произведений искусства. Некоторые веб-сайты и приложения могут загружать фотографии для передачи стиля без необходимости понимать основные принципы, но как технологи мы, безусловно, хотим реализовать эту модель сами.
Извлечение функций с помощью VGG
Классификатор CNN можно разделить на две части: первая часть называется экстрактором признаков (feature extractor
), который в основном состоит из сверточных слоев; последняя часть состоит из нескольких полносвязных слоев, которые выводят оценки вероятности класса, называемые головкой классификатора (classifier head
). CNN, предварительно обученные в ImageNet для задач классификации, также можно использовать для других задач, что называется трансферным обучением (transfer learning).transfer learning
), мы можем передавать или повторно использовать некоторые полученные знания в новых сетях или приложениях.
В CNN два этапа реконструкции изображения следующие:
- Изображение вычисляется вперед через CNN для извлечения признаков.
- Используйте случайно инициализированный ввод и обучите его, чтобы он восстанавливал функции, которые лучше всего соответствуют эталонным функциям из шага 1.
При обычном обучении сети входное изображение фиксируется, а веса сети обновляются с использованием градиентов с обратным распространением. При переносе нейронного стиля все сетевые слои замораживаются, и мы используем градиенты для изменения входных данных. используется в оригинальной статьеVGG19
,Keras
Существует предварительно обученная модель, которую можно использовать. Экстрактор признаков VGG состоит из пяти блоков с субдискретизацией в конце каждого блока. Каждый блок имеет от 2 до 4 сверточных слоев, а весь VGG19 имеет 16 сверточных слоев и 3 полносвязных слоя.
Далее мы реализуем рефакторинг контента, расширяя его для переноса стиля. Вот код для извлечения выходного слоя block4_conv2 с использованием предварительно обученного VGG:
# 因为我们只需要提取特征,所以在实例化VGG模型时使用include_top = False冻结网络参数
vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')
content_layers = ['block4_conv2']
content_outputs = [vgg.get_layer(x).output for x in content_layers]
model = Model(vgg.input, content_outputs)
предварительно обученныйKeras CNN
Модель разделена на две части. Нижняя часть состоит из сверточных слоев, часто называемых экстракторами признаков, а верхняя часть представляет собой заголовок классификатора, состоящий из полностью связанных слоев. Поскольку мы хотим извлекать только функции и не заботимся о классификаторе, при создании экземпляра модели VGG мы установимinclude_top = False
.
загрузка изображения
Сначала вам нужно загрузить изображение содержимого и изображение стиля:
def scale_image(image):
MAX_DIM = 512
scale = np.max(image.shape)/MAX_DIM
print(image.shape)
new_shape = tf.cast(image.shape[:2]/scale, tf.int32)
image = tf.image.resize(image, new_shape)
return image
content_image = scale_image(np.asarray(Image.open('7.jpg')))
style_image = scale_image(np.asarray(Image.open('starry-night.jpg')))
Предварительная обработка VGG
Keras
Предварительно обученная модель ожидает входное изображениеBGR
Диапазон[0, 255]
. Итак, первый шаг — инвертировать цветовые каналы так, чтобыRGB
преобразовать вBGR
.VGG
Чтобы использовать разные средние значения для разных цветовых каналов, вы можете использоватьtf.keras.applications.vgg19.preprocess_input()
предварительная обработка, вpreprocess_input()
Внутренне значения пикселей каналов B, G и R вычитаются103.939
,116.779
и123.68
.
Ниже приведен код прямого вычисления, который предварительно обрабатывает изображение перед прямым вычислением, прежде чем передать его в модель для возврата функций содержимого. Затем мы извлекаем функцию контента и используем ее в качестве цели:
def extract_features(image):
image = tf.keras.applications.vgg19。preprocess_input(image *255.)
content_ref = model(image)
return content_ref
content_image = tf.reverse(content_image, axis=[-1])
content_ref = extract_features(content_image)
В коде, поскольку изображение было нормализовано до[0., 1.]
, поэтому нам нужно восстановить его, умножив на 255.[0.,255.]
. Затем создайте случайно инициализированный ввод, который также будет стилизованным изображением:
image = tf.Variable(tf.random.normal( shape=content_image.shape))
Далее мы воспользуемся обратным распространением для восстановления изображения по функциям содержимого.
восстановить содержание
На этапе обучения мы загружаем изображения в замороженныйVGG
для извлечения функций контента, затем используйтеПотери измеряются по отношению к целевым объектам контента и используются для расчета потерь L2 для каждого слоя объектов:
def calc_loss(y_true, y_pred):
loss = [tf.reduce_sum((x-y)**2) for x, y in zip(y_pred, y_true)]
return tf.reduce_mean(loss)
использоватьtf.GradientTape()
Рассчитать градиент. При обычном обучении нейронной сети обновления градиента применяются к обучаемым переменным, весам нейронной сети. Однако при переносе нейронного стиля к изображениям применяются градиенты. После этого обрежьте значения изображения в[0., 1.]
между ними следующим образом:
for i in range(1,steps+1):
with tf.GradientTape() as tape:
content_features = self.extract_features(image)
loss = calc_loss(content_features, content_ref)
grad = tape.gradient(loss, image)
optimizer.apply_gradients([(grad, image)])
image.assign(tf.clip_by_value(image, 0., 1.))
Используйте block1_1 для восстановления изображения, и после 2000 шагов обучения получите восстановленное изображение содержимого:
Используйте block4_1 для восстановления изображения, и после 2000 шагов обучения получите восстановленное изображение содержимого:
Вы можете видеть, что при использовании слоя block4_1 такие детали, как форма листьев, начинают теряться. Когда мы используем block5_1, мы видим, что почти все детали исчезли и заполнены каким-то случайным шумом:
Если присмотреться, то структура и края листьев все же сохранились и находятся на своем законном месте. Теперь, когда мы извлекли контент, после извлечения функций контента следующим шагом будет извлечение функций стиля.
Восстановить стиль с помощью матрицы Грама
В реконструкции контента видно, что карты объектов (особенно первые несколько слоев) содержат как стиль, так и контент. Итак, как мы извлекаем элементы стиля из изображений? метод заключается в использованииGram
Матрица, которая вычисляет корреляцию между различными ответами фильтра. Предположим, что форма активации сверточного слоя 1 имеет вид(H, W, C)
,вH
иW
размерность пространства,C
- количество каналов, равное количеству фильтров, каждый из которых обнаруживает различные особенности изображения.
Считается, что они имеют одинаковую текстуру, если они имеют некоторые общие характеристики, такие как цвет и края. Например, если мы загрузим изображение травы в сверточный слой, фильтр, который обнаруживает вертикальные линии и зеленый цвет, даст больший отклик на своей карте объектов. Следовательно, мы можем использовать корреляцию между картами объектов для представления текстуры на изображении.
Чтобы передать форму как(H, W, C)
активация для созданияGram
матрица, мы сначала преобразуем ее вC
вектор. Каждый вектор имеет размерH×W
Одномерная карта признаков . правильноC
выполнить скалярное произведение векторов, чтобы получить симметричный C×C Gram
матрица. существуетTensorFlow
средний расчетGram
Подробные шаги матрицы следующие:
- использовать
tf.squeeze()
размер партии(1, H, W, C)
превратиться в(H, W, C)
; - Транспонируйте тензор, чтобы изменить форму с
(H, W, C)
преобразовать в(C, H, W)
; - Свести последние два измерения к
(C, H×W)
; - Сделайте скалярное произведение функций, чтобы создать форму как
(C, C)
изGram
матрица; - Разделив матрицу на количество элементов в каждой сглаженной карте объектов
(H×W)
нормализовать.
рассчитатьGram
Код для матрицы выглядит следующим образом:
def gram_matrix(x):
x = tf.transpose(tf.squeeze(x), (2,0,1));
x = tf.keras.backend.batch_flatten(x)
num_points = x.shape[-1]
gram = tf.linalg.matmul(x, tf.transpose(x))/num_points
return gram
Вы можете использовать эту функцию для каждого из указанных слоев стилей.VGG
получение слояGram
матрица. Затем мы сравниваем данные с целевого изображения и эталонного изображения.Gram
Использование матрицыпотеря. Функция потерь такая же, как и при реконструкции контента. СоздайтеGram
Код для матричного списка выглядит следующим образом:
def extract_features(image):
image = tf.keras.applications.vgg19.preprocess_input(image *255.)
styles = self.model(image)
styles = [self.gram_matrix(s) for s in styles]
return styles
Следующие изображения отличаются отVGG
Реконструировано на основе элемента стиля слоя:
в отblock1_1
В реконструированных изображениях стиля информация о содержимом полностью исчезает, и отображаются только высокочастотные детали текстуры. более высокий уровеньblock3_1
, показывающий некоторые изогнутые формы:
Эти формы захватывают более высокий уровень стиля входного изображения.Gram
Функция потерь для матрицы представляет собой сумму квадратов ошибок, а не среднеквадратичную ошибку. Следовательно, слои с более высокими иерархическими стилями имеют более высокий собственный вес. Это позволяет передавать более продвинутые стилистические представления, такие как мазки. Если используется среднеквадратическая ошибка, элементы стиля низкого уровня (такие как текстура) будут визуально более заметными и могут выглядеть как высокочастотный шум.
Реализация передачи нейронного стиля
Теперь мы можем объединить код из рефакторинга содержимого и стиля, чтобы выполнить нейронный перенос стиля.
Сначала мы создаем модель, которая извлекает два функциональных блока: один для контента и один для стиля. Реконструкция контента с использованиемblock5_conv1
слой, изblock1_conv1
прибытьblock5_conv1
Пять слоев используются для захвата стилей из разных иерархий следующим образом:
vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')
default_content_layers = ['block5_conv1']
default_style_layers = ['block1_conv1',
'block2_conv1',
'block3_conv1',
'block4_conv1',
'block5_conv1']
content_layers = content_layers if content_layers else default_content_layers
style_layers = style_layers if style_layers else default_style_layers
self.content_outputs = [vgg.get_layer(x).output for x in content_layers]
self.style_outputs = [vgg.get_layer(x).output for x in style_layers]
self.model = Model(vgg.input, [self.content_outputs, self.style_outputs])
Перед началом цикла обучения мы извлекаем элементы содержимого и стиля из соответствующих изображений, чтобы использовать их в качестве целей. Хотя мы можем использовать случайно инициализированные входные данные для реконструкции контента и стиля, обучение на изображениях контента происходит быстрее:
content_ref, _ = self.extract_features(content_image)
_, style_ref = self.extract_features(style_image)
Затем мы вычисляем и добавляем потери контента и стиля:
def train_step(self, image, content_ref, style_ref):
with tf.GradientTape() as tape:
content_features, style_features = self.extract_features(image)
content_loss = self.content_weight * self.calc_loss(content_ref, content_features)
style_loss = self.style_weight*self.calc_loss( style_ref, style_features)
loss = content_loss + style_loss
grad = tape.gradient(loss, image)
self.optimizer.apply_gradients([(grad, image)])
image.assign(tf.clip_by_value(image, 0., 1.))
return content_loss, style_loss
Показать результаты
Вот 4 стилизованных изображения, сгенерированных с разным весом и слоями контента:
Вы можете создать желаемый стиль, изменив веса и слои.
Конечно, эта модель также имеет тот недостаток, что для создания изображения требуется несколько минут, и она не может обеспечить миграцию в реальном времени.Связанная улучшенная модель будет обсуждаться позже.