BicycleGAN详解与实现

  • 使用BicyleGAN进行多样化图像转换
    • BicyleGAN效果展示
  • BicycleGAN架构
    • cVAE-GAN
    • cLR-GAN(Conditional Latent Regressor GAN)
  • BicycleGAN实现
    • 在生成器中插入潜在编码
    • cVAE-GAN
    • cLR-GAN
    • 训练步骤
  • 训练结果及分析

使用BicyleGAN进行多样化图像转换

  Pix2pixCycleGAN 是非常的流行GAN,不仅在学术界有许多变体,同时也有许多基于此的应用。但是,它们都有一个缺点——图像的输出看起来几乎总是相同的。例如,如果我们要执行斑马到马的转换,被转换的同一马的照片将始终具有相同的外观和色调。这是由于GAN固有的特性,它学会过滤了噪声的随机性。为了进行多样化图像转换,本文详解了 BicycleGAN 如何解决此问题以生成更丰富的图像,并利用 Tensorflow2 实现了 BicycleGAN

BicyleGAN效果展示

  使用 BicyleGAN 可以将一张图片进行多样化的转换,产生不同的样式和色彩:

BicycleGAN架构

  在开始实现 BicycleGAN 之前,首先简要介绍一下。第一次听到这个名字,你可能会认为 BicycleGANCycleGAN 的变体,但其实它与 CycleGAN 无关,反而它是对 pix2pix 的一种改进。
  pix2pix 是一对一映射,其中给定输入的输出始终相同。当试图将噪声添加到生成器输入时,网络会忽略噪声,并且在输出图像中并不会产生变化。因此,需要寻找一种方法,强制生成器不得忽略噪声,而是使用噪声来生成多样化的图像,即一对多映射。
  下图是 BicycleGAN 相关的模型和配置。图(a)是推理的配置,图像A与噪声相结合以生成图像B^\hat BB^,可以将此看作是 cGAN 。在BicyleGAN中,形状为(256, 256, 3)的图像A是条件,而从潜在编码 zzz 采样的噪声为大小为8的一维向量。图(b)pix2pix + 噪声 的训练配置。而 图(c)图(d) 的两个配置由 BicycleGAN 训练时使用:

  简而言之,BicycleGAN 可以找到潜在编码z与目标图像B之间的关系,因此生成器可以在给定不同的z时学会生成不同的图像B^\hat BB^。如上图所示,BicycleGAN 通过组合 cVAE-GANcLR-GAN 这两种模型来做到这一点。

cVAE-GAN

  VAE-GAN 的作者认为,L1L_1L1​损失并不是衡量图像视觉质量的良好指标。例如,如果图像向右移动几个像素,则人眼看起来可能没有什么不同,但会导致较大的L1L_1L1​损失。因此使用 GAN 的鉴别器来学习目标函数,以判断伪造的图像是否真实,并使用 VAE 作为生成器,生成的图像更清晰。如果忽略上图(c)中的图像 AAA,那就是 VAE-GAN ,由于以 AAA 为条件,其成为条件 cVAE-GAN 。训练步骤如下:

  1. VAE 将真实图片 BBB 编码为多元高斯分布的潜在编码,然后从它们中采样以创建噪声输入,此流程是标准的VAE工作流程;
  2. 使用图像 AAA 作为条件及从潜矢量 zzz 采样的噪声用于生成伪图像B^\hat BB^.

  训练中的数据流为 B−>z−>B^B->z->\hat BB−>z−>B^ ( 图(c) 中的实线箭头),总的损失函数由三个损失组成:

  1. LGANVAE\mathcal L_{GAN}^{VAE}LGANVAE​:对抗损失
  2. L1VAE\mathcal L_1^{VAE}L1VAE​:L1L_1L1​重建损失
  3. LKL\mathcal L_{KL}LKL​:KLKLKL散度损失

cLR-GAN(Conditional Latent Regressor GAN)

  在 cVAE-GAN 中,对真实图像B进行编码,以提供潜在矢量的真实样本并从中进行采样。但是, cLR-GAN 的处理方式有所不同,其首先使用生成器从随机噪声中生成伪图像 B^\hat BB^ ,然后对伪图像 B^\hat BB^ 进行编码,最后计算其与输入随机噪声差异。
