计算机系统应用教程网站

网站首页 > 技术文章 正文

使用VGG16,VGG19和ResNet50预训练模型进行神经风格转换实验

btikc 2024-10-18 04:43:29 技术文章 14 ℃ 0 评论

神经风格转移是一种图像修改技术(由Leon Gatys等人首次开发和介绍),将一幅图像的风格应用于另一幅图像的内容。在这篇博文中,我们将看看如何使用来自Keras的预训练的ConvNet模型来应用它。

神经风格转移的一个例子。

以上是神经风格转换的具体例子。在这种情况下,样式被定义为在图像中找到的纹理,颜色和一般图案(例如笔触)。由于风格明显,凡高的“星夜”作为一种风格形象受到欢迎。

风格转移成功的关键在于损失功能。我们希望尽量减少内容图像和生成图像之间的内容损失,同时最小化样式图像和生成图像之间的风格图像。我们先来看看内容丢失情况:

内容丢失:

我们网络中的较早层与更多本地信息相关联,而较高层将包含全局信息的激活。由于内容是由图像的宏结构定义的(例如上述示例中的建筑结构),因此我们网络中的最顶层将捕获图像内容。因此,我们将简单计算内容损失作为在内容图像上计算的顶层激活与生成的图像之间的L2距离(平方差的总和)。

def content_loss(base,combination):

return K.sum(K.square(combination - base))

风格损失:

为了捕捉风格损失,我们将使用原始2015年风格转换纸中概述的格雷姆矩阵方法。格拉姆矩阵是特征图与给定图层的内积。这导致层的特征之间的相关性的映射。这些特征相关性是在一定的空间尺度上捕捉纹理图案,这与在这种尺度下的纹理/颜色/图案的物理外观相对应。因此,样式损失函数的目标是最小化从样式图像到生成图像的每个图层激活中的特征相关性之间的差异。

def gram_matrix(x):

features = K.batch_flatten(K.permute_dimensions(x,(2,0,1)))

gram = K.dot(features,K.transpose(features))

return gram

def style_loss(style,combination):

S = gram_matrix(style)

C = gram_matrix(combination)

channels = 3

size = img_height * img_width

return K.sum(K.square(S-C))/(4. * * 2)*(size ** 2))

我们也将使用总变差损失,这是用来鼓励空间连续性,并防止过度集中:

def total_variation_loss(x):

a = K.square(

x [:,:img_height_1,:img_width_1,...] x [:,1:,:img_width -1,:])

b = K.square(

x [:::img_height - 1,:img_width - 1,:] - x [:,:img_height - 1,1 :, :)]

return K.sum(K.pow(a + b,1.25))

现在我们准备开始我们的风格转移。这些是我们将在这篇文章中为接下来的3个部分所采取的一般步骤:

  1. 设置网络以同时计算样式,内容和生成图像的激活。

  2. 使用这些激活来计算损失。总损耗将是一个加权平均style_loss,content_loss,and total_variation_loss 。

  3. 使用梯度下降来最小化损失函数以获得最终生成的图像。

第1部分使用VGG19

我们将首先设置我们的模型。我们将我们的样式,内容和生成的图像(现在的占位符)分组在一起。这允许我们一次计算所有三个激活。我们将把它input_tensor作为参数传递给我们的VGG19模型,以相应地改变图层的形状。

from keras import backend as K

target_image = K.constant(preprocess_image(target_image_path))

style_reference_image = K.constant(preprocess_image(style_reference_image_path))

# This placeholder will contain our generated image

combination_image = K.placeholder((1, img_height, img_width, 3))

# We combine the 3 images into a single batch

input_tensor = K.concatenate([target_image,

style_reference_image,

combination_image], axis=0)

# We build the VGG19 network with our batch of 3 images as input.

# The model will be loaded with pre-trained ImageNet weights.

model = vgg19.VGG19(input_tensor=input_tensor,

weights='imagenet',

include_top=False)

接下来,我们将定义哪些图层用于定义内容和样式。'block5_conv2'是Conv2D我们VGG19模型中的顶层,因此我们将使用它来评估内容。至于style_layers ,我们将使用Conv2D从网络顶部到底部的各种图层,以便计算样式错误。我们还会为样式,内容和总变化分配权重,这些权重将用于计算加权总损失。

outputs_dict = dict([(layer.name, layer.output) for layer in model.layers])

content_layer = 'block5_conv2'

style_layers = ['block1_conv1',

'block2_conv1',

'block3_conv1',

'block4_conv1',

'block5_conv1']

total_variation_weight = 1e-4

style_weight = 1.0

content_weight = 0.025

# Define the loss by adding all components to a `loss` variable

loss = K.variable(0.)

layer_features = outputs_dict[content_layer]

target_image_features = layer_features[0, :, :, :]

combination_features = layer_features[2, :, :, :]

loss += content_weight * content_loss(target_image_features,

combination_features)

for layer_name in style_layers:

layer_features = outputs_dict[layer_name]

style_reference_features = layer_features[1, :, :, :]

combination_features = layer_features[2, :, :, :]

sl = style_loss(style_reference_features, combination_features)

loss += (style_weight / len(style_layers)) * sl

loss += total_variation_weight * total_variation_loss(combination_image)

