Tensorflow2.0:用自己的网络实现神经风格迁移

在网上查如何进行神经风格迁移的时候,发现大多数人都是使用迁移学习来的网络。当然,迁移学习导入已经训练好的网络进行训练是非常方便且快速的,但是有时候我们可能会想自己训练一个网络进行神经风格迁移。所以在这篇文章中,我们尝试使用自定义的 GoogLeNet 网络模型进行神经风格迁移。
PS:对神经风格迁移的原理感兴趣的可以参考: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)

上述代码中:
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、自定义GoogLeNet模型,并打印出其中所有的层

GoogLeNet 模型的定义有兴趣的朋友可以参考:Tensorflow2.0之三种方法自定义GoogLeNet

4.1 定义Inception模块

class Inception(tf.keras.Model):
    def __init__(self, c1, c2, c3, c4):
        super().__init__()
        self.conv1 = tf.keras.layers.Conv2D(c1, kernel_size=1,
                                            activation='relu', padding='same')
        self.conv2_1 = tf.keras.layers.Conv2D(c2[0], kernel_size=1,
                                              activation='relu', padding='same')
        self.conv2_2 = tf.keras.layers.Conv2D(c2[1], kernel_size=3,
                                              activation='relu', padding='same')
        self.conv3_1 = tf.keras.layers.Conv2D(c3[0], kernel_size=1,
                                              activation='relu', padding='same')
        self.conv3_2 = tf.keras.layers.Conv2D(c3[1], kernel_size=5,
                                              activation='relu', padding='same')
        self.conv4_1 = tf.keras.layers.MaxPool2D(pool_size=3, strides=1,
                                                 padding='same')
        self.conv4_2 = tf.keras.layers.Conv2D(c4, kernel_size=1,
                                              activation='relu', padding='same')
        
    def call(self, inputs):
        x1 = self.conv1(inputs)
        x2 = self.conv2_2(self.conv2_1(inputs))
        x3 = self.conv3_2(self.conv3_1(inputs))
        x4 = self.conv4_2(self.conv4_1(inputs))
        return tf.concat((x1, x2, x3, x4), axis=-1)

4.2 定义GoogLeNet模型

在这里一定要用函数的方法定义 GoogLeNet 模型!!!否则会报错:

AttributeError: Layer inception_2 has no inbound nodes.
def GoogLeNet():
    inputs = tf.keras.layers.Input(shape=(None, None, 3))
   
    model = [
        tf.keras.layers.Conv2D(filters=64, kernel_size=7, strides=2,
                                  activation='relu', padding='same'),
        tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same'),
        tf.keras.layers.Conv2D(filters=64, kernel_size=1,
                              activation='relu', padding='same'),
        tf.keras.layers.Conv2D(filters=192, kernel_size=3,
                                  activation='relu', padding='same'),
        tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same'),
        Inception(64, (96, 128), (16, 32), 32),
        Inception(128, (128, 192), (32, 96), 64),
        tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same'),
        Inception(192, (96, 208), (16, 48), 64),
        Inception(160, (112, 224), (24, 64), 64),
        Inception(128, (128, 256), (24, 64), 64),
        Inception(112, (144, 288), (32, 64), 64),
        Inception(256, (160, 320), (32, 128), 128),
        tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same'),
        Inception(256, (160, 320), (32, 128), 128),
        Inception(384, (192, 384), (48, 128), 128),
        tf.keras.layers.GlobalAvgPool2D()
    ]
    
    x = inputs
    for layer in model:
        x = layer(x)
        
    outputs = tf.keras.layers.Dense(10)(x)
    
    return tf.keras.Model(inputs=inputs, outputs=outputs)

4.3 打印GoogLeNet中的所有层

google = GoogLeNet()
for layer in google.layers:
    print(layer.name)
input_1
conv2d
max_pooling2d
conv2d_1
conv2d_2
max_pooling2d_1
inception
inception_1
max_pooling2d_4
inception_2
inception_3
inception_4
inception_5
inception_6
max_pooling2d_10
inception_7
inception_8
global_average_pooling2d
dense

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

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

content_layers = ['global_average_pooling2d', 'inception_8']
style_layers = ['inception_2',
                'inception_3', 
                'inception_4',
                'inception_5',
                'inception_6',
                'inception_7',
                'inception_8']
num_content_layers = len(content_layers)
num_style_layers = len(style_layers)

在这之后要先对这个 GoogLeNet 模型进行训练,在这里就不再展示了。(因为我没找到一个小的训练集……)

6、改变GoogLeNet模型的输出

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

def google_layers(layer_names):
    outputs = [google.get_layer(name).output for name in layer_names]
    model = tf.keras.Model([google.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.google = google_layers(style_layers + content_layers)
        self.style_layers = style_layers
        self.content_layers = content_layers
        self.num_style_layers = len(style_layers)
        self.google.trainable = True

    def call(self, inputs):
        "Expects float input in [0,1]"
        inputs = inputs*255.0
        outputs = self.google(inputs)
        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次之后,得到图片:
在这里插入图片描述

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
### 回答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成为构建高性能、可扩展的机器学习模型的理想选择。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

cofisher

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

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

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

打赏作者

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

抵扣说明:

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

余额充值