前向计算步骤如下:

  1. 首先,类似于 cGAN ,随机产生一些噪声,然后串联图像A以生成伪图像 B^\hat BB^。
  2. 之后,使用来自 VAE-GAN 的同一编码器将伪图像 B^\hat BB^ 编码为潜矢量。
  3. 最后,从编码的潜矢量中采样 z^\hat zz^ ,并用输入噪声 zzz 计算损失。

  数据流为 z−>B^−>z^z-> \hat B -> \hat zz−>B^−>z^ ( 图(d) 中的实线箭头),有两个损失:

  1. LGAN\mathcal L_{GAN}LGAN​:对抗损失
  2. L1latent\mathcal L_1^{latent}L1latent​:噪声 N(z) 与潜在编码之间的 L1L_1L1​ 损失

  通过组合这两个数据流,在输出和潜在空间之间得到了一个双映射循环。 BicycleGAN 中的 bi 来自双映射(双向单射),这是一个数学术语,简单来说其表示一对一映射,并且是可逆的。在这种情况下,BicycleGAN 将输出映射到潜在空间,并且类似地从潜在空间映射到输出。总损失如下:
lossBicycle=LGANVAE+LGAN+λL1VAE+λlatentL1latent+λKLloss_{Bicycle}=\mathcal L_{GAN}^{VAE}+\mathcal L_{GAN}+λ\mathcal L_1^{VAE}+λ_{latent}\mathcal L_1^{latent}+λ_{KL}lossBicycle​=LGANVAE​+LGAN​+λL1VAE​+λlatent​L1latent​+λKL​
  在默认配置中,λ=10λ = 10λ=10、λlatent=0.5λ_{latent} = 0.5λlatent​=0.5、λlatent=0.01λ_{latent} = 0.01λlatent​=0.01。

BicycleGAN实现

  BicycleGAN 中有三种类型的网络——生成器,鉴别器和编码器。为 cVAE-GANcLR-GAN 使用单独的鉴别器可以提高图像质量,因此我们将使用四个网络-生成器,编码器和两个鉴别器。

在生成器中插入潜在编码

  将潜在编码插入到生成器中有两种方法,如下图所示:

  1. 与输入图像进行级联;
  2. 将其插入到生成器的下采样路径中的其他层中。

  实验发现前者效果很好。
  有多种方法可以将不同形状的输入和条件结合起来。 BicycleGAN 使用的方法是多次重复潜在编码然后与输入图像连接。
  在 BicycleGAN 中,潜在编码长度为8,我们从噪声分布中提取了8个样本,每个样本重复H×W次以形成形状为 (H, W, 8) 的张量。换句话说,在8个通道中,其 (H,W) 特征图都是相同的。以下代码显示了潜在编码的拼接和连接:

input_image = layers.Input(shape=image_shape, name='input_image')
input_z = layers.Input(shape=(self.z_dim,), name='z')
z = layers.Reshape((1,1, self.z_dim))(input_z)
z_tiles = tf.tile(z, [self.batch_size, self.input_shape[0], self.input_shape[1], self.z_dim])
x = layers.Concatenate()([input_image, z_tiles])

  下一步是创建两个模型,即 cVAE-GANcLR-GAN,以合并网络并创建前向信息流。

cVAE-GAN

  下面创建 cVAE-GAN 模型的代码,前向计算的实现:

images_A_1 = layers.Input(shape=input_shape, name='ImageA_1')
images_B_1 = layers.Input(shape=input_shape, name='ImageB_1')
z_encode, self.mean_encode, self.logvar_encode = self.encoder(images_B_1)
fake_B_encode = self.generator([images_A_1, z_encode])
encode_fake = self.discriminator_1(fake_B_encode)
encode_real = self.discriminator_1(images_B_1)
kl_loss =  - 0.5 * tf.reduce_sum(1 + self.logvar_encode - tf.square(self.mean_encode) - tf.exp(self.logvar_encode))
self.cvae_gan = Model(inputs=[images_A_1, images_B_1], outputs=[encode_real, encode_fake, fake_B_encode, kl_loss])

  我们在模型中使用了 KLKLKL 散度损失。由于可以直接根据均值和对数方差来计算 kl_loss ,而无需在训练步骤中传入外部标签,因此更加简单有效。

cLR-GAN

  下面是 cLR-GAN 的实现,前向计算的实现:

images_A_2 = layers.Input(shape=input_shape, name='ImageA_2')
images_B_2 = layers.Input(shape=input_shape, name='ImageB_2')
z_random = layers.Input(shape=(self.z_dim,), name='z')
fake_B_random = self.generator([images_A_2, z_random])
_, mean_random, _ = self.encoder(fake_B_random)
random_fake = self.discriminator_2(fake_B_random)
random_real = self.discriminator_2(images_B_2)
self.clr_gan = Model(inputs=[images_A_2, images_B_2,   z_random],outputs=[random_real, random_fake, mean_random])

  现在,我们现在已经定义了模型,下一步是实现训练步骤。

