Tensorflow2.0之神经风格迁移

什么是神经风格迁移

此文章使用深度学习来用其他图像的风格创造一个图像。 这被称为神经风格迁移。
神经风格迁移是一种优化技术,用于将两个图像(一个内容图像和一个风格参考图像)混合在一起,使输出的图像看起来像内容图像, 但是使用了风格参考图像的风格。
这是通过优化输出图像以匹配内容图像的内容统计数据和风格参考图像的风格统计数据来实现的。 这些统计数据可以使用卷积网络从图像中提取。
如左图为内容图片,右图为风格图片。
在这里插入图片描述 在这里插入图片描述
那么在进行完风格迁移之后,我们应该得到类似下图的图片。
在这里插入图片描述

代码实现

1、导入需要的库

import tensorflow as tf
import IPython.display as display
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['figure.figsize'] = (12,12)
mpl.rcParams['axes.grid'] = False

2、下载图片

content_path = tf.keras.utils.get_file('turtle.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/Green_Sea_Turtle_grazing_seagrass.jpg')
style_path = tf.keras.utils.get_file('kandinsky.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/Vassily_Kandinsky%2C_1913_-_Composition_7.jpg')

上面代码得到的是两张图片的存储路径。

3、将图片加载到程序中

def load_img(path_to_img):
    max_dim = 512
    img = tf.io.read_file(path_to_img)
    img = tf.image.decode_image(img, channels=3)
    img = tf.image.convert_image_dtype(img, tf.float32)
    
    shape = tf.cast(tf.shape(img)[:-1], tf.float32)
    long_dim = max(shape)
    scale = max_dim / long_dim
    
    new_shape = tf.cast(shape*scale, tf.int32)
    
    img = tf.image.resize(img, new_shape)
    img = img[tf.newaxis, :]
    return img
    
content_image = load_img(content_path)
style_image = load_img(style_path)
# 绘图函数
def imshow(image, title=None):
    if len(image.shape) > 3:
        image = tf.squeeze(image, axis=0)
    
    plt.imshow(image)
    if title:
        plt.title(title)

上述代码中:
img = tf.io.read_file(path_to_img) 将图片加载到程序中,但此时得到的img是经编码后的,我们并不能明白它代表什么。
img = tf.image.decode_image(img, channels=3) 将加载的图片解码成像素值数组。
img = tf.image.convert_image_dtype(img, tf.float32) 将所有图片像素同除255,并将该数组数据设置为浮点型。
其余部分的目的是将图片的长宽等比例缩放成使其最长边等于512。

4、加载VGG19模型,并打印出其中所有的层

vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')

for layer in vgg.layers:
    print(layer.name)
input_1
block1_conv1
block1_conv2
block1_pool
block2_conv1
block2_conv2
block2_pool
block3_conv1
block3_conv2
block3_conv3
block3_conv4
block3_pool
block4_conv1
block4_conv2
block4_conv3
block4_conv4
block4_pool
block5_conv1
block5_conv2
block5_conv3
block5_conv4
block5_pool

5、从VGG19中挑出风格层和内容层

风格层的输出旨在代表风格图片的特征,内容层的输出旨在表示内容图片的特征。

content_layers = ['block5_conv2'] 
style_layers = ['block1_conv1',
                'block2_conv1',
                'block3_conv1', 
                'block4_conv1', 
                'block5_conv1']

num_content_layers = len(content_layers)
num_style_layers = len(style_layers)

6、改变VGG19模型

因为原始的VGG19模型输出分类结果(概率),但在风格迁移时我们需要的是两张图片各自的特征,即不同层的输出,所以要重新对VGG19的输出进行定义。

def vgg_layers(layer_names):
    """ Creates a vgg model that returns a list of intermediate output values."""
    # 加载我们的模型。 加载已经在 imagenet 数据上预训练的 VGG 
    vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')
    vgg.trainable = False

    outputs = [vgg.get_layer(name).output for name in layer_names]

    model = tf.keras.Model([vgg.input], outputs)
    return model

此处详解参考Tensorflow2.0如何在网络中规定多个输出

