AIGC实战——BicycleGAN详解与实现

AIGC实战------BicycleGAN详解与实现

    • [0. 前言](#0. 前言)
    • [1. BicycleGAN 原理](#1. BicycleGAN 原理)
    • [2. cVAE-GAN](#2. cVAE-GAN)
    • [3. cLR-GAN](#3. cLR-GAN)
    • [4. 实现 BicycleGAN](#4. 实现 BicycleGAN)
      • [4.1 将潜编码插入生成器](#4.1 将潜编码插入生成器)
      • [4.2 cVAE-GAN](#4.2 cVAE-GAN)
      • [4.3 cLR-GAN](#4.3 cLR-GAN)
      • [4.4 训练步骤](#4.4 训练步骤)
    • 相关链接

0. 前言

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

1. BicycleGAN 原理

初看之下,可能会认为 BicycleGANCycleGAN 的升级,像是增加了另一个循环(从单循环到双循环)。但其实它与 CycleGAN 无关,而是对 pix2pix 的一种改进。
pix2pix 是一对一映射,其中给定输入的输出始终相同。BicycleGAN 作者试图将噪声添加到生成器输入中,但是网络会忽略噪声,并且没有在输出图像中产生变化。因此,他们试图寻找一种方法,其中生成器不忽略噪声,而是使用噪声来生成多样化的图像,因此是一对多映射。

在以下图中,我们可以看到与 BicycleGAN 相关的模型和配置。图(a) 是推理的配置,其中图像 A A A 与输入噪声相结合以生成图像 B ^ \hat B B^,可以看作是cGAN。在 cGAN 中,噪声起着主导作用,输入为具有大小为 100 的一维潜编码和作为条件的类别标签(大小为 10 的一维向量)。在 BicyleGAN 中,形状为 (256, 256, 3) 的图像 A 是条件,而从潜编码 z z z 采样的噪声为大小为 8 的一维向量。图(b)pix2pix +噪声 的训练配置,图底部的两个配置由 BicycleGAN 使用:

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

2. cVAE-GAN

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

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

训练中的信息流为 B → z → B ^ B\rightarrow z\rightarrow \hat B B→z→B^ (图(c) 中的实线箭头),总的损失函数由三个损失组成:

  • L G A N V A E \mathcal L_{GAN}^{VAE} LGANVAE:对抗损失
  • L 1 V A E \mathcal L_1^{VAE} L1VAE:L1 重建损失
  • L K L \mathcal L_{KL} LKL:KL 散度损失

3. cLR-GAN

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

前向计算步骤如下:

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

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

  1. L G A N \mathcal L_{GAN} LGAN:对抗损失
  2. L 1 l a t e n t \mathcal L_1^{latent} L1latent:噪声 N ( z ) N(z) N(z) 与潜在编码之间的 L1 损失

通过组合这两个数据流,在输出和潜空间之间得到了一个双映射循环。BicycleGAN 中的 bi 来自双映射(双向单射),这是一个数学术语,简单来说其表示一对一映射,并且是可逆的。在这种情况下,BicycleGAN 将输出映射到潜空间,并且类似地从潜空间映射到输出。总损失如下:
l o s s B i c y c l e = L G A N V A E + L G A N + λ L 1 V A E + λ l a t e n t L 1 l a t e n t + λ K L loss_{Bicycle}=\mathcal L_{GAN}^{VAE}+\mathcal L_{GAN}+λ\mathcal L_1^{VAE}+λ_{latent}\mathcal L_1^{latent}+λ_{KL} lossBicycle=LGANVAE+LGAN+λL1VAE+λlatentL1latent+λKL

在默认配置中, λ = 10 λ = 10 λ=10、 λ l a t e n t = 0.5 λ_{latent} = 0.5 λlatent=0.5、 λ l a t e n t = 0.01 λ_{latent} = 0.01 λlatent=0.01。

我们已经了解了 BicycleGAN 架构和损失函数,接下来使用 TensorFlow 实现 BicycleGAN

4. 实现 BicycleGAN

BicycleGAN 中有三种类型的网络-生成器,判别器和编码器。随着输入图像尺寸的增大,编码器将包含更多的卷积核和更深的网络层。原始的 BicycleGAN 使用两个 PatchGAN,其有效感受野分别为 70x70140x140

为简单起见,我们将使用一个 70x70PatchGAN。为 cVAE-GANcLR-GAN 使用单独的判别器可以提高图像质量,这意味着我们总共有四个网络-生成器,编码器和两个判别器。

4.1 将潜编码插入生成器

将潜编码插入到生成器中有两种方法,一种与输入图像进行拼接,另一种将其插入到生成器的下采样路径中的其他层中,如下图所示,实验表明前者效果更好。

有多种方法可以将不同形状的输入和条件结合起来。BicycleGAN 的方法是多次重复潜编码并与输入图像连接。

BicycleGAN 中,潜编码长度为 8。我们从噪声分布中提取了 8 个样本,每个样本重复 H×W 次以形成形状为 (H, W, 8) 的张量。换句话说,在 8 个通道中,其 (H, W) 特征图都是相同的。以下是 build_generator() 的代码片段,显示了潜编码的拼接和连接:

python 复制代码
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,以合并网络并创建前向信息流。

4.2 cVAE-GAN

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

python 复制代码
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])

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

4.3 cLR-GAN

接下来,实现 cLR-GAN

python 复制代码
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])

完成模型定义后,实现训练步骤。

4.4 训练步骤

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

python 复制代码
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. 执行前向传递并收集两个模型的输出:

    python 复制代码
    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])
  2. 接下来,我们反向传播并更新判别器:

    python 复制代码
    	    self.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))
  3. 然后,我们根据模型的输出计算损失。与 CycleGAN 相似,BicycleGAN 也使用 LSGAN 损失函数,即均方误差:

python 复制代码
			self.LAMBDA_IMAGE = 10
			self.LAMBDA_LATENT = 0.5
			self.LAMBDA_KL = 0.01
            # Generator and Encoder loss
            self.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_loss
            self.latent_loss = self.LAMBDA_LATENT * self.mae(z, mean_random)
  1. 最后,还有生成器和编码器权重的更新。L1 潜编码损失仅用于更新生成器,而不用于更新编码器。由于针对损失同时优化将导致它们隐藏与潜编码有关的信息,而不学习潜在编码中有意义的模式。因此,需要为生成器和编码器分别计算损失,并相应地更新权重:
python 复制代码
            encoder_loss = self.gan_1_loss + self.gan_2_loss + self.image_loss + self.kl_loss
            generator_loss = encoder_loss + self.latent_loss

            gradients_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 训练结果的展示示例。第一张图片是线稿,第二个图片是线稿对应的真实图像,右边的四个图片是生成的:

可以看到,由同一线稿生成的不同图片间的差异主要是颜色。

相关链接

AIGC实战------生成模型简介
AIGC实战------深度学习 (Deep Learning, DL)
AIGC实战------卷积神经网络(Convolutional Neural Network, CNN)
AIGC实战------自编码器(Autoencoder)
AIGC实战------变分自编码器(Variational Autoencoder, VAE)
AIGC实战------使用变分自编码器生成面部图像
AIGC实战------生成对抗网络(Generative Adversarial Network, GAN)
AIGC实战------WGAN(Wasserstein GAN)
AIGC实战------条件生成对抗网络(Conditional Generative Adversarial Net, CGAN)
AIGC实战------CycleGAN详解与实现
AIGC实战------自回归模型(Autoregressive Model)
AIGC实战------改进循环神经网络
AIGC实战------像素卷积神经网络(PixelCNN)
AIGC实战------归一化流模型(Normalizing Flow Model)
AIGC实战------能量模型(Energy-Based Model)
AIGC实战------扩散模型(Diffusion Model)
AIGC实战------GPT(Generative Pre-trained Transformer)
AIGC实战------Transformer模型
AIGC实战------ProGAN(Progressive Growing Generative Adversarial Network)
AIGC实战------StyleGAN(Style-Based Generative Adversarial Network)
AIGC实战------VQ-GAN(Vector Quantized Generative Adversarial Network)
AIGC实战------基于Transformer实现音乐生成
AIGC实战------MuseGAN详解与实现
AIGC实战------多模态模型DALL.E 2
AIGC实战------多模态模型Flamingo
AIGC实战------世界模型(World Model)

相关推荐
缘友一世6 小时前
PyTorch深度学习实战【11】之神经网络的学习和训练
pytorch·深度学习·神经网络
Sunhen_Qiletian6 小时前
深度学习----ResNet(残差网络)-彻底改变深度神经网络的训练方式:通过残差学习来解决深层网络退化问题(附PyTorch实现)
网络·深度学习·dnn
九章云极AladdinEdu6 小时前
VC维(Vapnik-Chervonenkis Dimension)的故事:模型复杂度的衡量
人工智能·深度学习·机器学习·gpu算力·模型·vc维
深度学习机器7 小时前
VLM做文档OCR效果如何?我拿了几个常见模型进行对比
llm·aigc·agent
蒋星熠7 小时前
支持向量机深度解析:从数学原理到工程实践的完整指南
人工智能·python·深度学习·神经网络·算法·机器学习·支持向量机
IT学长编程7 小时前
计算机毕业设计 基于Python的音乐推荐系统 Python 大数据毕业设计 Hadoop毕业设计选题【附源码+文档报告+安装调试】
大数据·hadoop·python·深度学习·毕业设计·课程设计·音乐推荐系统
Vahala0623-孔勇7 小时前
将深度学习与Spring Boot集成:使用DL4J构建企业级AI应用的完整指南
人工智能·spring boot·深度学习
Memene摸鱼日报8 小时前
「Memene 摸鱼日报 2025.9.24」阿里 Qwen 团队放出 Qwen3-Next、Qwen-VL 等多个大招,Sam 提出“丰盛智能”愿景
人工智能·aigc