训练步骤

  两种模型一起进行训练,但是具有不同的图像对。因此,在每个训练步骤中,我们两次获取数据,每个模型一次,这是通过创建数据管道来完成的,该数据管道将调用两次以加载数据:

images_A_1, images_B_1 = next(data_generator)
images_A_2, images_B_2 = next(data_generator)
self.train_step(images_A_1, images_B_1, images_A_2, images_B_2)

  我们可以使用两种不同的方法来执行训练。一种是使用优化器和损失函数定义和编译Keras模型,然后调用 train_on_batch() 来执行训练步骤,这种方法在定义明确的模型上效果很好。此外,我们也可以使用 tf.GradientTape 来更好地控制梯度更新。BicycleGAN 有两个模型,它们共享一个生成器和一个编码器,但是我们需要使用损失函数的不同组合来更新它们的参数,这使 train_on_batch 方法在不修改原始设置的情况下不可行。因此,我们使用 tf.GradientTape 将这两个模型的生成器和鉴别器组合为一个训练步骤,如下所示:

  1. 第一步是执行前向传递并收集两个模型的输出:
    def train_step(self, images_A_1, images_B_1, images_A_2, images_B_2):z = tf.random.normal((self.batch_size, self.z_dim))real_labels = tf.ones((self.batch_size, self.patch_size, self.patch_size, 1))fake_labels = tf.zeros((self.batch_size, self.patch_size, self.patch_size, 1))with tf.GradientTape() as tape_e, tf.GradientTape() as tape_g, tf.GradientTape() as tape_d1, tf.GradientTape() as tape_d2:encode_real, encode_fake, fake_B_encode, kl_loss = self.cvae_gan([images_A_1, images_B_1])random_real, random_fake, mean_random = self.clr_gan([images_A_2, images_B_2, z])
  1. 接下来,我们通过反向梯度传播更新鉴别器:
            # discriminator lossself.d1_loss = self.mse(real_labels, encode_real) + self.mse(fake_labels, encode_fake)gradients_d1 = tape_d1.gradient(self.d1_loss, self.discriminator_1.trainable_variables)self.optimizer_d1.apply_gradients(zip(gradients_d1, self.discriminator_1.trainable_variables))self.d2_loss = self.mse(real_labels, random_real) + self.mse(fake_labels, random_fake)gradients_d2 = tape_d2.gradient(self.d2_loss,self.discriminator_2.trainable_variables)self.optimizer_d2.apply_gradients(zip(gradients_d2, self.discriminator_2.trainable_variables))
  1. 然后,我们根据模型的输出计算损失。与 CycleGAN 相似,BicycleGAN 也使用 LSGAN 损失函数,即均方误差:
         self.LAMBDA_IMAGE = 10self.LAMBDA_LATENT = 0.5self.LAMBDA_KL = 0.01# Generator and Encoder lossself.gan_1_loss = self.mse(real_labels, encode_fake)self.gan_2_loss = self.mse(real_labels, random_fake)self.image_loss = self.LAMBDA_IMAGE * self.mae(images_B_1, fake_B_encode)self.kl_loss = self.LAMBDA_KL * kl_lossself.latent_loss = self.LAMBDA_LATENT * self.mae(z, mean_random)
  1. 最后,还有生成器和编码器权重的更新。 L1L_1L1​潜在编码损失仅用于更新生成器,而不用于更新编码器。由于针对损失同时优化将导致它们隐藏与潜在编码有关的信息,而不学习潜在编码中有意义的模式。因此,需要为生成器和编码器分别计算损失,并相应地更新权重:
            encoder_loss = self.gan_1_loss + self.gan_2_loss + self.image_loss + self.kl_lossgenerator_loss = encoder_loss + self.latent_lossgradients_generator = tape_g.gradient(generator_loss, self.generator.trainable_variables)self.optimizer_generator.apply_gradients(zip(gradients_generator, self.generator.trainable_variables))gradients_encoder = tape_e.gradient(encoder_loss, self.encoder.trainable_variables)self.optimizer_encoder.apply_gradients(zip(gradients_encoder, self.encoder.trainable_variables))

训练结果及分析

  训练BicycleGAN,可以选择两个数据集-建筑结构草图或鞋子的边线草图。鞋子数据集的图像更简单,因此更易于训练,因此以此训练为例。以下图像是 BicycleGAN 训练结果的展示示例。第一张图片是线稿,第二个图片是线稿对应的真实图像,右边的四个图片是生成的:

  可以看到,由同一线稿生成的不同图片间的差异主要是颜色。它们都几乎完美地捕捉了鞋子的结构,但就细节捕捉而言,效果并不十分理想,可以通过增加训练与超参数调整来获取更加优异的模型性能。