7、风格计算

图像的风格可以通过不同风格层的输出的平均值和相关性来描述。 通过在每个位置计算这些输出向量的外积,并在所有位置对该外积进行平均,可以计算出包含此信息的 Gram 矩阵。 对于特定层的 Gram 矩阵,具体计算方法如下所示:
在这里插入图片描述
上式中的 l , i , j , c , d l,i,j,c,d lijcd可以这样解释:
我们知道,输入神经网络的一张图片的形状应该类似这种形式:[1, 256, 128, 3]。那么 l l l表示第几张图片,这里为1; i i i表示256; j j j 表示128; c c c d d d都等于3(因为是同一张图片,所以 c = d c=d c=d)。

def gram_matrix(input_tensor):
    result = tf.linalg.einsum('bijc,bijd->bcd', input_tensor, input_tensor)
    input_shape = tf.shape(input_tensor)
    num_locations = tf.cast(input_shape[1]*input_shape[2], tf.float32)
    return result/(num_locations)

其中,tf.linalg.einsum(‘bijc,bijd->bcd’, input_tensor, input_tensor) 旨在计算Gram矩阵。其详细说明请参考:tf.linalg.einsum

8、建立风格迁移模型

class StyleContentModel(tf.keras.models.Model):
    def __init__(self, style_layers, content_layers):
        super(StyleContentModel, self).__init__()
        self.vgg =  vgg_layers(style_layers + content_layers)
        self.style_layers = style_layers
        self.content_layers = content_layers
        self.num_style_layers = len(style_layers)
        self.vgg.trainable = False

    def call(self, inputs):
        "Expects float input in [0,1]"
        inputs = inputs*255.0
        preprocessed_input = tf.keras.applications.vgg19.preprocess_input(inputs)
        outputs = self.vgg(preprocessed_input)
        style_outputs, content_outputs = (outputs[:self.num_style_layers], 
                                          outputs[self.num_style_layers:])

        style_outputs = [gram_matrix(style_output)
                         for style_output in style_outputs]

        content_dict = {content_name:value 
                        for content_name, value 
                        in zip(self.content_layers, content_outputs)}

        style_dict = {style_name:value
                      for style_name, value
                      in zip(self.style_layers, style_outputs)}

        return {'content':content_dict, 'style':style_dict}

extractor = StyleContentModel(style_layers, content_layers)

实例化此模型时,其输入为风格层和内容层。将图片输入此模型后,得到一个字典,包含 ‘content’ 和 ‘style’ ,分别表示风格层的输出(五层输出)和内容层的输出(一层输出)。

9、得到风格图片的风格特征和内容图片的内容特征

将风格图片和内容图片分别输入模型,分别取其风格特征和内容特征。

style_targets = extractor(style_image)['style']
content_targets = extractor(content_image)['content']

10、将内容图片设置为变量

因为输入的内容图片不断地改变其风格直到接近风格图片的风格,所以定义一个 tf.Variable 来表示要优化的图像。

image = tf.Variable(content_image)

11、定义优化函数

opt = tf.optimizers.Adam(learning_rate=0.02, beta_1=0.99, epsilon=1e-1)

12、定义损失函数

style_weight = 1e-2
content_weight = 1e4

def style_content_loss(outputs):
    style_outputs = outputs['style']
    content_outputs = outputs['content']
    # 计算风格损失
    style_loss = tf.add_n([tf.reduce_mean((style_outputs[name]-style_targets[name])**2) 
                           for name in style_outputs.keys()])
    style_loss *= style_weight / num_style_layers
	# 计算内容损失
    content_loss = tf.add_n([tf.reduce_mean((content_outputs[name]-content_targets[name])**2) 
                             for name in content_outputs.keys()])
    content_loss *= content_weight / num_content_layers
    # 计算总损失
    loss = style_loss + content_loss
    return loss

style_weightcontent_weight 分别表示风格上的损失和内容上的损失在计算总损失时所占的比重。

13、定义每一步的梯度下降

首先,由于这是一个浮点图像,因此我们需要定义一个函数来保持像素值保持在 0 和 1 之间:

def clip_0_1(image):
    return tf.clip_by_value(image, clip_value_min=0.0, clip_value_max=1.0)

然后,通过梯度带来进行参数优化。

@tf.function()
def train_step(image):
    with tf.GradientTape() as tape:
        outputs = extractor(image)
        loss = style_content_loss(outputs)

    grad = tape.gradient(loss, image)
    opt.apply_gradients([(grad, image)])
    image.assign(clip_0_1(image))

有关梯度带的详细信息可以参考:Tensorflow2.0中的梯度带(GradientTape)以及梯度更新

14、训练模型,优化图片

epochs = 10
steps_per_epoch = 100

step = 0
for n in range(epochs):
    for m in range(steps_per_epoch):
        step += 1
        train_step(image)
        print(".", end='')
    display.clear_output(wait=True)
    imshow(image.read_value())
    plt.title("Train step: {}".format(step))
    plt.show()

迭代1000次之后,得到图片:
在这里插入图片描述

15、总变分损失

此实现只是一个基础版本,它的一个缺点是它会产生大量的高频误差。 我们可以直接通过正则化图像的高频分量来减少这些高频误差。 在风格转移中,这通常被称为总变分损失

计算高频分量

本质上高频分量是一个边缘检测器,我们可以定义一个函数得到图片的高频分量。

def high_pass_x_y(image):
    x_var = image[:,:,1:,:] - image[:,:,:-1,:]
    y_var = image[:,1:,:,:] - image[:,:-1,:,:]

    return x_var, y_var

我们可以将图片的高频分量显示出来:

x_deltas, y_deltas = high_pass_x_y(content_image)

plt.figure(figsize=(14,10))
plt.subplot(2,2,1)
imshow(clip_0_1(2*y_deltas+0.5), "Horizontal Deltas: Original")

plt.subplot(2,2,2)
imshow(clip_0_1(2*x_deltas+0.5), "Vertical Deltas: Original")

x_deltas, y_deltas = high_pass_x_y(image)

plt.subplot(2,2,3)
imshow(clip_0_1(2*y_deltas+0.5), "Horizontal Deltas: Styled")

plt.subplot(2,2,4)
imshow(clip_0_1(2*x_deltas+0.5), "Vertical Deltas: Styled")

在这里插入图片描述

计算总变分损失

def total_variation_loss(image):
    x_deltas, y_deltas = high_pass_x_y(image)
    return tf.reduce_mean(x_deltas**2) + tf.reduce_mean(y_deltas**2)

16、将总变分损失计入总损失

total_variation_weight=1e8

@tf.function()
def train_step(image):
    with tf.GradientTape() as tape:
        outputs = extractor(image)
        loss = style_content_loss(outputs)
        loss += total_variation_weight*total_variation_loss(image)

    grad = tape.gradient(loss, image)
    opt.apply_gradients([(grad, image)])
    image.assign(clip_0_1(image))

17、重新训练模型

image = tf.Variable(content_image)

epochs = 10
steps_per_epoch = 100

step = 0
for n in range(epochs):
    for m in range(steps_per_epoch):
        step += 1
        train_step(image)
        print(".", end='')
    display.clear_output(wait=True)
    imshow(image.read_value())
    plt.title("Train step: {}".format(step))
    plt.show()

在这里插入图片描述
【注】有需要源码的小伙伴请戳:Tensorflow2.0之神经风格迁移

  • 18
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 91
    评论
