git clone https://github.com/pkmital/CycleGAN.git
This colab introduces you to the following work:
%matplotlib inline
import matplotlib
plt = matplotlib.pyplot
plt.figure(figsize=(20,10))
plt.imshow(plt.imread('CycleGAN/imgs/cycle-gan-paper.png'))
plt.axis('off')
图像到图像的翻译涵盖了计算机图形学,计算机视觉和图像和视频深度学习等非常广泛的应用。基本思想是将输入图像转换为输出图像。这与对图像进行自动编码不同,其中输入和输出完全相同。在这种情况下,预计它们是不同的图像集。例如,输入图像可能是风景照片,输出可能是该照片的艺术风格。或许输入的是一张马的照片,输出应该是斑马的照片。让我们说你有一张地图显示街道和高速公路的轮廓,你想要对这些图像应用纹理,使它看起来像卫星图像。或者您可能想要重新创建Google AI实验,他们将草图转换为猫的图片。或许您想要重新创建应用程序FaceApp,它可以为人们的脸部增添笑容。这些都是图像到图像转换的例子。
这个想法的最早示威之一是在2001年的一篇名为Image Analogies的论文中。 Aaron Hertzmann及其同事通过使用类似的图像对展示了图像到图像翻译的基本思想。你会给出一个类比的例子,例如街道地图的图像,以及相应的卫星图像,然后你可以给它任何其他的街道地图图像,它会为你提供一个新的卫星图像。
plt.figure(figsize=(13,10))
plt.imshow(plt.imread('CycleGAN/imgs/image-analogies-paper.png'))
plt.axis('off')
我们怎样才能建立一个神经网络来做类似的事情呢?自动编码器和生成对抗网络允许对图像集合进行一些非常令人印象深刻的无监督建模。例如,具有与输出不同的输入的自动编码器基本上可以学习将一个图像转换成另一个图像。比方说,如果您有黑白图像,并希望将它们转换为彩色图像,您可以创建任一图像的数据集,然后将每种类型的图像成对送入自动编码器。然后,所得到的损失函数将输出一些彩色图像,并输入黑白图像,并且网络需要学习基本上对图像着色。
虽然如果你曾经尝试过这个,你可能会发现自动编码器通常对图像有一个很大的问题:它们的损失函数通常使用L2或平方损失函数,这通常会导致真正模糊的重建或重建必须理解超出简单像素的图像内容。
然后我们看到生成对抗网络没有这个问题,因为它通过训练一个单独的网络,一个鉴别器来判断图像是真实的还是假的,从而学会了它自己的损失功能。那么我们是否可以建立一个可以学习着色图像的生成对抗网络?或者将街道地图转换为卫星地图?或图像到图像翻译的任何其他潜在应用?本教程介绍了一种称为CycleGAN的图像到图像转换的最先进技术。
CycleGAN建立在早期的作品Pix2Pix之上。 Pix2Pix网络需要成对翻译,这意味着在训练时,对于每个输入,您需要准确指定输出应该是什么样子。 相反,CycleGAN只需要两个不成对的图像集合,并将尽力找到它们之间的映射,而无需指定对的对象。 这是一个非常令人印象深刻的网络,构建起来非常简单。 我们需要许多与课程1课程5中构建的DCGAN和VAEGAN网络相同的组件。
plt.figure(figsize=(13,10))
plt.imshow(plt.imread('CycleGAN/imgs/cycle-gan-figure-2.png'))
plt.axis('off')
我们需要构建一组将一个图像集合映射到另一个图像集合的操作,并为每个图像集合 X X X和 Y Y Y构建这些操作。 我们还需要构建另一组操作,以便有一个完整的操作循环,试图将我们的第一个图像集合 X X X映射到第二个集合 Y Y Y,这样一个假的 Y Y Y我们用数学方式表示 Y ^ \hat{Y} Y^,然后另一个生成器将真实的 Y Y Y和假的 Y ^ \hat{Y} Y^再次映射回 X ^ \hat{X} X^。 在下图中,我们可以通过两个集合 X X X和 Y Y Y以图形方式查看其工作原理:
plt.figure(figsize=(13,10))
plt.imshow(plt.imread('CycleGAN/imgs/cycle-gan-figure-3.png'))
plt.axis('off')
该网络基本上使用一组2个发生器(上图中的G
和F
)和2个鉴别器(上图中的D_X
和D_Y
)训练2个自动编码器。如果您已经熟悉生成性对抗性网络,那么这个想法很容易理解,并且大多数棘手的部分都在实现的细节中。
生成器虽然略有不同。我们实际上从一个图像开始,然后编写一个与自动编码器不同的东西,而不是从100个值的随机特征向量开始。这个Autoencoder的结构有点不同。我们将有三个主要组件:编码器,变压器和解码器。
编码器由一些带有步幅2的卷积层组成,它将对每层的图像进行下采样。作者用3层创建它,并在第一层使用填充。
让我们把它看成是代码。先导入一些库。我们将包括TensorFlow以及contrib包,这使得编写图层变得更容易,类似于Keras。我们还将使用Instance Normalization(http://arxiv.org/abs/1607.08022)作为我们的图层规范化和Leaky ReLus,正如CycleGAN的作者所做的那样。我已经在cycle_gan.py
模块中包含了我的CycleGAN和所有实用程序函数的实现,您也可以在cadl
repo中找到它:https://github.com/pkmital/pycadl(easy pip install by :pip install cadl
):
!pip install cadl
import numpy as np
import tensorflow as tf
import tensorflow.contrib.layers as tfl
from cadl.cycle_gan import lrelu, instance_norm
现在让我们按照CycleGAN架构为编码器编写一个函数。 我们想要一个填充层,然后是3个带有步幅1的卷积层,然后是2个,然后是另一个2.第一个卷积层将有一个7x7卷积,其余的将是3x3。 此外,我们将使用正态分布将权重初始化为标准偏差0.02。 对于激活功能,我们将使用具有0.2泄漏的Leaky ReLu。 我们还将指数增加每层的过滤器数量,从32开始,然后到64,最后是128.最后,我们还将使用称为实例规范化的批量规范化,并使用TensorFlow图层模块来执行 我们在一个方便的功能中进行整个卷积操作:
def encoder(x, n_filters=32, k_size=3, normalizer_fn=instance_norm,
activation_fn=lrelu, scope=None, reuse=None):
with tf.variable_scope(scope or 'encoder', reuse=reuse):
h = tf.pad(x, [[0, 0], [k_size, k_size], [k_size, k_size], [0, 0]],
"REFLECT")
h = tfl.conv2d(
inputs=h,
num_outputs=n_filters,
kernel_size=7,
stride=1,
padding='VALID',
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
normalizer_fn=normalizer_fn,
activation_fn=activation_fn,
scope='1',
reuse=reuse)
h = tfl.conv2d(
inputs=h,
num_outputs=n_filters * 2,
kernel_size=k_size,
stride=2,
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
normalizer_fn=normalizer_fn,
activation_fn=activation_fn,
scope='2',
reuse=reuse)
h = tfl.conv2d(
inputs=h,
num_outputs=n_filters * 4,
kernel_size=k_size,
stride=2,
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
normalizer_fn=normalizer_fn,
activation_fn=activation_fn,
scope='3',
reuse=reuse)
return h
我们也明确了我们的范围和重用,因为我们需要重复使用这些变量几次,我们将在稍后看到。
生成器的下一部分是变压器。这些将是6或9个剩余块,这是一个非常强大的模块,几乎在每个新架构中出现了很多。我们不是简单地使用卷积层,而是先使用卷积层,然后将原始输出加在一起。所以它是一种应该学习的输入的残余函数,残差意味着剩下的东西。残余块允许基本激活持续存在,但随后在该层之上学习简单的添加。这很有用,因为它可以确保原始激活具有输出路径。同样地,它对于反向传播很有用,因为梯度具有较小的爆炸或消失机会,因为它们通常可以在非常深的网络中进行。要阅读有关剩余网络的更多信息,请查看原始论文,其中显示了如何创建1000层的网络,所有这些都没有消失或爆炸渐变的问题!
好吧,让我们编码残余块。所有的卷积都将是单步,128个通道。每个块将具有填充层,具有Leaky ReLu和实例规范化的3x3卷积,另一个填充层,具有实例规范化的另一卷积,除非没有非线性,然后添加具有开始激活。
def residual_block(x, n_channels=128, normalizer_fn=instance_norm,
activation_fn=lrelu, kernel_size=3, scope=None, reuse=None):
with tf.variable_scope(scope or 'residual', reuse=reuse):
h = tf.pad(x, [[0, 0], [1, 1], [1, 1], [0, 0]], "REFLECT")
h = tfl.conv2d(
inputs=h,
num_outputs=n_channels,
kernel_size=kernel_size,
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
normalizer_fn=normalizer_fn,
padding='VALID',
activation_fn=activation_fn,
scope='1',
reuse=reuse)
h = tf.pad(x, [[0, 0], [1, 1], [1, 1], [0, 0]], "REFLECT")
h = tfl.conv2d(
inputs=h,
num_outputs=n_channels,
kernel_size=kernel_size,
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
normalizer_fn=normalizer_fn,
padding='VALID',
activation_fn=None,
scope='2',
reuse=reuse)
h = tf.add(x, h)
return h
现在我们可以编写许多残差块来创建我们的Transformer:
def transform(x, img_size=256, reuse=None):
h = x
if img_size >= 256:
n_blocks = 9
else:
n_blocks = 6
for block_i in range(n_blocks):
with tf.variable_scope('block_{}'.format(block_i), reuse=reuse):
h = residual_block(h, reuse=reuse)
return h
太棒了,现在我们需要编写生成器的最后一块是解码器。 这基本上与我们的编码器完全相反。 我们将有三个反卷积层,其中步幅为2,步幅为2,步幅为1,内核大小为3,3和7.在最后一层之前,我们还将填充以避免使用较大的7x7内核的边界伪影,并且我们的最后一层 激活将是一个tanh,这意味着我们的图像将在-1和1的范围内。通常这是图像的理想标准化,因为它意味着起点基本上是灰色图像。 当我们将数据输入网络并确保我们使用相同范围的图像时,我们需要牢记这一点。
def decoder(x, n_filters=32, k_size=3, normalizer_fn=instance_norm,
activation_fn=lrelu, scope=None, reuse=None):
with tf.variable_scope(scope or 'decoder', reuse=reuse):
h = tfl.conv2d_transpose(
inputs=x,
num_outputs=n_filters * 2,
kernel_size=k_size,
stride=2,
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
normalizer_fn=normalizer_fn,
activation_fn=activation_fn,
scope='1',
reuse=reuse)
h = tfl.conv2d_transpose(
inputs=h,
num_outputs=n_filters,
kernel_size=k_size,
stride=2,
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
normalizer_fn=normalizer_fn,
activation_fn=activation_fn,
scope='2',
reuse=reuse)
h = tf.pad(h, [[0, 0], [k_size, k_size], [k_size, k_size], [0, 0]],
"REFLECT")
h = tfl.conv2d(
inputs=h,
num_outputs=3,
kernel_size=7,
stride=1,
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
padding='VALID',
normalizer_fn=normalizer_fn,
activation_fn=tf.nn.tanh,
scope='3',
reuse=reuse)
return h
把它们放在一起,我们的Generator首先编码,然后转换,然后最终解码如下:
def generator(x, scope=None, reuse=None):
img_size = x.get_shape().as_list()[1]
with tf.variable_scope(scope or 'generator', reuse=reuse):
h = encoder(x, reuse=reuse)
h = transform(h, img_size, reuse=reuse)
h = decoder(h, reuse=reuse)
return h
另一个主要组成部分是鉴别器。该网络将输入图像作为输入,然后输出单个值。在真实图像的情况下,它应该输出1,并且在假图像的情况下,它应该输出0.对于生成器,我们希望相反的是真实的。在任何情况下,鉴别器应该在0或1处饱和,因此需要sigmoid作为其最终激活。网络将输入一个256 x 256像素的图像,并使用一系列5个卷积层,与我们已经使用过的层不同。前三层将是步幅2,然后最后两层将是步幅1.我们将以指数方式增加输出数量,直到最后一层将具有单个通道作为输出。
与典型的GAN不同,我们创建的是Pix2Pix和CycleGAN作者称之为PatchGAN鉴别器的内容。该网络实际上不会将图像缩小为单个值,而是将256 x 256像素图像缩小为具有1个通道作为输出的空间图。得到的地图有效地具有个体鉴别器,我们将它们平均在一起以得到最终结果。作者展示了步幅和层尺寸的一些可能组合,以在最后一层中有效地获得不同的感受野大小,并且表明这5层的组合似乎具有最佳性能和70的感受野大小。
让我们稍微分解一下,看看它们是如何产生70的感知字段大小的:
plt.figure(figsize=(13,10))
plt.imshow(plt.imread('CycleGAN/imgs/receptive-field-sizes.png'))
plt.axis('off')
在上图中,我们可以在顶部看到输入图层,在底部看到最后一层。 从最后一层工作到顶部,我们可以看到1个神经元如何对前面层中越来越多的神经元做出贡献。 最后一层中单个神经元的每层的感受域写在右边缘:[1,4,7,16,34,70]
鉴别器的代码如下所示:
def discriminator(x, n_filters=64, k_size=4, activation_fn=lrelu,
normalizer_fn=instance_norm, scope=None, reuse=None):
with tf.variable_scope(scope or 'discriminator', reuse=reuse):
h = tfl.conv2d(
inputs=x,
num_outputs=n_filters,
kernel_size=k_size,
stride=2,
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
activation_fn=activation_fn,
normalizer_fn=None,
scope='1',
reuse=reuse)
h = tfl.conv2d(
inputs=h,
num_outputs=n_filters * 2,
kernel_size=k_size,
stride=2,
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
activation_fn=activation_fn,
normalizer_fn=normalizer_fn,
scope='2',
reuse=reuse)
h = tfl.conv2d(
inputs=h,
num_outputs=n_filters * 4,
kernel_size=k_size,
stride=2,
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
activation_fn=activation_fn,
normalizer_fn=normalizer_fn,
scope='3',
reuse=reuse)
h = tfl.conv2d(
inputs=h,
num_outputs=n_filters * 8,
kernel_size=k_size,
stride=1,
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
activation_fn=activation_fn,
normalizer_fn=normalizer_fn,
scope='4',
reuse=reuse)
h = tfl.conv2d(
inputs=h,
num_outputs=1,
kernel_size=k_size,
stride=1,
weights_initializer=tf.truncated_normal_initializer(stddev=0.02),
biases_initializer=None,
activation_fn=tf.nn.sigmoid,
scope='5',
reuse=reuse)
return h
现在我们已经拥有了创建CycleGAN所需的所有主要组件。 我们只需要使用几个占位符连接它们,创建我们的损失函数,最后构建我们的训练方法。 让我们从占位符开始吧。
We’ll start with placeholders for each of the two collections which I’ll call X
and Y
:
img_size = 256
X_real = tf.placeholder(name='X', shape=[1, img_size, img_size, 3], dtype=tf.float32)
Y_real = tf.placeholder(name='Y', shape=[1, img_size, img_size, 3], dtype=tf.float32)
为了获得这些“真实”输入的“假”输出,我们将它们提供给相应的生成器。 我们将为每个方向设置一个生成器。一个将X样式转换为Y样式,反之亦然。
X_fake = generator(Y_real, scope='G_yx')
Y_fake = generator(X_real, scope='G_xy')
因为这是一个CycleGAN,我们将对生成的输出强制执行额外约束,以使原始图像质量与L1-Loss相匹配。 这将通过从X到Y生成然后再次返回到X来有效地测试两个生成器。 类似地,对于Y,我们将生成X,再生成Y.为了获得这些图像,我们简单地重用现有的生成器并创建循环图像:
X_cycle = generator(Y_fake, scope='G_yx', reuse=True)
Y_cycle = generator(X_fake, scope='G_xy', reuse=True)
然后,我们的鉴别器将对真假图像采取行动,如下所示:
D_X_real = discriminator(X_real, scope='D_X')
D_Y_real = discriminator(Y_real, scope='D_Y')
D_X_fake = discriminator(X_fake, scope='D_X', reuse=True)
D_Y_fake = discriminator(Y_fake, scope='D_Y', reuse=True)
为了创建我们的生成器的损失,我们将计算“循环”和“真实”( cycle
and real
)图像之间的L1距离,并测试生成器“欺骗”鉴别器的程度:
l1 = 10.0
loss_cycle = tf.reduce_mean(l1 * tf.abs(X_real - X_cycle)) + \
tf.reduce_mean(l1 * tf.abs(Y_real - Y_cycle))
loss_G_xy = tf.reduce_mean(tf.square(D_Y_fake - 1.0)) + loss_cycle
loss_G_yx = tf.reduce_mean(tf.square(D_X_fake - 1.0)) + loss_cycle
作者建议对L1循环损失使用恒定加权10.0。
最后,我们需要计算鉴别器的损失。 与使用当前生成的伪图像的生成器不同,我们实际上将使用生成图像的历史缓冲区,并从该历史缓冲区中随机采样生成的图像。 以前关于GAN的工作表明这可以帮助训练,而且CycleGAN的作者也建议使用它。 我们将负责在CPU的一侧跟踪这个历史缓冲区,并为TensorFlow图创建一个占位符,以帮助将历史图像发送到图中:
X_fake_sample = tf.placeholder(name='X_fake_sample',
shape=[None, img_size, img_size, 3], dtype=tf.float32)
Y_fake_sample = tf.placeholder(name='Y_fake_sample',
shape=[None, img_size, img_size, 3], dtype=tf.float32)
现在我们要求鉴别者评估这些图像:
D_X_fake_sample = discriminator(X_fake_sample, scope='D_X', reuse=True)
D_Y_fake_sample = discriminator(Y_fake_sample, scope='D_Y', reuse=True)
现在我们可以为鉴别者创造我们的损失。 与原始GAN实现不同,我们使用平方损失而不是二进制交叉熵损失。 事实证明这不容易出错:
loss_D_Y = (tf.reduce_mean(tf.square(D_Y_real - 1.0)) + \
tf.reduce_mean(tf.square(D_Y_fake_sample))) / 2.0
loss_D_X = (tf.reduce_mean(tf.square(D_X_real - 1.0)) + \
tf.reduce_mean(tf.square(D_X_fake_sample))) / 2.0
现在让我们来看看如何为这样的模型构建优化器。 我已将我们刚刚完成的所有内容包装到一个名为cycle_gan
的方便模块中。 我们可以这样创建整个网络:
tf.reset_default_graph()
from cadl.cycle_gan import cycle_gan
net = cycle_gan(img_size=img_size)
这将为我们返回整个网络的dict:
list(net.items())
就像在原始GAN实现中一样,我们将创建单独的优化器,它们只能更新网络的某些部分。 原始GAN有两个优化器,一个用于发生器,另一个用于鉴别器。 即使鉴别器依赖于来自发生器的输入,我们也只会在训练鉴别器时优化属于鉴别器的变量。 如果我们不这样做,我们将使生成器更糟,当我们真正想要发生的是两个网络变得更好。 我们在这里做同样的事情,除了现在我们实际上有3个网络要优化,所以我们需要3个优化器:G_xy
和G_yx
变量将被优化为生成器,而D_X
和D_Y
,应该更新两个不同的鉴别器。
首先我们从变量入手:
training_vars = tf.trainable_variables()
D_X_vars = [v for v in training_vars if v.name.startswith('D_X')]
D_Y_vars = [v for v in training_vars if v.name.startswith('D_Y')]
G_xy_vars = [v for v in training_vars if v.name.startswith('G_xy')]
G_yx_vars = [v for v in training_vars if v.name.startswith('G_yx')]
G_vars = G_xy_vars + G_yx_vars
接着创建优化器:
learning_rate = 0.001
D_X = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(
net['loss_D_X'], var_list=D_X_vars)
D_Y = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(
net['loss_D_Y'], var_list=D_Y_vars)
G = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(
net['loss_G'], var_list=G_vars)
请注意,我们将两个生成器连接成一个变量列表:
print(G)
作为鉴别器培训的一部分,我们测试它如何对真实图像和生成的图像进行分类。 对于生成的图像,鉴别器从最后50个生成的图像中获取随机生成的图像。 根据:Shrivastava,A.,Pfister,T.,Tuzel,O.,Susskind,J.,Wang,W。,&Webb,R。(2016),这是为了使训练更加稳定。 通过对抗训练学习模拟和无监督图像。 取自http://arxiv.org/abs/1612.07828 - 有关详细信息,请参阅第2.3节。 这里的想法是鉴别器应该仍然可以说较旧的生成图像是假的。 可能的情况是,生成器只是重新学习鉴别器已经忘记的事情,这可能有助于使事情更稳定。
为了设置它,我们确定我们的’容量’( capacity
),例如50个图像,并创建一个全部初始化为0的图像列表:
# How many fake generations to keep around
capacity = 50
# Storage for fake generations
fake_Xs = capacity * [np.zeros((1, img_size, img_size, 3), dtype=np.float32)]
fake_Ys = capacity * [np.zeros((1, img_size, img_size, 3), dtype=np.float32)]
最后,我们几乎准备好训练了。 我们只需要数据! 最重要的部分! 我已经包含了两种批处理生成器,可以帮助您将数据输入您的CycleGAN网络。 一个将您的X和Y图像集合作为数组。 另一个为X和Y拍摄单个图像,并随机裁剪。 我已经成功地将这个网络用于非常大的图像,包括Hieronymous Bosch的Earthly Delights花园。 第一个系列是草图渲染,第二个是高分辨率图像。
from cadl.cycle_gan import batch_generator_dataset, batch_generator_random_crop
To use the dataset generator, feed in two arrays images shaped: N
x H
x W
x 3:
# Load your data into imgs1 and imgs2 here!
# I've loaded in random noise as an example, but you'll want to use
# plt.imread or skimage to load in images into a list of images
ds_X, ds_Y = np.random.rand(10, img_size, img_size, 3), \
np.random.rand(10, img_size, img_size, 3)
现在,您可以使用batch_generator_dataset
函数将批次导入CycleGAN网络:
X_i, Y_i = next(batch_generator_dataset(ds_X, ds_Y))
X_i.shape, Y_i.shape
或者,您可以使用batch_generator_random_crop
函数抓取较大图像的随机裁剪,然后将这些作品输入您的网络。 你需要设置min_size
和max_size
参数来确定可以裁剪的内容以及应该将裁剪重新整形的内容。
ds_X, ds_Y = np.random.rand(1024, 1024, 3), np.random.rand(1024, 1024, 3)
X_i, Y_i = next(batch_generator_random_crop(
ds_X, ds_Y, min_size=img_size, max_size=512))
X_i.shape, Y_i.shape
CADLcycle_gan
模块包括训练功能。 但如果您想知道训练的细节,我已经评论了下面的代码:
idx = 0
it_i = 0
n_epochs = 10
ckpt_path = './'
# Train
with tf.Session() as sess:
# Build an init op for our variables
init_op = tf.group(tf.global_variables_initializer(),
tf.local_variables_initializer())
sess.run(init_op)
# We'll also save our model so we can load it up again
saver = tf.train.Saver()
writer = tf.summary.FileWriter(ckpt_path)
for epoch_i in range(n_epochs):
# You'll want to use the approriate batch generator here!
for X, Y in batch_generator_random_crop(ds_X, ds_Y):
# First generate in both directions
X_fake, Y_fake = sess.run(
[net['X_fake'], net['Y_fake']],
feed_dict={net['X_real']: X,
net['Y_real']: Y})
# Now sample from history
if it_i < capacity:
# Not enough samples yet, fill up history buffer
fake_Xs[idx] = X_fake
fake_Ys[idx] = Y_fake
idx = (idx + 1) % capacity
elif np.random.random() > 0.5:
# Swap out a random idx from history
rand_idx = np.random.randint(0, capacity)
fake_Xs[rand_idx], X_fake = X_fake, fake_Xs[rand_idx]
fake_Ys[rand_idx], Y_fake = Y_fake, fake_Ys[rand_idx]
else:
# Use current generation
pass
# Optimize G Networks
loss_G = sess.run(
[net['loss_G'], G],
feed_dict={
net['X_real']: X,
net['Y_real']: Y,
net['Y_fake_sample']: Y_fake,
net['X_fake_sample']: X_fake
})[0]
# Optimize D_Y
loss_D_Y = sess.run(
[net['loss_D_Y'], D_Y],
feed_dict={
net['X_real']: X,
net['Y_real']: Y,
net['Y_fake_sample']: Y_fake
})[0]
# Optimize D_X
loss_D_X = sess.run(
[net['loss_D_X'], D_X],
feed_dict={
net['X_real']: X,
net['Y_real']: Y,
net['X_fake_sample']: X_fake
})[0]
print(it_i, 'G:', loss_G, 'D_X:', loss_D_X, 'D_Y:', loss_D_Y)
# Update summaries
if it_i % 100 == 0:
summary = sess.run(
net['summaries'],
feed_dict={
net['X_real']: X,
net['Y_real']: Y,
net['X_fake_sample']: X_fake,
net['Y_fake_sample']: Y_fake
})
writer.add_summary(summary, it_i)
it_i += 1
# Save
if epoch_i % 50 == 0:
saver.save(
sess,
os.path.join(ckpt_path, 'model.ckpt'),
global_step=epoch_i)
# Show generative images:
fig, axs = plt.subplots(2, 2, figsize=(10, 10))
axs[0][0].set_title('X Real')
axs[0][0].imshow(np.clip(X[0], 0.0, 1.0))
axs[0][1].set_title('X Fake')
axs[0][1].imshow(np.clip(X_fake[0], 0.0, 1.0))
axs[1][0].set_title('Y')
axs[1][0].imshow(np.clip(Y[0], 0.0, 1.0))
axs[1][1].set_title('Y Fake')
axs[1][1].imshow(np.clip(Y_fake[0], 0.0, 1.0))
fig.show()
当然,在随机数据上训练CycleGAN并不像真实数据那样有趣! 以下是一些让您入门的示例提示:
经典的图像生成问题是将标记的图像转换为现实的图像。 例如,我们可能有一个描绘道路,人行道,湖泊等的街道场景的标记图像…并希望它将我们所有的低聚标签填充到丰富的高分辨率纹理中。 例如,我们可以使用谷歌地图探索这个过程,因为它提供高质量的街道纹理和卫星图像。 生成的CycleGAN如下所示:
plt.figure(figsize=(13,10))
plt.imshow(plt.imread('CycleGAN/imgs/terrain-generation.png'))
plt.axis('off')
我们尝试了Pokemon的图像集和另一个8位低分辨率英雄的图像集。 通过在低分辨率集合上应用编码器模型,我们期望获得高分辨率,口袋妖怪式的图像。 对于相反的过程,我们希望将我们的Pokemon图像转换为8位渲染。 当然,这很简单,没有深度学习,所以第一个过程更有趣。 有趣的是,颜色往往会完全改变。 尽管如此,我们可以添加一些扩展来帮助强制执行颜色保持不变,这些已经在文献中进行了探索。
plt.figure(figsize=(13,10))
plt.imshow(plt.imread('CycleGAN/imgs/character-generation.png'))
plt.axis('off')
CycleGAN论文的作者在他们的GitHub上提供了更多的想法以及使用Torch和Pytorch自己实现的CycleGAN: https://junyanz.github.io/CycleGAN/
Was this useful or did you make something awesome with CycleGAN? Share it with me at https://twitter.com/pkmital - I’d love to hear!
Also, if you are interested in learning more about these networks and related techniques, including Seq2Seq, DRAW, MDN, WaveNet, and plenty more at https://www.kadenze.com/programs/creative-applications-of-deep-learning-with-tensorflow