BicycleGAN详解与实现相关推荐

  1. 从命令行到IDE,版本管理工具Git详解(远程仓库创建+命令行讲解+IDEA集成使用)

    首先,Git已经并不只是GitHub,而是所有基于Git的平台,只要在你的电脑上面下载了Git,你就可以通过Git去管理"基于Git的平台"上的代码,常用的平台有GitHub.Gi ...

  2. JVM年轻代,老年代,永久代详解​​​​​​​

    秉承不重复造轮子的原则,查看印象笔记分享连接↓↓↓↓ 传送门:JVM年轻代,老年代,永久代详解 速读摘要 最近被问到了这个问题,解释的不是很清晰,有一些概念略微模糊,在此进行整理和记录,分享给大家.在 ...

  3. docker常用命令详解

    docker常用命令详解 本文只记录docker命令在大部分情境下的使用,如果想了解每一个选项的细节,请参考官方文档,这里只作为自己以后的备忘记录下来. 根据自己的理解,总的来说分为以下几种: Doc ...

  4. 通俗易懂word2vec详解词嵌入-深度学习

    https://blog.csdn.net/just_so_so_fnc/article/details/103304995 skip-gram 原理没看完 https://blog.csdn.net ...

  5. 深度学习优化函数详解(5)-- Nesterov accelerated gradient (NAG) 优化算法

    深度学习优化函数详解系列目录 深度学习优化函数详解(0)– 线性回归问题 深度学习优化函数详解(1)– Gradient Descent 梯度下降法 深度学习优化函数详解(2)– SGD 随机梯度下降 ...

  6. CUDA之nvidia-smi命令详解---gpu

    nvidia-smi是用来查看GPU使用情况的.我常用这个命令判断哪几块GPU空闲,但是最近的GPU使用状态让我很困惑,于是把nvidia-smi命令显示的GPU使用表中各个内容的具体含义解释一下. ...

  7. Bert代码详解(一)重点详细

    这是bert的pytorch版本(与tensorflow一样的,这个更简单些,这个看懂了,tf也能看懂),地址:https://github.com/huggingface/pytorch-pretr ...

  8. CRF(条件随机场)与Viterbi(维特比)算法原理详解

    摘自:https://mp.weixin.qq.com/s/GXbFxlExDtjtQe-OPwfokA https://www.cnblogs.com/zhibei/p/9391014.html C ...

  9. pytorch nn.LSTM()参数详解

    输入数据格式: input(seq_len, batch, input_size) h0(num_layers * num_directions, batch, hidden_size) c0(num ...

最新文章

  1. .net程序员使用Oracle新手上路指南
  2. 英语和数学不好可以学python-Day2 怎么学 Python?
  3. 基于Qt的光盘刻录开发
  4. android 圆角按钮渐变,Android实现圆形渐变加载进度条
  5. html页面左右布局透明背景,HTML透明背景
  6. 【毕设】ASP.net校友录毕业设计(源代码+论文+开题报告+答辩PPT)
  7. 1.根据MAC地址抓包
  8. 454. 四数相加 ||
  9. VALSE学习(一):high-resolution representation learning-高分辨率表示学习-姿态估计
  10. 强的离谱!串烧70+个Transformer模型,涵盖CV、NLP、金融、隐私计算...
  11. 阶段3 1.Mybatis_09.Mybatis的多表操作_1 mybatis表之间关系分析
  12. 华为云服务器安装win10系统,云服务器安装win10
  13. Mysql用户与权限操作
  14. 计算机主板扩展槽,计算机内部所有插槽介绍,10分钟读懂电脑主板,高手必备!...
  15. nyoj 54-小明的存钱计划 (遍历 + 判断)
  16. 孢子社群:今日推荐人工智能微信群:智能群3群 智能大数据合作交流群
  17. 最小公倍数用c语言,如何用C语言求最小公倍数。。。
  18. 获取服务器端的webapps路径
  19. Windows Server 2003 64位操作系统安装sqlserver2000企业版
  20. 华为云与阿里云简要区别

热门文章

  1. Java基础知识强化87:BigInteger类之BigInteger加减乘除法的使用
  2. 用户控件页为什么找不到.ClientScript.RegisterClientScriptBlock原因
  3. verilog中的initial块、always块详细解释
  4. 一次频繁Full GC问题排查过程分享
  5. 深度学习基础(基础知识0)
  6. 访问vector元素方法的效率比较(转)
  7. 与我们息息相关的internet服务(2)---WWW服务
  8. 【BZOJ-1097】旅游景点atr SPFA + 状压DP
  9. OD使用教程10 - 调试篇10|解密系列
  10. PHP最常用的2种设计模式工厂模式和单例模式