### 回答1: TensorFlow 2.0的设计理念是简单粗暴,旨在提供更加直观、易用的编程体验,让用户更快速地构建、训练和部署机器学习模型。以下是TensorFlow 2.0的简单粗暴特性: 1. 动态图计算:TensorFlow 2.0默认采用动态图计算方式,即使用即定义计算图。这使得用户能够像编写Python代码一样自然地构建和调试模型,无需担心图构建过程中的繁琐细节。 2. Keras集成:TensorFlow 2.0将Keras作为其高级神经网络API的标准前端,实现了更加简洁、易懂的模型构建和训练接口。用户无需再额外安装和配置Keras,而且可以直接利用Keras强大的功能,如模型序列化、多种损失函数和优化器等。 3. 切换模式:TensorFlow 2.0提供了一个方便的转换工具,用户可以将TensorFlow 1.x的代码迁移到2.0版本,以享受新的特性,无需重写整个代码。这种平滑迁移的设计使得用户更容易接受新版本并从中受益。 4. Eager Execution(即时执行):TensorFlow 2.0中的Eager Execution模式使得用户可以逐行执行模型代码并立即返回结果,这有助于快速验证和调试模型,尤其对于初学者来说更容易上手。 5. SavedModel格式:TensorFlow 2.0引入了SavedModel格式作为模型的默认保存格式,该格式具有更好的跨平台和版本控制的兼容性。用户能够更方便地保存和分享自己的模型,同时也能更好地与其他TensorFlow开发者进行模型交流。 总之,TensorFlow 2.0的简单粗暴特性使得机器学习的开发变得更加直观、高效,并能够吸引更多的开发者加入到机器学习的领域中。 ### 回答2: TensorFlow 2.0是一种简单粗暴的机器学习框架。相较于以往版本,2.0在易用性、灵活性和效率方面都有很大的提升。 首先,TensorFlow 2.0引入了eager execution(即即时执行),这意味着我们可以像编写Python代码一样编写和运行TensorFlow操作,而无需定义计算图。这样可以更容易地调试和理解代码,使得开发过程更加直观和高效。 其次,TensorFlow 2.0取消了许多低级API,如tf.Session和tf.placeholder,大大减少了代码的复杂度。取而代之的是一些更高级且易用的API,如tf.keras,它提供了一个简单而且强大的接口来构建神经网络模型。我们可以使用一些简单的函数调用来定义和训练模型,从而减少了样板代码,同时还能保持高度的灵活性。 此外,TensorFlow 2.0还提供了一个称为tf.data的新的高性能数据输入管道。我们可以使用tf.data.Dataset将数据集导入模型中,并进行预处理、批处理等操作,以便更好地利用硬件资源,并实现更高效的训练过程。 最后,TensorFlow 2.0充分利用了现代硬件的加速能力,如GPU和TPU。它使用了tf.function装饰器来自动转换函数为高性能的图执行模式,并支持分布式训练,以便在分布式系统上进行大规模的模型训练。 综上所述,TensorFlow 2.0确实是一种简单粗暴的机器学习框架。它简化了开发过程,提高了代码的可读性和可维护性,并利用了现代硬件的优势,从而大大提升了训练效率和性能。无论是初学者还是有经验的开发者,都可以受益于这个强大而易用的框架。 ### 回答3: 简单粗暴的TensorFlow 2.0是一个更新版本的Google开源机器学习框架,旨在让使用变得更加简便。TensorFlow 2.0采用了Eager Execution模式,移除了一些繁琐的操作,与Python的语义更加契合。这使得建立和训练神经网络变得更加直观和容易。 TensorFlow 2.0引入了Keras作为其主要高级API,将其整合为TensorFlow的一部分。Keras提供了丰富而直观的高级API,可以方便地构建各种神经网络模型。通过将Keras集成到TensorFlow中,使用者可以轻松地创建、训练和部署深度学习模型。 TensorFlow 2.0还引入了tf.function装饰器,允许将普通Python函数转换为高效的TensorFlow图,加速模型的训练和推理过程。 此外,TensorFlow 2.0对于分布式训练、模型部署和端到端生态系统的支持都有所改进。它提供了更好的工具和接口,使得分布式机器学习变得更加容易。TensorFlow 2.0还支持模型在移动设备、Web和边缘设备上部署,并提供了可拓展的生态系统,包括TensorFlow Hub、TensorBoard和TensorFlow.js等。 总之,简单粗暴的TensorFlow 2.0通过增强开发者的体验和提供更加直观的API,大大简化了神经网络的构建和训练过程。新版本的引入了许多新特性和改进,使得TensorFlow 2.0成为构建高性能、可扩展的机器学习模型的理想选择。
评论 91
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cofisher

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值