我们也将使用Python类Evaluator 。这个类允许我们同时计算损失函数和梯度。从本质上说,它在计算这两个值时消除了冗余计算,并且将我们的程序加速了2倍。

# Get the gradients of the generated image

grads = K.gradients(loss, combination_image)[0]

# fetch the values of the current loss and the current gradients

fetch_loss_and_grads = K.function([combination_image], [loss, grads])

class Evaluator(object):

def __init__(self):

self.loss_value = None

self.grads_values = None

def loss(self, x):

assert self.loss_value is None

x = x.reshape((1, img_height, img_width, 3))

outs = fetch_loss_and_grads([x])

loss_value = outs[0]

grad_values = outs[1].flatten().astype('float64')

self.loss_value = loss_value

self.grad_values = grad_values

return self.loss_value

def grads(self, x):

assert self.loss_value is not None

grad_values = np.copy(self.grad_values)

self.loss_value = None

self.grad_values = None

return grad_values

evaluator = Evaluator()

现在我们将使用SciPy的L-BFGS算法运行我们的梯度下降过程。我们将运行10次迭代,每次迭代包含10个梯度下降步骤。我们还会在每次迭代时保存生成的图像,以便跟踪我们的进度。

from scipy.optimize import fmin_l_bfgs_b

from scipy.misc import imsave

import time

result_prefix = 'vgg19_try1'

iterations = 10

x = preprocess_image(target_image_path)

x = x.flatten()

for i in range(iterations):

print('Start of iteration', i)

start_time = time.time()

x, min_val, info = fmin_l_bfgs_b(evaluator.loss, x,

fprime=evaluator.grads, maxfun=10)

print('Current loss value:', min_val)

# Save current generated image

img = x.copy().reshape((img_height, img_width, 3))

img = deprocess_image(img)

fname = result_prefix + '_at_iteration_%d.png' % i

imsave(fname, img)

end_time = time.time()

print('Image saved as', fname)

print('Iteration %d completed in %ds' % (i, end_time - start_time))

结果如下:

风格转换工作得很好。不过,我们上面选择的样式图片并不好。它缺乏明确定义的图案和纹理。让我们再次尝试使用不同的模型和新的样式图像。

第2部分使用VGG16

这次我们将使用Keras的VGG16模型,如下所示:

model = vgg16.VGG16(input_tensor=input_tensor,

weights='imagenet',

include_top=False)

我们还必须更新我们的preprocess_image功能以反映这一变化:

def preprocess_image(image_path):

img = load_img(image_path, target_size=(img_height, img_width))

img = img_to_array(img)

img = np.expand_dims(img, axis=0)

img = vgg16.preprocess_input(img)

return img

为了获得更好的结果,我们也增加了style_weight和减少content_weight了(也是为了弥补从VGG19降级到VGG16)。结果如下:

我们能够取得很好的成果。图像的内容绝对保留,以及样式图像中的颜色和纹理。

第3部分与ResNet50

这一次,我们将尝试使用Keras的ResNet50进行样式转换:

model = resnet50.ResNet50(input_tensor = input_tensor,

weights ='imagenet',

include_top = False,

pooling ='max')

在我们的第一次尝试中,我们将使用以下值:

content_layer ='res5b_branch2a'style_layers

= ['res3a_branch2a','res4a_branch2a','res5a_branch2a']

total_variation_weight = 0

style_weight = 400000

content_weight = 0.0001

这些是10次迭代后的结果:

不幸的是,我们未能捕捉到风格图像的完整风格。我们看到纹理的开始是受风格形象的启发。但是,在对生成的图像进行检查之后,我发现在内容图像中没有发现在样式图像中发现的许多颜色。我们可以看到,内容图像的大多数颜色是蓝色的。因为样式图像不包含太多的蓝色,所以没有多少样式可以从样式图像应用到内容图像。这样,我们就得到了一个看起来半成品的生成图像。

提示:确保使用具有类似调色板的样式图像来放置您的内容图像(否则样式将无法正确应用)。

回到我们在第2部分中使用的映像,我们得到以下结果:

我们所做的稍好。然而,很明显,ResNet50在风格转移上比VGG网络更糟糕。以下是一些原因:

  • VGG网络非常大(两者都是500MB+与ResNet50的99MB相比),因此可以顺便捕获和存储比其他模型更多的信息。

  • VGG是相对较浅和模块化的,没有剩余连接或快捷方式可以跳过原始数据层。这就产生了一个清晰的、层次分明的抽象系列。另一方面,ResNet过于分散;个体特征存在,但可以通过许多抽象层混合起来。

  • 与其他模型相比,VGG不会像其他模型那样(仅在多个卷积后才会使用最大的池)。

未来工作:

以下是一些建议的方法来测试上述原因:

  • 训练更大的ResNet /更小的VGG,以查看性能差距是否缩小。如果一个小的VGG不能比一个相同大小的ResNet进行风格转换,这表明VGG的优势在于模型的大小。

  • 在ResNet中计算特性是如何计算的。由于这些特性可能在许多层中分散,所以在计算Gram矩阵之前,创建一个方法来对特征图进行深度的求和。

  • 在ResNet50中,Brute-force有很多内容和样式层的组合。我已经尝试了一种好的组合(这可能是特定于不同样式的图像,适合不同类型的图像)。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表